我主要是.Net开发人员,现在已经调查了Qt一段时间了。我现在正处于尝试在Qt中实现模型/视图框架的阶段。我认为我掌握了基本原则,但不清楚如何在一个更复杂的UI中将事物挂在一起,在这些UI中,窗口小部件需要相互通信。鉴于以下内容:
// 'domain' model classes
class NestedDomainModel1
{
public:
NestedDomainModel1();
QString name() const;
void setName(const QString& newName);
// other properties
private:
QString m_name;
};
class NestedDomainModel2
{
public:
NestedDomainModel2();
QString name() const;
void setName(const QString& newName);
// other properties
};
class MyDomainModel
{
public:
MyDomainModel();
void addNestedModel1(const NestedDomainModel1& modelToAdd);
NestedDomainModel& nestedObjectModel1At(int index);
int nestedObjectModel1Count() const;
// repeat for model 2
private:
QList<NestedDomainModel1> m_nestedModels1;
QList<NestedDomainModel2> m_nestedModels2;
};
// 'GUI' classes
class MainWindow : public QMainWindow
{
private:
MyDomainModel* m_model;
MyTreeViewWidget* m_treeWidget; // -> this sits in a left dock window
MyInfoDisplayWidget* m_infoWidget; // -> this sits in a right dock window and display details about the item selected in the tree
};
class MyDomainModelTreeModel : public QAbstractItemModel
{
public:
explicit MyDomainModelTreeModel(MyDomainModel* model);
// required overrides for QAbstractItemModel
private:
MyDomainModel* m_model;
};
class MyTreeViewWidget : public QWidget
{
public:
// Take a pointer to the domain model and create a model for the 'view'.
// Will create a tree like:
// Nested Objects 1
// |- object 001
// |- object 002
// |- you get the idea
// Nested Objects 2
// |- other object 001
// |- more of the same
explicit MyTreeViewWidget(MyDomainModel* model);
public slots:
// Used to notify widget when an item is added to the underlying model.
void nestedModel1Added();
void nestedModel2Added();
signals:
void nestedModel1Selected(NestedDomainModel1& selectedModel);
void nestedModel2Selected(NestedDomainModel2& selectedModel);
private slots:
// connect to tree view event when an item is selected and if all ok, emit one of the selected events
void onTreeItemSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected);
private:
QTreeView* m_treeView;
MyDomainModelTreeModel* m_treeModel;
};
class MyNestedClass1ViewModel : QAbstractItemModel
{
public:
explicit MyNestedClass1ViewModel(NestedDomainModel1* model);
setModel(NestedDomainModel1* model);
// required overrides for QAbstractItemModel
private:
NestedDomainModel1* m_model
};
class MyInfoDisplayWidget : public QWidget
{
public:
explicit MyInfoDisplayWidget(QWidget* parent = 0);
public slots:
// this is connected to the 'tree' widget signal in MainWindow
void setModel(NestedDomainModel1& selectedModel);
};
UI的基本前提是与Visual Studio类似。该树类似于Solution Explorer,“info display”类似于属性窗口。
这是您使用模型/视图框架的方式吗?对于那些熟悉WPF / Silverlight开发的人来说,模型/视图框架是否类似于MVVM(在高级别),因为它是“视图模型”并包装/包含域模型?
这是你如何使用模型/视图框架连接小部件(即一个小部件将模型的指针或引用传递给另一个小部件)?或者我应该使用SelectionModel?这是否有效,因为树模型包含不同类型的对象?
如何识别根节点?例如,当创建MyNestedObject1并且需要将其添加到树时,我依赖于根节点位于模型索引QModelIndex(0,0)的知识(即具有无效父索引的行0)?
答案 0 :(得分:1)
我发现你使用的术语有点尴尬,例如MyNestedClass1ViewModel只是一个模型。我不确定ViewModel是什么。
此示例中您缺少的是实际视图。 MyTreeViewWidget只是一个愚蠢的小部件,根本不是Qt术语中的视图,它只是一个你想要显示数据的愚蠢的'画布'。所以这样做的方法是:
您在普通对象(如NestedDomainModel2)中拥有基础数据。这些不是Qt意义上的模型,我不会这样命名。它们只是普通的对象,并没有实现任何MVC接口。
您的MyNestedClass1ViewModel,它是一个Qt模型类。它在执行它的data()和setData()方法时访问上面(1)的底层数据对象。
从QAbstractItemView继承的视图类。这就是你所缺少的。它具有从上面的(2)插入模型类的API的所有神奇钩子。它从模型中获取信号,告诉它何时发生了变化,从而调用dataChanged(),rowsInserted()等方法。您可以实现这些方法,在下面的第(4)点中对显示窗口小部件进行适当的更改。
您的显示小部件。它不实现任何模型/视图API本身,并由您的视图更新。如果它是交互式的并且可以用于更改模型数据,则可以通过在模型上调用setData(),insertRows(),removeRows()等来实现。显示更改将自动通过视图传播回窗口小部件。注意不要生成从widget-&gt; model-&gt; view-&gt; widget-&gt; model-&gt; view等传播的无限更改循环。
我做了类似的事情,使用QGraphicsScene / QGraphicsView来显示模型中的项目。尽管名称QGraphicsView不是模型/视图框架的一部分,但我实现了一个自定义视图类,它在QGraphicsScene上绘制了模型数据。
这是我的代码,用Python编写。它在地图上为SF战争游戏绘制世界:
class WorldItemView(QtGui.QAbstractItemView):
""" Hidden view which interfaces between the model and the scene.
"""
def __init__(self, model, parent=None):
QtGui.QAbstractItemView.__init__(self, parent)
self.hide()
self.setModel(model)
self.my_model = model
self.scene = MapScene(self.my_model)
self.resetWorlds()
def dataChanged(self, topLeft, bottomRight):
top_row = topLeft.row()
bottom_row = bottomRight.row()
#debug_log("Top row " + str(top_row) + " Bottom row " + str(bottom_row))
for row in range(top_row, (bottom_row + 1)):
self.scene.worldChanged(row)
def rowsInserted(self, parent, start, end):
for row in range(start, (end + 1) ):
pmi = self.my_model.getPMI(row)
self.scene.insertWorld(pmi)
def rowsAboutToBeRemoved(self, parent, start, end):
for row in range(start, (end + 1)):
self.scene.removeWorld(row)
def resetWorlds(self):
self.scene.clearWorlds()
# Add worlds to scene
last_row = self.my_model.rowCount() - 1
self.rowsInserted(None, 0, last_row)
我希望有所帮助。