实现自定义模型

Qt的预定义模型为数据的处理和查看提供了很好的方法。但是,有些数据源不能有效地和预定义模型一起工作,这时就需要创建自定义模型,以方便对底层数据源进行优化。

在介绍如何创建自定义模型之前,让我们先看看在Qt模型/视图架构中的一些重点概念。在模型中,每一个数据元素都有一个模型索引和一套属性(attribute) ,称为角色 (role) ,这些角色可以保存任意值。前面部分中已看到过最常用的角色Qt::DisplayRole 和 Qt::EditRole。其他角色都是用来补充说明数据的(例如,Qt::ToolTipRole、Qt::StatusTipRole 和 Qt::WhatsThisRole),还有一些是用来控制基本显示属性的(例如,Qt: : FontRole、Qt::TextAlignmentRole 和 Qt::BackgroundColorRole)。

对于列表模型,唯一和索引部分相关的就是行号,可以通过 QModelIndex::row() 得到。对于表模型,与索引部分相关的就是行号和列号,分别可以通过 QModelIndex::row()和 QModelIndex::column()得到。对于列表模型和表模型,每一个项的父对象都是根,通常由一个无效的 QModelIndex表示。

树模型和表模型类似,但有如下不同之处:像表模型一样,最顶层项的父对象是根(一个无效QModelIndex) ,但是其他每一个项的父对象都是继承树中的其他一些项。这些父对象可以通过 QModelIndex::parent() 得到。每一个项都有自己的角色数据,都有0个或多个子对象,每一个项都有属于自己的东西。因为项可以拥有其他项作为子对象,这样它就可以用来显示递归(树形的)的数据结构。

Currencies

这个例子是一个显示各个货币之间汇率关系的只读表结构。本可以用一个简单的表模型实现这个例子,但是我们想使用一个自定义模型,它可以使我们得到数据的常用属性并且节省存储空间。如果要在一个表中存储 162 个货币单位之间的汇率,则需要存储 162 x 162 = 26244 个值。利用自定义 CurrencyModel 模型,只需要存储 162 个值(每一个货币和美元之间的汇率值)。

main.cpp

CurrencyModel类将和一个标准的QTableView一同使用。CurrencyModel由QMap<QString, double>组装而成,每一个键都是一个货币编码,并且每一个值都是以美元为单位的货币值。

下面的这段代码显示的就是如何组装这个映射和如何使用这个模型:

#include <QtGui>#include "currencymodel.h"int main(int argc, char *argv[])
{QApplication app(argc, argv);QMap<QString, double> currencyMap;currencyMap.insert("AUD", 1.3259);currencyMap.insert("CHF", 1.2970);currencyMap.insert("CZK", 24.510);currencyMap.insert("DKK", 6.2168);currencyMap.insert("EUR", 0.8333);currencyMap.insert("GBP", 0.5661);currencyMap.insert("HKD", 7.7562);currencyMap.insert("JPY", 112.92);currencyMap.insert("NOK", 6.5200);currencyMap.insert("NZD", 1.4697);currencyMap.insert("SEK", 7.8180);currencyMap.insert("SGD", 1.6901);currencyMap.insert("USD", 1.0000);CurrencyModel currencyModel;currencyModel.setCurrencyMap(currencyMap);QTableView tableView;tableView.setModel(&currencyModel);tableView.setAlternatingRowColors(true);tableView.setWindowTitle(QObject::tr("Currencies"));tableView.show();return app.exec();
}

CurrencyModel.h

#ifndef CURRENCYMODEL_H
#define CURRENCYMODEL_H#include <QAbstractTableModel>
#include <QMap>class CurrencyModel : public QAbstractTableModel
{public:CurrencyModel(QObject *parent = 0);void setCurrencyMap(const QMap<QString, double> &map);int rowCount(const QModelIndex &parent) const;int columnCount(const QModelIndex &parent) const;QVariant data(const QModelIndex &index, int role) const;QVariant headerData(int section, Qt::Orientation orientation,int role) const;private:QString currencyAt(int offset) const;QMap<QString, double> currencyMap;
};#endif

我们选择 AbstractTableModel 作为模型的基类,这是因为它和数据源最为接近。Qt提供了几个模型基类,其中包括 QAbstractListModel、QAbstractTableModel 和 QAbstraxtItemModel,见图 10.11。AbstractItemModel 类用于支持很多种模型,其中包括那些基于递归数据结构的模型,而 AbstractTableModel 两个类主要用于提供一维和二维数据集。

对于只读的表模型,必须重新实现三个函数:rowCount()、columnCount() 和 data()。在这种情况下,还需要重新实现headerData(),并且还提供了一个初始化数据的函数[setCurrencyMap()]。

CurrencyModel.cpp

#include <QtCore>#include "currencymodel.h"CurrencyModel::CurrencyModel(QObject *parent): QAbstractTableModel(parent)
{}void CurrencyModel::setCurrencyMap(const QMap<QString, double> &map)
{currencyMap = map;reset();
}int CurrencyModel::rowCount(const QModelIndex & /* parent */) const
{return currencyMap.count();
}int CurrencyModel::columnCount(const QModelIndex & /* parent */) const
{return currencyMap.count();
}QVariant CurrencyModel::data(const QModelIndex &index, int role) const
{if (!index.isValid())return QVariant();if (role == Qt::TextAlignmentRole) {return int(Qt::AlignRight | Qt::AlignVCenter);} else if (role == Qt::DisplayRole) {QString rowCurrency = currencyAt(index.row());QString columnCurrency = currencyAt(index.column());if (currencyMap.value(rowCurrency) == 0.0){return "####";}double amount = currencyMap.value(columnCurrency)/ currencyMap.value(rowCurrency);return QString("%1").arg(amount, 0, 'f', 4); // 四位小数}return QVariant();
}QVariant CurrencyModel::headerData(int section,Qt::Orientation /* orientation */,int role) const
{if (role != Qt::DisplayRole)return QVariant();return currencyAt(section);
}QString CurrencyModel::currencyAt(int offset) const
{return (currencyMap.begin() + offset).key();
}

CurrencyModel()
在构造函数中,除了要把parent 参数传递给基类之外,不需要再做其他任何事情。

rowCount() columnCount()
对于这种表模型,行号和列号就是这个汇率映射中货币的种类。parent 参数对于表模型没有任何意义。之所以在此处保留这个参数,是因为 rowCount( )和 colurnnCount ()都是从更加通用的 QAbstractItemModel 这个基类中继承的,在这个类中支持层次结构。

data()
data() 函数返回一个项的任意角色的值,这个项被指定为 QModelIndex。 对于表模型,QModelIndex 中有意义的部分是它的行号和列号,可以通过调用 row()和 column() 得到它们。

如果角色是Qt::TextAlignmentRol,就返回一个与数字相匹配的对齐方式;如果角色是 Qt::DisplayRole,就查找每一种货币对应的值并且计算出兑换汇率。

可以返回 double类型的计算结果,但是那样就无法控制显示的精度(除非使用的是一个自定义的委托)。所以,我们返回字符串类型的值,这个字符串已经按照我们的想法进行了格式化。

headerData()
当视图组装水平表头和垂直表头时,就会调用 headerData()函数。section 参数是指行号或者列号(这取决于实际方向)。因为这里的行和列都是相同的货币代码,所以不需要考虑方向,只需简单地根据给定的序号返回相应货币的代码即可。

setCurrencyMap()
调用者可以使用setCurrencyMap()函数改变货币映射。AbstractItemModel::reset()调用告诉任何一个使用这个模型的视图,它们所有的数据都无效了,这样就会强制它们为可见的项刷新数据。

currencyAt()
currencyAt()函数返回在货币映射中给定位移的键(即货币代码)。我们使用STL风格的迭代器查找这个条目并且对它调用 key()。

正如现在所看到的,依赖于底层数据的实际情况创建只读模型并不困难,而且具有良好设计的模型还可以节约内存,提升速度。

Cities

这个实例是城市应用程序也是基于表的,但是这一次所有的数据都是由用户输入的。

这个应用程序用于存储任意两个城市之间的距离的值。像前一个实例一样,也可以简单地使用QTableWidget,并且为每一个城市对存储一个项。但是,一个自定义的模型将会更加有效率,因为无论从任意城市A到另外任意一个不同的城市B,或者是从B到A,两者之间的距离都是相同的,所以这些项将会是按主对角线而对称。

main.cpp

#include <QApplication>
#include <QHeaderView>
#include <QTableView>#include "citymodel.h"int main(int argc, char *argv[])
{QApplication app(argc, argv);QStringList cities;cities << "Arvika" << "Boden" << "Eskilstuna" << "Falun"<< "Filipstad" << "Halmstad" << "Helsingborg" << "Karlstad"<< "Kiruna" << "Kramfors" << "Motala" << "Sandviken"<< "Skara" << "Stockholm" << "Sundsvall" << "Trelleborg";CityModel cityModel;cityModel.setCities(cities);QTableView tableView;tableView.setModel(&cityModel);tableView.setAlternatingRowColors(true);tableView.setWindowTitle(QObject::tr("Cities"));tableView.show();return app.exec();
}

为了查看自定义模型和简单表之间的区别,我们假设有三个城市:A、B和C 。如果为每一个组合存储一个值,就需要存储9个值。而一个仔细设计过的模型只需要三个项:(A, B)、(A, C) 和 (B, C)。

CityModel.h

#ifndef CITYMODEL_H
#define CITYMODEL_H#include <QAbstractTableModel>
#include <QStringList>
#include <QVector>class CityModel : public QAbstractTableModel
{Q_OBJECTpublic:CityModel(QObject *parent = 0);void setCities(const QStringList &cityNames);int rowCount(const QModelIndex &parent) const;int columnCount(const QModelIndex &parent) const;QVariant data(const QModelIndex &index, int role) const;bool setData(const QModelIndex &index, const QVariant &value,int role);QVariant headerData(int section, Qt::Orientation orientation,int role) const;Qt::ItemFlags flags(const QModelIndex &index) const;private:int offsetOf(int row, int column) const;QStringList cities;QVector<int> distances;
};#endif

为了使模型可以被编辑,还必须重新实现自setData() 和flags()。对于这个模型,使用了两个数据结构:类型为 QStringList 的 cities 保存了城市的名称,类型为QVector <Ínt>的 distances 保存了每一对城市之间的距离。

CityModel.cpp

#include <QtCore>#include "citymodel.h"CityModel::CityModel(QObject *parent): QAbstractTableModel(parent)
{}void CityModel::setCities(const QStringList &cityNames)
{cities = cityNames;distances.resize(cities.count() * (cities.count() - 1) / 2);distances.fill(0);reset();
}int CityModel::rowCount(const QModelIndex & /* parent */) const
{return cities.count();
}int CityModel::columnCount(const QModelIndex & /* parent */) const
{return cities.count();
}QVariant CityModel::data(const QModelIndex &index, int role) const
{if (!index.isValid())return QVariant();if (role == Qt::TextAlignmentRole) {return int(Qt::AlignRight | Qt::AlignVCenter);} else if (role == Qt::DisplayRole) {if (index.row() == index.column())return 0;int offset = offsetOf(index.row(), index.column());return distances[offset];}return QVariant();
}bool CityModel::setData(const QModelIndex &index,const QVariant &value, int role)
{if (index.isValid() && index.row() != index.column()&& role == Qt::EditRole) {int offset = offsetOf(index.row(), index.column());distances[offset] = value.toInt();QModelIndex transposedIndex = createIndex(index.column(),index.row());emit dataChanged(index, index);emit dataChanged(transposedIndex, transposedIndex);return true;}return false;
}QVariant CityModel::headerData(int section,Qt::Orientation /* orientation */,int role) const
{if (role == Qt::DisplayRole)return cities[section];return QVariant();
}Qt::ItemFlags CityModel::flags(const QModelIndex &index) const
{Qt::ItemFlags flags = QAbstractItemModel::flags(index);if (index.row() != index.column()){flags |= Qt::ItemIsEditable;}return flags;
}int CityModel::offsetOf(int row, int column) const
{if (row < column){qSwap(row, column);}return (row * (row - 1) / 2) + column;
}

CityModel()
构造函数除了把parent参数传递给基类外,就没有其他任何的操作了。

rowCount()
因为是一个正方形的城市列表,所以行总数和列总数就是列表中的城市总数。

data()
这个data()函数和我们在CurrencyModel中所做的类似。如果行和列相同,它返回0 ,因方它对应当两个城市相同的时候的情况。否则,它在distances 矢量中查找给定的行和列所对应的条目,并且返回这个特定城市对的距离。

headerData()
headerData()函数非常简单,因为表是正方形的,它的水平表头和垂直表头一样。我们可以简单地返回在cities 字符串列表中给定偏移量的城市名称。

setData()
当用户编辑一个项的时候,就会调用setData()函数。提供的模型索引必顽有效,两个城市必须不同,并且当修改的数据元素是Qt::EditRole 时,这个函数会把用户输入的值存储到distances矢量中。

createlndex()函数用于产生一个模型索引。我们需要使用它获得在主对角线另外一侧和当前正在被设置的项所对应项的模型索引,因为这两个项必须显示相同的数据。createIndex()函数中的参数顺序是行号在列号之前。这里调换这两个参数,这样就可以获得由index指定的项所对应项的模型索引。

我们用被改变的项的模型索引作为参数发射dataChanged()信号。这个信号使用两个参数的原因是:它可以用来表示对一个矩形范围影响的改变,而不仅仅是一个项,所以这里传递的是被改变区域的左上角和右下角两个项的索引。我们还对另外一个对应的项发射出恒Cha吨ed() 信号,以
确保视图刷新它。最后,返回true 或者fale表明编辑操作是否成功。

flags()
模型会使用flags() 函数得到该如何对一个项进行相关的操作(例如,是否可以编辑)。在QAbstractTableModel 中的默认实现是返回Qt::temIsSelectable l Qt::: ItemIsEnabled。对于所有不在主对角线上的项(它们总是0),都会添加这个Qt::ItemIsEditable标记。

setCities()
如果给定一个新的城市列表,就设置私有的QStringList 为新的列表,重新定义distances 矢量的大小,清空所有的值,并且调用QAbstractItemModel::reset()通知所有视图,它们的可见项必须被重新获取。

offsetOf()
offsetOf()私有函数用于计算给定的城市对在distances 矢量中的索引。例如,如果有城市A 、B 、C 和D,并且用户更新第3 行第1 列,也就是从B 到D ,则偏移量应该是3x(3-1)/2+1=4。 如果用户要更新第1 行第3 列,也就是从D 到B ,则要归功于qSwap() 的调用,它会执行相同的计算,并且也将返回同样的偏移量。图10.13 阐明了城市、距离及其相应表模型之间的关系。

Boolean Parser

这个例子是显示给定布尔表达式的解析树的模型。布尔表达式可以是一个简单的包含文字和数字的标识符,如 “bravo”,也可以是一个利用"&&"、“II"或”!“等算子连接简单表达式的复合表达式,还可以是括号表达式。例如"all (b && !c)” 就是一个布尔表达式。

如图 10.14 所示的布尔解析器应用程序由4个类组成:

● BooleanWindow 是一个让用户输入布尔表达式并且显示相应解析树的窗口。
● BooleanParser 从一个布尔表达式生成一个解析树。
● BooleanModel 是一个封装解析树的树模型。
● Node 代表解析树中的一个节点

Node.h

#ifndef NODE_H
#define NODE_H#include <QList>
#include <QString>class Node
{public:enum Type { Root, OrExpression, AndExpression, NotExpression, Atom,Identifier, Operator, Punctuator };Node(Type type, const QString &str = "");~Node();Type type;QString str;Node *parent;QList<Node *> children;
};#endif

每一个节点都有一个类型、一个宇符串(可以为空)、一个父对象(可以为 null) 和一个子节点的列表(可以为空)。

Node.cpp

#include <QtCore>#include "booleanparser.h"Node::Node(Type type, const QString &str)
{this->type = type;this->str = str;parent = 0;
}Node::~Node()
{qDeleteAll(children);
}

Node()
构造函数只是简单地初始化这个节点的类型和宇符串,并将父对象设置为 null(即没有父对象)。因为所有数据都是公有的,使用 Node 的代码可以直接操作类型、宇符串、父对象和子节点。

~Node()
qDeleteAll()函数遍历一个容器的所有指针并且对每一个指针都调用 delete。但是它并不把这些指针设置为 null,所以在一个析构函数外面使用这个函数时,通常在它之后需要调用 clear() ,以清空这个容器中保存的所有指针。

BooleanModel.h

#ifndef BOOLEANMODEL_H
#define BOOLEANMODEL_H#include <QAbstractItemModel>class Node;class BooleanModel : public QAbstractItemModel
{public:BooleanModel(QObject *parent = 0);~BooleanModel();void setRootNode(Node *node);QModelIndex index(int row, int column,const QModelIndex &parent) const;QModelIndex parent(const QModelIndex &child) const;int rowCount(const QModelIndex &parent) const;int columnCount(const QModelIndex &parent) const;QVariant data(const QModelIndex &index, int role) const;QVariant headerData(int section, Qt::Orientation orientation,int role) const;private:Node *nodeFromIndex(const QModelIndex &index) const;Node *rootNode;
};#endif

这一次使用 QAbstractItemModel 作为基类,而不是它的方便的子类 QAbstractTableModel,这是因为我们想创建一个分层模型。必须重新实现的最基本的函数仍然相同,除此之外还需要实现index()和 parent()。为了设置模型的数据,还有一个setRootNode()函数,调用这个函数的时候,必须使用解析树的根节点作为参数。

BooleanModel.cpp

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

BooleanModel()
在模型的构造函数中,只需要把根节点设置为一个安全的 null 值并且把 parent 传递给基类。

~BooleanModel()
在析构函数中,我们删除根节点。如果根节点有子对象,每一个子对象也都会被删除,并且依次递归往下删除,这是由 Node 的析构函数完成的。

setRootNode()
当设置新的根节点设置时,我们从删除任何之前的根节点(和它所有的子对象)开始。然后设置新的根节点并且调用 reset() 通知所有视图,它们必须为任何一个可见的项重新获取数据。

index()
index()函数是由 QAbstractItemModel 重新实现的。只要模型或者视图需要为一个特定的子项(或者如果parent 是一个无效的 QModelIndex 时为顶级项)创建一个 QModelIndex 的时候 这个函数就会被调用。对于表模型和列表模型,不需要重新实现这个函数,因为 QAbstractListModel和QAbstractTableModel 默认的实现已经足够了。

在index() 实现中,如果没有设置解析树,就返回一个无效的 QModelIndex。 否则,就根据给定的行和列以及一个 Node*为被请求的子对象创建一个 QModelIndexo。对于层次模型,已知一个项相对于它的父对象的行和列并不能够唯一确定它,还必须知道它的父对象是谁。

为了解决这个问题,可以在 QModelIndex 中存储一个指向内部节点的指针。除了行号和列号之外, QModelIndex 还为我们提供了存储一个 void*或者 int 的选择。

nodeFromIndex()
可以通过父节点的 children列表获得子节点的 Node*。我们使用 nodeFromIndex()私有函数从parent 模型索引中提取父节点。

nodeFromIndex()函数把给定索引的 void**强制转换成为 Node*,或者如果这个索引是无效的,就返回根节点,因为在模型中,一个无效的模型索引用来表示根。

rowCount()
一个给定项的行总数就是它有多少个子对象。

columnCount()
列总数被固定为2。第一列用来保存节点类型,第二列用来保存节点值。

parent()
从一个子节点获得父节点的 QModelIndex,要比查找一个父节点的子节点多做一些工作。我们可以很容易地通过使用 nodeFrornIndex()得到一个节点,然后通过使用这个 Node 的父指针获得父节点,但是为了获得行号(父节点在它的层次中的位置) ,还需要继续向上到祖父节点并且查找这个父节点在它的父节点(也就是这个子节点的祖父节点)中的索引位置。

data()
在data()中,可以获得请求的项的 Node*,并且我们使用它访问底层的数据。如果调用者想得到 Qt::DisplayRole 之外的其他任何角色的值,或者如果不能从给定的模型索引中获得一个Node,就返回一个无效的 QVariant。 如果列为0,就返回这个节点类型的名称;如果列为1,就返回这个节点的值(也就是它的字符串)。

headerData()
在headerData()的重新实现中,我们返回合适的水平表头标签。在用于可视化层次模型的QTreeView 类中,没有垂直表头,所以可以忽略它了。

BooleanWindow.h

#ifndef BOOLEANWINDOW_H
#define BOOLEANWINDOW_H#include <QWidget>class QLabel;
class QLineEdit;
class QTreeView;
class BooleanModel;class BooleanWindow : public QWidget
{Q_OBJECTpublic:BooleanWindow();private slots:void booleanExpressionChanged(const QString &expr);private:QLabel *label;QLineEdit *lineEdit;BooleanModel *booleanModel;QTreeView *treeView;
};#endif

我们看看当用户在行编辑器中改变文本的时候根节点是如何创建的。

BooleanWindow.cpp

#include <QtGui>#include "booleanmodel.h"
#include "booleanparser.h"
#include "booleanwindow.h"BooleanWindow::BooleanWindow()
{label = new QLabel(tr("Boolean expression:"));lineEdit = new QLineEdit;booleanModel = new BooleanModel(this);treeView = new QTreeView;treeView->setModel(booleanModel);connect(lineEdit, SIGNAL(textChanged(const QString &)),this, SLOT(booleanExpressionChanged(const QString &)));QGridLayout *layout = new QGridLayout;layout->addWidget(label, 0, 0);layout->addWidget(lineEdit, 0, 1);layout->addWidget(treeView, 1, 0, 1, 2);setLayout(layout);setWindowTitle(tr("Boolean Parser"));
}void BooleanWindow::booleanExpressionChanged(const QString &expr)
{BooleanParser parser;Node *rootNode = parser.parse(expr);booleanModel->setRootNode(rootNode);
}

booleanExpressionChanged()
用户改变这个应用程序的行编辑器的文本时,会调用主窗口的 booleanExpression()槽。在这个槽中,用户的文本被解析并且解析器返回这个解析树的根节点的指针。

BooleanParser.h

#ifndef BOOLEANPARSER_H
#define BOOLEANPARSER_H#include "node.h"class BooleanParser
{public:Node *parse(const QString &expr);private:Node *parseOrExpression();Node *parseAndExpression();Node *parseNotExpression();Node *parseAtom();Node *parseIdentifier();void addChild(Node *parent, Node *child);void addToken(Node *parent, const QString &str, Node::Type type);bool matchToken(const QString &str) const;QString in;int pos;
};#endif

这里没有讲解BooleanParser类,是因为它和GUI或者模型/视图编程无关。

BooleanParser.cpp

#include <QtCore>#include "booleanparser.h"
#include "node.h"Node *BooleanParser::parse(const QString &expr)
{in = expr;in.replace(" ", "");pos = 0;Node *node = new Node(Node::Root);addChild(node, parseOrExpression());return node;
}Node *BooleanParser::parseOrExpression()
{Node *childNode = parseAndExpression();if (matchToken("||")) {Node *node = new Node(Node::OrExpression);addChild(node, childNode);while (matchToken("||")) {addToken(node, "||", Node::Operator);addChild(node, parseAndExpression());}return node;} else {return childNode;}
}Node *BooleanParser::parseAndExpression()
{Node *childNode = parseNotExpression();if (matchToken("&&")) {Node *node = new Node(Node::AndExpression);addChild(node, childNode);while (matchToken("&&")) {addToken(node, "&&", Node::Operator);addChild(node, parseNotExpression());}return node;} else {return childNode;}
}Node *BooleanParser::parseNotExpression()
{if (matchToken("!")) {Node *node = new Node(Node::NotExpression);while (matchToken("!"))addToken(node, "!", Node::Operator);addChild(node, parseAtom());return node;} else {return parseAtom();}
}Node *BooleanParser::parseAtom()
{if (matchToken("(")) {Node *node = new Node(Node::Atom);addToken(node, "(", Node::Punctuator);addChild(node, parseOrExpression());addToken(node, ")", Node::Punctuator);return node;} else {return parseIdentifier();}
}Node *BooleanParser::parseIdentifier()
{int startPos = pos;while (pos < in.length() && in[pos].isLetterOrNumber())++pos;if (pos > startPos) {return new Node(Node::Identifier,in.mid(startPos, pos - startPos));} else {return 0;}
}void BooleanParser::addChild(Node *parent, Node *child)
{if (child) {parent->children += child;parent->str += child->str;child->parent = parent;}
}void BooleanParser::addToken(Node *parent, const QString &str,Node::Type type)
{if (in.mid(pos, str.length()) == str) {addChild(parent, new Node(type, str));pos += str.length();}
}bool BooleanParser::matchToken(const QString &str) const
{return in.mid(pos, str.length()) == str;
}

main.cpp

#include <QApplication>#include "booleanwindow.h"int main(int argc, char *argv[])
{QApplication app(argc, argv);BooleanWindow window;window.show();return app.exec();
}

Qt4_实现自定义模型相关推荐

  1. 关于DEDECMS自定义模型当中添加自定义字段后在后台添加内容后不显示解决方案...

    问题:我们自定义模型,添加自定义字段,比如单行文本(varchar)字段时,在后台添加内容,无法显示,但数据库里字段是有数据的. 解决办法:看看你的字段命名是否有大写,如果有全部改成小写就好了. 转载 ...

  2. Qt中的自定义模型类

    文章目录 1 Qt中的通用模型类 1.1 Qt中的通用模型类 1.2 Qt中的变体类型QVariant 2 自定义模型类 2.1 自定义模型类设计分析 2.2 自定义模型类数据层.数据表示层.数据组织 ...

  3. (四)Qt实现自定义模型基于QAbstractTableModel (一般)

    Qt实现自定义模型基于QAbstractTableModel 两个例子 例子1代码 Main.cpp #include <QtGui>#include "currencymode ...

  4. TensorFlow 2.0 - 自定义模型、训练过程

    文章目录 1. 自定义模型 2. 学习流程 学习于:简单粗暴 TensorFlow 2 1. 自定义模型 重载 call() 方法,pytorch 是重载 forward() 方法 import te ...

  5. 用于将带有查询字符串的复杂对象传递到Web API方法的自定义模型绑定器

    目录 介绍 查询复杂对象的字符串字段 使用和测试FieldValueModelBinder类 FieldValueModelBinder如何工作? 获取源字段和值 将字段部分与对象属性匹配 解析枚举类 ...

  6. (五)Qt实现自定义模型基于QAbstractItemModel

    目录: (一) Qt Model/View 的简单说明 .预定义模型 (二)使用预定义模型 QstringListModel例子 (三)使用预定义模型QDirModel的例子 (四)Qt实现自定义模型 ...

  7. (四)Qt实现自定义模型基于QAbstractTableModel

    目录: (一) Qt Model/View 的简单说明 .预定义模型 (二)使用预定义模型 QstringListModel例子 (三)使用预定义模型QDirModel的例子 (四)Qt实现自定义模型 ...

  8. Qt4_实现自定义委托

    实现自定义委托 委托(delegate) 用来渲染和编辑视图中不同的项.在大多数情况下,视图中默认的委托已经足够了.如果想更好地控制有关项的显示,通常可以通过使用自定义模型很简单地实现我们所想要的:在 ...

  9. Pytorch学习记录(七):自定义模型 Auto-Encoders 使用numpy实现BP神经网络

    文章目录 1. 自定义模型 1.1 自定义数据集加载 1.2 自定义数据集数据预处理 1.3 图像数据存储结构 1.4 模型构建 1.5 训练模型 2. Auto-Encoders 2.1 无监督学习 ...

最新文章

  1. 自动添加端口添加至Windows防火墙脚本
  2. boost::local_time模块实现创建各种dst_calc_rule的测试程序
  3. 1185 威佐夫游戏 V2
  4. java 过滤器 弹出提示_JavaWeb 过滤器——验证登录 防止未登录进入界面
  5. Programmer,Developer,Engineer——软件从业人员的职业规划
  6. 内存颗粒和闪存颗粒的区别_国产闪存颗粒终于熬出头 紫光存储S100固态硬盘评测...
  7. 国脉信息学院计算机网络,福建工程学院国脉信息学院《计算机网络模拟题》
  8. 解析烧录固件失败_Sophos UTM固件反编译Perl源码
  9. 计算机网络常用通讯方式,通信方式
  10. displayport1.4
  11. gc buffer busy acquire 、gc buffer busy release
  12. Facebook受邀者的邮箱地址披露
  13. 003-信息技术学科知识与教学能力
  14. java 判断今天星期几_java判断今天星期几
  15. Unity开发 MMORPG类游戏引导系统
  16. Flutter 页面更新流程剖析
  17. 用TW8836驱动ST7701S TTL屏调试记录
  18. 推荐几款实用的PDF文件压缩大小软件,还不快码住
  19. 第16章 货币政策与财政政策
  20. 一年时间,拿到了人生中的第一个10万

热门文章

  1. Spring-tx-TransactionInterceptor类
  2. javaweb(10) cookiesession
  3. python字符串怎么查找_python 字符串 查找 基本操作
  4. Xcode打包上传时,最后一步出现An error occurred uploading to the iTunes Store.的解决方法...
  5. Eclipse SVN插件安装
  6. Redis常用API-使用文档
  7. 和菜鸟一起学产品之产品经理的三大文档
  8. 《我的WCF之旅》博文系列汇总
  9. Javascript对象扩展 - JsPoint类
  10. Vue报错:‘vue-cli-service‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。