1 Qt元对象系统
由于C++的RTTI机制(通过dynamic_cast , 和 typeid)只能提供有限的类型信息,于是Qt构建了自己的元对象系统(Meta-Object)。
Qt的元对象系统是Qt框架中的一个核心特性,它提供了一种在运行时处理对象的类型信息和属性的机制。元对象系统是Qt的信号和槽机制的基础,它允许对象之间进行通信和交互。
在Qt中,每个QObject派生类都可以使用元对象系统。通过使用特殊的宏(例如Q_OBJECT),类将被标记为具有元对象特性。这些宏会在编译时生成额外的代码,用于创建对象的元对象描述符(meta-object descriptor)。
元对象描述符是一个包含了类的名称、父类信息、信号和槽等元数据的数据结构。它存储在每个QObject派生类的静态元对象中。 通过元对象描述符,Qt可以在运行时提供关于对象的类型信息,包括类名、父类关系、属性列表和方法列表等。
元对象系统的一个重要特性是信号和槽机制。信号和槽是Qt中用于对象之间通信的机制,它们可以跨线程、跨对象进行连接。当一个对象发出信号时,连接到该信号的槽函数将会被自动调用。这种机制通过元对象系统的支持实现,元对象描述符中存储了信号和槽的相关信息,包括名称和参数类型等。
元对象系统还支持动态属性的添加和查询。QObject派生类可以使用setProperty()方法为对象添加自定义属性,并使用property()方法查询属性的值。这种机制为对象的扩展和自定义提供了灵活性。
总结来说,Qt的元对象系统是一个在运行时处理对象类型信息和属性的机制。它为Qt提供了信号和槽机制、动态属性支持等重要功能,使得Qt框架在事件驱动编程和对象间通信方面具有强大的灵活性和可扩展性。
2 信号和槽
信号是在特定事件发生时由对象发出的通知,它可以被连接到一个或多个槽函数。槽函数是用于接收信号并执行相应操作的成员函数。当信号被发出时,连接到该信号的槽函数将会被自动调用。 (采用观察者模式实现)
以下是使用信号和槽的基本步骤:
定义信号:在QObject派生类的声明中使用signals关键字定义信号。例如:
信号的返回类型是一个void
class MyObject : public QObject
{
Q_OBJECT
public:
// 定义一个信号
signals:
void mySignal(int value);
};
实现槽函数:在QObject派生类的声明或实现中定义槽函数。槽函数可以具有任意名称、参数和返回类型,但必须位于public、protected或private部分。例如:
槽函数的参数量不能大于信号传递的参数量,但可以小于。
class MyObject : public QObject
{
Q_OBJECT
public slots:
// 定义一个槽函数
void mySlot(int value)
{
// 执行槽函数操作
}
};
连接信号和槽:使用QObject的connect静态函数连接信号和槽。连接可以在任何QObject派生类的实例之间进行,也可以跨线程进行连接。例如:
MyObject* obj1 = new MyObject();
MyObject* obj2 = new MyObject();
// 连接信号和槽 方式1
QObject::connect(obj1, SIGNAL(mySignal(int)), obj2, SLOT(mySlot(int)));
//方式2
QObject::connect(obj1, &MyObject::mySignal, obj2, &MyObject::mySlot);
发出信号:通过使用emit关键字发出信号。例如:
emit mySignal(42);
当信号被发出时,连接到该信号的所有槽函数都会按照连接的顺序被调用。信号和槽的连接可以通过QObject::connect()函数建立,也可以使用Qt提供的信号与槽编辑器进行可视化连接。
信号和槽机制是Qt框架的重要特性,它提供了一种松耦合的对象间通信方式,使得程序的设计更加灵活和可维护。通过信号和槽,可以实现事件驱动的编程模型,处理用户交互、状态变化和异步操作等场景。
3 布局
布局的父类可以是布局也可以是组件对象,而组件的对象不能是布局
MainWindow可以这样设置布局
QWidget* centralWidget = new QWidget(this); QMainWindow 更改布局
QHBoxLayout* layout = new QHBoxLayout(centralWidget);
layout->addWidget(m_tabWidget);
this->setCentralWidget(centralWidget);
- QHBoxLayout
水平布局管理器,它会将所有的控件水平排列,控件从左到右依次排列。可以设置控件之间的间距和对齐方式。QHBoxLayout 可以嵌套使用,例如在垂直布局中放置水平布局。
- QVBoxLayout
垂直布局管理器,类似于 QHBoxLayout,但是控件是垂直排列的,从上到下依次排列。它也可以嵌套使用,并且允许设置控件之间的间距和对齐方式。
- QGridLayout
网格布局管理器,它将控件放置在一个矩形的网格中。你可以指定每个控件占据的行数和列数,以及行和列之间的间距。QGridLayout 提供了灵活的布局方式,适合需要复杂布局的界面。
//添加到0,0位置占据1行2列
layout->addWidget(button, 0,0, 1, 2);
- QFormLayout
表单布局管理器,它将控件组织成两列,左列用于标签(如文本描述),右列用于控件(如输入框、按钮等)。QFormLayout 自动调整标签和控件的间距,使得界面看起来整齐有序。
- QStackedLayout
堆栈布局管理器,它管理一个控件栈。在任何给定时间,只有栈顶的控件是可见的。这允许你在同一个位置显示不同的界面,通过切换栈中的控件来实现界面的切换。
- QSpacer
虽然 QSpacer 不是一个布局管理器,但它在布局中扮演着重要的角色。QSpacer 可以被添加到布局中,用来推动其他控件的位置,或者用来填充空间。它可以是水平的、垂直的,或者同时具有水平和垂直的方向。
- QSplitter
QSplitter 不是一个布局管理器,而是一个可以分割空间并允许用户调整子控件大小的控件。它通常用于创建具有可调整大小的窗格的复杂界面。
4 QStackedWidget
QStackedWidget 是 Qt 框架中的一个控件,它用于管理一个页面堆栈,每个页面可以是一个 QWidget 的子类。QStackedWidget 允许你在同一个位置显示不同的界面,通过切换堆栈中的页面来实现视图的切换。这在创建向导、多页面设置对话框或标签页界面等场景中非常有用。
主要特点:
页面堆栈:QStackedWidget 管理一个页面集合,每个页面都是一个独立的 QWidget 对象。
切换动画:页面之间的切换可以有动画效果,如淡入淡出或滑动效果。
当前页面:可以通过 QStackedWidget 的 currentWidget() 和 setCurrentIndex(int) 方法来获取和设置当前显示的页面。
页面索引:每个页面都有一个从 0 开始的索引,可以通过 addWidget(QWidget *widget) 方法添加页面,并获取页面的索引。
信号和槽:QStackedWidget 发出 currentChanged(int) 信号,当前页面改变时可以连接这个信号来执行特定的操作。
class MyWidget : public QWidget {
public:
MyWidget() {
// 创建 QStackedWidget 实例
stackedWidget = new QStackedWidget(this);
// 创建三个页面
QWidget *page1 = new QWidget();
QWidget *page2 = new QWidget();
QWidget *page3 = new QWidget();
// 向 QStackedWidget 添加页面
stackedWidget->addWidget(page1);
stackedWidget->addWidget(page2);
stackedWidget->addWidget(page3);
// 设置页面的布局和控件
QVBoxLayout *layout1 = new QVBoxLayout(page1);
layout1->addWidget(new QPushButton("Page 1", page1));
QVBoxLayout *layout2 = new QVBoxLayout(page2);
layout2->addWidget(new QPushButton("Page 2", page2));
QVBoxLayout *layout3 = new QVBoxLayout(page3);
layout3->addWidget(new QPushButton("Page 3", page3));
// 设置 QStackedWidget 的布局
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(stackedWidget);
// 设置当前页面为第一个页面
stackedWidget->setCurrentIndex(0);
}
QStackedWidget *stackedWidget; // 指向 QStackedWidget 的指针
};
5 Qt中的mvc
在Qt框架中,MVC(Model-View-Controller)模式是一种设计模式,用于将应用程序的数据、界面和用户交互逻辑分离开来。这种模式有助于提高代码的可维护性和可扩展性,并且使得应用程序的不同部分更容易管理和复用。
MVC组件:
Model(模型):
负责存储和管理应用程序的数据。
提供数据访问的API,通常以属性(properties)的形式。
通知视图(View)数据的变化,通常通过信号和槽(signals and slots)机制。
View(视图):
负责显示模型(Model)中的数据。
可以有多个视图显示同一个模型的数据,每个视图可能以不同的方式展示数据。
视图不直接修改模型的数据,而是通过控制器(Controller)来响应用户交互。
Controller(控制器):
负责接收用户的输入和命令。
处理用户交互逻辑,调用模型(Model)的方法来更新数据。
可以管理一个或多个视图,并根据模型的变化来更新视图。
Qt中的MVC实现:
在Qt中,MVC模式可以通过以下方式实现:
Model:
使用 QAbstractItemModel 或 QAbstractListModel 等类来创建自定义模型。
模型可以是任何数据结构,如树形结构、列表、字典等。
通过实现 data()、setData()、rowsInserted() 等方法来提供数据访问和更新通知。
View:
使用 QListView、QTreeView、QTableView、QChartView 等视图控件来显示数据。
视图控件通过设置模型(setModel())来显示数据。
视图控件负责渲染数据,并在数据变化时更新显示。
Controller:
可以是一个或多个 QWidget 或 Q_OBJECT 类的实例,如对话框、窗口或其他自定义控件。
控制器通过槽函数来处理用户的输入,如按钮点击、菜单选择等。
控制器通过调用模型的方法来更新数据,并在必要时更新视图。
//Model 通过重载一下几个函数给view返回数据
QVariant data(const QModelIndex &index, int role) const override;
bool setData(const QModelIndex & index, const QVariant & value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
//view 视图
加载model提供的数据
委托当视图要修改数据时可重载一下函数
QWidget * createEditor(QWidget* parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
###################################################
void TableViewDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
TableModel* model = (TableModel*)index.model();
int row = index.row();
int col = index.column();
QString type = model->getType(col);
QString valueStr = model->getValue(row, col);
if(type == "Number")
{
QDoubleSpinBox* spinBox = static_cast<QDoubleSpinBox*>(editor);
qreal value = valueStr.toDouble();
spinBox->setValue(value);
}else
{
QLineEdit* lineEdit = static_cast<QLineEdit*>(editor);
lineEdit->setText(valueStr);
}
}
6 Dom解析xml文件
整体结构
class XmlModel : public QStandardItemModel
{
Q_OBJECT
public:
XmlModel(QObject* parent = nullptr);
bool loadXml(const QString &fileName);
QString getAppPath(QString path);
void parseXmlFile();
void saveXmlFile();
protected:
QString m_fileName;
QStringList m_colNameList;
//xml文件的根节点,可用于更新源文件
QDomDocument m_document;
QMap<QString, QString> m_colTypeMap;
//每一行xml的属性值
QVector<QVector<XmlValueElement>> m_rowValueVec;
};
class XmlValueElement
{
public:
void setElement(QString name, QString value )
{
m_element.setAttribute(name, value);
}
public:
QString m_name = "";
QString m_value = "";
QDomElement m_element;
};
具体步骤如下
#include "XmlModel.h"
XmlModel::XmlModel(QObject* parent)
: QStandardItemModel(parent)
{
QString xmlName = getAppPath(qApp->applicationDirPath());
xmlName = xmlName + "/xml/test.xml";
qDebug() << xmlName;
m_fileName = xmlName;
loadXml(xmlName);
}
bool XmlModel::loadXml(const QString &fileName) {
QFile file(fileName);
if (!file.open(QIODevice::ReadWrite))
{
qDebug() << "Failed to open XML file.";
return false;
}
if(!m_document.setContent(&file))
{
file.close();
qDebug() << "Failed to parse XML content";
return false;
}
file.close();
return true;
}
QString XmlModel::getAppPath(QString path)
{
QString str = path;
// 定义正则表达式
QRegularExpression regexPattern(".+?\\.app");
// 在路径中搜索匹配的部分
QRegularExpressionMatch match = regexPattern.match(path);
if (match.hasMatch())
{
str = match.captured(0);
int index = str.lastIndexOf("/");
if(index>=0)
{
str = str.left(index);
}
}
return str;
}
void XmlModel::parseXmlFile()
{
QDomElement root = m_document.documentElement();
QDomNamedNodeMap map = root.attributes();
int colSize = map.size();
this->setColumnCount(colSize);
m_colTypeMap.clear();
m_colNameList.clear();
for(int i = 0; i < colSize; ++ i)
{
QDomNode node = map.item(i);
QString name = node.nodeName();
QString value = node.nodeValue();
m_colNameList.append(name);
m_colTypeMap[name] = value;
}
this->setHorizontalHeaderLabels(m_colNameList);
//获取xml每一行属性及对应的值
QDomElement infoElement = root.firstChildElement();
while(!infoElement.isNull())
{
QVector<XmlValueElement> tmpXmlVec;
for(int i = 0; i < m_colNameList.size(); ++ i)
{
XmlValueElement valueElement;
valueElement.m_name = m_colNameList.at(i);
valueElement.m_value = infoElement.attribute(m_colNameList.at(i));
valueElement.m_element = infoElement;
tmpXmlVec.append(valueElement);
}
m_rowValueVec.append(tmpXmlVec);
infoElement = infoElement.nextSiblingElement();
}
}
void XmlModel::saveXmlFile()
{
QFile file(m_fileName);
if(!file.open(QIODevice::WriteOnly))
{
return;
}
QTextStream out(&file);
out.setCodec("UTF-8");
m_document.save(out, 4);
file.close();
}