Qt5:自定义窗口部件 | How to Create Qt Plugins in Qt5

  1. 1. IconEditorPlugin.h
  2. 2. Plugin loading
  3. 3. IconEditorPlugin.h
  4. 4. iconeditorplugin.json

成功在VS2012上配置Qt5后,小叶开始了一段新的征程。

犹记前几日看Introducing Qt 5.0这篇文章时,俺对Compatibility一项的理解是Qt5几乎完全向下兼容Qt4.x:

Qt Creator, being the biggest application we develop inside the Qt Project, is a prime example that shows this. A couple of weeks ago, we released Qt Creator 2.6 for use with Qt 4.8. The Qt 5 packages ship with Qt Creator 2.6.1 (which apart from bug fixes contains the same code base), but compiled against Qt 5.

但是俺新的征程踏出的第一步,就掉坑里了,这次学习的玩意儿是自定义窗口部件,也就是类似于MFC里面直接拖来用的控件。而且这个坑稍微大了些,值得记下来,方便后来者绕过。
以IconEditor为例,在Qt4.x里,标准的写法差不多是这样:

IconEditorPlugin.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#ifndef ICONEDITORPLUGIN_H
#define ICONEDITORPLUGIN_H
#include <QObject>
#include <QtDesigner/QDesignerCustomWidgetInterface>
#include <QtPlugin>
#define IconEditorPlugin_iid "org.myPlugins.IconEditorPluginFactoryInterface"
class IconEditorPlugin : public QObject, public QDesignerCustomWidgetInterface
{
trueQ_OBJECT
trueQ_INTERFACES(QDesignerCustomWidgetInterface)
public:
trueIconEditorPlugin(QObject *parent = 0);
trueQString name() const;
trueQString includeFile() const;
trueQString group() const;
trueQIcon icon() const;
trueQString toolTip() const;
trueQString whatsThis() const;
truebool isContainer() const;
trueQWidget *createWidget(QWidget *parent);
};
#endif // ICONEDITORPLUGIN_H

最后,别忘了在源文件末尾加上

1
2
// 第一个参数为插件的名字,第二个是插件类的名字(而不是自定义控件的类名)
Q_EXPORT_PLUGIN2(iconeditorplugin, IconEditorPlugin)

详情参考:QtDesigner自定义窗口部件有两种方法:改进法(promotion)和插件法(plugin)Qt自定义控件(插件)并添加到QtDesigher
到了Qt5,Q_EXPORT_PLUGIN2宏将会得到一个ASSERT_FAILD:

19 IntelliSense: static assertion failed with “Old plugin system used” d:\Projects\QT\learn\learn\iconeditorplugin\IconEditorPlugin.h 28 1 learn (Visual Studio 2010)

打开宏定义:

1
2
3
4
5
6
#define Q_EXPORT_PLUGIN(PLUGIN) \
Q_EXPORT_PLUGIN2(PLUGIN, PLUGIN)
#define Q_EXPORT_PLUGIN2(PLUGIN, PLUGINCLASS) \
Q_STATIC_ASSERT_X(false, "Old plugin system used")
#define Q_EXPORT_STATIC_PLUGIN2(PLUGIN, PLUGINCLASS) \
Q_STATIC_ASSERT_X(false, "Old plugin system used")

哎哟!这玩意儿在Qt5里看来已经被判了死刑了。
Google一番,结果又来到了Porting from Qt 4 to Qt 5,这次需要的是这段话:

Plugin loading

Another significant porting burden, in plugin-heavy systems at least is that the user code required for plugin loading has changed. The moc tool is now responsible for generating plugin metadata, so rather than a preprocessor macro in a C++ file (Q_EXPORT_PLUGIN2), as in Qt 4, in Qt 5 a new macro must be used in the header file, where moc can see it.
The process is described by Lars, and is relatively straightforward. Where it becomes difficult though is in cases where the Q_EXPORT_PLUGIN2 macro is wrapped with another macro, as done in KDE in K_EXPORT_PLUGIN.
Such a wrapping of the new macro would not be possible with Qt 5 because moc would not parse any wrapper (mocdoes not do full preprocessing).

果然,Qt5的插件系统发生了变化,Q_EXPORT_PLUGIN2被淘汰,“a new macro” 诞生了。接下来的工作,就是找出这个 new macro。
顺着指引来到described by Lars,说的是 Remove support for Qt 4 style plugins 的事:

Remove support for Qt 4 style plugins
The new plugin format allows us to avoid loading the plugins in
all cases. Remove the old format, as we could get bad behavior
with the old format if Qt would try to dlopen a Qt 4.x plugin.Change-Id: I2193e6874d6cca3c0b12298c2b9beb4105a42fd5
Reviewed-by: Thiago Macieira thiago.macieira@intel.com
Reviewed-by: Lars Knoll lars.knoll@nokia.com

Google一下这玩意儿,最终找到这里:Commit 963b4c1647299fd023ddbe7c4a25ac404e303c5d,一个很直观的Qt4和Qt5实现用户插件代码的对比,但是这东西俺还是看不大明白,咋办?似乎忘了一个重要的东西,既然知道了问题所在,去查查Qt5官方文档试试:How to Create Qt Plugins。就是它了,关键在这里:

Writing a plugin involves these steps:

  1. Declare a plugin class that inherits from QObject and from the interfaces that the plugin wants to provide.
  2. Use the Q_INTERFACES() macro to tell Qt’s meta-object system about the interfaces.
  3. Export the plugin using the Q_PLUGIN_METADATA() macro.
  4. Build the plugin using a suitable .pro file.

Qt5自带文档里也能找到:

Writing a plugin involves these steps

但是这也太精简了吧!
同时俺震精的发现,说好的 “For example, here’s the definition of an interface class:” 居然是空的有木有!!!
信息收集到此为止,剩下的问题就是 Q_PLUGIN_METADATA() macro 怎么用的问题,既然没有完全现成的饼可以吃,只能自己尝试画一个了。
好在进展比较顺利,最终代码如下:

IconEditorPlugin.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#ifndef ICONEDITORPLUGIN_H
#define ICONEDITORPLUGIN_H
#include <QObject>
#include <QtDesigner/QDesignerCustomWidgetInterface>
#include <QtPlugin>
#define IconEditorPlugin_iid "org.myPlugins.IconEditorPluginFactoryInterface"
class IconEditorPlugin : public QObject, public QDesignerCustomWidgetInterface
{
trueQ_OBJECT
trueQ_INTERFACES(QDesignerCustomWidgetInterface)
trueQ_PLUGIN_METADATA(IID IconEditorPlugin_iid FILE "iconeditorplugin.json")
public:
trueIconEditorPlugin(QObject *parent = 0);
trueQString name() const;
trueQString includeFile() const;
trueQString group() const;
trueQIcon icon() const;
trueQString toolTip() const;
trueQString whatsThis() const;
truebool isContainer() const;
trueQWidget *createWidget(QWidget *parent);
};
#endif // ICONEDITORPLUGIN_H

iconeditorplugin.json

1
{ "Keys": [ "IconEditor"] }

编译通过,打开QtCreater,并没有发现俺的插件,看来还得做点事情,再次回到QtDesigner自定义窗口部件有两种方法:改进法(promotion)和插件法(plugin),发现这段话:

3). 使用自定义插件

  1. 只需要把通过Release模式生成的项目.lib和项目.dll文件拷到C:\Qt\4.7.4\plugins\designer中,
  2. 然后在QtDesigner中,选择菜单Help/About Plugin就可以看到你的自定义控件是否已经载入成功。

在QtDesigner中控件列表中有一项My Widget 中就有你的自定义控件。
但是打开Release 文件夹,发现里面只有项目.exe和项目.lib,木有传说中的项目.dll。纠结几分钟后再次顿悟,项目属性里不是可以设置 Configuration Type ,马上修改为 Dynamic Library(.dll):

设置 Configuration Type

继续编译,还是出现错误:

_OutputFileFromLink.FullPath)' != '$([System.IO.Path]::GetFullPath($(TargetPath)))'" Code="MSB8012" Type="Warning" Arguments="TargetPath;$(TargetPath);Linker;%(_OutputFileFromLink.FullPath);Link"/>

%(_OutputFileFromLink.FullPath)

说的好像是 OutputFileLink 神马的问题,打开属性菜单,在 Linker/Output File 发现仍然是.exe,修改为 .dll:

Linker/Output File 修改为 .dll

再次编译,.dll 终于出现了!马上复制到 QtDesigner插件目录 D:\Qt\Qt5.0.0\5.0.0\msvc2010\plugins\designer,然后打开 QtDesigner,如图,终于从这个坑里爬出来了。。。睡觉去。

Qt5 自定义窗口插件