zoukankan      html  css  js  c++  java
  • 编写Qt Designer自定义控件

    一)流程概述

             在使用Qt Designer设计窗体界面时,我们可以使用Widget Box里的窗体控件非常方便的绘制界面,比如拖进去一个按钮,一个文本编辑器等。虽然Qt Designer里的控件可以满足我们大部分的需求,但是有时候,也会产生一些特殊的需要,比如一个输入框,我们要输入的是经纬度,此时就会有两种输入方式,一种是小数形式,一种是度分秒的形式,此时只使用一个简单的LineEdit是无法满足需求的。我们设想构造这样一个输入控件,它可以支持浮点数输入,同时它还具有一个属性,更改这个属性可以使其切换为经纬度输入形式。如果我们的多个窗体上都需要输入经纬度,那么构造这样一个控件,将会非常方便。下面就以此为例,讲解一下如何创建自定义的窗体控件。

            第一步:创建QtDesigner自定义控件工程

             打开Qt Creator,创建一个Qt 设计师自定义控件,如下图所示:

          根据向导提示,创建好工程,这里取名为LogLatEdit,工程目录如下图所示:

            第二步:编译控件工程

            为了淌通整个自定义控件的编写流程,我们先不做任何更改,切换为Release版本,直接编译一下。

            第三步:部署插件

            编译完成后,在输出目录下,将生成的dll文件和lib文件一起拷贝到Qt的插件目录下,以我使用的Qt 4.8.4为例,在Qt 4.8.4的安装目录D:\Qt\4.8.4下,找到plugins目录,在其中找到designer目录,然后把dll和lib放进去,完整路径为:D:\Qt\4.8.4\plugins\designer。之后,启动D:\Qt\4.8.4\bin下的designer.exe,创建一个窗体,此时就会发现在左侧的Widget Box里出现了我们自己的LogLatEdit控件,我们可以像使用其它控件一样,把我们自己的控件拖绘到窗体上,如下图所示:

          如果自定义控件没有出现在Widgetbox里,那么此时你可以通过【帮助-关于插件】菜单,打开插件信息对话框,点击刷新按钮,只要你没有忘记把dll和lib文件拷贝到正确的位置,插件都会自动识别并加载。对于其它版本的Qt也一样,比如我自己的电脑里安装了好几个版本的Qt,对于其它版本的Qt,做法也是一样,只需要把插件工程生成的dll和lib文件放置到相应版本的插件目录下去即可。

           到此,我们就理清了如何创建一个自定义控件,并且知道了如何部署、加载并使用自定义控件。下面我们开始编写我们需要的控件,对于只想了解自定义控件开发过程的读者,至此就已经知道如何做了,那么后面的内容您可以略过不读了。

     二)控件界面设计

             既然是控件,就应该有界面,默认生成的控件类只是一个继承了QWidget的类,如下:

             [cpp]  
    1. #ifndef LOGLATEDIT_H  
    2. #define LOGLATEDIT_H  
    3.   
    4. #include <QWidget>  
    5.   
    6. class LogLatEdit : public QWidget  
    7. {  
    8.     Q_OBJECT  
    9. public:  
    10.     LogLatEdit(QWidget *parent = 0);  
    11. };  
    12.   
    13. #endif  

           我们需要的是如下的控件组合:

           该控件在输入浮点型模式下,是上面那样,直接输入以度为单位的浮点数即可,如果经纬度信息不是以度为单位的,此时自己换算的话非常麻烦,因此可以切换为度分秒的输入模式,也就是下面哪种样子。为此,我们需要添加一个LineEidt,两个SpinBox和一个DoubleSpinBox以及一些Label控件。

            为了简单起见,我不想自己手动去写这些界面相关的代码,为此我们可以先删掉默认生成的loglatedit.h和loglatedit.cpp文件,这样我们就可以重新使用LogLatEdit这个名称重新新建一个ui类了。右键工程,选择“添加新文件”,使用Qt下的“Qt设计师界面类”模板,创建一个ui类,如下图所示:

           这个界面类,我们重新命名为我们需要的控件名称LogLatEdit,之后编辑ui文件,调整QWidget的大小,然后绘制控件,如图:

           这里使用了HorizontalLayout控件对控件组合进行分组,为了演示方便,此处将二者拖放开来,最终的控件,实际上是两个水平布局叠加在一起,每次只有一个控件组合可见。

           当然另一个做法也是可行的,那就是不要删除最初默认生成的loglatedit文件,而是把这个新建的ui类命名为别的名字,比如test,在绘制好控件之后,编译一下,然后找到Moc生成的ui_test.h文件,打开该文件,我们把相应的代码拷贝到我们的LogLatEdit控件类中去,然后做适当的修改,比如设定两个控件组合的位置及可见性,以及控件容器QWidget的大小等。这里为了把所有相关的代码都放在一起,便于读者测试,使用的就是这种方式。

           使用ui类的方式比较方便,所有与界面相关的处理都可以在窗体设计器中完成,并且界面与代码分离,这种代码管理模式逻辑清晰,便于管理。不过使用ui类时,如果我们的ui类名称不是默认的LogLatEdit,则需要修改一下插件类中创建插件的代码,主要是修改loglateditplugin.cpp文件里createWidget方法中返回的控件对象。

           经过修改之后我们的经纬度控件的代码如下:

             [cpp] 
    1. #ifndef LOGLATEDIT_H  
    2. #define LOGLATEDIT_H  
    3.   
    4. #include <QWidget>  
    5. #include <QtCore/QVariant>  
    6. #include <QtGui/QAction>  
    7. #include <QtGui/QApplication>  
    8. #include <QtGui/QButtonGroup>  
    9. #include <QtGui/QDoubleSpinBox>  
    10. #include <QtGui/QHBoxLayout>  
    11. #include <QtGui/QHeaderView>  
    12. #include <QtGui/QLabel>  
    13. #include <QtGui/QLineEdit>  
    14. #include <QtGui/QSpacerItem>  
    15. #include <QtGui/QSpinBox>  
    16. #include <QtGui/QWidget>  
    17.   
    18. class LogLatEdit : public QWidget  
    19. {  
    20.     Q_OBJECT  
    21. public:  
    22.     LogLatEdit(QWidget *parent = 0);  
    23.   
    24. private:  
    25.     QWidget *horizontalLayoutWidget;  
    26.     QHBoxLayout *horizontalLayout;  
    27.     QSpinBox *spinBox;  
    28.     QLabel *label_7;  
    29.     QSpinBox *spinBox_2;  
    30.     QLabel *label_8;  
    31.     QDoubleSpinBox *doubleSpinBox;  
    32.     QLabel *label_9;  
    33.     QWidget *horizontalLayoutWidget_2;  
    34.     QHBoxLayout *horizontalLayout_2;  
    35.     QLineEdit *lineEdit;  
    36.     QLabel *label_6;  
    37.     QSpacerItem *horizontalSpacer;  
    38.     QSpacerItem *horizontalSpacer_2;  
    39.   
    40. };  
    41.   
    42. #endif  

    源文件:

             [cpp] 
    1. #include "loglatedit.h"  
    2.   
    3. LogLatEdit::LogLatEdit(QWidget *parent) :  
    4.     QWidget(parent)  
    5. {  
    6.     this->resize(160, 22);  
    7.     this->setMinimumSize(QSize(160,22)); //限定控件的大小  
    8.     this->setMaximumSize(QSize(200,22));  
    9.     horizontalLayoutWidget = new QWidget(this);  
    10.     horizontalLayoutWidget->setObjectName(QString::fromUtf8("horizontalLayoutWidget"));  
    11.     horizontalLayoutWidget->setGeometry(QRect(0, 0, 160, 22));  
    12.     horizontalLayout = new QHBoxLayout(horizontalLayoutWidget);  
    13.     horizontalLayout->setSpacing(1);  
    14.     horizontalLayout->setObjectName(QString::fromUtf8("horizontalLayout"));  
    15.     horizontalLayout->setContentsMargins(0, 0, 0, 0);  
    16.     spinBox = new QSpinBox(horizontalLayoutWidget);  
    17.     spinBox->setObjectName(QString::fromUtf8("spinBox"));  
    18.     spinBox->setMinimumSize(QSize(35, 20));  
    19.     spinBox->setMaximumSize(QSize(35, 20));  
    20.     spinBox->setMaximum(90);  
    21.   
    22.     horizontalLayout->addWidget(spinBox);  
    23.   
    24.     label_7 = new QLabel(horizontalLayoutWidget);  
    25.     label_7->setObjectName(QString::fromUtf8("label_7"));  
    26.     QSizePolicy sizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);  
    27.     sizePolicy.setHorizontalStretch(0);  
    28.     sizePolicy.setVerticalStretch(0);  
    29.     sizePolicy.setHeightForWidth(label_7->sizePolicy().hasHeightForWidth());  
    30.     label_7->setSizePolicy(sizePolicy);  
    31.     label_7->setMinimumSize(QSize(3, 20));  
    32.     label_7->setSizeIncrement(QSize(1, 0));  
    33.   
    34.     horizontalLayout->addWidget(label_7);  
    35.   
    36.     horizontalSpacer = new QSpacerItem(0, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);  
    37.   
    38.     horizontalLayout->addItem(horizontalSpacer);  
    39.   
    40.     spinBox_2 = new QSpinBox(horizontalLayoutWidget);  
    41.     spinBox_2->setObjectName(QString::fromUtf8("spinBox_2"));  
    42.     spinBox_2->setMinimumSize(QSize(35, 20));  
    43.     spinBox_2->setMaximumSize(QSize(35, 20));  
    44.     spinBox_2->setMaximum(90);  
    45.   
    46.     horizontalLayout->addWidget(spinBox_2);  
    47.   
    48.     label_8 = new QLabel(horizontalLayoutWidget);  
    49.     label_8->setObjectName(QString::fromUtf8("label_8"));  
    50.     sizePolicy.setHeightForWidth(label_8->sizePolicy().hasHeightForWidth());  
    51.     label_8->setSizePolicy(sizePolicy);  
    52.     label_8->setMinimumSize(QSize(3, 20));  
    53.     label_8->setSizeIncrement(QSize(1, 0));  
    54.   
    55.     horizontalLayout->addWidget(label_8);  
    56.   
    57.     horizontalSpacer_2 = new QSpacerItem(0, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);  
    58.   
    59.     horizontalLayout->addItem(horizontalSpacer_2);  
    60.   
    61.     doubleSpinBox = new QDoubleSpinBox(horizontalLayoutWidget);  
    62.     doubleSpinBox->setObjectName(QString::fromUtf8("doubleSpinBox"));  
    63.     doubleSpinBox->setMinimumSize(QSize(66, 20));  
    64.     doubleSpinBox->setMaximumSize(QSize(66, 20));  
    65.     doubleSpinBox->setDecimals(4);  
    66.     doubleSpinBox->setMaximum(90);  
    67.   
    68.     horizontalLayout->addWidget(doubleSpinBox);  
    69.   
    70.     label_9 = new QLabel(horizontalLayoutWidget);  
    71.     label_9->setObjectName(QString::fromUtf8("label_9"));  
    72.     sizePolicy.setHeightForWidth(label_9->sizePolicy().hasHeightForWidth());  
    73.     label_9->setSizePolicy(sizePolicy);  
    74.     label_9->setMinimumSize(QSize(3, 20));  
    75.     label_9->setSizeIncrement(QSize(1, 0));  
    76.   
    77.     horizontalLayout->addWidget(label_9);  
    78.   
    79.     horizontalLayoutWidget_2 = new QWidget(this);  
    80.     horizontalLayoutWidget_2->setObjectName(QString::fromUtf8("horizontalLayoutWidget_2"));  
    81.     horizontalLayoutWidget_2->setGeometry(QRect(0, 0, 160, 22));  
    82.     horizontalLayout_2 = new QHBoxLayout(horizontalLayoutWidget_2);  
    83.     horizontalLayout_2->setSpacing(1);  
    84.     horizontalLayout_2->setObjectName(QString::fromUtf8("horizontalLayout_2"));  
    85.     horizontalLayout_2->setContentsMargins(0, 0, 0, 0);  
    86.     lineEdit = new QLineEdit(horizontalLayoutWidget_2);  
    87.     lineEdit->setObjectName(QString::fromUtf8("lineEdit"));  
    88.     lineEdit->setInputMethodHints(Qt::ImhDigitsOnly|Qt::ImhFormattedNumbersOnly);  
    89.   
    90.     horizontalLayout_2->addWidget(lineEdit);  
    91.   
    92.     label_6 = new QLabel(horizontalLayoutWidget_2);  
    93.     label_6->setObjectName(QString::fromUtf8("label_6"));  
    94.   
    95.     horizontalLayout_2->addWidget(label_6);  
    96.   
    97.     label_7->setText(QApplication::translate("LogLatEdit", "\302\260", 0, QApplication::UnicodeUTF8));  
    98.     label_8->setText(QApplication::translate("LogLatEdit", "\342\200\262", 0, QApplication::UnicodeUTF8));  
    99.     label_9->setText(QApplication::translate("LogLatEdit", "\342\200\263", 0, QApplication::UnicodeUTF8));  
    100.     label_6->setText(QApplication::translate("LogLatEdit", "\302\260", 0, QApplication::UnicodeUTF8));  
    101.   
    102.     horizontalLayoutWidget_2->setVisible(false);  
    103.     QMetaObject::connectSlotsByName(this);  
    104.   
    105. }  

           至此,界面相关的事情就做完了,后面我们需要给这个控件添加两个属性,一个输入模式属性,更改这个属性时,我们的控件可以在两种输入模式下切换,另一个属性就是经纬度值,我们要使其可以设置和返回经纬度值。

    三)控件属性函数实现

            窗体控件都有属性,比如QLineEdit就有text属性,另外还有设置属性,比如QLineEdit的readOnly属性。下面就讲解一下如何给自己的控件添加属性的问题。对于我们的经纬度输入控件,它应该具有一个设置属性和一个值属性,我们把设置属性命名为inputMode,把值属性命名为value,先来讲解设置属性inputMode。

           对于inputMode属性,它应该是一个枚举值,分别对应控件的两种状态,即浮点输入模式和经纬度输入模式,因此这个类型应该定义为枚举型。另外对于Qt的控件类,如何声明属性,我们可以参考Qt的源代码,比如D:\Qt\4.8.4\src\gui\widgets目录下QMainWindow的定义,我们可以仿照其做法,实现输入模式属性,这里不多做解释,直接给出代码:

             [cpp]  
    1. class LogLatEdit : public QWidget  
    2. {  
    3.     Q_OBJECT  
    4.   
    5.     Q_ENUMS(InputMode)  
    6.     Q_PROPERTY(InputMode inputMode READ inputMode WRITE setInputMode)  
    7.     Q_PROPERTY(float value READ value WRITE setValue)  
    8. public:  
    9.     LogLatEdit(QWidget *parent = 0);  
    10.   
    11.     enum InputMode  
    12.     {  
    13.         Float,      //float number mode  
    14.         DegSecMin   //Degree second minute mode  
    15.     };  
    16.     InputMode inputMode()const;  
    17. void setInputMode(const InputMode mode);  
    18.   
    19.     double value() const;  
    20. void setValue(const double val);  
    21.     //其余略  
    22. };  

           仿照QMainWindow的DockOptions属性和iconSize属性,我们实现我们的inputMode和value属性。这里注意一下使用Q_PROPERTY宏声明属性的方法,用法很简单,不多解释。下面是对应属性的实现代码:

             [cpp]
    1. LogLatEdit::InputMode LogLatEdit::inputMode() const  
    2. {  
    3.     return m_Mode;  
    4. }  
    5.   
    6.   
    7. void LogLatEdit::setInputMode(const InputMode mode)  
    8. {      
    9.     double val = this->value();  
    10.     if(mode==DegSecMin)  
    11.     {  
    12.         horizontalLayoutWidget->setVisible(true);  
    13.         horizontalLayoutWidget_2->setVisible(false);  
    14.     }  
    15.     else  
    16.     {  
    17.         horizontalLayoutWidget->setVisible(false);  
    18.         horizontalLayoutWidget_2->setVisible(true);  
    19.     }  
    20.     m_Mode = mode;  
    21.     this->setValue(val);  
    22. }  
    23.   
    24.   
    25. double LogLatEdit::value() const  
    26. {  
    27.     if(this->inputMode()==Float)  
    28.     {  
    29.         return this->lineEdit->text().toDouble();  
    30.     }  
    31.     else  
    32.     {  
    33.         double val = 0;  
    34.         val = this->spinBox->value() +  
    35.                 double(this->spinBox_2->value())/60.0 +  
    36.                 this->doubleSpinBox->value()/3600.0;  
    37.         return val;  
    38.     }  
    39. }  
    40.   
    41. void LogLatEdit::setValue(const double val)  
    42. {  
    43.     m_Value = val;  
    44.     if(this->inputMode()==Float)  
    45.     {  
    46.         this->lineEdit->setText(tr("%1").arg(val));  
    47.     }  
    48.     else  
    49.     {  
    50.         this->spinBox->setValue(int(val));  
    51.         this->spinBox_2->setValue(int((val-int(val))*60));  
    52.         this->doubleSpinBox->setValue(((val-int(val))*60-int((val-int(val))*60)));  
    53.     }  
    54. }  

            控件编写完毕以后,把生成的dll和lib文件一起拷贝到Qt安装目录下的插件目录里,比如我安装在D盘里的Qt 4.8.4,路径为:D:\Qt\4.8.4\plugins\designer,拷贝进去以后,这个插件就可以被Qt Designer加载了,此时这个自定义控件就可以像普通的控件一样使用了。当然如有必要,还可以给这个控件添加一些信号和槽,另外按照经纬度的取值范围不同,做一下区分经度和维度的处理,本例中不需要这么复杂,就不再深入探索了,下面是使用自定义的经纬度输入控件的效果:

           到目前为止,我们已经可以在Qt Designer中使用自定义的控件绘制界面了,但是这个时候,事情还没有完,因为使用QtCreator时,会发现其界面设计器中并没有我们的自定义控件。另外当我们用Qt Designer绘制完控件后,编译时会发生找不到“loglatedit.h”头文件的编译错误。这个问题很容易理解,首先我们可以想到的是Qt Creator和Qt Designer的自定义控件目录是不同的,我自己的机器里,Qt和Qt Creator的安装目录如下:

           当我把自定义控件的dll和lib文件拷贝到D:\Qt\4.8.4\plugins\designer目录下,再启动D:\Qt\4.8.4\bin目录下的designer.exe,这个插件可以加载到Widget Box里面去,但是如果我启动D:\Qt\qtcreator-2.8.1\bin目录下的Qt Creator,我们的插件并不会出现在Qt Creator的设计器中。因此,对于Qt Creator,我们也需要执行以下控件的安装,那么具体要安装到哪里去呢?经过在Qt Creator安装目录下一番查找,发现D:\Qt\qtcreator-2.8.1\bin\plugins\designer这个目录下存在和D:\Qt\4.8.4\plugins\designer目录下同名的dll文件,于是我们猜测这个目录就是Qt Creator的控件安装目录,接下来拷贝loglateditplugin.dll到这个目录下,之后重新打开Qt Creator,发现自定义控件加载成功了,于是第一个问题解决了。

           对于第二个问题,我们很容易想到使用一个动态库时,除了要有dll和lib文件外,还需要头文件,而对于某一版本的Qt SDK,其界面相关的头文件都放置在QtGui目录下,于是我将工程中经纬度输入控件的头文件loglatedit.h拷贝到D:\Qt\4.8.4\include\QtGui目录下,再次试验,发现编译可以通过,但是连接失败,尝试将相应的lib文件放到D:\Qt\4.7.4\lib目录下并且把该lib库添加为工程的依赖库,依然连接失败。这表明Qt的自定义控件工程并没有导出我们的自定义控件,因此其生成的库文件loglateditplugin.lib里并没有自定义控件的信息。因此要使用自定义控件,只能引入源码。

            通过以上实验,总结一下具体的做法,列举如下:

             1. 创建Qt 设计师自定义控件工程,编写自定义控件;

             2. 拷贝release版的dll和lib文件到Qt Designer的插件目录下,如D:\Qt\4.8.4\plugins\designer,这样自定义控件即可在Qt Designer中使用;

             3.拷贝dll文件到Qt Creator的集成Qt Designer的插件目录下,如:D:\Qt\qtcreator-2.8.1\bin\plugins\designer,使集成于Qt Creator中的Qt Designer可以加载并使用该控件;

             4.拷贝自定义控件的头文件和源文件到使用自定义控件的工程中,并且添加到工程里面去,这样就可以正确编译并连接了。

  • 相关阅读:
    JAVA基础——编程练习(二)
    JAVA基础——面向对象三大特性:封装、继承、多态
    JVM内存
    50. Pow(x, n) (JAVA)
    47. Permutations II (JAVA)
    46. Permutations (JAVA)
    45. Jump Game II (JAVA)
    43. Multiply Strings (JAVA)
    42. Trapping Rain Water (JAVA)
    41. First Missing Positive (JAVA)
  • 原文地址:https://www.cnblogs.com/liuyihai/p/7856767.html
Copyright © 2011-2022 走看看