Qt 学习(4)

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 和业务逻辑使得代码的可维护性提高,整体代码的可读性和可维护性都提高了很多。

posted @ 2018-08-31 09:50  brifuture  阅读(188)  评论(0编辑  收藏  举报