Qt UI 文件机制
使用 Qt 设计界面程序时,若界面是静态的,可以借助 Qt Designer 进行所见即所得的界面设计。设计好界面后,在界面类中对 ui 对象进行操作非常方便。
QtCreator 自动生成的界面类
构建运行一个有 Qt 界面文件的项目时,会在程序构建目录下面生成一些前缀为 ui_
的文件,在界面类(以 window 为例)中引用的就是对应 ui_window 文件中的内容。window.h 和 window.cpp 中的内容示例:
// window.h
namespace Ui {
class Window;
}
class Window : public QWidget {
Q_OBJECT
public:
explicit Window(QWidget *parent = 0);
private:
Ui::Window *ui;
}
// window.cpp
#include "window.h"
#include "ui_window.cpp"
Window::Window() : QWidget(parent), ui(new Ui::Window)
{
}
由 Qt 的 moc 系统自动生成的 ui_window
中的内容示例:
QT_BEGIN_NAMESPACE
class Ui_Window
{
public:
QAction *action;
...
void setupUi(QWidget *Window)
{
...
retranslateUi(Window);
QMetaObject::connectSlotsByName(Window);
}
void retranslateUi(QWidget *Window)
{
....
action->setText(QApplication::translate("Window", "aciton", 0));
}
}
namespace Ui {
class Window: public Ui_Window {};
} // namespace Ui
QT_END_NAMESPACE
ui_window
文件中包含有 Ui_Window 类,还有个 Ui 命名空间,命名空间中包含有一个和界面类同名的 Window 类,而这个类是 Ui_Window 的子类。
Ui::Window 类的方法有: setupUi 和 retranslateUi,从字面上看能够知道一个是用于界面初始化的,一个是用来做多语言功能的。
分离界面和业务逻辑
Qt Designer 使得界面程序的开发变得简单,在 Designer 中拖动组件就可以快速完成界面的开发。但有些界面程序需要在界面上右键显示弹出菜单,如果能够像 Qt Designer 所作的那样,把右键菜单和具体的业务逻辑分开,那么界面类实现起来感觉就会更加流畅。(似乎没法直接用 Qt Designer 制作一个 QMenu 的 ui 文件)
自己动手写界面代码,就按照 Qt Creator 中提供的模版进行编写就可以了。将要用到的界面全都放进 ui_curve.h
中,然后在 curve.cpp
中引用 Ui::Curve。
#ifndef UI_CURVE_H
#define UI_CURVE_H
#include <QVariant>
#include <QApplication>
#include <QWidget>
#include <QGridLayout>
#include <QMenu>
#include <QAction>
// 界面类
class Ui_Curve{
public:
QGridLayout *mainLayout;
QMenu *mainMenu;
QMenu *subCurveMenu;
QAction *autoXAxisRange;
QAction *autoYAxisRange;
QAction *saveDataAction;
QAction *xCurveAction;
QAction *yCurveAction;
QAction *zCurveAction;
QAction *subPlotAction;
QAction *resetAction;
QAction *rescaleAction;
QAction *setXRangeAction;
QAction *setYRangeAction;
QAction *unionSubYAxis;
QAction *wheelOnYOnly;
void setupUi(QWidget *curve) {
if( curve->objectName().isEmpty() )
curve->setObjectName("Curve");
mainLayout = new QGridLayout();
curve->setLayout( mainLayout );
mainMenu = new QMenu( curve );
// setup Sub Curve on One plot => menu
subCurveMenu = new QMenu( curve );
xCurveAction = new QAction( curve );
xCurveAction->setData( "X" ); // used for identify
xCurveAction->setCheckable( true );
xCurveAction->setChecked( true );
subCurveMenu->addAction( xCurveAction );
yCurveAction = new QAction( curve );
yCurveAction->setData( "Y" );
yCurveAction->setCheckable( true );
yCurveAction->setChecked( true );
subCurveMenu->addAction( yCurveAction );
zCurveAction = new QAction( curve );
zCurveAction->setData( "Z" );
zCurveAction->setCheckable( true );
zCurveAction->setChecked( true );
subCurveMenu->addAction( zCurveAction );
mainMenu->addMenu( subCurveMenu );
mainMenu->setProperty( "SubCurveCount", 3 );
// display sub curve in independent widgets => action
subPlotAction = new QAction( curve );
subPlotAction->setCheckable( true );
subPlotAction->setChecked(true);
mainMenu->addAction( subPlotAction );
mainMenu->addSeparator();
resetAction = new QAction( curve );
mainMenu->addAction( resetAction );
mainMenu->addSeparator();
// rescale Y AXis => action
rescaleAction = new QAction( curve );
mainMenu->addAction( rescaleAction );
//
autoXAxisRange = new QAction( curve );
autoXAxisRange->setCheckable( true );
autoXAxisRange->setChecked( true );
mainMenu->addAction( autoXAxisRange );
autoYAxisRange = new QAction( curve );
autoYAxisRange->setCheckable( true );
autoYAxisRange->setChecked( true );
mainMenu->addAction( autoYAxisRange );
mainMenu->addSeparator();
setXRangeAction = new QAction( curve );
mainMenu->addAction( setXRangeAction );
setYRangeAction = new QAction( curve );
mainMenu->addAction( setYRangeAction );
unionSubYAxis = new QAction( curve );
unionSubYAxis->setCheckable( true );
unionSubYAxis->setChecked( true );
mainMenu->addAction( unionSubYAxis );
wheelOnYOnly = new QAction( curve );
wheelOnYOnly->setCheckable( true );
wheelOnYOnly->setChecked( true );
mainMenu->addAction( wheelOnYOnly );
mainMenu->addSeparator();
saveDataAction = new QAction( curve );
saveDataAction->setCheckable( true );
saveDataAction->setChecked( false );
mainMenu->addAction( saveDataAction );
retranslateUi( curve );
QMetaObject::connectSlotsByName(curve);
}
void retranslateUi(QWidget *curve) {
subCurveMenu->setTitle(QApplication::translate("Curve", "Show Sub Curve On Main Widget", 0));
xCurveAction->setText(QApplication::translate("Curve", "Display X (red) Line", 0));
yCurveAction->setText(QApplication::translate("Curve", "Display Y (green) Line", 0));
zCurveAction->setText(QApplication::translate("Curve", "Display Z (blue) Line", 0));
subPlotAction->setText(QApplication::translate("Curve", "Display Sub Curves In Other Widgets", 0));
resetAction->setText(QApplication::translate("Curve", "Reset Timer and clear ALL points", 0));
rescaleAction->setText(QApplication::translate("Curve", "Rescale Y Axis", 0));
autoXAxisRange->setText(QApplication::translate("Curve", "Auto Set X Axis Range", 0));
autoYAxisRange->setText(QApplication::translate("Curve", "Auto Set Y Axis Range", 0));
setXRangeAction->setText(QApplication::translate("Curve", "Set X Axis Range", 0));
setYRangeAction->setText(QApplication::translate("Curve", "Set Y Axis Range", 0));
unionSubYAxis->setText(QApplication::translate("Curve", "union All three sub y axis (x-y-z)", 0));
wheelOnYOnly->setText(QApplication::translate("Curve", "Wheel Grag or Zomm On Y axis only", 0));
saveDataAction->setText(QApplication::translate("Curve", "Save All Data On Close", 0));
}
};
namespace Ui {
class Curve : public Ui_Curve {};
}
#endif // UI_CURVE_H
然后在界面类中重写 mousePressEvent 事件,检测到鼠标右击后调用 ui->mainMenu->popup( event->globalPos() );
弹出菜单。
上述文件中有两个方法值得注意:
QMetaObject::connectSlotsByName
这是用来自动连接信号和槽(槽的名称模式为:void on_<object name>_<signal name>(<signal parameters>);
)的,但是Ui_Curve
中我没有给各个 QAction 对象写 objectName,所以这里对connectSlotsByName
的调用没有效果。QApplication::translate
是提供翻译功能的,和 Qt 中常用的QObject::tr
功能相同,但显然QObject::tr
用起来更方便。
虽然编写界面代码没什么技术含量,但是更重要的事情是理解 Qt 的 UI 模型。这个模型能够将界面代码从逻辑代码中分离出来,分离后的 UI 和业务逻辑使得代码的可维护性提高,整体代码的可读性和可维护性都提高了很多。