Qt學習之路(47): 自定義Model之三

原創作品,允許轉載,轉載時請務必以超鏈接形式標明文章 原始出處 、作者信息和本聲明。否則將追究法律責任。http://devbean.blog.51cto.com/448512/268468

今天來說的是自定義model中最複雜的例子。這個例子同樣也是出自C++ GUI Programming with Qt 4, 2nd Edition這本書。

 

這個例子是將布爾表達式分析成一棵樹。這個分析過程在離散數學中經常遇到,特別是複雜的布爾表達式,類似的分析可以比較方便的進行表達式化簡、求值等一系列的計算。同樣,這個技術也可以很方便的分析一個表達式是不是一個正確的布爾表達式。在這個例子中,一共有四個類:

  • Node:組成樹的節點;

  • BooleaModel:布爾表達式的model,實際上是一個tree model,用於將布爾表達式表示成一棵樹;

  • BooleanParser:將布爾表達式生成分析樹的分析器;

  • BooleanWindow:輸入布爾表達式並進行分析,展現成一棵樹。

這個例子可能是目前爲止最複雜的一個了,所以先來看看最終的結果,以便讓我們心中有數:

 

 

先來看這張圖片,我們輸入的布爾表達式是!(a||b)&&c||d, 在下面的Node欄中,用樹的形式將這個表達式分析了出來。如果你熟悉編譯原理,這個過程很像詞法分析的過程:將一個語句分析稱一個一個獨立的詞素。

 

我們從最底層的Node類開始看起,一步步構造這個程序。

 

Node.h

InBlock.gifclass Node 
InBlock.gif
InBlock.gifpublic
InBlock.gif        enum Type 
InBlock.gif        { 
InBlock.gif                Root, 
InBlock.gif                OrExpression, 
InBlock.gif                AndExpression, 
InBlock.gif                NotExpression, 
InBlock.gif                Atom, 
InBlock.gif                Identifier, 
InBlock.gif                Operator, 
InBlock.gif                Punctuator 
InBlock.gif        }; 
InBlock.gif 
InBlock.gif        Node(Type type, const QString &str = ""); 
InBlock.gif        ~Node(); 
InBlock.gif 
InBlock.gif        Type type; 
InBlock.gif        QString str; 
InBlock.gif        Node *parent; 
InBlock.gif        QList<Node *> children; 
InBlock.gif}; 

 

Node.cpp

InBlock.gifNode::Node(Type type, const QString &str) 
InBlock.gif
InBlock.gif        this->type = type; 
InBlock.gif        this->str = str; 
InBlock.gif        parent = 0; 
InBlock.gif
InBlock.gif 
InBlock.gifNode::~Node() 
InBlock.gif
InBlock.gif        qDeleteAll(children); 
InBlock.gif}

 

Node很像一個典型的樹的節點:一個Node指針類型的parent屬性,保存父節點;一個QString類型的str,保存數據。另外,Node裏面還有一個Type屬性,指明這個Node的類型,是一個詞素,還是操作符,或者其他什麼東西;children是一個QList<Node *>類型的列表,保存這個 node 的子節點。注意,在Node類的析構函數中,使用了qDeleteAll()這個全局函數。這個函數是將[start, end)範圍內的所有元素進行delete。因此,它的參數的元素必須是指針類型的。並且,這個函數使用delete之後並不會將指針賦值爲0,所以,如果要在析構函數之外調用這個函數,建議在調用之後顯示的調用clear()函數,將所有子元素的指針清爲0.

 

在構造完子節點之後,我們開始構造model:

 

booleanmodel.h

InBlock.gifclass BooleanModel : public QAbstractItemModel 
InBlock.gif
InBlock.gifpublic
InBlock.gif        BooleanModel(QObject *parent = 0); 
InBlock.gif        ~BooleanModel(); 
InBlock.gif 
InBlock.gif        void setRootNode(Node *node); 
InBlock.gif 
InBlock.gif        QModelIndex index(int row, int column, 
InBlock.gif                                            const QModelIndex &parent) const
InBlock.gif        QModelIndex parent(const QModelIndex &child) const
InBlock.gif 
InBlock.gif        int rowCount(const QModelIndex &parent) const
InBlock.gif        int columnCount(const QModelIndex &parent) const
InBlock.gif        QVariant data(const QModelIndex &index, int role) const
InBlock.gif        QVariant headerData(int section, Qt::Orientation orientation, 
InBlock.gif                                                int role) const
InBlock.gifprivate
InBlock.gif        Node *nodeFromIndex(const QModelIndex &index) const
InBlock.gif 
InBlock.gif        Node *rootNode; 
InBlock.gif};

 

booleanmodel.cpp

InBlock.gifBooleanModel::BooleanModel(QObject *parent) 
InBlock.gif        : QAbstractItemModel(parent) 
InBlock.gif
InBlock.gif        rootNode = 0; 
InBlock.gif
InBlock.gif 
InBlock.gifBooleanModel::~BooleanModel() 
InBlock.gif
InBlock.gif        delete rootNode; 
InBlock.gif
InBlock.gif 
InBlock.gifvoid BooleanModel::setRootNode(Node *node) 
InBlock.gif
InBlock.gif        delete rootNode; 
InBlock.gif        rootNode = node; 
InBlock.gif        reset(); 
InBlock.gif
InBlock.gif 
InBlock.gifQModelIndex BooleanModel::index(int row, int column, 
InBlock.gif                                                                const QModelIndex &parent) const 
InBlock.gif
InBlock.gif        if (!rootNode || row < 0 || column < 0) 
InBlock.gif                return QModelIndex(); 
InBlock.gif        Node *parentNode = nodeFromIndex(parent); 
InBlock.gif        Node *childNode = parentNode->children.value(row); 
InBlock.gif        if (!childNode) 
InBlock.gif                return QModelIndex(); 
InBlock.gif        return createIndex(row, column, childNode); 
InBlock.gif
InBlock.gif 
InBlock.gifNode *BooleanModel::nodeFromIndex(const QModelIndex &index) const 
InBlock.gif
InBlock.gif        if (index.isValid()) { 
InBlock.gif                return static_cast<Node *>(index.internalPointer()); 
InBlock.gif        } else { 
InBlock.gif                return rootNode; 
InBlock.gif        } 
InBlock.gif
InBlock.gif 
InBlock.gifint BooleanModel::rowCount(const QModelIndex &parent) const 
InBlock.gif
InBlock.gif        if (parent.column() > 0) 
InBlock.gif                return 0; 
InBlock.gif        Node *parentNode = nodeFromIndex(parent); 
InBlock.gif        if (!parentNode) 
InBlock.gif                return 0; 
InBlock.gif        return parentNode->children.count(); 
InBlock.gif
InBlock.gif 
InBlock.gifint BooleanModel::columnCount(const QModelIndex & /* parent */const 
InBlock.gif
InBlock.gif        return 2; 
InBlock.gif
InBlock.gif 
InBlock.gifQModelIndex BooleanModel::parent(const QModelIndex &child) const 
InBlock.gif
InBlock.gif        Node *node = nodeFromIndex(child); 
InBlock.gif        if (!node) 
InBlock.gif                return QModelIndex(); 
InBlock.gif        Node *parentNode = node->parent; 
InBlock.gif        if (!parentNode) 
InBlock.gif                return QModelIndex(); 
InBlock.gif        Node *grandparentNode = parentNode->parent; 
InBlock.gif        if (!grandparentNode) 
InBlock.gif                return QModelIndex(); 
InBlock.gif 
InBlock.gif        int row = grandparentNode->children.indexOf(parentNode); 
InBlock.gif        return createIndex(row, 0, parentNode); 
InBlock.gif
InBlock.gif 
InBlock.gifQVariant BooleanModel::data(const QModelIndex &index, int role) const 
InBlock.gif
InBlock.gif        if (role != Qt::DisplayRole) 
InBlock.gif                return QVariant(); 
InBlock.gif 
InBlock.gif        Node *node = nodeFromIndex(index); 
InBlock.gif        if (!node) 
InBlock.gif                return QVariant(); 
InBlock.gif 
InBlock.gif        if (index.column() == 0) { 
InBlock.gif                switch (node->type) { 
InBlock.gif                case Node::Root: 
InBlock.gif                         return tr("Root"); 
InBlock.gif                case Node::OrExpression: 
InBlock.gif                        return tr("OR Expression"); 
InBlock.gif                case Node::AndExpression: 
InBlock.gif                        return tr("AND Expression"); 
InBlock.gif                case Node::NotExpression: 
InBlock.gif                        return tr("NOT Expression"); 
InBlock.gif                case Node::Atom: 
InBlock.gif                        return tr("Atom"); 
InBlock.gif                case Node::Identifier: 
InBlock.gif                        return tr("Identifier"); 
InBlock.gif                case Node::Operator: 
InBlock.gif                        return tr("Operator"); 
InBlock.gif                case Node::Punctuator: 
InBlock.gif                        return tr("Punctuator"); 
InBlock.gif                default
InBlock.gif                        return tr("Unknown"); 
InBlock.gif                } 
InBlock.gif        } else if (index.column() == 1) { 
InBlock.gif                return node->str; 
InBlock.gif        } 
InBlock.gif        return QVariant(); 
InBlock.gif
InBlock.gif 
InBlock.gifQVariant BooleanModel::headerData(int section, 
InBlock.gif                                                                    Qt::Orientation orientation, 
InBlock.gif                                                                    int role) const 
InBlock.gif
InBlock.gif        if (orientation == Qt::Horizontal && role == Qt::DisplayRole) { 
InBlock.gif                if (section == 0) { 
InBlock.gif                        return tr("Node"); 
InBlock.gif                } else if (section == 1) { 
InBlock.gif                        return tr("Value"); 
InBlock.gif                } 
InBlock.gif        } 
InBlock.gif        return QVariant(); 
InBlock.gif}

 

現在,我們繼承了QAbstractItemModel。之所以不繼承前面說的QAbstractListModel或者QAbstractTableModel,是因爲我們要構造一個tree model,而這個model是有層次結構的。所以,我們直接繼承了那兩個類的基類。在構造函數中,我們把根節點的指針賦值爲0,因此我們提供了另外的一個函數setRootNode(),將根節點進行有效地賦值。而在析構中,我們直接使用delete操作符將這個根節點delete掉。在setRootNode()函數中,首先我們delete掉原有的根節點,再將根節點賦值,然後調用reset()函數。這個函數將通知所有的view對界面進行重繪,以表現最新的數據。

 

使用QAbstractItemModel,我們必須重寫它的五個純虛函數。首先是index()函數。這個函數在QAbstractTableModel或者QAbstractListModel中不需要覆蓋,因此那兩個類已經重寫過了。但是,我們繼承QAbstractItemModel時必須覆蓋。這個函數的簽名如下:

 

InBlock.gifvirtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const = 0;

 

這是一個純虛函數,用於返回第row行,第column列,父節點爲parent的那個元素的QModelIndex對象。對於tree model,我們關注的是parent參數。看一下我們的實現:

 

InBlock.gifQModelIndex BooleanModel::index(int row, int column, 
InBlock.gif                                                                const QModelIndex &parent) const 
InBlock.gif
InBlock.gif        if (!rootNode || row < 0 || column < 0) 
InBlock.gif                return QModelIndex(); 
InBlock.gif        Node *parentNode = nodeFromIndex(parent); 
InBlock.gif        Node *childNode = parentNode->children.value(row); 
InBlock.gif        if (!childNode) 
InBlock.gif                return QModelIndex(); 
InBlock.gif        return createIndex(row, column, childNode); 
InBlock.gif}

 

如果rootNode或者row或者column非法,返回一個非法的QModelIndex。然後使用nodeFromIndex()函數取得索引爲parent的節點,然後我們使用children屬性(這是我們前面定義的Node裏面的屬性)獲得子節點。如果子節點不存在,返回一個非法值。最後,當是一個有效值時,由createIndex()函數返回有效地QModelIndex對象。

 

對於具有層次結構的model來說,只有row和column值是不能確定這個元素的位置的,因此,QModelIndex中除了row和column之外,還有一個void*或者int的空白屬性,可以存儲一個值。在這裏我們就把父節點的指針存入,這樣,就可以由這三個屬性定位這個元素。因此,createIndex()中第三個參數就是這個內部的指針。所以我們自己定義一個nodeFromIndex()函數的時候要注意使用QModelIndex的internalPointer()函數獲得這個內部指針,從而定位我們的node。

 

後面的rowCount()和columnCount()這兩個函數比較簡單,就是要獲得model的行和列的值。由於我們的model定義成2列,所以在columnCount()函數中始終返回2.

 

parent()函數要返回子節點的父節點的索引,我們要從子節點開始尋找,直到找到父節點的父節點,這樣就能定位到父節點,從而得到子節點的位置。而data()函數要返回每個單元格的返回值,經過前面兩個例子,我想這個函數已經不會有很大的困難了的。headerData()函數返回列頭的名字,同前面一樣,這裏就不再贅述了。

 

前面的代碼很長,BooleanWindow部分就很簡單了。就是把整個view和model組合起來。另外的一個BooleanParser類沒有什麼GUI方面的代碼,是純粹的算法問題。如果我看得沒錯的話,這裏應該使用的是編譯原理裏面的遞歸下降詞法分析,有興趣的朋友可以到網上查一下相關的資料。我想在以後的《自己動手寫編譯器》中再詳細介紹這個算法。

 

好了,今天的內容很多,爲了方便大家查看和編譯代碼,我已經把這接種出現的所有代碼打包傳到附件中。

本文出自 “豆子空間” 博客,請務必保留此出處http://devbean.blog.51cto.com/448512/268468


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章