MFC转QT - Qt界面开发 - Qt Designer
Qt Designer概述
Qt Designer是Qt开发环境中的可视化UI设计工具,它允许开发者通过拖放方式设计用户界面,而不需要手写界面代码。这与MFC的对话框编辑器类似,但功能更强大、更灵活。
与MFC对话框编辑器的对比
特性 | Qt Designer | MFC对话框编辑器 |
布局系统 | 灵活的布局管理器 | 基于坐标的定位 |
预览能力 | 所见即所得,支持实时预览 | 有限的预览功能 |
自定义控件 | 支持直接集成自定义控件 | 有限支持 |
国际化支持 | 内置文本提取与翻译支持 | 需要额外处理 |
表单继承 | 支持基于现有表单创建新表单 | 不直接支持 |
代码生成 | 生成XML格式UI文件,独立于代码 | 生成资源脚本和C++代码 |
信号槽连接 | 可视化信号槽编辑器 | 消息映射需手动编码 |
样式预览 | 支持样式表预览 | 不支持 |
对于从MFC迁移的开发者,Qt Designer提供了更直观、更高效的界面设计体验。
Qt Designer界面与操作
主要界面组件
- 控件面板(Widget Box) - 包含所有可用控件
- 对象检查器(Object Inspector) - 显示对象层次结构
- 属性编辑器(Property Editor) - 编辑选中控件的属性
- 信号槽编辑器(Signal/Slot Editor) - 建立对象间的连接
- 动作编辑器(Action Editor) - 管理应用程序动作
- 资源浏览器(Resource Browser) - 管理项目资源
- 设计区域(Form Editor) - 主要设计界面
基本操作流程
- 创建表单 - 从模板创建新表单(对话框、主窗口等)
- 添加控件 - 从控件面板拖放控件到设计区域
- 设置布局 - 选择控件应用布局管理器
- 调整属性 - 在属性编辑器中设置控件属性
- 建立连接 - 使用信号槽编辑器建立对象间连接
- 预览表单 - 使用Ctrl+R预览设计效果
- 保存表单 - 保存为.ui文件
常用快捷键
- Ctrl+R - 预览表单
- Ctrl+A - 选择所有控件
- Ctrl+Z/Y - 撤销/重做
- Ctrl+G - 打开布局网格设置
- F3/F4 - 选择上/下一个控件
- Ctrl+Shift+L - 切换表单视图
- Alt+方向键 - 调整控件在布局中的关系
.ui文件与代码生成
Qt Designer创建的界面保存为XML格式的.ui文件,而不是直接生成C++代码。这种设计有几个重要优点:
- 界面设计与代码分离,便于协作开发
- 可以在不重新编译程序的情况下修改界面
- 支持在运行时动态加载界面
- 更容易维护和更新
.ui文件结构
.ui文件是一个XML文档,描述用户界面的结构、属性和布局:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<!-- 中央窗口小部件内容 -->
</widget>
<widget class="QMenuBar" name="menubar">
<!-- 菜单栏内容 -->
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>
在代码中使用.ui文件
有两种主要方式使用.ui文件:
1. 使用uic工具生成C++代码
Qt的User Interface Compiler (uic) 可以将.ui文件转换为C++代码:
// ui_mainwindow.h (自动生成)
namespace Ui {
class MainWindow {};
}
// 在你的类中使用生成的UI
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};
// 实现
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
{
ui->setupUi(this); // 这一行设置ui
// 现在可以通过ui访问控件
ui->pushButton->setText("点击我");
// 连接信号槽
connect(ui->pushButton, &QPushButton::clicked, this, &MainWindow::onButtonClicked);
}
MainWindow::~MainWindow()
{
delete ui;
}
这是最常用的方式,qmake自动处理uic的调用。
2. 在运行时加载.ui文件
对于某些情况,可以在运行时动态加载界面:
#include <QUiLoader>
#include <QFile>
QWidget* loadUiFile(const QString &uiFilePath)
{
QFile file(uiFilePath);
file.open(QFile::ReadOnly);
QUiLoader loader;
QWidget *widget = loader.load(&file);
file.close();
return widget;
}
// 使用
QWidget *myForm = loadUiFile(":/forms/myform.ui");
QPushButton *button = myForm->findChild<QPushButton*>("pushButton");
if (button) {
button->setText("动态加载的按钮");
}
这种方法需要包含QtUiTools模块。
与MFC资源对比
MFC | Qt |
.rc和.rc2文件 | .ui和.qrc文件 |
资源编辑器修改.rc | Qt Designer修改.ui |
对话框ID和控件ID | 对象名称 |
对话框模板代码类向导生成 | uic自动生成ui_*.h |
通过DDX/DDV交换数据 | 直接通过ui->控件访问 |
UpdateData()显式更新 | 无需显式调用更新 |
自定义控件的创建和使用
Qt Designer支持自定义控件,这是从MFC迁移时一个强大的功能,使得复杂界面组件可重用。
创建自定义控件
1. 组合现有控件
最简单的自定义控件是组合现有控件:
// 例:创建带标签的输入框
class LabeledLineEdit : public QWidget
{
Q_OBJECT
Q_PROPERTY(QString label READ label WRITE setLabel)
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
public:
LabeledLineEdit(QWidget *parent = nullptr) : QWidget(parent)
{
// 创建布局
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setMargin(0);
// 创建子控件
m_label = new QLabel(this);
m_lineEdit = new QLineEdit(this);
// 添加到布局
layout->addWidget(m_label);
layout->addWidget(m_lineEdit);
// 连接信号
connect(m_lineEdit, &QLineEdit::textChanged,
this, &LabeledLineEdit::textChanged);
}
QString label() const { return m_label->text(); }
void setLabel(const QString &text) { m_label->setText(text); }
QString text() const { return m_lineEdit->text(); }
void setText(const QString &text) { m_lineEdit->setText(text); }
signals:
void textChanged(const QString &text);
private:
QLabel *m_label;
QLineEdit *m_lineEdit;
};
2. 从头创建自定义绘制控件
对于需要自定义绘制的控件:
// 例:自定义LED指示灯控件
class LedIndicator : public QWidget
{
Q_OBJECT
Q_PROPERTY(bool on READ isOn WRITE setOn)
Q_PROPERTY(QColor onColor READ onColor WRITE setOnColor)
public:
LedIndicator(QWidget *parent = nullptr) : QWidget(parent), m_on(false), m_onColor(Qt::green)
{
setMinimumSize(24, 24);
}
bool isOn() const { return m_on; }
void setOn(bool on) {
if (m_on != on) {
m_on = on;
update();
}
}
QColor onColor() const { return m_onColor; }
void setOnColor(const QColor &color) {
m_onColor = color;
update();
}
protected:
void paintEvent(QPaintEvent *) override
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing);
// 绘制LED
QRect r = rect().adjusted(2, 2, -2, -2);
painter.setPen(Qt::black);
painter.setBrush(m_on ? m_onColor : Qt::gray);
painter.drawEllipse(r);
// 绘制高光
if (m_on) {
painter.setPen(Qt::NoPen);
painter.setBrush(QColor(255, 255, 255, 70));
painter.drawEllipse(r.adjusted(r.width()/4, r.height()/4, -r.width()/2, -r.height()/2));
}
}
private:
bool m_on;
QColor m_onColor;
};
将自定义控件注册到Qt Designer
要在Qt Designer中使用自定义控件,需要创建插件:
// 1. 创建插件类
class MyCustomWidgetsPlugin : public QObject, public QDesignerCustomWidgetCollectionInterface
{
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetCollectionInterface)
Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetCollectionInterface")
public:
MyCustomWidgetsPlugin(QObject *parent = nullptr) : QObject(parent)
{
m_widgets.append(new LabeledLineEditPlugin(this));
m_widgets.append(new LedIndicatorPlugin(this));
}
QList<QDesignerCustomWidgetInterface*> customWidgets() const override
{
return m_widgets;
}
private:
QList<QDesignerCustomWidgetInterface*> m_widgets;
};
// 2. 为每个控件创建插件接口
class LabeledLineEditPlugin : public QObject, public QDesignerCustomWidgetInterface
{
Q_OBJECT
Q_INTERFACES(QDesignerCustomWidgetInterface)
public:
// 实现所需接口方法...
QString name() const override { return "LabeledLineEdit"; }
QString group() const override { return "My Custom Widgets"; }
QWidget *createWidget(QWidget *parent) override
{
return new LabeledLineEdit(parent);
}
// ...其他必要方法
};
编译插件后,将其放入Qt Designer的插件目录即可在控件面板中看到自定义控件。
使用提升(Promotion)
对于不想创建Designer插件的场景,可以使用"提升"功能:
- 在Designer中放置一个基类控件(如QWidget)
- 右键点击 -> "Promote to..."
- 添加自定义类名和头文件
- 确认提升
这样在生成的代码中,会使用你的自定义类而不是基类。
表单设计最佳实践
1. 合理使用布局
- 总是使用布局管理器而非固定位置
- 嵌套布局创建复杂界面
- 使用间隔(spacers)创建平衡的界面
- 设置合适的边距和间距
2. 组织控件层次
- 使用QGroupBox分组相关控件
- 利用对象检查器保持清晰的对象层次
- 为重要控件设置有意义的对象名
3. 优化属性设置
- 设置buddy关系让标签支持快捷键
- 使用Tab顺序工具设置合理的Tab导航
- 为控件添加工具提示和状态提示
- 设置合适的尺寸策略(SizePolicy)
4. 国际化考虑
- 使用tr()包装所有用户可见文本
- 为可能变长的文本预留足够空间
- 避免在设计中假设特定文本长度
5. 表单间复用
- 利用QUiLoader动态加载公共UI组件
- 为常用组件创建单独的.ui文件
- 在大型应用中组织.ui文件到逻辑目录
从MFC迁移到Qt Designer的技巧
对于有MFC经验的开发者,以下是迁移到Qt Designer的实用建议:
1. 思维转换
- 放弃固定坐标的思维,拥抱布局系统
- 使用对象名而非控件ID识别控件
- 专注于声明性UI设计,而非程序式
2. 功能映射
- MFC对话框模板 → Qt .ui文件
- 对话框资源ID → 窗口类名
- 控件变量 → ui->控件名
- DDX/DDV → 直接属性访问
- UpdateData() → 无需显式调用
- 消息处理函数 → 信号槽连接
3. 工作流调整
- 在Designer中完成所有界面设计
- 在代码中处理逻辑和数据处理
- 使用信号槽(而非消息)处理用户交互
- 利用属性系统简化数据绑定
- 使用.ui文件预览快速验证设计