0
点赞
收藏
分享

微信扫一扫

d的QtE56快速入门指南.

柠檬的那个酸_2333 2022-02-09 阅读 33


​​​​

QtE56快速入门指南.

下面方案是按​​Windows​​术语描述的.对​​Linux​​,文件名为​​libXXXX.so​​,还应考虑​​32​​位或​​64​​位位深实现.如有​​Qt64​​位,则库​​(so/dll文件)​​也必须是​​64​​位.

示例:

QtE56core32.dll---32位Windows实现
libQtE56core64.so---64位Linux实现

架构:

QtXX(*.dll)--->,`dll/so`文件集,`XX`为位大小
|
+----- |qte56coreXX.dll |基本原语
+----- |qte56widgetsXX.dll |组件
| |
| + QtE56.d ---> D绑定
| |
| + app.d ---> D应用
|
+ QtE56.cpp ---> C++绑定
|
+ app.cpp --->C++应用

注意,​​Qt​​​可按不同编译器表示(编译),如​​MINGW​​​或​​MSVC​​​.为了简单,​​QtE56​​​在​​Windows​​​上仅用​​MINGW​​​,在​​Linux​​​上用​​gcc​​​.也可用​​msvc​​编译,但未详细测试.

构建QtE56:

最重要的是构建​​qte56coreXX.dll/so​​​和​​qte56windowsXX.dll/so​​​文件(其中​​XX​​​是​​32/64​​​).为此,切换至​​build/qte56​​​文件夹.里面有​​*.pro(Qt),*.cpp,*.h​​​构建​​QtE56​​​所必需的​​(qte56coreXX.dll/so,qte56widgetsXX.dll/so)源文件​​​.现在用​​Qt​​​的​​C++​​编译器,构建所需文件.

​Linux​​最简单的选择:

$ cd build/qte56
$ qmake-qt5 qte56core.pro
$ make

输出为​​qte56coreXX.so​

类似,可为​​Windows​​构建文件.可用​​QtCreator​​来构建他们,主要是创建​​qte56coreXX.dll/so​​和​​qte56windowsXX.dll/so​​(其中​​XX​​为​​32/64​​).

构建遇到问题之一是多种多样的​​Qt​​版本,类中​​方法和属性集​​不同.我不知道如何解决它.当前我专注于​​Qt6​​,如果用另一个​​Qt​​版本,构建中出错,只会注释掉带有此错误的代码行.这样禁用,会导致​​qte56.d​​调用方法之一停止工作.目前调用​​禁止方法​​,没法使其崩溃.

Qt库路径.

现在目录中有三个文件​​(qte56coreXX.dll/so+qte56windowsXX.dll/so+qte56.d)​​,可开始用​​D​​和​​Qt​​创建应用.但是,还有个问题.应用要求​​qte56.d​​加载​​qte56coreXX.dll/so​​和​​qte56windowsXX.dll/so​​到内存中,而他们(​​dll​​)要加载其他​​Qt*.dll/so​​,如果失败,会收到​​错误​​消息.

为确保成功加载,(如已经安装了​​Qt​​)需要将​​Qt*.dll/so​​文件路径放在​​PATH​​环境变量中,或复制所需的​​Qt*.dll/so​​文件到我们包含​​三个文件​​的目录中.对于​​Windows​​,复制少量​​Qt*.dll​​文件就行.而​​linux​​上要置​​LD_LIBRARY_PATH​​变量来指定加载库的​​额外目录​​.

在​​Linux​​​上​​构建和运行​​典型应用示例:

$ export LD_LIBRARY_PATH=`pwd`
$ dmd app.d qte56.d -release -m64
$ ./app

第一个应用.

现在已经配置好了,来创建第一个应用.这将是对​​QtE56​​的一种健康测试.

创建​​test.d​​文件:

import core.runtime,qte56;
int main(string[] args) {
if(LoadQt(dll.QtE6Widgets, false)) return 1;
QApplication app = new QApplication(&Runtime.cArgs.argc,
Runtime.cArgs.argv, 1);
QLabel lb = new QLabel(null);
lb.setText("Hello from D").show();
return app.exec();
}

编译并运行

$ export LD_LIBRARY_PATH=`pwd`
$ dmd test.d qte56.d -release -m64
$ ./test

如果屏幕上有应用窗口了,则能创建和执行​​D+Qt​​应用了.

插槽和信号.

查看​​QtE56​​中​​插槽和信号模型​​前,我想说几点:

​QtE56​​是庞大的​​Qt​​框架的​​很小子集​​.

实现​​QtE56​​时,我尽量保留​​Qt​​的​​类结构,类名,方法和属性​​及​​Qt​​的工作模型.

​QtE56​​中实现类的所有​​标准信号和槽​​都可用.

要理解​​QtE56​​,就要理解​​Qt​​的工作原理.

多数情况下,​​Qt​​的标准文档对​​QtE56​​类也管用.

让我们看看​​QtE56​​插槽和信号模型,与​​Qt​​相比,实现的根本区别.

最重要区别.标准​​Qt​​模型期望,在编译​​C++​​源代码时创建​​插槽​​.插槽自身是类方法.​​得到数量​​就是,加了​​多少​​方法(槽)给类.​​QtE56​​用不同的方法,即用​​QAction​​类的修改版本,作为​​两个集​​插槽的存储库.有​​20%​​的不同插槽,涵盖了需要的​​80%​​的选项.

QtE56中可用插槽.

​插槽​

函数原型

​"Slot_AN()"​

void call(AdrClass, Nnumber);

​"Slot_ANI(int)"​

void call(AdrClass, Nnumber, int);

​"Slot_ANII(int, int)"​

void call(AdrClass, Nnumber, int, int);

​"Slot_ANIII(int, int, int)"​

void call(AdrClass, Nnumber, int, int, int);

​"Slot_ANB(bool)"​

void call(AdrClass, Nnumber, bool);

​"Slot_ANQ(QObject*)"​

void call(AdrClass, Nnumber, ​​Qbbject*​​);

如果愿意,总可向​​C++​​​加新变体,但很少见.一个难点是​​Qt​​​检查​​信号和槽​​​参数​​数量和类型​​​.因此,必须增加​​不同插槽变体​​​数来满足​​Qt​​限制.

好的.通过创建​​QAction action = new QAaction(...);​​​,我们得到了实际存储​​槽集​​​的动作.这样,可​​创建插槽信号链接​​,类似:

连接(按钮,"点击()",动作,"Slot_AN()"); 
连接(按钮,"按下(int)",动作,"Slot_ANI(int)");
//e
conect(button, "clicked()", action, "Slot_AN()");
conect(button, "press(int)", action, "Slot_ANI(int)");

有了想要槽的​​Qaction​​​类实例,可关联​​信号​​​与​​槽​​.

看看下面示例:

创建​​test1.d​​文件:

import core.runtime,  qte56;
extern (C) { // (1)
// (2)
void onAction(void* uk, int n) {
runAction();
}
}
// (3)
void runAction() { // this is slot
msgbox("applicationDisplayNameChanged() ...");
}
QAction acAction;
int main(string[] args) {
if(LoadQt(dll.QtE6Widgets, false)) return 1;
QApplication app = new QApplication(&Runtime.cArgs.argc,
Runtime.cArgs.argv, 1);
QLabel lb = new QLabel(null);
lb.resize(640, 480);
lb.setText("Hello from D").show();
// 用槽创建动作
// (4)
acAction = new QAction(null, &onAction, &app, 2);
// 连接`标准信号`与`QAction`中插槽
// (5)
acAction.connects(app, "applicationDisplayNameChanged()", acAction, "Slot_AN()");
//生成标准信号
// (6)
app.setApplicationDisplayName!string("newNameApp");
return app.exec();
}

编译并运行

$ dmd test1.d qte56.d -release -m64
$ ./test1

该例中,看看如何连接标准​​Qt​​信号与基于我们​​QAction​​的槽.四标签–创建新​​QAction​​.构造器接受四个参数:

​父​​.一般为​​null​​或​​this​​.这里为​​null​​,因为无父.

触发槽(抓到信号)时,调用函数的​​C​​地址.该例为​​onAction()​​.

​槽处理方法​​所在​​类实例​​的地址.本例中,为任何地址,因为未用它.

​int​​值(数字).该数字存储在每个特定的​​QAction​​实例中,调用函数会检索它,该例为​​onAction()​​.

查看插槽列表(​​QtE56​​中可用插槽列表).会在1号处看见​​我们槽​​如何调用​​C代码(标签2)​​.具体调用描述是​​带正确参数​​的​​onAction(...)​​调用.

​连接​​函数(标签5)允许​​关联​​信号与插槽.这是标准​​Qt​​函数.​​该函数​​有四个参数:

​对象​​,信号发射器

信号符号描述

​对象​​,插槽物主

插槽符号说明

例子中,用​​"Slot_AN()"的acAction​​动作关联​​applicationDisplayNameChanged​​应用.现在,发射"​​applicationDisplayNameChanged()​​“信号时,激活特定的​​acAction:​​”​​Slot_AN()​​"槽.激活并调用​​onAction(...)​​函数(​​标记2​​),​​2​​函数调用​​runAction(...)函数​​(标记3).现在,要准备激活​​信号自身​​.由一行改变应用名的​​(标签6)​​完成,因此生成了用​​插槽抓它​​的信号.

看一下​​处理槽和信号​​的最标准​​源代码布局​​.这是包含​​Qt​​可视​​元素类​​及处理信号的​​方法(槽)​​的简单应用.

创建​​test2.d​​文件

import core.runtime,  qte56;
extern (C) {
// (1)
void onAction(CMyWidget* uk, int n) {
(*uk).runAction();
}
}
class CMyWidget : QWidget {
QLabel lb;
QPushButton kn;
QAction acAction;
this(QWidget parent) {
super(null); resize(300, 130);
lb = new QLabel(this);
lb.resize(300, 50).setStyleSheet("background: pink");
kn = new QPushButton("Press Me", this);
kn.move(110, 60).resize(80, 30);
// (2)
acAction = new QAction(this, &onAction, aThis);
// (3)
connects(kn, "clicked()", acAction, "Slot_AN()");
}
// (4)
void runAction() {
lb.setText("按了个按钮...");
}
}
int main(string[] args) {
if(LoadQt(dll.QtE6Widgets, false)) return 1;
QApplication app = new QApplication(&Runtime.cArgs.argc,Runtime.cArgs.argv, 1);
// (5)
CMyWidget mw = new CMyWidget(null);
// (6)
mw.saveThis(&mw);
mw.show();
return app.exec();
}

编译并运行:

$ export LD_LIBRARY_PATH=`pwd`
$ dmd test2.d qte56.d -release -m64
$ ./test2

仔细看看该例.主要区别是有个从​​QWidget​​​继承的​​CMyWidget​​​新类.构造器与前一样,很简单.看一下创建​​QAction​​​(标签2).定义了三个参数(不是四个),因为第四个参数默认为零.第一个参数是​​父​​​类(参见​​Qt​​​文档),​​第二个​​​参数是​​函数(代理)​​​的C地址,第三个参数是允许从​​C函数​​​传递​​控制​​​到​​D类(标签1)的槽(方法)​​​的​​类实例地址​​.

注意创建​​CMyWidget​​​类实例行(标签5).​​父​​​为空,无​​本​​​,不用引用.及记住​​类实例地址(a)​​​的​​非常​​​重要一行(标签6),可通过​​aThis​​​属性(标签2)读取​​a​​.

为何要使用额外的​​代理(C函数)​​​?因为我们不仅希望​​QtE56​​​与D编程语言一起使用,还希望与其他编程语言一起使用.我尝试将​​QtE56​​​与​​forth​​​和​​tcl​​一起使用.一切正常.

再看一个例子.这里拦截包含​​QString​​的信号.

创建​​test3.d​​文件

import std.stdio, std.conv;
import qte56, core.runtime;
extern (C) {
void onTest(CTest* uk, int n) { (*uk).runTest(); }
void onChTitle(CTest* uk, int n, void* qs) { (*uk).runChTitle(qs); }
}
class CTest: QWidget {
QPushButton kn1;
QAction acTest, acChTitle;
int nn;
this() {
super(null);
// Qt 文档
resize(200, 50); setStyleSheet("background: Moccasin");
kn1 = new QPushButton("点我", this);
// (1)
acTest = new QAction(this, &onTest, aThis);
connects(kn1, "clicked()", acTest, "Slot_AN()");
// (2)
acChTitle = new QAction(this, &onChTitle, aThis);
connects(this, "windowTitleChanged(QString)", acChTitle, "Slot_ANQS(QString)");
}
void runTest() { // slot ... (3)
setWindowTitle("Hello " ~ to!string(nn++));
}
void runChTitle(void* qs) { // slot ... (4)
QString qss = new QString('+', qs); // (5)
msgbox("cath signal: " ~ qss.String, "run slot ...");
}
}
int main(string[] args) {
if (1 == LoadQt(dll.QtE6Widgets, false)) return 1;
QApplication app = new QApplication(&Runtime.cArgs.argc, Runtime.cArgs.argv, 1);
CTest wn = new CTest(); wn.saveThis(&wn).show; // (6)
return app.exec();
}

编译并运行:

$ export LD_LIBRARY_PATH=`pwd`
$ dmd test3.d qte56.d -release -m64
$ ./test3

该例与上一个类似.唯一区别是我们拦截​​QString​​​为参数的信号.​​串​​​(标签1)定义了​​QAction​​​和连接,帮助创建​​处理按钮操作​​​插槽(标签3).​​槽​​​用来改变窗口标题,从而使​​Qt​​​生成"​​windowTitleChanged(QString)​​​"信号.​​行​​​(标签​​2​​​)通过​​&onChTitle -- void onChTitle(...) -- *.runChTitle(qs) -- void runChTitle(void* qs)​​​定义槽(4标签),来处理接收到​​改标题​​的信号.

​行​​​(标签​​5​​​)很重要,它允许从来自​​Qt​​​的​​QString​​​指针的​​指针​​​创建​​QtE56​​​类,​​+​​​构造器中第1参说,正在抓并保存来自​​Qt​​​的​​QString​​​指针到​​qss​​​中.​​msgbox(...)​​​函数只是使用​​qss.String​​显示生成串.

QtE56和Qt事件.

​Qt​​​框架中有​​大量​​​事件.​​QtE56​​​的事件处理程序数有限.可浏览​​qte56.d​​​文件找到已实现的事件.用类处理事件.例子是​​QResizeEvent​​​.可运行如下命令获得​​大概​​列表:

$ grep -E "\bQ(.*)vent" qte56.d

事件类名示例:​​QResizeEvent,QWheelEvent,QMouseEvent​​​等.既然有了事件类,就以按以下方法设置事件抓:​​setKeyPressEvent(...),setResizeEvent(...)​​等.可如下得到大概列表:

$ grep -E "\bset(.*)vent" qte56.d

这些方法(事件抓置器)有两个参数.为​​adr​​(C函数代理地址)和​​AdrThis​​(包含​​事件处理器​​的类实例地址).

如何使用安装处理器示例:

...
extern (C) {
// (2)
void onPressKeyEvent(CMgw* uk, void* qs) { (*uk).runPressKeyEvent(qs); }
}
class CMyWidget: QWSidget {
...
QEdit teEdit;
...
teEdit = new QEdit(this);
teEdit.setKeyPressEvent(&onPressKeyEvent, aThis ); // (1)
...
void runPressKeyEvent(void* qs) { // (3)
QKeyEvent qe = new QKeyEvent('+', ev);
switch(qe.key) {
case '"': insParaSkobki("\""); break;
case '(': insParaSkobki(")"); break;
case '[': insParaSkobki("]"); break;
case '{': insParaSkobki("}"); break;
case QtE.Key.Key_Return:
strBeforeEnter = tb.text!string();
break;
case QtE.Key.Key_L:
if(qe.modifiers == QtE.KeyboardModifier.ControlModifier) {
editSost = Sost.Cmd;
}
break;
default: break;
}
}
...
CMyWidget w = new CMyWidget(null);
w.saveThis(&w);
...

如果更仔细地检查代码,会发现许多与前面示例相同的元素.有个​​代理赋值​​​(C​​函数​​​)(​​标签​​​1),及调用插槽(标签2).在参数中是有​​事件类实例​​​之一指针的​​*void​​​.及输入插槽(3标签),(按​​QtE56​​​术语为创建正确的事件).等价于​​QKeyEvent qe=new QKeyEvent('+',ev);​​.

​​QtE56​​和​​Qt​​的​​QDesigner​​.

​Qdesigner​​生成两种输出文件:

​*.ui​​(描述表单,内为元素的​​xml​​文件).

​*.qrc​​(​​Qt​​资源文件)​​.``Qt​​资源编译器解析它并生成适合嵌入在D源代码中的​​二进制文件​​.

有特殊的​​QFormBuilder​​类来处理​​*.ui​​文件.​​load(...)​​方法从D串加载​​ui​​文件.​​输出​​是​​表单​​中按​​主部件​​嵌入的​​QtE5QWidget​​.标准应用如下:

class QtE56Help: QMainWindow {
...
QFormBuilder qfb;
QAction acAboutQt, knNabor ...
QPushButton knNabor;
...
this() {
super(null);
qfb = new QFormBuilder(this); // (1)
//在主部件表单上从QDesigner设置QWidget
setQtObj((qfb.load(":/fQtE56help.ui")).QtObj); // (2)
//查找地址元素并保存在QtE56类中.
// (3)
acAboutQt = new QAction('+', qfb.findChildAdr!string("actionAboutQt"), this, &onAbout, aThis, 2);
acAboutApp = new QAction('+', qfb.findChildAdr!string("actionAboutApp"), this, &onAbout, aThis, 1);
knNabor = new QPushButton('+', qfb.findChildAdr!string("pushButton_Nabor"));
// 等等...

...
}
...
}

该例中,可看到​​要点​​:

(1标签)创建​​QFormBuilder​​实例.

(2标签)加载​​ui​​文件​​按主表单​​组件锚定结果组件.

(3标签)​​找并链接表单​​元素和​​QtE56​​类.

从上例可知如何处理​​*.ui​​文件.可同样用​​qrc​​文件.为此,需要编译它们得到二进制形式.如下为​​Qt​​资源编译器编译资源文件至​​输出​​二进制.​​*.qrc​​资源文件可从​​QDesigner​​取(见​​Qt​​文档).从​​QDesigner​​编译​​a3.qrc​​:

$ rcc -binary a3.qrc -o a3.rcc

预处理后,得到可包含​​ui​​文件中文本等的​​二进制​​文件.还需定义处理​​(QResource)​​资源类,并用它在应用中注册资源.注意,资源是之前用​​D​​导入​​字节数组​​加载的.

示例:

ubyte* gResource = cast(ubyte*)import("a3.rcc");
...
QResource qrs = new QResource(); qrs.registerResource(gResource);

现在​​setQtObj((qfb.load(":/fQtE56help.ui")).QtObj;)​​​,访问元素,​​:​​​帮助见​​qt文档​​.

结论.

本文简短介绍了​​QtE56​​​工作原理.我在​​Windows,Linux​​​和​​MacOs​​​上的​​32/64​​​项目中用了该库.有了它,我使用了​​:D,C++,Forth,tclsh​​​同​​Qt​​对接.

致谢.

我要向优秀的D编程语言作者:​​WalterBright​​​,表示衷心感谢.自从我开始用他的​​ZortechC++​​​学习​​C++​​编程以来,每天用D写代码,对我来说是双倍愉快.



举报

相关推荐

0 条评论