Form Qml e classe in c++
-
Salve a tutti, sono nuovo del forum e mi sto avvicinando solo adesso al mondo Qt.
Vorrei dare un minimo di grafica ad un programma che ho scritto e vorrei farlo con qml.
Ho una classe "Game" che riporto qui:class Game { public: Game(); ~Game(); enum ModeGame { U_Mode = 0, D_Mode, T_Mode }; //Modalita Gioco ModeGame get_mode_game(); void set_mode_game(ModeGame modegame); //Players bool add_players(std::string *nick = nullptr, std::string *name = nullptr, std::string *surname = nullptr); bool rem_players(std::string *nick = nullptr, std::string *name = nullptr, std::string *surname = nullptr); bool mod_players(std::string *nick = nullptr, std::string *nick_new = nullptr, std::string *name = nullptr,std::string *name_new = nullptr, std::string *surname = nullptr,std::string *surname_new = nullptr); int get_list_players(std::vector<Players> &list_players); private: time_t time_start_game; ModeGame modegame; std::vector<Players> player; int found_id(std::string *nick = nullptr, std::string *name = nullptr, std::string *surname = nullptr); };
Nel file main.qml uso il loader che carica rispettivamente il file qml per la selezione del tipo di game e il file qml per l'aggiunta di un players.
Riporto lo spezzone di codice del main.qml//----------------------------------MENU--------------------------------// Loader { id: loaderMenu state: "SelezioneModalita" anchors.fill: parent states: [ State { name: "SelezioneModalita" PropertyChanges { target: loaderMenu; source: "qrc:/GUI/Menu_Modalita/MenuModalita.qml"; } PropertyChanges { target: indietro_btn; visible: false;} PropertyChanges { target: avanti_btn; visible: true;} }, State { name: "SelezioneNumPlayers" PropertyChanges { target: loaderMenu; source: "qrc:/GUI/Menu_Num_Players/MenuNumPlayers.qml"; } PropertyChanges { target: indietro_btn; visible: true;} PropertyChanges { target: avanti_btn; visible: true;} } ] }
Nel file MenuModalita.qml ho 3 pulsanti (rettangoli con una mausearea) che sono le rispettive modalita di gioco della classe Game mentre in MenuNumPlayers.qml seleziono il numero di players e quindi ho nick, name e surname.
Ho letto un bel po su come far interaggire qml e c++ ma al momento ho solo confusione. Sapreste darmi anche solo consigli su come proseguire o organizzare il codice?
Grazie anticipatamente -
Hai ragione, prendo in considerazione i tuoi consigli.
La domanda è: avendo un arrey di oggetti (Players) dentro la mia classe (Game) come potrei creare un interfaccia grafica in Qml per fare Add / Remove?Come posso esportare questo array da c++ in qml cosi da modificarlo?
-
Non è così semplice. Attenzione che qml è sostanzialmente javascript per cui ciò che puoi fare e ciò che si può fare con javascript.
Ti passo però 2 righe di codice che ti faranno risparmiare un po' di tempo.
Se da qml vuoi invocare un metodo cpp il metoto va dichiarato così:Q_INVOKABLE QString propertyGetString(QString Property);
Se da cpp vuoi invocare una funzione qml devi emettere un evento
void temperatureUpdate();
Poi se cerchi trovi molta documentazione.
Ciao ciao.
-
Ti rigranzio infinitamente per le dritte.
Ne approfitto per chiederti se sto seguendo una "retta via". Volevo provare a fare ciò che ho detto con una QVariantList in c++ e una "function readValues(anArray)" in qml per poter visualizzare la lista di elementi.Inoltre avevo pensato di non usare un arrey di oggetti ma qualcosa (ancora devo capire cosa) che possa andar bene con QVariantList.
Potrebbe andare bene questa strada?
-
Come posso esportare questo array da c++ in qml cosi da modificarlo?
Personalmente credo che tu debba fare il contrario. Il principio di QML e' che la logica sia nella parte C++ e QtQuick gestisca solo l'interfaccia.
Facciamo un esempio:
- In qml metti 3 linedit (nick, name surname), un pulsante di aggiunta e uno di rimozione e un tableview (quella dei Qt Quick Controls 2).
- C++ espone un modello (
QStandardItemModel
ad esempio) tramiteQ_PROPERTY(QAbstractItemModel* model, READ model)
- Il pulsante di aggiunta passa i contenuti dei lineedit al C++ che li aggiunge al modello
- L'edit e' gia' implementato, non devi fare nulla manualmente
- il pulsante di remove passa al C++ l'indice di riga/ghe selezionata/e e lui la/e rimuovera' dal modello
-
Ringrazio anche te per essere intervenuto nella discussione. Ho seguito il tuo consiglio ma sono fermo in un punto.
Ho creato un QAbstractTableModel in questo modo#ifndef GAME_H #define GAME_H #include <QAbstractTableModel> #include <QList> #include "players.h" class Game : public QAbstractTableModel { Q_OBJECT Q_ENUMS(ModeGame) Q_PROPERTY(Game::ModeGame modegame READ get_ModeGame WRITE set_ModeGame NOTIFY modegame_Changed) public: explicit Game(QObject *parent = 0); enum ModeGame{NS, Static, Projector}; void set_ModeGame(Game::ModeGame modegame); Game::ModeGame get_ModeGame(); void setPlayers(QList<Players> players); // Basic functionality: int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; // Header Data: QVariant headerData(int section, Qt::Orientation orientation, int role) const override; // Editable: bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; Qt::ItemFlags flags(const QModelIndex& index) const override; // Add data: bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; // Remove data: bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; signals: void modegame_Changed(); private: QList<Players> players; Game::ModeGame modegame; }; #endif // GAME_H
in un file di prova qml ho questo:
import QtQuick 2.10 import QtQuick.Window 2.10 import QtQuick.Layouts 1.3 import QtQuick.Controls 1.4 Window { visible: true width: 640 height: 480 title: qsTr("Hello World") ColumnLayout { id: rowLayout anchors.fill: parent TextInput { id: name color: "red" text: "name" Layout.fillWidth: true focus: true } TextInput { id: surname color: "red" text: "surname" Layout.fillWidth: true focus: true } TextInput { id: nick color: "red" text: "nick" Layout.fillWidth: true focus: true } Button { text: "Add" // onClicked: Game.data(); } Button { text: "Rem" // onClicked: Game.data(); } ListView{ id:list_view Layout.fillWidth: true Layout.fillHeight: true model: Game } } }
Non riesco pero a vedere tutti i metodi in qml. Esempio, non riesco a vedere insertRows e removeRows. Dove mi sto perdendo?
-
ti manca solo da aggiungere
Q_INVOKABLE
davanti a quei metodi.Occhio che subclassare un modello e' un campo minato. Se puoi, dichiara Players come
Q_GADGET
, e usaQStandardItemModel
direttamente:class Player{ Q_GADGET Q_PROPERTY(QString nick MEMBER m_nick) Q_PROPERTY(QString name MEMBER m_name) Q_PROPERTY(QString surname MEMBER m_surname) public: Player(const QString& nick,const QString& name ,const QString& surname) :m_nick(nick),m_name(name),m_surname(surname){} Player() = default; Player(const Player& other)=default; Player& operator=(const Player& other)=default; QString m_nick; QString m_name; QString m_surname; };
class Game : public QObject{ Q_OBJECT Q_PROPERTY(QAbstractItemModel* model READ model) Q_PROPERTY(ModeGame modeGame READ modeGame WRITE setModeGame NOTIFY modeGameChanged) Q_DISABLE_COPY(Game) public: enum ModeGame { U_Mode = 0, D_Mode, T_Mode }; Q_ENUM(ModeGame) Game(QObject* parent = nullptr) :QObject(parent){ m_model=new QStandardItemModel(this); m_model->inserColumn(0); } Q_SLOT void addPlayer(const QString& nick,const QString& name ,const QString& surname){ const int newRow = m_model->rowCount(); m_model->insertRow(newRow); m_model->setData(m_model->index(newRow,0),Player(nick,name,surname)); } Q_SLOT void removePlayer(int index){ m_model->removeRow(index); } QAbstractItemModel* model() const {return m_model} Q_SIGNAL void modeGameChanged(ModeGame modegame); void setModeGame(ModeGame modegame){ if(m_modegame==modegame) return; m_modegame=modegame; modeGameChanged(modegame); } ModeGame modeGame() const{return m_modegame;} private: QAbstractItemModel* m_model; ModeGame m_modegame; };
P.S.
Q_ENUMS
e' deprecato, usaQ_ENUM
: https://woboq.com/blog/q_enum.html -
Perfetto ho capito che la mia soluzione è meglio che la metta di lato e uso la tua. Della tua soluzione però non mi sono chiare due cose e approfitto della tua pazienza per togliermi questi dubbi.
La prima è: perche mi conviene usare dichiarare Players come Q_GADGET?
La seconda: ho un errore con il setData ( m_model->setData(m_model->index(newRow,0),Player(nick,name,surname));)
Ll'ho usato nel tuo stesso modo ma mi sfugge l'errore -
@GiGa91 said in Form Qml e classe in c++:
perche mi conviene usare dichiarare Players come Q_GADGET?
E' semplicemente per incapsulare
Player
. se non lo dichiari come Q_GADGET dovresti, dentroGame
, dichiarare cose tipoQ_INVOKABLE QString getNick(int row);
mentre se e' Q_GADGET puoi usare direttamente le sue proprieta' nel delegateho un errore con il setData
Cosa ti scrive l'errore?
-
Ah giusto non ci avevo pensato.
Per quanto riguarda l'errore, me lo da a causa del fatto che gli sto dando "players". Precisamente questo:
error: no matching function for call to ‘QAbstractItemModel::setData(QModelIndex, Player)’
m_model->setData(m_model->index(newRow,0),Player(nick,name,surname));
no known conversion for argument 2 from ‘Player’ to ‘const QVariant&’
^ -
Quasi tutto chiaro.. mi manca un passaggio solo.
Avendo esportato la classe verso qml:
Game game; engine.rootContext()->setContextProperty("Game", &game);
ho a disposizione i metodi addPlayer e removePlayer che posso usare in modo molto semplice:
onClicked: Game.addPlayer(qsTr(text_input_name.text), qsTr(text_input_surname.text), qsTr(text_input_nick.text))
tramite la property io dovrei poter leggere il modello su Qml
Q_PROPERTY(QAbstractItemModel* model READ model)
Pero non riesco. Ad esempio:
Button { text: "Add" onClicked: { Game.addPlayer(qsTr(text_input_name.text), qsTr(text_input_surname.text), qsTr(text_input_nick.text)) } } Button { text: "Rem" onClicked: Game.removePlayer(index) } ListView { id: listView Layout.fillWidth: true model: Game.model delegate: listDelegate } Component { id: listDelegate Text { id: name_text text: Game.model.data(0,1) } }
-
qsTr
non funziona cosi'. Puo' solo tradurre costanti e, comunque, non vedo il punto di tradurre un nome proprio- Il problema e' nel delegate
text: Game.model.data(0,1)
non funziona cosi'. E' un po' che non uso QML ma se ricordo bene era role.property quindi nel tuo caso qualcosa tipotext: display.nick + " - " + display.name + " " + display.surname
-
ops ho sbagliato a ricopiare. Questo non centra nulla:
text: Game.model.data(0,1)
Il punto che non capisco è proprio questo. Per esempio ti riporto un codice di esempio di Qml:
ListModel { id: nameModel ListElement { name: "Alice"; team: "Crypto" } ListElement { name: "Bob"; team: "Crypto" } ListElement { name: "Jane"; team: "QA" } ListElement { name: "Victor"; team: "QA" } ListElement { name: "Wendy"; team: "Graphics" } } Component { id: nameDelegate Text { text: name; font.pixelSize: 24 anchors.left: parent.left anchors.leftMargin: 2 } }
Nel delegate dovrebbe bastare mettere "name" e in modo trasparente dovrebbe vedermi la propieta di ListElement (nel caso dell'esempio)
Usando la Q_PROPERTY sbaglio a pensare di ottenere la stessa cosa verso il modello Game? -
esatto,
name
in quel caso e' il role in cui salvi "Alice".
Quando chiamim_model->setData
in realta' i parametri sono 3: indice della cella da cambiare, valore da inserire e role del valore. Siccome noi non mettiamo nulla nel terzo parametro il default e'Qt::EditRole
In QML non usiQt::EditRole
ma il nome corrispondente (qui trovi la lista dei nomi per i role standard).
Io usodisplay
invece diedit
perche' inQStandardItemModel
display e edit sono la stessa cosa (ma questo e' un dettaglio irrilevante)
Oradisplay
ritorna unaQVariant
che contiene unPlayer
. Puoi usare.
per accedere alle sue proprieta' quindidisplay.nick
(oedit.nick
se preferisci)quindi:
Component { id: nameDelegate Text { text: edit.nick font.pixelSize: 24 anchors.left: parent.left anchors.leftMargin: 2 } }
dovrebbe funzionare
Edit:
set ti piace di piu' puoi anche modificare il costruttore diGame
cosi':Game(QObject* parent = nullptr) :QObject(parent){ m_model=new QStandardItemModel(this); m_model->inserColumn(0); QHash<int, QByteArray> roleNames = m_model->roleNames(); roleNames.insert(Qt::UserRole,"player"); static_cast<QStandardItemModel*>(m_model)->setItemRoleNames(roleNames); }
e quando chiami setData aggiungi il terzo parametro:
m_model->setData(m_model->index(newRow,0),QVariant::fromValue(Player(nick,name,surname)),Qt::UserRole);
adesso in QML puoi fare:
Component { id: nameDelegate Text { text: player.nick font.pixelSize: 24 anchors.left: parent.left anchors.leftMargin: 2 } }
-
Strano perche' ha funzionato per me. Avevo praticamente la stessa tua domanda: https://forum.qt.io/topic/71433/best-practice-to-add-dynamic-qml-from-c-data/12
Probabilmente devi solo aggiungere
Q_DECLARE_METATYPE(Player)
in player.h eqmlRegisterType<Player>("com.game", 1, 0, "Player");
nella main() -
Perdona la mia ignoranza, ma io posso esportare la classe player tramite qmlRegistrerType? Non è definita come public Qobject e se devo essere sincero ci avevo gia provato ma ottenevo/ottengo questo errore:
error: cannot convert ‘QQmlPrivate::QQmlElement<Player>*’ to ‘QObject*’ for argument ‘1’ to ‘void QQmlPrivate::qdeclarativeelement_destructor(QObject*)’ QQmlPrivate::qdeclarativeelement_destructor(this); ^
Ho aggiunto Q_DECLARE_METATYPE(Player) in players.h
Considerando che a te il codice funzione e la probabilità che io abbia dimenticato qualcosa è alta riporto i miei quattro file:
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include <QtQml> #include "Classe_Game/Game.h" #include "Classe_Players/Players.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); //qmlRegisterType<Player>("com.helloworld.qmlcomponents",1,0,"Player"); QQmlApplicationEngine engine; Game game; engine.rootContext()->setContextProperty("Game", &game); engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); }
Players.h
#ifndef PLAYERS_H #define PLAYERS_H #include <QObject> class Player { Q_GADGET Q_PROPERTY(QString nick MEMBER m_nick) Q_PROPERTY(QString name MEMBER m_name) Q_PROPERTY(QString surname MEMBER m_surname) public: Player(const QString& nick,const QString& name ,const QString& surname) :m_nick(nick),m_name(name),m_surname(surname){} Player() = default; Player(const Player& other)=default; Player& operator=(const Player& other)=default; QString m_nick; QString m_name; QString m_surname; }; Q_DECLARE_METATYPE(Player) #endif // PLAYERS_H
Game.h
#ifndef GAME_H #define GAME_H #include <QStandardItemModel> #include <QList> #include "Classe_Players/Players.h" class Game : public QObject{ Q_OBJECT Q_PROPERTY(QAbstractItemModel* model READ model) Q_PROPERTY(ModeGame modeGame READ modeGame WRITE setModeGame NOTIFY modeGameChanged) Q_DISABLE_COPY(Game) public: enum ModeGame{NS, Static, Projector}; Q_ENUM(ModeGame) Game(QObject* parent = nullptr) :QObject(parent){ m_model=new QStandardItemModel(this); m_model->insertColumn(0); QHash<int, QByteArray> roleNames = m_model->roleNames(); roleNames.insert(Qt::UserRole,"player"); static_cast<QStandardItemModel*>(m_model)->setItemRoleNames(roleNames); } Q_SLOT void addPlayer(const QString& nick,const QString& name ,const QString& surname){ const int newRow = m_model->rowCount(); m_model->insertRow(newRow); m_model->setData(m_model->index(newRow,0),QVariant::fromValue(Player(nick,name,surname)),Qt::UserRole); } Q_SLOT void removePlayer(int index){ m_model->removeRow(index); } QAbstractItemModel* model() const { return m_model; } Q_SIGNAL void modeGameChanged(ModeGame modegame); void setModeGame(ModeGame modegame){ if(m_modegame==modegame) return; m_modegame=modegame; modeGameChanged(modegame); } ModeGame modeGame() const{return m_modegame;} private: QAbstractItemModel* m_model; ModeGame m_modegame; }; #endif // GAME_H
main.qml
import QtQuick 2.10 import QtQuick.Window 2.10 import QtQuick.Layouts 1.3 import QtQuick.Controls 1.4 Window { visible: true width: 640 height: 480 title: qsTr("Hello World") ColumnLayout { id: rowLayout objectName: "rowLayout" anchors.fill: parent TextInput { id: text_input_name color: "red" text: "name" Layout.fillWidth: true focus: true } TextInput { id: text_input_surname color: "red" text: "surname" Layout.fillWidth: true focus: true } TextInput { id: text_input_nick color: "red" text: "nick" Layout.fillWidth: true focus: true } Button { text: "Add" onClicked: { Game.addPlayer(text_input_name.text, text_input_surname.text, text_input_nick.text) } } Button { text: "Rem" //onClicked: Game.removePlayer(index) } ListView { width: 200; height: 250 model: Game delegate: Text { text: players.name } } } }
-
@GiGa91 said in Form Qml e classe in c++:
model: Game
il modello non e'
Game
, e'Game.model
text: players.name
il role non e'
players
, e'player
Game.addPlayer(text_input_name.text, text_input_surname.text, text_input_nick.text)
Hai sbagliato l'ordine degli argomenti:
Game.addPlayer(text_input_nick.text, text_input_name.text, text_input_surname.text)
Q_DECLARE_METATYPE(Player)
Non e' necessario, Q_GADGET fa gia' tutto
//onClicked: Game.removePlayer(index)
devi dare un id a ListView (per esepio
id: playerView
) e poi fareonClicked: Game.removePlayer(playerView.currentIndex)