I have a little problem accessing a QList objects since Javascript. I have a C ++ class that allows me to perform SQL queries since QML / JS. Everything works, I get my results in C ++.
My problem is that I'd returned to QML an QList object.
This is my function in C++ to return SQL result (Note is a simple object with different attributes) :
QList<Note> Storage::setQuery(QString query)
{
QList<Note> noteItems;
QSqlQuery qsqlQuery;
bool ok = qsqlQuery.exec(query);
if(!ok)
{
qDebug() << "Error setQuery" << m_sqlDatabase.lastError();
}
else
{
while (qsqlQuery.next()) {
Note my_note;
QString note = qsqlQuery.value("message").toString();
my_note.setMessage(note);
noteItems.append(my_note);
}
}
return noteItems;
}
But when I call this function from JS I get this error: Unknown method return type: QList<Note>
The problem is the return type, QML JS doesn't know the type QList<Object>, why? What do I do wrong
If you want to use c++ QList as a model in Qml, I would recommend you to use the following procedure. I'm using my own example, you might change it according to your needs.
Storage.h
class Storage : public QObject {
Q_PROPERTY(QQmlListProperty<Note> getList READ getList)
public:
QQmlListProperty<Note> getList();
void setQuery(QString query);
QList<Note> noteItems;;
private:
static void appendList(QQmlListProperty<Note> *property, Note *note);
static Note* cardAt(QQmlListProperty<Note> *property, int index);
static int listSize(QQmlListProperty<Note> *property);
static void clearListPtr(QQmlListProperty<Note> *property);
};
Storage.cpp
void Field::appendList(QQmlListProperty<Card> *property, Note *note) {
Q_UNUSED(property);
Q_UNUSED(note);
}
Note* Field::cardAt(QQmlListProperty<Note> *property, int index) {
return static_cast< QList<Note> *>(property->data)->at(index);
}
int Field::listSize(QQmlListProperty<Note> *property) {
return static_cast< QList<Note> *>(property->data)->size();
}
void Field::clearListPtr(QQmlListProperty<Note> *property) {
return static_cast< QList<Note> *>(property->data)->clear();
}
QQmlListProperty<Note> Field::getList() {
return QQmlListProperty<Note>( this, &list[0], &appendList, &listSize, &cardAt, &clearListPtr );
}
void Storage::setQuery(QString query)
{
QList<Note> noteItems;
QSqlQuery qsqlQuery;
bool ok = qsqlQuery.exec(query);
if(!ok)
{
qDebug() << "Error setQuery" << m_sqlDatabase.lastError();
}
else
{
while (qsqlQuery.next()) {
Note my_note;
QString note = qsqlQuery.value("message").toString();
my_note.setMessage(note);
noteItems.append(my_note);
}
}
}
main.cpp
int main(int argc, char *argv[])
{
qmlRegisterType<Note>();
}
The QQmlListProperty class allows applications to expose list-like properties to QML. To provide a list property, a C++ class must implement the operation callbacks, and then return an appropriate QQmlListProperty value from the property getter. List properties should have no setter. When extending QML with C++ code, a C++ class can be registered with the QML type system to enable the class to be used as a data type within QML code.
Did you register Note as a meta type? Probably that's what's missing:
http://doc.qt.io/qt-5/qmetatype.html#Q_DECLARE_METATYPE
In Qt 4 you'll also have to register QList<Note>, but not in Qt 5.
Oh, and Note should probably be a Q_GADGET, otherwise you cannot access its contents from QML either. Make sure you use Qt 5.5+. Otherwise, you'll need QList<Note*> and make those Note objects inherit from QObject.
Related
For instance, I have the below pseudo code:
public action1():
return stuff.pipe(delay(15000));
public action2():
return stuff.pipe(delay(15000));
public action3():
return stuff.pipe(delay(15000));
The delay is identical for each return and will not change. But instead of having it as a static value, I would like to make it into a constant that can be easily referenced and modified if needed. Something like the below pseudo code:
Const delay = pipe(delay(15000))
public action1():
return stuff.delay;
public action2():
return stuff.delay;
public action3():
return stuff.delay;
I have tried to directly reference the delay by using const but I am getting errors when attempting this. I am using the RJXS Delay operator. The primary question is, Is there a way to convert this operator into a constant that can be used in multiple areas?
You can define a generic function which will accept observer and and bind delay with it and return.
You can do something like bellow
const getAfterDelay = (observer) => observer.pipe(delay(15000));
public action1():
return getAfterDelay(stuff);
public action2():
return getAfterDelay(stuff);
public action3():
return getAfterDelay(stuff);
Create an Extension Method
declare module 'rxjs/internal/Observable' {
interface Observable<T> {
delay(): Observable<T>;
}
}
Observable.prototype.delay = function(): Observable<any> {
return this.pipe(delay(15000));
};
This allows for the syntax that you're looking for: stuff.delay();.
Observable Delay Extension Method in Typescript for StackBlitz example.
I'm trying to implement the possibility to script an existing QT application.
It works fine, but some function of my class return a TypeError.
Myclass.h (really simplified) :
class Myclass: public QObject
{
Q_OBJECT
public slots:
int firstfunction() const;
int secondfunction() const;
private:
int m_firstResult;
int m_secondResult;
}
Myclass.cpp :
int Myclass::firstfunction() const
{
return m_firstResult;
}
int Myclass::secondfunction() const
{
return m_secondResult;
}
The main :
Myclass qtObjectClass();
QScriptEngine scriptEngine;
QScriptValue qValue= scriptEngine.newQObject(&qtObjectClass);
Q_ASSERT (qtObjectClass.isQObject());
scriptEngine.globalObject().setProperty("QTscriptEngine", qValue);
QFile file("testScript.js");
file.open(QIODevice::ReadOnly);
QScriptValue result = scriptEngine.evaluate(file.readAll());
if(result.toString() != "undefined")
std::cout << result.toString().toStdString() << std::endl;
file.close();
if (scriptEngine.hasUncaughtException())
{
int lineNo = scriptEngine.uncaughtExceptionLineNumber();
printf("lineNo : %i", lineNo);
}
The script :
print(QTscriptEngine.firstfunction());
print(QTscriptEngine.secondfunction());
And the (strange) result :
5
TypeError: Result of expression 'QTscriptEngine.secondfunction' [1] is not a function.
Where 5 is the result of firstfunction() and [1] the result of secondfunction().
Of course, the result of my function is not a function, it's pretty normal, no ?
I don't understand why one is working when the other one is not (but evaluated because [1] is clearly the good returned value of secondfunction())
(I have really simplify all the code, and maybe the problem come from an other place, but it's clearly strange)
Any idea ?
Thanks.
The answer was stupid.
I had properties defined like that :
Q_PROPERTY(int m_firstResult READ firstfunction)
The function who are in a Q_PROPERTY return a TypeError.
And I havn't even put my properties in my question, so no one was able to answer my question, sorry for that.
I followed this article to use hostpage to pass an array to client:
https://developers.google.com/web-toolkit/articles/dynamic_host_page
Currently,I can see follow content in firebug
<html style="overflow: hidden;">
<head>
......
<script type="text/javascript">
var rcmdFriends=[{"Name":"Friend-0","Image":"url"}];
</script>
</head>
......
</html>
Then I tried to use these code to get js variable(a json array actually) from hostpage and print it to user:
//get array from host page
private native JsArrayExt<People> getRecommendedFriends()/*-{
return $wnd.rcmdFriends;
}-*/;
#Override
public void onModuleLoad()
{
final FlowPanel fPanel = new FlowPanel();
JsArrayExt<People> channels = getRecommendedFriends();
for (int i = 0, len = channels.length(); i < len; i++)
{
//"print" name to user
fPanel.add(new Label(channels.get(i).getName()));
}
RootPanel.get().add(fPanel);
}
//model definition
#SingleJsoImpl(PeopleImpl.class)
public interface People extends HasName
{
String getImage();
void setImage(String Image);
}
But got this eror:
java.lang.ClassCastException: com.google.gwt.core.client.JavaScriptObject$ cannot be cast to com.pkg.People
Strangely,I can already see the length of "channels" is 1,and why do I get this casting error?How to solove this problem?
You cannot cast to an ordinary Java pojo. You must implement an overlay type
public class PersonJSON extends JavaScriptObject {
protected PersonJSON() {
}
public final native String getName() /*-{
return this.Name;
}-*/;
public final native String getImage() /*-{
return this.Image;
}-*/;
}
Then you can call
JsArray<PersonJSON> channels = getRecommendedFriends();
and read out the values from the PersonJSON elements;
Assuming JsArrayExt is the interface from Why can't I define interface for overlay type lightweight collections?, I suppose that the fact you do not use an explicit JSO subclass confuses the DevMode.
Because you directly call a JSNI method, I don't understand why you don't use a JsArrayExtImpl<PersonImpl> which I believe would Just Workâ˘; there's no point in using the interfaces here.
If you really can't make it work, I'd suggest using AutoBeans instead (it unfortunately requires a small serialize/parse dance in DevMode: AutoBeanCodex.decode(factory, Person.class, new JSONObject(rawJso).toString()), whereas in prod mode you can simply use AutoBeanCodex.decode(factory, Person.class, (JsoSplittable) rawJso)). In your case, it'd require another dance because you're using an array as the root object; see GWT Autobean - how to handle lists?
I'm having trouble adding proper exception handling to existing code that makes heavy use of Silverlight - JavaScript interoperability. In this case, my JavaScript can throw an exception that I want to handle meaningfully in Silverlight.
From Silverlight, I'm creating an instance of a JavaScript object, then later I'm calling a method on that object:
public class MyWrapper
{
dynamic _myJSObject;
public MyWrapper()
{
_myJSObject = HtmlPage.Window.CreateInstance("MyJSObject");
}
public int MyMethod()
{
try
{
int result = (int)_myJSObject.MyMethod();
}
catch (Exception ex)
{
// I want to add meaningful exception handling here
}
}
}
Whenever MyJSObject.MyMethod throws an exception, there are two problems:
The browser shows a message that an exception has occurred.
Information about the exception is not passed to my managed code. Instead I get a RuntimeBinderException which just says "Cannot invoke a non-delegate type" and contains no other information whatsoever. This does not seem to match what is described here; I'd expect an InvalidOperationException.
I've tried avoiding to cast the returned value of the method:
object tmp= _myJSObject.MyMethod();
This makes no difference. Changing the type of exception thrown on the JavaScript side has no effect either.
MyJSObject.prototype.MyMethod = function ()
{
throw "Hello Silverlight!";
}
The only solution I can think of right now is abusing the function's return value to pass information about the exception, but that will make my code a whole lot uglier... so:
Why is the behavior I'm seeing different from what is described in documentation? Does it have to do with my use of dynamic somehow? How can I properly handle exceptions that occur in JavaScript in my managed code?
After quite a bit of experimentation, I concluded that there is no way to directly handle the JavaScript exception from Silverlight. In order to be able to process the exception, the JavaScript code needs to be changed slightly.
Instead of throwing the error, I return it:
function MyMethod()
{
try
{
// Possible exception here
}
catch (ex)
{
return new Error(ex);
}
}
Then on the Silverlight side, I use a wrapper around ScriptObject to turn the return value into an exception again. The key here is the TryInvokeMember method:
public class ScriptObjectWrapper : DynamicObject
{
private ScriptObject _scriptObject;
public ScriptObjectWrapper(ScriptObject scriptObject)
{
_scriptObject = scriptObject;
}
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
result = _scriptObject.Invoke(binder.Name, args);
ScriptObject s = result as ScriptObject;
if (s != null)
{
// The JavaScript Error object defines name and message properties.
string name = s.GetProperty("name") as string;
string message = s.GetProperty("message") as string;
if (name != null && message != null && name.EndsWith("Error"))
{
// Customize this to throw a more specific exception type
// that also exposed the name property.
throw new Exception(message);
}
}
return true;
}
public override bool TrySetMember(SetMemberBinder binder, object value)
{
try
{
_scriptObject.SetProperty(binder.Name, value);
return true;
}
catch
{
return false;
}
}
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
try
{
result = _scriptObject.GetProperty(binder.Name);
return true;
}
catch
{
result = null;
return false;
}
}
}
Potentially you could improve this wrapper so it actually injects the JavaScript try-catch mechanism transparently, however in my case I had direct control over the JavaScript source code, so there was no need to do this.
Instead of using the built in JavaScript Error object, it's possible to use your custom objects, as long as the name property ends with Error.
To use the wrapper, the original code would change to:
public MyWrapper()
{
_myJSObject = new ScriptObjectWrapper(
HtmlPage.Window.CreateInstance("MyJSObject"));
}
I'm, accessing Silverlight ObservableCollection count in javascript, but I get the following error.,
Microsoft JScript runtime error: System.FormatException: Input string was not in a correct format.
at System.Number.StringToNumber(String str, NumberStyles options, NumberBuffer& number, NumberFormatInfo info, Boolean parseDecimal)
at System.Number.ParseInt32(String s, NumberStyles style, NumberFormatInfo info)
at System.String.System.IConvertible.ToInt32(IFormatProvider provider)
at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
at System.Windows.Hosting.ScriptingInterface.GetScriptParamValueForType(ScriptParam scriptParam, Type desiredType)
at System.Windows.Hosting.ScriptingInterface.ConvertFromScriptParams(Type[] desiredTypes, ScriptParam[] args)
at System.Windows.Browser.ManagedObjectInfo.ListIndexerMember.Invoke(ManagedObject obj, InvokeType invokeType, ScriptParam[] args)
at System.Windows.Browser.ManagedObjectInfo.Invoke(ManagedObject obj, InvokeType invokeType, String memberName, ScriptParam[] args)
at System.Windows.Hosting.ManagedHost.InvokeScriptableMember(IntPtr pHandle, Int32 nMemberID, Int32 nInvokeType, Int32 nArgCount, ScriptParam[] pArgs, ScriptParam& pResult, ExceptionInfo& pExcepInfo)
I'm using following code, where children is a observablecollection of custom object.,
reg.OnDropping = function (sender, args) {
if (args.toItem.Children.Count > 0) {
args.cancel = true;
}
else {
args.cancel = false;
}
}
Is there any other way to access the count in Javascript?
Regards,
Karthik
In order to access a property of an object from Javascript either the property needs to be marked with the ScriptableMember attribute or the class to which it belongs is marked as ScriptableType. Neither of these is true of the ObservableCollection<T> class.
A pragmatic solution would be to add a HasChildren property to your Custom object:-
[ScriptableMember]
public bool HasChildren
{
get { return Children.Count > 0; }
}
Have you made that observablecollection ScriptableMember? You need to add this attribute to make in available in javascript. Check the following link for more information:
Walkthrough: Calling Managed Code from JavaScript
HtmlPage.RegisterScriptableObject Method
I hope it helps.