翻译:集成一个自定义部件到Qt Designer

原文:https://www.ics.com/blog/integrating-custom-widget-qt-designer

This blog post will describe how to write a custom Qt widget and how to integrate it into Qt Designer so that you can drag and drop it onto your designs. It will also provide an understanding of important differences between Qt 4 and Qt 5 when it comes to creating designable widgets.

本博文将描述如何编写一个自定义的 Qt Widget 并且如何将其集成到Qt Designer 中,这样你就能够在设计的时候拖放此控件。同时,本文还说明 Qt4 和 Qt5 中创建 widget 控件时的重要的不同之处。

The example we will work through is an LED (Light Emitting Diode) object. It is designed to be a realistic representation of the real thing:

这个例子将通过一个 LED ( 发光二极管) 对象开始,它被设计成真实呈现实体的特性。

The diameter of the LED is measured in millimeters (like real LEDs) and can be any color you want (unlike real LEDs). The on/off state can be changed programmatically and it can be set to flash at a specified rate in milliseconds.

LED 的直径以毫米单位度量(像真实的LED),可以被设置成不同颜色(不同真实的LED)。亮灭的状态可以通过编程改变,并且可以设置成以毫秒为单位的闪烁。

Qt Designer uses a plug-in architecture so we need to build a shared library (on Linux, UNIX and MacOS) or a DLL (Windows).

Qt Designer 使用插件机制,所以哦我们需要构建一个 shared library 共享库(在 Linux, UNIX 和 MacOS) 或者 DLL 文件(Windows)。

We’ll start by looking at portions of the header for the LED.

我们首先看一下 LED 的头文件部分内容。

#ifndef LED_H
#define LED_H

#include <QtDesigner/QtDesigner>
#include <QWidget>

class QTimer;

class QDESIGNER_WIDGET_EXPORT LED : public QWidget
{
	Q_OBJECT

	Q_PROPERTY(double diameter READ diameter WRITE setDiameter) // mm
	Q_PROPERTY(QColor color READ color WRITE setColor)
	Q_PROPERTY(Qt::Alignment alignment READ alignment WRITE setAlignment)
	Q_PROPERTY(bool state READ state WRITE setState)
	Q_PROPERTY(bool flashing READ isFlashing WRITE setFlashing)
	Q_PROPERTY(int flashRate READ flashRate WRITE setFlashRate)

On line 4, we include the Qt Designer header file. This pulls in everything needed to make the widget designable. On line 9, we declare the LED class. It inherits from QWidget. The QDESIGNER_WIDGET_EXPORT macro inserts OS-specific code that ensures the LED class will be exported in the shared library or DLL properly. On some platforms, it evaluates to empty. LED is a QObject that defines properties and slots so we need the Q_OBJECT macro (line 11).

在行4,我们包含了 Qt Designer 的头文件。这将获取设计部件需要的所需。在行9,声明了 LED 类。它继承自  QWidget 类。宏 QDESIGNER_WIDGET_EXPORT 插入操作系统相关的代码,以确保 LED 类被正确导出到共享库 Shared library 或者 DLL 文件。在某些平台上,可能为空。LED 是一个 定义了属性和信号槽的 QObject 类,所以我们需要 Q_OBJECT 宏(行11)。

Properties are an important feature of the Qt meta-object system. A property is a value that can be read and (optionally) written. More importantly for our purposes, properties are exposed in the Qt Designer property editor. Lines 13 to 18 specify six properties of the LED widget. Q_PROPERTY is a C++ macro that evaluates to nothing in the C++ code that is output by the C++ pre-processor. Q_PROPERTY is for the benefit of the Qt meta-object compiler (moc). Moc recognizes Q_PROPERTY and parses its parameters; the C++ pre-processor never sees it. The first parameter is the data type and the second is the property name. For each property, we define “setter” and “getter” methods using the WRITE and READ parameters (leaving out the WRITE parameter makes the property read-only). Additional parameters are described in the Qt documentation.

属性是 Qt 元数据对象系统重要的特性。一个属性是一个可以被读和写(可选)的数值。对我们更为重要的是,属性是在Qt Designer的属性编辑器中暴露出来的。行13到行18指定了六个LED组件的属性。Q_PROPERTY 是一个 C++ 宏,在 C++ 预处理器输出的代码中没有任何值。Q_PROPERTY 是用于 Qt 元对象编译器(meta-object compiler 即moc)。moc 识别到 Q_PROPERTY 并解析其参数,C++ 预处理器永远不会见到它。第一个参数是数据类型,第二个参数是属性名称。对于每一个属性,我们使用 WRITE和READ参数来定义“写”(setter)和“读” (getter)两种方法(省略 WRITE 参数则使属性只读)。其他的参数参见Qt文档。

Lines 24 to 45 define the property reader and writer methods:

行24到行45 定义了属性读和写的方法:

	double diameter() const;
	void setDiameter(double diameter);

	QColor color() const;
	void setColor(const QColor& color);

	Qt::Alignment alignment() const;
	void setAlignment(Qt::Alignment alignment);

    bool state() const;

    bool isFlashing() const;
    
    int flashRate() const;

public slots:
	void setState(bool state);
	void toggleState();
	void setFlashing(bool flashing);
	void setFlashRate(int rate);
	void startFlashing();
	void stopFlashing();

Notice that setState, setFlashing, and setFlashRate are marked as slots (lines 40, 42, and 43). This allows a programmer to connect signals from other widgets to the LED slots to control it. The other slots are variations on the setState and setFlashing setters.

注意到 setState、setFlashing 和 setFlashRate 被标记为槽函数(行40,42,43)。这允许编程者从其他部件连接信号到 LED 槽函数并控制它。其他槽函数是 setState 和 setFlashing 设置器的变体。

It is important for a widget to behave properly when managed by one of Qt’s layout managers in terms of size and location. The virtual method sizeHint (line 49 below) should be overridden by your custom widget to give Qt’s layout objects a clue about how to size the widget. The heightForWidth method (line 48) returns the preferred height for the widget given the width. The minimumSizeHint method returns the recommended minimum size for your widget.

当一个部件的大小和位置被Qt 的布局管理器管理时,能够正确呈现就很重要。虚函数 sizeHint (代码 行49) 应该被自定义部件重写,给Qt布局对象提供有关如何调整部件尺寸的线索。 heightForWidth 方法(源码行48)返回给定宽度的部件的首选高度。minimumSizeHint 方法返回部件建议的最小尺寸。

public:
	int heightForWidth(int width) const;
	QSize sizeHint() const;
	QSize minimumSizeHint() const;

Probably the most important method is paintEvent (line 53):

也许最重要的方法就是 paintEvent (源码行53):

protected:
	void paintEvent(QPaintEvent* event);

This is where all the painting happens whenever the widget is shown (either initially or because an overlying window has moved away) or updated (for instance, by one of the property setters).

此函数负责所有的重绘,无论是组件显示(初始化或者遮挡窗口被移走)或者更新(例如属性被设置时)。

Let’s look at the implementation of LED now. The “getter” (a.k.a. accessor) methods are very simple – they just return the value of internal private variables. For instance:

现在让我们来看看 LED 的实现。“读”  ( gettter或者叫 访问器 accessor) 方法非常简单 – 他们只返回内部私有变量的值。例如:

bool LED::
state() const
{
    return state_;
}

The corresponding “setter” methods are (slightly) more interesting:

相应的 “写”(setter)发方法也(稍微)更有趣:

void LED::
setState(bool state)
{
	state_ = state;
	update();
}

On line 113 the private variable state_ is set and then on line 114 we make a call to update(). The call results in a QPaintEvent object being put on the Qt event queue. If we didn’t call update the state change would not be evident immediately. We would next see it if we caused the LED widget to be hidden and then exposed again. We want to see changes immediately though, so we call update. When the Qt event loop finds this event, it invokes LED::paintEvent (line 143). Line 145 squelches warnings about unused variables.

在行113私有变量 state_ 被设置然后行114调用update(),该调用导致 QPaintEvent 对象被放在Qt事件队列中。如果我们没有调用更新,则状态变化不会立即显现。如果我们让LED控件隐藏然后再显现,我们将看到变化。我们像看到立即变化,所以调用更新。当Qt 事件循环找到这个事件,它将调用 LED::paintEvent (行143),行145 则压制未使用变量的警告。

On line 147 we create a QPainter object. QPainter has many methods for drawing lines, rectangles, text, etc. on QPaintDevice objects, which include QWidget and QPixmap. First, we handle the alignment property which specifies how the LED should fit into the space allocated to it by whatever layout within which the LED widget is contained (lines 149 to 168). On line 170, we define a QRadialGradient, which produces the “reflective” look of the LED widget. On lines 173 to 178, we set the final color of the gradient to black if the state variable is false (off), or to the value of the color property. We then create a QBrush that will be used to fill anything drawn by the QPainter object.

在行 147 我们创建了一个 QPainter 对象。QPainter 有很多方法可以在QPaintDevice对象上绘制直线,矩形,文本等,包括 QWidget 和 QPixmap。首先,我们处理对齐属性,该属性指定LED应如何适应分配给它的控件,包括 LED 控件所包含的任何布局(行149到168)。在行170,我们定义了一个 QRadialGradient 对象,它产生 LED 控件的“反射”外观。在行173到行178,如果状态变量是 false(关),则将渐变的的最终颜色设置为菏泽,否则设置为color属性的值。然后,我们创建一个 QBrush 用于填充 QPainter 对象所绘制的任何内容。

On lines 180 to 183, we finally draw something. The pen is set to the color property’s value, we turn anti-aliasing on (to avoid “jaggies”), set the brush to the radial gradient, and draw an ellipse, which is the actual LED. Finally, on lines 185 to 188, we turn on a timer to the flash rate property value if the flash property is true, or we turn off the timer if the flash property is false.

在行180到183,我们开始画了一些东西。笔被设置为颜色属性的值,我们打开消除锯齿(以避免“锯齿”),将画笔设置为径向渐变,并绘制椭圆,这是实际的 LED。最后,在行 185到行188,我们打开定时器,如果闪烁属性被设置为true,则将闪烁时间赋值给定时器,否认如果闪烁属性为 false,我们关闭定时器。

paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);

	QPainter p(this);

	QRect geo = geometry();
	int width = geo.width();
	int height = geo.height();

	int x=0, y=0;
	if ( alignment_ & Qt::AlignLeft )
		x = 0;
	else if ( alignment_ & Qt::AlignRight )
		x = width-diamX_;
	else if ( alignment_ & Qt::AlignHCenter )
		x = (width-diamX_)/2;
	else if ( alignment_ & Qt::AlignJustify )
		x = 0;

	if ( alignment_ & Qt::AlignTop )
		y = 0;
	else if ( alignment_ & Qt::AlignBottom )
		y = height-diamY_;
	else if ( alignment_ & Qt::AlignVCenter )
		y = (height-diamY_)/2;

	QRadialGradient g(x+diamX_/2, y+diamY_/2, diamX_*0.4,
		diamX_*0.4, diamY_*0.4);

	g.setColorAt(0, Qt::white);
	if ( state_ )
		g.setColorAt(1, color_);
	else
		g.setColorAt(1, Qt::black);
	QBrush brush(g);

	p.setPen(color_);
	p.setRenderHint(QPainter::Antialiasing, true);
	p.setBrush(brush);
	p.drawEllipse(x, y, diamX_-1, diamY_-1);

	if ( flashRate_ > 0 && flashing_ )
		timer_->start(flashRate_);
	else
		timer_->stop();
}

At this point, we have a useful custom widget. We can insert it into layouts programmatically and we can use promotion in Qt Designer to place LED widgets into designs. The problem with using promotion is that the appearance and behavior of the promoted widget doesn’t appear in Designer. We have to actually compile and run our form to see how the LED lays out. So now, we will create a Qt Designer plug-in that will add the LED widget to the Designer palette and allow users to drag and drop it into a design and see how it looks before compiling and running the application. Again, we’ll start with the header file:

此时,我们有了一个有用的自定义控件。我们可以通过编程方式将其插入到布局中,我们可以在 Qt Designer 中使用提升(Promotion)将LED控件放入到设计中。使用提升的问题在于提升的控件的外观和行为不会出现在Designer中。我们只能实际编译并运行窗体才能看到LED的布局。所以,现在我们将创建一个 Qt Designer 插件,该插件将添加 LED 控件到 Designer 面板,并允许用户将其拖放到设计中,并在编译和运行应用程序前查看它的外观。同样,我们从头文件开始:

#include <QDesignerCustomWidgetInterface>

class LEDPlugin : public QObject, public QDesignerCustomWidgetInterface
{
	Q_OBJECT
	Q_INTERFACES(QDesignerCustomWidgetInterface)
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
    Q_PLUGIN_METADATA(IID "com.ics.Qt.CustomWidgets")
#endif

public:
    LEDPlugin(QObject* parent=nullptr);

    QString name() const;
    QString group() const;
    QString toolTip() const;
    QString whatsThis() const;
    QString includeFile() const;
    QIcon icon() const;

    bool isContainer() const;

    QWidget *createWidget(QWidget *parent);

private:
    bool initialized;
};

On line 6, the LEDPlugin class inherits from QDesignerCustomWidgetInterface. The methods in LEDPlugin are implementations of pure virtual methods in QDesignerCustomWidgetInterface.

在行6,LEDPlugin 类继承自  QDesignerCustomWidgetInterface,LEDPlugin中的方法是 QDesignerCustomWidgetInterface 中纯虚方法的实现。

The Q_INTERFACES macro on line 9 tells the meta-object system about the interface. On lines 10-12, we see one of the differences from Qt 4 to Qt 5, the use of the new macro Q_PLUGIN_METADATA. This macro allows us to add any sort of metadata we like to an object. In this case, the Qt 5 Designer executable will not load your plug-in unless it sees the IID, a unique identifier for your plug-in. There is an optional FILE parameter that we don’t need here. The plugin implementation is almost trivial:

行9的 Q_INTERFACES 宏告诉元对象系统有关接口的信息。在行10到12,我们看到了 Qt5和Qt4的一个不同之处,即使用了新的宏 Q_PLUGIN_METADATA。这个宏允许我们向对象添加我们喜欢的任何类型的元数据。在这种情况,Qt5 Designer 可执行文件不会加载你的插件,除非它看到IID,即插件的唯一标识符。这里还有一个我们不需要的 FILE 参数。插件的实现几乎是微不足道的:

LEDPlugin::
LEDPlugin(QObject* parent) :
	QObject(parent),
	initialized(false)
{
}

QString LEDPlugin::
name() const
{
	return "LED";
}

QString LEDPlugin::
group() const
{
	return tr("ICS Custom Widgets");
}

QString LEDPlugin::
toolTip() const
{
	return tr("An LED");
}

QString LEDPlugin::
whatsThis() const
{
	return tr("An LED");
}

QString LEDPlugin::
includeFile() const
{
	return "LED.h";
}

QIcon LEDPlugin::
icon() const
{
    return QIcon(":/icon/led.png");
}

bool LEDPlugin::
isContainer() const
{
	return false;
}

QWidget * LEDPlugin::
createWidget(QWidget *parent)
{
	return new LED(parent);
}

#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
Q_EXPORT_PLUGIN2(ledplugin, LEDPlugin)
#endif

The return values of name(), group() and icon() will appear in the Qt Designer palette while the return of includeFile() is output to the .ui file that Qt Designer generates. The methods toolTip() and whatsThis() define what tool tip and what is this clue will be displayed at run time. Notice that icon() returns an invalid icon. Qt Designer will use a default icon. We return false from isContainer() since LED is not designed to hold other widgets. Qt Designer calls createWidget() to obtain an instance of the LED widget, We simply allocate a new instance on the heap and return it.

name(), group()icon() 的返回值将出现在 Qt Designer 面板中,而 includeFile() 的返回值将输出到 Qt Designer 生成的 .ui 文件中。方法toolTip()whatsThis() 定义了工具提示和运行时显示的线索。注意到 icon() 返回无效图标,Qt Designer 将使用默认图标。 我们让 isCOntainer() 返回 false,因为 LED 不是为了容纳其他控件而设计的。Qt Designer 调用 creatWidget() 来获取LED控件的实例,我们只需要在堆上分配一个新实例并返回它。

Lines 61 to 63 are particularly important. The Q_EXPORT_PLUGIN2 macro ensures that the Qt 4 Designer will see our plug-in. Without this, we will not see the LED widget in the Designer palette. We do not need this macro for Qt 5, the Q_PLUGIN_METADATA macro replaces it.

行61到行63特别重要,Q_EXPORT_PLUGIN2 宏确保 Qt4 Designer 将看到我们的插件。如果没有这个,我们将不会在 Designer 面板中看到 LED控件。Qt5 中我们不需要这个宏, Q_PLUGIN_METADATA 宏取代了它。

The last item we need to look at is our qmake file, led-designer-plugin.pro:

我们需要查看的最后一个项目时 qmake 文件,led-designer-plugin.pro:

greaterThan(QT_MAJOR_VERSION, 4) {
    QT += widgets designer
}

lessThan(QT_MAJOR_VERSION, 5) {
    CONFIG += designer 
}

CONFIG += plugin release

TEMPLATE = lib
TARGET = $$qtLibraryTarget($$TARGET)
target.path = $$[QT_INSTALL_PLUGINS]/designer
INSTALLS += target

INCLUDEPATH += .

# Input
HEADERS += LED.h LEDPlugin.h
SOURCES += LED.cpp LEDPlugin.cpp

In Qt 4, designer needs to be added to the CONFIG variable. In Qt 5, it needs to be added to the QT variable instead. On Linux, you can just run qmake to create a Makefile. When compilation completes do ‘make install’ to install the LED widget shared library into the default Qt Designer plug-in directory. Designer looks in this directory for any plug-ins and loads them (you may need administrative privileges to install the widget). You can install the plug-in shared library in any directory and define the QT_PLUGIN_PATH environment variable to help Qt Designer find it if you like.

在Qt 4中,Designer 需要被添加到 CONFIG 变量中。在 Qt 5中,需要将其添加到 QT 变量中。在 Linix,你可以运行 qmake 来创建 Makefile。编译完成后,执行 “make install”将LED控件共享库安装到默认的QT Designer 插件目录。Designer 在此目录中查找任何插件并加载他们(你可以能需要管理员权限才能安装控件)。如果你喜欢,可以在任何目录中安装插件共享库,并定义 QT_PLUGIN_PATH 环境变量以帮助 Qt Designer 找到它。

Below is what Qt Designer looks like with the LED widget on its palette. Notice the ICS Custom Widgets group at the bottom of the palette on the left, the selected LED on the widget form, and the property sheet on the right. You can see the properties we defined listed there. You can change any of the properties and see the change in place in Designer.

下面是 Qt Designer 面板上使用 LED 控件的样子。注意到左侧面板下方的 ICS Custom Widget 分组,控件窗体上选定的LED,右侧的属性表。你可以看到我们在那里列出的属性。你可以更改任何属性并在Desinger 中查看更改。

So there you have it, a simple custom widget added to Qt Designer. If you use a custom widget only once or twice, promotion is a perfectly good way to use it, but if you think a widget will be used many times and potentially by many people, creating a plug-in is certainly a worthwhile use of time and will increase productivity.

到此你实现了它,一个添加到 Qt Designer 的简单的自定义控件。如果你使用一次或者两次自定义控件,提升(promotion)是一种非常好的使用方式,但是如果你认为一个小控件将被使用多次并且可能被很多人使用,那么创建一个插件肯定是值得花时间并能够提升生产力。

You can download the source code via ftp from here.

你可以通过ftp从这里下载源代码。

<完>

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.