I'm trying to improve my QML skills, by creating a memory game as a multiplayer application. (also known as Concentration).
I created already c++ classes like Player, Game and Card. The Game class holds a vector of a vector of Cards (QVector< QVector< Card*>> m_field;), which represents the field of all cards. The Game class is registered to the QML engine as singleton type, using "qmlRegisterSingletonType(..)".
For now I'm creating the UI, using repeaters as shown in the code below.
Is there a more convenient way to create such a UI? Can I somehow attach an instance of the c++ class Card to the QML instance of Card?
import QtQuick 2.11
import QtQuick.Window 2.11
import de.memory.game_instance 1.0
Window {
id: root
visible: true
width: 450
height: 700
title: qsTr("Memory Game")
Column {
id: col
Repeater {
id: rows
model: Constants.GAME_FIELD_HEIGHT
delegate: Row {
property int row_idx: modelData
Repeater {
id: cells
model: Constants.GAME_FIELD_WIDTH
delegate: Card {
height: root.height / Constants.GAME_FIELD_HEIGHT
width: root.width / Constants.GAME_FIELD_WIDTH
img_source: Game.getImageAt(row_idx, modelData);
img_shown: false
}
}
}
}
}
}
Related
I'm trying to make an equivalent to wpf stackpanel, I already had a logic and implemented it but something is wrong about width, I don't know how to create new components without getting into width loop binding, here is my stackpanel:
StackPanel.qml
import QtQuick 2.12
import QtQuick.Controls 2.12
import KiMa.Models 1.0
Item {
id:root
property var orientation : UOrientation.Horizontal
property int itemSpacing : 10
default property list<Item> pageData
Loader{
property var childs
anchors.fill: parent
id:loader
onChildsChanged: {
if(root.pageData != null){
for(var z = 0;z<root.pageData.length;++z){
root.pageData[z].parent = loader.childs
}
}
}
}
state: orientation == UOrientation.Horizontal ? "row": "col"
states: [
State {
name: "row"
PropertyChanges {
target: loader
sourceComponent : row
}
},
State{
name: "col"
PropertyChanges {
target: loader
sourceComponent : col
}
}
]
Component{
id:col
Column{
Component.onCompleted: {
childs = _col;
}
id:_col
width: parent.width
spacing: root.itemSpacing
}
}
Component{
id:row
Row{
Component.onCompleted: {
childs = _row
}
id:_row
width: parent.width
layoutDirection: Qt.RightToLeft
spacing: root.itemSpacing
}
}
}
and my orientation enum is like this:
#ifndef UORIENTATION_H
#define UORIENTATION_H
#include<QObject>
class UOrientation
{
Q_GADGET
public:
explicit UOrientation();
enum Orientation{
Horizontal,
Vertical
};
Q_ENUM(Orientation)
};
#endif // UORIENTATION_H
and usage example should be like this:
StackPanel{
x: 320
height: 50
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 25
Button{
}
Button{
}
}
you need to add this into main.cpp:
qmlRegisterUncreatableType<UOrientation>("KiMa.Models",1,0,"UOrientation","its not creatable type!");
This code is working , if you have anything to suggest me to change or you think I made a mistake let me know, the only problem I can see here is width binding.
I already tried using childrenRect but it is not working:
width: childrenRect.width
height: childrenRect.height
Note : stackpanel allowing you to stack item after item on top of each other you can set orientation to horizontal or vertical so in qt its a column and row together which i made it already.
vertical one :
horizontal one :
You can do this easily with a Grid by setting the number of columns.
If you want a separate component, you can create your StackPanel.qml with the following:
import QtQuick 2.0
Grid {
property int orientation: Qt.Horizontal
columns: orientation === Qt.Horizontal ? -1 : 1
}
If you want a scrollable object, you could also use a ListView with an ObjectModel model. ListView has an orientation property.
Suppose on C++ side I've created a QList<QObject *> myObjects which contains several custom objects derived from QObject.
And then expose it to QML by setContextProperty( "myModel", QVariant::fromValue( myObjects ) );
The question is, in my QML code, how can I get and use a specific element (by index) in myModel (which is a QList). For instance, I would like to take a random element from the list and show it?
The example is here: http://doc.qt.io/qt-5/qtquick-models-objectlistmodel-example.html, where all elements of the model are shown in a ListView`, while I just want to show one (or several) of them.
its pretty easy...
to get item number i from the model:
myModel[i]
and to access its properties/roles:
myModel[i].propertyName
To get an item from the list you can use the [] operator:
myModel[index]
The elements of a QList are similar to arrays in javascript since QML is based on the latter.
The following example shows getting the names in random form (it only replaces the code in the example).
view.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
import QtQuick.Controls 1.4
//![0]
ColumnLayout{
ListView {
width: 100; height: 100
model: myModel
delegate: Rectangle {
height: 25
width: 100
color: model.modelData.color
Text {
text: name
}
}
}
Button {
text: "random"
onClicked: {
t.text = myModel[ Math.floor(Math.random()*myModel.length)].name;
}
}
Text{
id: t
text: ""
}
}
Background.qml
import QtQuick 1.1
Item {
MouseArea {
id: backgroundMouseArea
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
console.log("Background")
}
}
}
Foreground.qml
import QtQuick 1.1
Item {
Background {
width: 1920
height: 1080
}
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
console.log("Foreground")
[mouse.accepted = false] - Not working (as the docs say)
[backgroundMouseArea.onPositionChanged(mouse)] - Not working
}
}
}
I need to execute onPositionChanged event on both background and foreground items.
F.ex. for onPressed I would do it by setting mouse.accepted = false in the foreground item.
Can I call onPositionChanged of the background item manually? If yes, how do I do it?
I am not completely sure what you are trying to achieve here.
A MouseArea is meant to grab mouse events from hardware. If you really want to propagate mouse events to background from a different MouseArea, maybe what you actually want to do is give Background a simple property mousePosition instead of the MouseArea, and then set that position from the Foreground onPositionChanged handler.
Also, your Foreground code relies on an internal id parameter inside of Background. This smells really bad. It is often more useful to think about the "Public API" of the Background and Foreground "classes". If what I described above is really what you want to do, this is what it should look like IMHO:
// Background.qml
import QtQuick 1.1
Rectangle {
// an object with just x and y properties
// or the complete mouseevent, whatever you want
// Use variant for QtQuick 1/Qt4, var for QtQuick 2.0 / Qt5
property variant mousePosition
onMousePositionChanged: console.log(
"Background " + mousePosition.x + " " + mousePosition.y
)
}
//Foreground.qml
import QtQuick 1.1
Item {
// use only ids defined in the same file
// else, someone might change it and not know you use it
Background { id: background }
MouseArea {
anchors.fill: parent
hoverEnabled: true
onPositionChanged: {
console.log("Foreground")
background.mousePosition = {x: mouse.x, y: mouse.y}
}
}
}
...........
I have a main.qml which loads Page1.qml using loaders. How can I find object 'whiteArea' within Page1.qml from my cpp code?
I am currently using the following to fetch an object and would like to obtain the loaded qml as well like this as well.
QObject * object = engine.rootObjects().at(0)->findChild<QObject *> ("yourObjectName");
main.qml
import QtQuick 2.3
import QtQuick.Controls 1.2
import myplugin 1.0
ApplicationWindow {
id:app
visible: true
width: 640
height: 480
title: qsTr(" World")
objectName: "Appwindow"
property ApplicationWindow appwindow:app
Label {
objectName: "label"
text: qsTr(" World")
anchors.centerIn: parent
}
MyItemTest{
objectName: "myItem"
anchors.fill: parent
}
Rectangle{
objectName: "Rectangle"
id:rect
width: 50
height: 50
color: "yellow"
}
Button {
objectName: "MyButton"
id: btnClick
text : "btn"
Loader { id: pageLoader }
onClicked: {
pageLoader.source = "Page1.qml"
}
}
}
Page1.qml
import QtQuick 2.0
import QtQuick 2.3
import QtQuick.Controls 1.2
import myplugin 1.0
Item {
Rectangle{
objectName: "whiteArea"
id:rect
width: 50
height: 50
color: "white"
}
}
From the Qt documentation:
The loaded object can be accessed using the item property.
So you should do some subsearch inside a loaded item, for example like this:
QObject * loader = engine.rootObjects().at(0)->findChild<QObject*>("loader");
qWarning() << loader;
QObject * item = qvariant_cast<QObject*>(QQmlProperty::read(loader,"item"));
qWarning() << item;
QObject *whiteArea = item->findChild<QObject *>("whiteArea");
qWarning() << whiteArea;
The output:
QQuickLoader(0x24918240, name = "loader")
QQuickItem(0x24919740)
QQuickRectangle(0x24919728, name = "whiteArea")
First of all , give Loader an object name property, like "loader".
then be sure at the time you running the below code , loader.item is set with"Page1.qml" then do something like this:
QObject* loader = m_engine->rootObjects()[0]->findChild<QObject*>("loader");
QObject* page= qvariant_cast<QObject *>(loader->property("item"));
QObject* whiteArea = page->findChild<QObject*>("whiteArea");
I want to use qml with master-detail interface, but i don't know how to pass current item to detail view right way. The ListView in master view uses C++ model (add-on of QSQLTableModel, it's work fine) and I see two ways to pass item:
Create C++ classes with fields with static name like QSqlRecord field names and pass it to qml with w->rootContext()->setContextProperty() (w is QDeclarativeView *), but now i don't use any classes like this and can change my database and qml views without changing c++ code, I would like to save it
Create a lot of properties in any detail qml like
Rectangle {
id: mainRect
property alias myFieldName: txt_nominal.text
Column {
width: parent.width
Text {
id: txt_nominal
font.bold: true
}
}
}
and set this properties from c++ code by setting w->rootContext()->setContextProperty(record.fieldName(i),record.field(i).value()); (record - QSqlRecort at current row)
Is there any easier way to solve my problem?
PS The code I wrote above is not checked for accuracy, and is written to make it more clear what I mean
UPD
Maybe it will be useful for somebody, I discovered 3-rd way, rather, the modification of second - you can wrap fields into QVariantMap and pass only one object to qml. This is exactly what I wanted
in cpp:
QVariantMap testObject;
testObject["testField"]="first string from cpp";
testObject["testField2"]="second string from cpp";
rootContext()->setContextProperty("testObject",testObject);
in qml:
Text {
id: simpleTextt
text: testObject.testField
anchors.centerIn: parent
}
You could use the isCurrentItem property of the delegate to pass the data from ListView delegate to your details qml. That way you could get away without have to add additional c++ code. This is basically your second approach but without c++. You also do not need to add many properties as long as each of your QML elements that you want to change have an id.
If you have a number of different QML for different details views you would also have to use the Loader to load the appropriate details QML.
Just a toy example assuming that you have only one details template for all of your elements in the list (as mentioned above if that is not the case than you can use loader instead of detailsRect):
Rectangle {
width: 300; height: 400
Rectangle {
id: detailsRect
anchors.right: parent.right
width: 100
height: 500
color: "blue"
Text {
id: detailsText
text: ""
}
}
ListView {
id: list
anchors.fill: parent
model: 20
delegate: Rectangle {
color: ListView.isCurrentItem ? "red" : "green"
width: 40
height: 40
Text {
text: index
}
ListView.onIsCurrentItemChanged: {
if(ListView.isCurrentItem)
{
detailsRect.color = "yellow"
detailsText.text = index
}
}
MouseArea {
anchors.fill: parent
onClicked: {
list.currentIndex = index
}
}
}
}
}