模型/视图教程
每位 UI 开发人员都应了解模型-视图编程,本教程旨在为您提供关于该主题的通俗易懂的入门介绍。
表格、列表和树形控件是图形用户界面(GUI)中常用的组件。这些控件访问数据有两种不同的方式。传统方式是控件内部包含用于存储数据的容器。这种方法非常直观,但在许多非简单的应用程序中,会导致数据同步问题。 第二种方法是模型/视图编程,在此方法中,控件不维护内部数据容器。它们通过标准化接口访问外部数据,从而避免了数据冗余。这乍看之下似乎很复杂,但一旦仔细研究,不仅容易理解,而且模型/视图编程的诸多优势也会变得更加清晰。

在此过程中,我们将了解 Qt 提供的一些基础技术,例如:
- 标准控件与模型/视图控件之间的区别
- 表单与模型之间的适配器
- 开发一个简单的模型/视图应用程序
- 预定义模型
- 中级主题,例如:
- 树形视图
- 选择
- 委托
- 使用模型测试进行调试
您还将了解到,使用模型/视图编程是否能更轻松地编写新应用程序,或者经典控件是否同样适用。
本教程包含示例代码,供您编辑并集成到您的项目中。教程的源代码位于 Qt 的examples/widgets/tutorials/modelview目录下。
如需更详细的信息,您还可以查阅参考文档
1. 简介
模型/视图(Model/View)是一种用于在处理数据集的小部件中将数据与视图分离的技术。标准小部件并非为将数据与视图分离而设计,因此 Qt 提供了两种不同类型的小部件。这两种小部件的外观相同,但与数据的交互方式不同。
| 标准控件使用作为控件组成部分的数据。 |
|
| 视图类则操作外部数据(即模型) |
|
1.1 标准控件
让我们仔细看看一个标准的表格控件。表格控件是一个二维数组,其中包含用户可以修改的数据元素。通过读取和写入表格控件提供的数据元素,可以将其集成到程序流程中。 这种方法非常直观,在许多应用中都很实用,但使用标准表格控件显示和编辑数据库表可能会遇到问题。必须协调两份数据的副本:一份在控件外部,一份在控件内部。开发者有责任同步这两个版本。此外,呈现与数据之间的紧密耦合使得编写单元测试变得更加困难。
1.2 模型/视图模式的救场
模型/视图(Model/View)应运而生,提供了一种基于更灵活架构的解决方案。模型/视图消除了标准控件中可能出现的数据一致性问题。此外,由于一个模型可以传递给多个视图,模型/视图还使得对同一数据使用多个视图变得更加容易。 最重要的区别在于,模型/视图小部件不会在表格单元格背后存储数据。实际上,它们直接基于您的数据进行操作。由于视图类不知道您数据的结构,您需要提供一个包装器,使您的数据符合 `QAbstractItemModel ` 接口。视图使用该接口从您的数据中读取和写入数据。 任何实现QAbstractItemModel 的类实例都被称为模型。一旦视图接收到指向模型的指针,它就会读取并显示其内容,并充当其编辑器。
1.3 模型/视图控件概述
以下是对模型/视图控件及其对应的标准控件的概述。
| 控件 | 标准控件 (基于项的便利类) | 模型/视图视图类 (用于处理外部数据) |
|---|---|---|
![]() | QListWidget | QListView |
![]() | QTableWidget | QTableView |
![]() | QTreeWidget | QTreeView |
![]() | QColumnView 将树结构显示为列表的层级结构 | |
![]() | QComboBox 既可作为视图类,也可作为传统控件使用 | |
1.4 在表单和模型之间使用适配器
在表单和模型之间使用适配器会非常有用。
虽然我们可以直接在表格内部编辑存储在其中的数据,但在文本字段中编辑数据要方便得多。 对于那些不处理数据集、而是针对单一值进行操作的小部件(例如QLineEdit 、QCheckBox 等),模型/视图架构中没有直接对应的组件来分离数据和视图,因此我们需要一个适配器来连接表单与数据源。
QDataWidgetMapper 是一个绝佳的解决方案,因为它将表单控件映射到表格行,从而极大地简化了数据库表表单的构建过程。

适配器的另一个示例是QCompleter 。Qt 提供了QCompleter 来为 Qt Widgets(如QComboBox 以及下文所示的QLineEdit )提供自动完成功能。QCompleter 使用模型作为其数据源。

2. 一个简单的模型/视图应用程序
如果您想开发一个模型/视图应用程序,应该从哪里开始呢?我们建议从一个简单的示例入手,并逐步扩展它。这样会大大有助于理解其架构。 事实证明,对于许多开发人员而言,在启动 IDE 之前就试图详细理解模型/视图架构并不方便。从一个带有演示数据的简单模型/视图应用程序入手要容易得多。不妨试试看!只需将下面的示例中的数据替换成你自己的数据即可。
下面有 7 个非常简单且相互独立的应用程序,它们展示了模型/视图编程的不同方面。源代码位于examples/widgets/tutorials/modelview 目录中。
2.1 一个只读表
我们首先从一个使用QTableView 来显示数据的应用程序开始。稍后我们将添加编辑功能。
(文件来源:examples/widgets/tutorials/modelview/1_readonly/main.cpp)
// main.cpp
#include <QApplication>
#include <QTableView>
#include "mymodel.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTableView tableView;
MyModel myModel;
tableView.setModel(&myModel);
tableView.show();
return a.exec();
}我们有一个常规的main()函数:
这里是关键部分:我们创建了一个 MyModel 的实例,并使用 `tableView.setModel(&myModel);` 将其指针传递给 `tableView`。`tableView ` 将调用接收到的指针所指向的对象的方法,以获取以下两项信息:
- 应显示多少行和列。
- 每个单元格应打印什么内容。
模型需要编写一些代码来响应这一点。
我们有一个表格数据集,因此让我们从 `QAbstractTableModel ` 开始,因为它比更通用的 `QAbstractItemModel` 更易于使用。
(文件来源:examples/widgets/tutorials/modelview/1_readonly/mymodel.h)
// mymodel.h
#include <QAbstractTableModel>
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
explicit MyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
};QAbstractTableModel 需要实现三个抽象方法。
(文件来源:examples/widgets/tutorials/modelview/1_readonly/mymodel.cpp)
// mymodel.cpp
#include "mymodel.h"
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent)
{
}
int MyModel::rowCount(const QModelIndex & /*parent*/) const
{
return 2;
}
int MyModel::columnCount(const QModelIndex & /*parent*/) const
{
return 3;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if (role == Qt::DisplayRole)
return QString("Row%1, Column%2")
.arg(index.row() + 1)
.arg(index.column() +1);
return QVariant();
}行数和列数由MyModel::rowCount()和MyModel::columnCount()提供。当视图需要获取单元格的文本内容时,会调用MyModel::data()方法。行和列信息通过参数index 指定,角色设置为Qt::DisplayRole 。其他角色将在下一节中介绍。 在本示例中,应显示的数据是生成的。在实际应用中,MyModel 会包含一个名为MyData 的成员,该成员作为所有读写操作的目标。
这个简短的示例展示了模型的被动特性。模型并不知道何时会被使用,也不知道需要哪些数据。它只是在视图每次请求时提供数据。
当模型的数据需要更改时会发生什么?视图如何意识到数据已发生变化并需要重新读取?模型必须发出一个信号,以指示哪些单元格范围发生了变化。这一点将在第 2.3 节中进行演示。
2.2 通过角色扩展“只读”示例
除了控制视图显示的文本内容外,模型还控制着文本的外观。当我们对模型进行微调时,会得到以下结果:

实际上,除了data()方法之外,无需进行任何更改即可设置字体、背景色、对齐方式和复选框。以下是生成上述结果的data()方法。不同之处在于,这次我们使用了int类型的参数role,根据其值返回不同的信息。
(文件来源:examples/widgets/tutorials/modelview/2_formatting/mymodel.cpp)
// mymodel.cpp
QVariant MyModel::data(constQModelIndex&index, introle)const
{
introw=index.row();
intcol=index.column();
// 当此方法被调用时生成一条日志消息
qDebug() << QString("row %1, col%2, role %3")
.arg(行).arg(列).arg(角色);
switch(角色) {
caseQt::DisplayRole:
if(行== 0&&列== 1)returnQString("<--left");
if(row== 1&&col== 1)returnQString("right-->");
returnQString("行%1, 列%2")
.arg(row+ 1)
.arg(col+ 1);
caseQt::FontRole:
if(行== 0&&列== 0) {// 仅对单元格(0,0)更改字体
QFont boldFont;
boldFont.setBold(true);
returnboldFont;
}
break;
caseQt::BackgroundRole:
if(row== 1&&col== 2) // 仅对单元格(1,2)更改背景色
returnQBrush(Qt::red);
break;
caseQt::TextAlignmentRole:
if(行== 1&&列== 1)// 仅对单元格(1,1)更改文本对齐方式
return int(Qt::AlignRight|Qt::AlignVCenter);
break;
caseQt::CheckStateRole:
if(row== 1&&col== 0)// 仅在单元格(1,0)处添加复选框
returnQt::Checked;
break;
}
returnQVariant();
}每个格式化属性都将通过单独调用data() 方法从模型中获取。role 参数用于告知模型正在请求哪个属性:
| enum Qt::ItemDataRole | 含义 | 类型 |
|---|---|---|
| Qt::DisplayRole | text | QString |
| Qt::FontRole | font | QFont |
| BackgroundRole | 单元格背景的画笔 | QBrush |
| Qt::TextAlignmentRole | 文本对齐 | enum Qt::AlignmentFlag |
| Qt::CheckStateRole | 使用QVariant() 隐藏复选框, 使用以下代码设置复选框:Qt::Checked | enum Qt::ItemDataRole |
请参阅 Qt 命名空间文档,以进一步了解 `Qt::ItemDataRole ` 枚举的功能。
现在我们需要确定使用分离模型会对应用程序的性能产生何种影响,因此让我们追踪视图调用data()方法的频率。为了追踪视图调用模型的频率,我们在data()方法中添加了一条调试语句,该语句会将日志写入错误输出流。 在这个简短的示例中,data() 将被调用 42 次。每次将光标悬停在字段上时,data() 都会再次被调用——每个单元格 7 次。因此,确保在调用data() 时数据已就绪,并将耗时的查找操作缓存起来,这一点至关重要。
2.3 表格单元格内的时钟

我们仍然有一个只读表格,但这次内容每秒都会变化,因为我们正在显示当前时间。
(文件来源:examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
QVariant MyModel::data(const QModelIndex &index, int role) const
{
int row = index.row();
int col = index.column();
if (role == Qt::DisplayRole && row == 0 && col == 0)
return QTime::currentTime().toString();
return QVariant();
}要让时钟正常运行,还缺了点什么。我们需要每秒向视图通知时间已发生变化,并需要重新读取数据。我们通过定时器来实现这一点。在构造函数中,我们将定时器的间隔设置为1秒,并连接其超时信号。
(文件来源:examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
MyModel::MyModel(QObject *parent)
: QAbstractTableModel(parent)
, timer(new QTimer(this))
{
timer->setInterval(1000);
connect(timer, &QTimer::timeout , this, &MyModel::timerHit);
timer->start();
}以下是相应的槽函数:
(文件来源:examples/widgets/tutorials/modelview/3_changingmodel/mymodel.cpp)
void MyModel::timerHit()
{
// we identify the top left cell
QModelIndex topLeft = createIndex(0,0);
// emit a signal to make the view reread identified data
emit dataChanged(topLeft, topLeft, {Qt::DisplayRole});
}我们通过发出dataChanged()信号,请求视图再次读取左上角单元格中的数据。请注意,我们并未将dataChanged()信号显式地连接到视图上。这是在调用setModel()时自动完成的。
2.4 为列和行设置标题
可以通过视图方法隐藏标题:tableView->verticalHeader()->hide();

不过,表头内容是通过模型设置的,因此我们需要重写headerData()方法:
(文件来源:examples/widgets/tutorials/modelview/4_headers/mymodel.cpp)
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
switch (section) {
case 0:
return QString("first");
case 1:
return QString("second");
case 2:
return QString("third");
}
}
return QVariant();
}请注意,headerData() 方法还包含一个参数角色,其含义与MyModel::data() 中的相同。
2.5 最简编辑示例
在此示例中,我们将构建一个应用程序,该应用程序通过重复表格单元格中输入的值,自动将内容填充到窗口标题中。为了方便访问窗口标题,我们将 `QTableView ` 放置在 `QMainWindow` 中。
模型决定是否提供编辑功能。我们只需修改模型,即可启用可用的编辑功能。具体操作是重写以下虚方法:setData() 和flags()。
(文件来源:examples/widgets/tutorials/modelview/5_edit/mymodel.h)
// mymodel.h
#include <QAbstractTableModel>
#include <QString>
const int COLS= 3;
const int ROWS= 2;
class MyModel : public QAbstractTableModel
{
Q_OBJECT
public:
MyModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
QString m_gridData[ROWS][COLS]; //holds text entered into QTableView
signals:
void editCompleted(const QString &);
};我们使用the 二维数组QString m_gridData 来存储数据。这使得m_gridData 成为MyModel 的核心。MyModel 的其余部分充当包装器,并将m_gridData 适配到QAbstractItemModel 接口。我们还引入了editCompleted() 信号,它使得将修改后的文本传输到窗口标题成为可能。
(文件来源:examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)
bool MyModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role == Qt::EditRole) {
if (!checkIndex(index))
return false;
//save value from editor to member m_gridData
m_gridData[index.row()][index.column()] = value.toString();
//for presentation purposes only: build and emit a joined string
QString result;
for (int row = 0; row < ROWS; row++) {
for (int col= 0; col < COLS; col++)
result += m_gridData[row][col] + ' ';
}
emit editCompleted(result);
return true;
}
return false;
}setData() 将在用户每次编辑单元格时被调用。index 参数告知我们哪个字段已被编辑,而value 则提供编辑过程的结果。 由于我们的单元格仅包含文本,因此 role 参数将始终设置为Qt::EditRole 。如果存在复选框,且用户权限设置为允许选中该复选框,则调用时 role 参数也会设置为Qt::CheckStateRole 。
(文件来源:examples/widgets/tutorials/modelview/5_edit/mymodel.cpp)
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
return Qt::ItemIsEditable | QAbstractTableModel::flags(index);
}可以通过 `flags()` 调整单元格的各种属性。
只需返回Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled 即可向编辑器表明该单元格可被选中。
如果编辑一个单元格会修改该单元格本身以外的其他数据,则模型必须发出dataChanged() 信号,以便读取已更改的数据。
3. 中级主题
3.1 TreeView
您可以将上面的示例转换为一个带有树视图的应用程序。只需将 `QTableView ` 替换为 `QTreeView`,即可获得一个可读写树。模型无需进行任何修改。由于模型本身不存在层次结构,因此该树也不会具有任何层次结构。

QListView、QTableView 和QTreeView 都使用了一种模型抽象,即列表、表格和树的合并体。这使得可以针对同一个模型使用多种不同类型的视图类。

截至目前,我们的示例模型如下所示:

我们希望呈现一棵真实的树。在上面的示例中,我们已将数据封装起来以构建模型。这次我们使用QStandardItemModel ,这是一个用于存储分层数据的容器,同时也实现了QAbstractItemModel 。要显示一棵树,必须将QStandardItemModel 填充为QStandardItem,这些 能够包含项目的所有标准属性,例如文本、字体、复选框或画笔。

(文件来源:examples/widgets/tutorials/modelview/6_treeview/mainwindow.cpp)
// modelview.cpp
#include "mainwindow.h"
#include <QTreeView>
#include <QStandardItemModel>
#include <QStandardItem>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, treeView(new QTreeView(this))
, standardModel(new QStandardItemModel(this))
{
setCentralWidget(treeView);
QList<QStandardItem *> preparedRow = prepareRow("first", "second", "third");
QStandardItem *item = standardModel->invisibleRootItem();
// adding a row to the invisible root item produces a root element
item->appendRow(preparedRow);
QList<QStandardItem *> secondRow = prepareRow("111", "222", "333");
// adding a row to an item starts a subtree
preparedRow.first()->appendRow(secondRow);
treeView->setModel(standardModel);
treeView->expandAll();
}
QList<QStandardItem *> MainWindow::prepareRow(const QString &first,
const QString &second,
const QString &third) const
{
return {new QStandardItem(first),
new QStandardItem(second),
new QStandardItem(third)};
}我们只需实例化一个QStandardItemModel ,并在构造函数中添加几个QStandardItems 。由于QStandardItem 可以包含其他QStandardItems ,因此我们可以构建一个分层数据结构。节点可在视图中折叠和展开。
3.2 处理选择项
我们需要访问所选项的内容,以便将其与层次结构级别一起输出到窗口标题中。

那么,让我们创建几个项目:
(文件来源:examples/widgets/tutorials/modelview/7_selections/mainwindow.cpp)
#include "mainwindow.h"
#include <QTreeView>
#include <QStandardItemModel>
#include <QItemSelectionModel>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, treeView(new QTreeView(this))
, standardModel(new QStandardItemModel(this))
{
setCentralWidget(treeView);
auto *rootNode = standardModel->invisibleRootItem();
// defining a couple of items
auto *americaItem = new QStandardItem("America");
auto *mexicoItem = new QStandardItem("Canada");
auto *usaItem = new QStandardItem("USA");
auto *bostonItem = new QStandardItem("Boston");
auto *europeItem = new QStandardItem("Europe");
auto *italyItem = new QStandardItem("Italy");
auto *romeItem = new QStandardItem("Rome");
auto *veronaItem = new QStandardItem("Verona");
// building up the hierarchy
rootNode-> appendRow(americaItem);
rootNode-> appendRow(europeItem);
americaItem-> appendRow(mexicoItem);
americaItem-> appendRow(usaItem);
usaItem-> appendRow(bostonItem);
europeItem-> appendRow(italyItem);
italyItem-> appendRow(romeItem);
italyItem-> appendRow(veronaItem);
// register the model
treeView->setModel(standardModel);
treeView->expandAll();
// selection changes shall trigger a slot
QItemSelectionModel *selectionModel = treeView->selectionModel();
connect(selectionModel, &QItemSelectionModel::selectionChanged,
this, &MainWindow::selectionChangedSlot);
}视图通过独立的选择模型来管理选择,可通过selectionModel()方法获取该模型。我们获取选择模型,是为了将一个槽与它的selectionChanged()信号连接起来。
(源文件:examples/widgets/tutorials/modelview/7_selections/mainwindow.cpp)
void MainWindow::selectionChangedSlot(const QItemSelection & /*newSelection*/, const QItemSelection & /*oldSelection*/)
{
// get the text of the selected item
const QModelIndex index = treeView->selectionModel()->currentIndex();
QString selectedText = index.data(Qt::DisplayRole).toString();
// find out the hierarchy level of the selected item
int hierarchyLevel = 1;
QModelIndex seekRoot = index;
while (seekRoot.parent().isValid()) {
seekRoot = seekRoot.parent();
hierarchyLevel++;
}
QString showString = QString("%1, Level %2").arg(selectedText)
.arg(hierarchyLevel);
setWindowTitle(showString);
}我们通过调用treeView->selectionModel()->currentIndex()获取与选中项对应的模型索引,并利用该索引获取字段的字符串。 然后,我们只需计算该项的hierarchyLevel 。顶级项没有父项,parent()方法将返回一个默认构造的QModelIndex() 。这就是为什么我们使用parent()方法来遍历至顶级,同时计数迭代过程中执行的步骤。
如上所示,可以选择模型进行检索,但也可以通过QAbstractItemView::setSelectionModel 进行设置。正因如此,三个视图类才能实现选择的同步,因为仅使用了一个选择模型实例。 要在 3 个视图之间共享一个选择模型,请使用selectionModel(),并将结果通过setSelectionModel() 赋值给第二个和第三个视图类。
3.3 预定义模型
使用模型/视图的典型方式是将特定数据进行封装,以便视图类能够使用。不过,Qt 还为常见的底层数据结构提供了预定义模型。如果现有数据结构中有一项适合您的应用程序,那么预定义模型便是一个不错的选择。
| QRangeModel | 适配现有的 C++ 容器或范围 |
| QStringListModel | 存储字符串列表 |
| QStandardItemModel | 存储任意层次结构的项目 |
| QFileSystemModel | 封装本地文件系统 |
| QSqlQueryModel | 封装 SQL 结果集 |
| QSqlTableModel | 封装一个 SQL 表 |
| QSqlRelationalTableModel | 封装包含外键的 SQL 表 |
| QSortFilterProxyModel | 对另一个模型进行排序和/或过滤 |
3.4 委托
在迄今为止的所有示例中,数据均以文本或复选框的形式显示在单元格中,并以文本或复选框的形式进行编辑。提供这些展示和编辑服务的组件被称为“委托”。我们目前才刚刚开始接触委托,因为视图使用的是默认委托。 但试想,如果我们希望使用不同的编辑器(例如滑块或下拉列表),或者希望以图形形式呈现数据。让我们来看一个名为“Star Delegate”的示例,其中使用星号来显示评分:

该视图有一个setItemDelegate()方法,用于替换默认委托并安装自定义委托。可以通过创建一个继承自QStyledItemDelegate 的类来编写新的委托。为了编写一个仅显示星号且不具备输入功能的委托,我们只需重写两个方法。
class StarDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
StarDelegate(QWidget *parent = nullptr);
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const;
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const;
};paint() 方法根据底层数据的内容绘制星级。可通过调用index.data() 方法查询数据。委托的sizeHint() 方法用于获取每颗星的尺寸,从而确保单元格提供足够的高度和宽度来容纳星级。
若希望在视图类的网格内以自定义图形形式展示数据,编写自定义委托是正确的选择。若需跳出网格,则不应使用自定义委托,而应采用自定义视图类。
Qt 文档中关于委托的其他参考资料:
- 委托类
- QAbstractItemDelegate Class Reference
- QSqlRelationalDelegate Class Reference
- QStyledItemDelegate Class Reference
- QItemDelegate Class Reference
3.5 使用 ModelTest 进行调试
模型的被动特性给程序员带来了新的挑战。模型中的不一致性可能会导致应用程序崩溃。由于模型会收到来自视图的大量调用,因此很难查明是哪次调用导致了应用程序崩溃,以及是哪项操作引入了问题。
Qt Labs 提供了一款名为ModelTest 的软件,它能在程序运行时检查模型。每当模型发生变化时,ModelTest 都会扫描模型,并通过断言报告错误。这对树形模型尤为重要,因为其分层结构容易导致许多难以察觉的不一致。
与视图类不同,ModelTest 会利用超出范围的索引来测试模型。这意味着即使应用程序在未启用 ModelTest 时运行完美,启用 ModelTest 后仍可能导致崩溃。因此,在使用 ModelTest 时,您还需要处理所有超出范围的索引。
示例
Qt 提供了多个模型/视图示例。您可以在“项视图示例”页面上找到它们。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.





