使用 QAbstractItemModel 将 2D C++ 游戏板暴露给 QML
Expose 2D C++ Game Board to QML using QAbstractItemModel
我正在用 C++ 中的游戏板模型编写一个简单的贪吃蛇游戏,其中包含一个二维状态向量 (std::vector<std::vector<board::state>>
)。现在我想将此板公开给 QML,以便它基本上是某种 grid/chess 板,可以访问模型的状态。
我已经阅读了很多关于这个主题的文章,但仍然无法理解足够的机制来解决我的问题。将文档、教程和博客条目应用到我的问题是我的障碍。
我为我的游戏板模型子类化 QAbstractItemModel
并实现了必要的功能。现在我想进入 QML 和 use/show 我的模型的内容。
这是我的代码:
board.h
#pragma once
#include <vector>
#include <QAbstractItemModel>
class board : public QAbstractItemModel
{
Q_OBJECT
public:
enum class state
{
empty,
snake,
fruit
};
board(int x, int y);
state get_state(int x, int y) const;
void set_state(int x, int y, state state);
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex& index) const;
int rowCount(const QModelIndex& parent = QModelIndex()) const;
int columnCount(const QModelIndex& parent = QModelIndex()) const;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
private:
std::vector<std::vector<state>> m_board;
};
Q_DECLARE_METATYPE(board::state)
board.cpp
#include "board.h"
board::board(int x, int y) :
m_board(x, std::vector<board::state>(y, board::state::empty))
{
}
//----------------------------------------------------------------------------------------------------------------------
board::state board::get_state(int x, int y) const
{
if((size_t) x >= m_board.size() || x < 0)
return board::state::empty;
if((size_t) y >= m_board.at(0).size() || y < 0)
return board::state::empty;
return m_board.at(x).at(y);
}
//----------------------------------------------------------------------------------------------------------------------
void board::set_state(int x, int y, state state)
{
if(get_state(x, y) == state)
return;
m_board.at(x).at(y) = state;
}
//----------------------------------------------------------------------------------------------------------------------
QModelIndex board::index(int row, int column, const QModelIndex&) const
{
if((size_t) row >= m_board.size() || row < 0)
return QModelIndex();
if((size_t) column >= m_board.at(0).size() || column < 0)
return QModelIndex();
return createIndex(row, column);
}
//----------------------------------------------------------------------------------------------------------------------
QModelIndex board::parent(const QModelIndex& index) const
{
if((size_t) index.row() >= m_board.size() || index.row() < 0)
return QModelIndex();
if((size_t) index.column() >= m_board.at(0).size() || index.column() < 0)
return QModelIndex();
return createIndex(index.row(), index.column());
}
//----------------------------------------------------------------------------------------------------------------------
int board::rowCount(const QModelIndex&) const
{
return m_board.size();
}
//----------------------------------------------------------------------------------------------------------------------
int board::columnCount(const QModelIndex&) const
{
return m_board.at(0).size();
}
//----------------------------------------------------------------------------------------------------------------------
QVariant board::data(const QModelIndex& index, int role) const
{
if(!index.isValid())
return QVariant();
if(role != Qt::DisplayRole)
return QVariant();
if((size_t) index.row() >= m_board.size() || index.row() < 0)
return QVariant();
if((size_t) index.column() >= m_board.at(0).size() || index.column() < 0)
return QVariant();
return qVariantFromValue(get_state(index.row(), index.column()));
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickView>
#include <board.h>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
board game_board(10, 10);
game_board.set_state(4, 9, board::state::snake);
game_board.set_state(3, 10, board::state::fruit);
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myGameBoard", &game_board);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
}
GridView {
model: myGameBoard
delegate: Rectangle {
width: 30
height: 30
color: blue
}
}
}
我敢肯定我遗漏了很多东西或者是明显错误的东西。我也知道将 C++ 模型暴露给 QML 是经常被问及的问题,但我仍然无法做到。
再说几个具体的问题:
- 如何在 QML 中显示模型中的数据?我已经看到这是用角色名称完成的,但我的行没有为其列的角色名称
- 我的模型是否正确实施?
- 我需要使用什么 QML 组件来拥有一个 tile/chess 板,如果我需要一个自定义组件,它需要哪些属性才能从模型中读取数据?
感谢观看!
QtQuick,使用 QML 的主要 UI 框架,基本上只处理列表模型,因此使用角色来模拟 tables,例如使用 TableView
parent()
方法是错误的,因为它基本上再次 returns 相同的索引。这不会对你的情况造成任何问题,因为你有一个 table 而不是一棵树。
建议:如果您只需要一个 table 模型,从 QAbstractTableModel
派生并让它处理 index()
和 parent()
- 没有可以处理 table 模型的标准 QtQuick 元素,因此您必须构建自己的模型或使用列表模型,只需将 "list items" 排列在网格中即可。
如果是自定义项目,您可以构建一个适用于您的模型的项目,甚至可以直接处理数据,而无需模型。
如果您使用该模型并希望从 QML 实例化该模型,那么您需要 "pointer to your model class" 的 属性 或“指向 QAbstractItemModel
.
的指针
如果您不想使用模型或不需要从 QML 实例化它,那么您根本不需要任何特定的 属性。
无论哪种情况,您的自定义项目都可以使用以下方法之一:
- 它可以自己绘制所有东西,即瓷砖的网格和瓷砖本身
- 只需提供网格并使用委托来绘制不同类型的图块,即像
ListView
允许为列表元素、页眉、页脚等设置委托
我有一个适合我的解决方案。这是我所做的和一些代码:
- 我使用 Q_ENUM 作为状态枚举以使其在 QML 中可用
- 已将 rootContext 连接到 QML 引擎
- 编写了一个自定义 QML 组件,该组件由带有转发器的列组成,包含带有转发器的行
- 保存外层中继器和内层中继器的索引,编制访问数据的索引
- 一些清理工作
代码
Board.qml
import QtQuick 2.0
Column {
Repeater {
model: myGameBoard.columnCount()
Row {
property int y_pos: index
Repeater {
id: repeatr
model: myGameBoard.rowCount()
Cell {
x_cord: index
y_cord: y_pos
size: 20
}
} //Repeater
} //Row
} //Repeater
} //Column
Cell.qml
import QtQuick 2.0
Rectangle {
id: cell
property int x_cord: 0
property int y_cord: 0
property int size: 10
width: size
height: size
color: getCorrectColor()
Connections {
target: myGameBoard
onDataChanged: {
cell.color = cell.getCorrectColor()
}
}
function getCorrectColor() {
switch(myGameBoard.data(myGameBoard.index(cell.x_cord, cell.y_cord)) + 0) {
case 0 :
return "honeydew"
case 1 :
return "black"
case 2 :
return "orangered"
default:
return "yellow"
}
}
}
C++ 方面基本保持不变,除了在 board.h
上的状态枚举上使用 Q_ENUM。
感谢转发者的帮助和提示!
我正在用 C++ 中的游戏板模型编写一个简单的贪吃蛇游戏,其中包含一个二维状态向量 (std::vector<std::vector<board::state>>
)。现在我想将此板公开给 QML,以便它基本上是某种 grid/chess 板,可以访问模型的状态。
我已经阅读了很多关于这个主题的文章,但仍然无法理解足够的机制来解决我的问题。将文档、教程和博客条目应用到我的问题是我的障碍。
我为我的游戏板模型子类化 QAbstractItemModel
并实现了必要的功能。现在我想进入 QML 和 use/show 我的模型的内容。
这是我的代码:
board.h
#pragma once
#include <vector>
#include <QAbstractItemModel>
class board : public QAbstractItemModel
{
Q_OBJECT
public:
enum class state
{
empty,
snake,
fruit
};
board(int x, int y);
state get_state(int x, int y) const;
void set_state(int x, int y, state state);
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex& index) const;
int rowCount(const QModelIndex& parent = QModelIndex()) const;
int columnCount(const QModelIndex& parent = QModelIndex()) const;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
private:
std::vector<std::vector<state>> m_board;
};
Q_DECLARE_METATYPE(board::state)
board.cpp
#include "board.h"
board::board(int x, int y) :
m_board(x, std::vector<board::state>(y, board::state::empty))
{
}
//----------------------------------------------------------------------------------------------------------------------
board::state board::get_state(int x, int y) const
{
if((size_t) x >= m_board.size() || x < 0)
return board::state::empty;
if((size_t) y >= m_board.at(0).size() || y < 0)
return board::state::empty;
return m_board.at(x).at(y);
}
//----------------------------------------------------------------------------------------------------------------------
void board::set_state(int x, int y, state state)
{
if(get_state(x, y) == state)
return;
m_board.at(x).at(y) = state;
}
//----------------------------------------------------------------------------------------------------------------------
QModelIndex board::index(int row, int column, const QModelIndex&) const
{
if((size_t) row >= m_board.size() || row < 0)
return QModelIndex();
if((size_t) column >= m_board.at(0).size() || column < 0)
return QModelIndex();
return createIndex(row, column);
}
//----------------------------------------------------------------------------------------------------------------------
QModelIndex board::parent(const QModelIndex& index) const
{
if((size_t) index.row() >= m_board.size() || index.row() < 0)
return QModelIndex();
if((size_t) index.column() >= m_board.at(0).size() || index.column() < 0)
return QModelIndex();
return createIndex(index.row(), index.column());
}
//----------------------------------------------------------------------------------------------------------------------
int board::rowCount(const QModelIndex&) const
{
return m_board.size();
}
//----------------------------------------------------------------------------------------------------------------------
int board::columnCount(const QModelIndex&) const
{
return m_board.at(0).size();
}
//----------------------------------------------------------------------------------------------------------------------
QVariant board::data(const QModelIndex& index, int role) const
{
if(!index.isValid())
return QVariant();
if(role != Qt::DisplayRole)
return QVariant();
if((size_t) index.row() >= m_board.size() || index.row() < 0)
return QVariant();
if((size_t) index.column() >= m_board.at(0).size() || index.column() < 0)
return QVariant();
return qVariantFromValue(get_state(index.row(), index.column()));
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickView>
#include <board.h>
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
board game_board(10, 10);
game_board.set_state(4, 9, board::state::snake);
game_board.set_state(3, 10, board::state::fruit);
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
QQmlContext *ctxt = view.rootContext();
ctxt->setContextProperty("myGameBoard", &game_board);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
return app.exec();
}
main.qml
import QtQuick 2.6
import QtQuick.Window 2.2
Window {
visible: true
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
Text {
text: qsTr("Hello World")
anchors.centerIn: parent
}
GridView {
model: myGameBoard
delegate: Rectangle {
width: 30
height: 30
color: blue
}
}
}
我敢肯定我遗漏了很多东西或者是明显错误的东西。我也知道将 C++ 模型暴露给 QML 是经常被问及的问题,但我仍然无法做到。
再说几个具体的问题:
- 如何在 QML 中显示模型中的数据?我已经看到这是用角色名称完成的,但我的行没有为其列的角色名称
- 我的模型是否正确实施?
- 我需要使用什么 QML 组件来拥有一个 tile/chess 板,如果我需要一个自定义组件,它需要哪些属性才能从模型中读取数据?
感谢观看!
QtQuick,使用 QML 的主要 UI 框架,基本上只处理列表模型,因此使用角色来模拟 tables,例如使用
TableView
parent()
方法是错误的,因为它基本上再次 returns 相同的索引。这不会对你的情况造成任何问题,因为你有一个 table 而不是一棵树。
建议:如果您只需要一个 table 模型,从 QAbstractTableModel
派生并让它处理 index()
和 parent()
- 没有可以处理 table 模型的标准 QtQuick 元素,因此您必须构建自己的模型或使用列表模型,只需将 "list items" 排列在网格中即可。
如果是自定义项目,您可以构建一个适用于您的模型的项目,甚至可以直接处理数据,而无需模型。
如果您使用该模型并希望从 QML 实例化该模型,那么您需要 "pointer to your model class" 的 属性 或“指向 QAbstractItemModel
.
如果您不想使用模型或不需要从 QML 实例化它,那么您根本不需要任何特定的 属性。
无论哪种情况,您的自定义项目都可以使用以下方法之一:
- 它可以自己绘制所有东西,即瓷砖的网格和瓷砖本身
- 只需提供网格并使用委托来绘制不同类型的图块,即像
ListView
允许为列表元素、页眉、页脚等设置委托
我有一个适合我的解决方案。这是我所做的和一些代码:
- 我使用 Q_ENUM 作为状态枚举以使其在 QML 中可用
- 已将 rootContext 连接到 QML 引擎
- 编写了一个自定义 QML 组件,该组件由带有转发器的列组成,包含带有转发器的行
- 保存外层中继器和内层中继器的索引,编制访问数据的索引
- 一些清理工作
代码
Board.qml
import QtQuick 2.0
Column {
Repeater {
model: myGameBoard.columnCount()
Row {
property int y_pos: index
Repeater {
id: repeatr
model: myGameBoard.rowCount()
Cell {
x_cord: index
y_cord: y_pos
size: 20
}
} //Repeater
} //Row
} //Repeater
} //Column
Cell.qml
import QtQuick 2.0
Rectangle {
id: cell
property int x_cord: 0
property int y_cord: 0
property int size: 10
width: size
height: size
color: getCorrectColor()
Connections {
target: myGameBoard
onDataChanged: {
cell.color = cell.getCorrectColor()
}
}
function getCorrectColor() {
switch(myGameBoard.data(myGameBoard.index(cell.x_cord, cell.y_cord)) + 0) {
case 0 :
return "honeydew"
case 1 :
return "black"
case 2 :
return "orangered"
default:
return "yellow"
}
}
}
C++ 方面基本保持不变,除了在 board.h
上的状态枚举上使用 Q_ENUM。
感谢转发者的帮助和提示!