一)流程概述

在使用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的类,如下:

  1. #ifndef LOGLATEDIT_H
  2. #define LOGLATEDIT_H
  3. #include <QWidget>
  4. class LogLatEdit : public QWidget
  5. {
  6. Q_OBJECT
  7. public:
  8. LogLatEdit(QWidget *parent = 0);
  9. };
  10. #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方法中返回的控件对象。

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

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

源文件:

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

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

三)控件属性函数实现

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

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

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

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

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

控件编写完毕以后,把生成的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.拷贝自定义控件的头文件和源文件到使用自定义控件的工程中,并且添加到工程里面去,这样就可以正确编译并连接了。

编写Qt Designer自定义控件的更多相关文章

  1. 编写Qt Designer自定义控件(二)——编写自定义控件界面

    接上文:编写Qt Designer自定义控件(一)——如何创建并使用Qt自定义控件 既然是控件,就应该有界面,默认生成的控件类只是一个继承了QWidget的类,如下: #ifndef LOGLATED ...

  2. 编写Qt Designer自定义控件(一)——如何创建并使用Qt自定义控件

    在使用Qt Designer设计窗体界面时,我们可以使用Widget Box里的窗体控件非常方便的绘制界面,比如拖进去一个按钮,一个文本编辑器等.虽然Qt Designer里的控件可以满足我们大部分的 ...

  3. 把自定义控件集成到Qt Designer中

    要想在Qt Designer中使用自定义控件,必须要使Qt Designer能够知道我们的自定义控件的存在.有两种方法可以把新自定义控件的信息通知给Qt Designer:“升级(promotion) ...

  4. 利用Eric+Qt Designer编写倒计时时钟

    [前言]前几日通过编写命令行通讯录,掌握了Python的基本语法结构,于是开始向更高水平冲击,利用Eric与Qt Designer 编写一个带界面的小程序.本次实操中也确实遇到了不少问题,通过学习也都 ...

  5. Qt Designer中自定义控件的使用(提升法与插件法)

    准备乱写一点Qt自定义Widget在Designer中的使用.可是又不想重复提升法(promotion)及插件法基本用法,因为Manual中Using Custom Widgets with Qt D ...

  6. [Qt插件]-03创建Qt Designer自定义部件

    如何创建自定义部件并添加到Qt Designer来爽快的拖动部件可视化界面设计?   Qt Designer基于插件的架构使得它可以使用用户设计或者第三方提供的自定义部件,就像使用标准的Qt部件一样. ...

  7. Qt Designer怎样加入资源文件

    Qt Designer中如果在设计UI界面的时候要加入一些图素,图标等资源的时候是不能直接添加进去的,需要在Qt开发目录下编写QRC文件 qrc文件格式如下: <RCC> <qres ...

  8. 自定义的插件如何加载到Qt Designer中(详细)

    要想在Qt Designer中使用自定义控件,必须要使Qt Designer能够知道我们的自定义控件的存在.有两种方法可以把新自定义控件的信息通知给Qt Designer:“升级(promotion) ...

  9. 痞子衡嵌入式:超级好用的可视化PyQt GUI构建工具(Qt Designer)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是PyQt GUI构建工具Qt Designer. 痞子衡开博客至今已有好几年,一直以嵌入式开发相关主题的文章为主线,偶尔穿插一些其他技术 ...

随机推荐

  1. jsp中EL表达式不起作用的问题1

    问题:在jsp页面中使用el表达式取值,取不到值,但是使用jsp中嵌套java代码可以取到值,对应代码如下: 解决: 只要在 jsp中 头文件中写上 : <%@page isELIgnored= ...

  2. 监听键盘弹起View上调

    可以用三方库IQKeyboardManager 用这个第三方 http://www.jianshu.com/p/f8157895 #pragma mark - keyboard events - // ...

  3. css:background-position > 精灵技术

    background-position : length || length background-position : position || position 取值: length  : 百分数 ...

  4. 669. Trim a Binary Search Tree

      Given a binary search tree and the lowest and highest boundaries as `L`and `R`, trim the tree so t ...

  5. SpringJDBC的JdbcTemplate在MySQL5.7下不支持子查询的问题

    org.springframework.jdbc.BadSqlGrammarException: PreparedStatementCallback; bad SQL grammar [ SELECT ...

  6. 78、excel的读写操作

    本篇主要是用python来自动生成excel数据文件也就是简单的excel读写操作.python读写excel文件主要是第三方模块库xlrd.xlwt. 本篇导航: 写excel 读excel 一.写 ...

  7. MVCC的一些理解

    link 一.MVCC简介 MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控 ...

  8. 【二十七】php之绘图技术(gd、jpgraph、短信随机验证码)

    1.绘图技术(GD库) 注意:使用该库,php.ini文件中的extension=php_gd2.dll必须是开启状态,不然无法使用 图片格式:目前网站开发常见的图片格式有gif,jpg/jpeg,p ...

  9. Lambda表达式补充

    l“Lambda 表达式”是一个匿名函数,它可以包含表达式和语句,并且可用于创建委托或表达式树类型. lLambda 表达式的运算符 =>,该运算符读为“goes to”. l=> 运算符 ...

  10. Linux下巧用cat与EOF实现文件的替换和追加

    本文地址http://comexchan.cnblogs.com/,作者Comex Chan,尊重知识产权,转载请注明出处,谢谢! ================================== ...