使用嵌套对象集合开始Qt模型/视图

时间:2012-06-12 01:39:22

标签: qt model-view

我主要是.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”类似于属性窗口。

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

  2. 这是你如何使用模型/视图框架连接小部件(即一个小部件将模型的指针或引用传递给另一个小部件)?或者我应该使用SelectionModel?这是否有效,因为树模型包含不同类型的对象?

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

1 个答案:

答案 0 :(得分: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-&gt; model-&gt; view-&gt; widget-&gt; model-&gt; view等传播的无限更改循环。

  5. 我做了类似的事情,使用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)
    

    我希望有所帮助。