0

我主要是一名 .Net 开发人员,并且已经研究 Qt 有一段时间了。我现在正处于尝试在 Qt 中实现模型/视图框架的阶段。我想我掌握了基本原理,但不清楚如何在一个更复杂的 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。树类似于解决方案资源管理器,“信息显示”类似于属性窗口。

  1. 这就是你使用模型/视图框架的方式吗?对于那些熟悉 WPF/Silverlight 开发的人来说,模型/视图框架是否类似于 MVVM(在高层次上),因为它是“视图模型”并包装/包含域模型?

  2. 这是您使用模型/视图框架连接小部件的方式吗(即,一个小部件将模型的指针或引用传递给另一个小部件)?还是我应该使用 SelectionModel?由于树模型包含不同类型的对象,这是否有效?

  3. 如何识别根节点?例如,当创建 MyNestedObject1 并需要将其添加到树时,我是否依赖于根节点位于模型索引 QModelIndex(0, 0) 的知识(即第 0 行,父索引无效)?

4

1 回答 1

1

我发现您使用的术语有点尴尬,例如 MyNestedClass1ViewModel 只是一个模型。我不确定 ViewModel 会是什么。

您在此示例中缺少的是实际视图。MyTreeViewWidget 只是一个愚蠢的小部件,实际上根本不是 Qt 术语中的视图,它本质上只是一个您想要在其中显示数据的愚蠢“画布”。所以这样做的方法是:

  1. 您在 NestedDomainModel2 等普通对象中有基础数据。这些不是 Qt 意义上的模型,我不会这样命名它们。它们只是普通对象,不实现任何 MVC 接口。

  2. 您的 MyNestedClass1ViewModel,它是一个 Qt 模型类。它在其 data() 和 setData() 方法的实现中访问上述 (1) 的底层数据对象。

  3. 从 QAbstractItemView 子类化的视图类。这就是你所缺少的。它具有从上面 (2) 插入模型类 API 的所有魔术钩子。它从模型中获取信号,告诉它何时发生更改,这些信号调用诸如 dataChanged()、rowsInserted() 之类的方法。您实现这些方法以在下面第 (4) 点中的显示小部件中进行适当的更改。

  4. 您的显示小部件。它本身不实现任何模型/视图 API,而是由您的视图更新。如果它是交互式的并且可用于更改模型数据,您可以通过在模型上调用 setData()、insertRows()、removeRows() 等来实现。显示更改将通过视图自动传播回小部件。注意不要生成从 widget->model->view->widget->model->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)

我希望这有帮助。

于 2012-06-28T08:41:42.450 回答