I'm trying to make a QML-based dictionary application. It fetches the word definition via an XML RESTful API and displays them in a ListView. I have it working in this rudimentary mode. But now I'm trying to implement two states for the ListView: standard view with the definitions and a "did you mean" type suggestions list for when the search failed.
My current code for the ListView is like this:
ListView
{
SuggestionModel{id:suggestionModel; currentWord : "test"}
SuggestionDelegate{id:suggestionDelegate}
model : XmlModel{id: standardModel; currentWord : "test"}
delegate : ListDelegate{id:standardDelegate}
clip : true
anchors.top : hbox.bottom
y : hbox.height + 3
width : parent.width
height : parent.height - hbox.height
id : list
states :
State { name: "suggestion"; when: list.model == suggestionModel ||
list.model.status == XmlListModel.Ready && list.count == 0
PropertyChanges {
target: list
model : suggestionModel
delegate : suggestionDelegate
}
}
focus : true
keyNavigationWraps : true
}
which gives this error:
Unable to assign QObject* to QDeclarativeComponent*
for the PropertyChanges declaration. There is also a binding loop but that's not really an issue I couldn't fix. My problem is how do I define the states. I can't instantiate the model and delegate inside the State declaration either as the interpreter will complain about creating a state-specific object.
SuggestionDelegate is being instantiated. The delegate property requires a Component which it will instantiate itself for each item it displays. So to provide a Component rather than an instance you need to wrap the SuggestionDelegate in a Component and use the Component id in the PropertyChanges:
Component {
id: suggestionDelegate
SuggestionDelegate { }
}
Though Martin's solution fixed the problem I was having, I came up with a better design for the UI. Since the definitions and suggestions view are mutually exclusive, I implemented each as its own item which have the same geometry and are displayed or hidden according to the current state. This also allows for nice transition animations.
Related
Qt Creator suggests that the onDragChanged slot exists in MouseArea.
MouseArea {
id: mouseArea
...
onDragChanged: console.log('Drag changed')
}
But at runtime it fails with:
Cannot assign to non-existent property "onDragChanged"
The proper way would be:
drag.onActiveChanged: console.log("Drag active:", drag.active)
This is because drag is a group of properties (under the hood it's a QObject or alike), so you need to reference that group first.
Your initial attempt doesn't work because drag is declared as CONSTANT Q_PROPERTY, which doesn't have a on...Changed signal
Silly workaround (but it works...)
readonly property bool _dragActive: drag.active
on_DragActiveChanged: {
... = drag.active
}
I am trying to display a list of contacts in a ListView in QML. The list comes from the c++ side of my app and the contents are displaying just fine.
Now I want to be able to add sections from the name, but it doesn't work as I want.
What is weird is that the following code does work and shows the sections I want :
Working solution :
main.cpp :
QList<QObject*> contactList;
engine.rootContext()->setContextProperty("contactsModel", QVariant::fromValue(contactList));
contoller.cpp :
...
// Call setContextProperty every time the list is updated
m_qmlEngine->setContextProperty("contactsModel", QVariant::fromValue(m_currentUser->contactListObjects()));
...
qml file :
ListView {
id: contactListView
model: contactsModel
section {
property: "name"
criteria: ViewSection.FirstCharacter
delegate: Rectangle {
...
}
}
}
However, the way that i find more elegant only shows the data from the model, not the section headers :
main.cpp :
Controller ctrl(engine.rootContext());
engine.rootContext()->setContextProperty("Controller", &ctrl);
contoller.h :
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(QList<QObject*> contactListModel READ contactListModel NOTIFY contactListModelChanged)
...
};
qml file :
ListView {
id: contactListView
model: Controller.contactListModel
section {
property: "name"
criteria: ViewSection.FirstCharacter
delegate: Rectangle {
...
}
}
}
I find the second method more elegant because the data will be updated automatically from the signal that is emitted, whereas the first solution means I need to call the setContextProperty method each time.
I found a very similar question here, but unfortunately there are no answers.
What difference am I missing between the 2 solutions ?
I'm trying different approaches to styling a QT's app QML items. My goal is to:
limit the amount of code in the main files (hide all styling stuff in styling files, unlike in the Style Singleton approach)
not have to define every single type of item I'm going to use (unlike in the Custom Component approach)
possibly be able to mix and match different pre-defined styles in a single item.
Maybe there is a clear strategy to get this, I just didn't read about it yet. And maybe it doesn't make any sense anyway :-)
I've been playing with the idea of defining different components, one for each style type I want to define. The idea is to:
- define a component which is going to modify its parent
- insert that component where I want to adopt that specific style
A first approach relies on some javascript calls:
MyStyle.qml:
Item {
Component.onCompleted: {
parent.color = "lightblue"
parent.radius = 5
parent.border.width = 3
parent.border.color = "black"
}
}
And in my main code:
Rectangle {
id: testRectangle
anchors.fill: parent
MyStyle {}
}
This gives me the result I want, but it doesn't feel right:
I'd like for the styling to be applied statically, once and for all
if I start using this all over the place, won't I see artifacts when objects get created, and slow down my interface?
So I tried this too:
MyRectangleStyle.qml:
Item {
property Rectangle styleTarget
styleTarget.color: "lightblue"
styleTarget.radius: 5
styleTarget.border.width: 3
styleTarget.border.color: "black"
}
and in my main code:
Rectangle {
id: testRectangle
anchors.fill: parent
MyStyle {styleTarget: testRectangle}
}
But:
well, it doesn't work :-) (no warnings though, qml simply doesn't load)
and I'm sort of back to having to define every single type of items I'm going to use.
So, is there a better way to achieve what I'm trying to do here? Thanks!
Your second method does not work because your Component sets properties to an item that does not necessarily exist at the time of creating the Component, instead it sets the property when the styleTarget changes. On the other hand, it is not necessary for MyStyle to be an Item since it is not shown, instead use QtObject, and finally to generalize your method change property Item styleTarget toproperty Rectangle styleTarget:
QtObject {
property Item styleTarget
onStyleTargetChanged: {
styleTarget.color = "lightblue"
styleTarget.radius = 5
styleTarget.border.width = 3
styleTarget.border.color= "black"
}
}
I have a C++ property
Q_PROPERTY(QList<qreal> XTickPos MEMBER _xTickPos);
which I want to use in a Repeater. In the same QML file, the c++ class has been given the id
id: pw
The repeater looks like this
Item {
anchors.fill: parent
visible: true
Repeater {
model: pw.XTickPos.length
Rectangle{
height: 50
width: 2
x: pw.XTickPos[index]
y:10
visible: true
color: "black"
border.width: 2
}
}
}
However, nothing is drawn on the screen. If instead I make property in the QML file:
var xTickPos = []
and set it via a Q_Invokable function in c++
Q_INVOKABLE QList<qreal> getXTickPositions();
and in QML
root.xTickPos=pw.getXTickPositions();
and use the QML property xTickPos as model in the above repeater it is working. I checked that pw.XTickPos is correctly filled via a console.log
What am I missing here?
This one is kinda tricky.
The documentation states that you can use a JS array as a model, and it does state that QList<qreal> is automatically converted to a JS array when returned to QML.
But it seems that you can't use a QList<qreal> that is automatically converted to a JS array as a model. Go figure...
Naturally, it is preferable to have a proper model with its proper notifications for top efficiency. But in case you really want to go for the list property, it appears you will have to do the conversion manually in a getter:
QVariantList model() {
QVariantList vl;
for (auto const & v : yourList) vl.append(v);
return vl;
}
Amazingly, although Qt supposedly makes that conversion automatically, it doesn't seem to be able to make a QVariantList from a QList<qreal>.
That's Qt for you...
I'm using QML for my project, I want to know if am instantiating a file in another file, is it like instantiating object for a c++ class?
File.qml
Rectangle {
id: idRect1
.
.
}
File2.qml
Rectangle {
id: idRect2
File1 {
id:idFile1
.
.
}
}
In File2.qml i have initialized File1, does it means i have created an object of type File1? Please share some knowledge(links) on how all this mechanism works. Thanks in Advance
In QML when creating a file with first letter uppercase, you're creating a component. Components are implemented using OOP aggregation (not subclassing). That mean if I write
// MyButton.qml
import QtQuick 2.0;
Rectangle {
id: base;
width: 120;
height: 40;
color: "lightgray";
Text {
text: "foobar";
anchors.centerIn: parent;
}
}
... I haven't subclassed Rectangle, I just created a component that contains a Rectangle as root object, and configurates it in a certain way, and adds a Text object in it.
As soon as a component is created, it can be instanciated by simply writing :
MyComponent { id: myNewInstance; }
Because that's a way it works in QML.
The component name is a kind of class (but not in the C++ or JS way to define it) and it can also be used as a type for a property :
property MyComponent theComponent : myNewInstance;
Then it can hold the ID of an object created with the given component, acting somewhat like a C/C++ pointer : the property holds a link to the actual object.
But because of the way QML was designed, even if it's more aggregating than subclassing, a property of the type of the root object of a custom component can also hold ID of a derived component, in my case :
property Rectangle theComponent : myNewInstance;
Will work, but if I try to put an ID of an Image or Text or something else, QML engine will throw incompatible types error.
I hope it helps you.