MFC程序中使用QT开发界面
如果你有一个现成的MFC项目在做维护,但是你厌倦了使用MFC繁琐的操作来做界面美化,或者你需要在这个项目中用到QT里面好用的某些功能;亦或者是你需要使用某些只能在MFC中使用的组件,但是界面这部分已经用QT做好了。那么这篇文章可能可以帮助到你
演示环境使用Visual Studio 2019 + QT5.12.8 版本
添加QT依赖
首先创建一个基于对话框的MFC工程,当然其他的像是多文档、单文档工程也是可以的,只是为了简单起见我这里用的是对话框
然后通过鼠标右键点击项目,然后依次点击属性 --> C/C++ -->常规在工程的附加头文件中添加上QCore、QGui、QWidget和QT的头文件路径
这里记得按照对应编译选项来选择包含64位或者32。
接着在连接器–>常规 中的附加库目录中添加qt的lib库
最后再在连接器–>输入中添加依赖的lib文件,需要注意的是,debug版本需要链接上带d的lib文件,release则链接上不带d的。
先编译一下,如果没有问题,qt相关的配置已经完成了
添加信号槽机制
MFC是基于Windows 消息队列来处理和响应ui事件的,而qt是采用信号槽机制来响应的,我们虽然添加了qt的依赖,但是现在只能使用其他的qt库,无法使用qt中的信号槽,需要额外添加一些组件来使mfc支持信号槽。
好在这部分需求qt相关的研发人员已经考虑到了,可以在github中找到 QMfcApp
我们可以将这两个文件给拷贝下来,添加到项目中。并且在cpp文件相应位置添加上 #include "pch.h"
包含预处理头
中间会有报错,这是因为在Unicode 字符集下 CString 中的字符串类型是 wchar_t*
QString::fromLocal8bit 无法 从 wchar_t*
转化为 char*
所以这里可以修改一下,使用 QString::fromStdWString()
,然后进行编译
在QMfcApp.cpp的注释里面可以看到,如何使用它
/*!
Creates an instance of QApplication, passing the command line of
\a mfcApp to the QApplication constructor, and returns the new
object. The returned object must be destroyed by the caller.
Use this static function if you want to perform additional
initializations after creating the application object, or if you
want to create Qt GUI elements in the InitInstance()
reimplementation of CWinApp:
\code
BOOL MyMfcApp::InitInstance()
{
// standard MFC initialization
// ...
// This sets the global qApp pointer
QMfcApp::instance(this);
// Qt GUI initialization
}
BOOL MyMfcApp::Run()
{
int result = QMfcApp::run(this);
delete qApp;
return result;
}
\endcode
\sa run()
*/
首先在app类的InitInstance 函数中初始化QApplication类
BOOL CMFCWithQtApp::InitInstance()
{
CWinApp::InitInstance();
QMfcApp::instance(this);
return true;
}
然后需要重写 app类的run 方法,在该方法中调用QMFC 的run方法
int CMFCWithQtApp::Run()
{
int result = QMfcApp::run(this);
delete qApp;
return result;
}
再次编译一下,完成了往mfc中添加信号槽机制的功能
添加qt界面
在项目中新建一个界面类,让他继承自QWidget,如下
// MainUI.h
#pragma once
#include <QWidget>
class MainUI: public QWidget
{
Q_OBJECT
public:
MainUI(QWidget* parent = nullptr);
~MainUI();
};
//MainUI.cpp
#include "pch.h"
#include "MainUI.h"
#include <QPushButton>
MainUI::MainUI(QWidget* parent) :
QWidget(parent)
{
setWindowTitle("Qt Windows");
setFixedSize(800, 720);
QPushButton* pBtn = new QPushButton(QString::fromLocal8Bit("这是一个Qt按钮"), this);
}
MainUI::~MainUI()
{
}
然后在App 类的 InitInstance 中启动该界面
BOOL CMFCWithQtApp::InitInstance()
{
CWinApp::InitInstance();
QMfcApp::instance(this);
MainUI ui;
ui.show();
QMfcApp::exec();
return FALSE;
}
然后编译,这个时候发现会在链接的时候包一些错误,找不到一些 meta
的函数的定义
配置元编译过程
传统的c/c++ 从源代码到生成可执行文件的过程需要经过预编译、编译、链接。而qt在预编译前会进行元编译,生成一个moc_
开头的源码文件,后续编译的真正的文件其实是这个元编译生成的文件。MFC项目不会经历这一步,所以会报错。
在MainUI.h
上点击右键,选择属性, 将项目类型选择为自定义生成工具
然后应用,这个时候会出现新的选项
在命令行和输入这两栏中分别填入 "moc.exe" "%(FullPath)" -o ".\GeneratedFiles\moc_%(Filename).cpp" "-fpch.h" "-f../MainUI.h"
和 .\GeneratedFiles\moc_%(Filename).cpp
命令行的含义是会使用moc元编译器编译当前文件,并将生成的文件放入到当前目录下的GeneratedFiles子目录中,并且以moc_开头作为文件名,后面 -f
表示生成的新文件中会包含 #include "pch.h"
和 #include "../MainUI.h"
然后在文件中选择右键,编译。这样将会生成moc文件(两边的双引号也好包含进去)
如果编译的时候报找不到moc.exe 这样的错误,请配置QT中的bin路径到环境变量中
我们将新生成的文件添加到项目中
再次编译,成功过后点击运行就可以看到qt界面已经展示出来了
一些问题的处理
窗口出来了,但是在我的环境下出现两个问题,关闭窗口后进程无法退出;程序退出后出现内存泄露的问题
针对这两个问题可以在QMfcApp.cpp 文件中修改
// 表示在最后一个qt窗口退出时,关闭QApplication
setQuitOnLastWindowClosed(true); //将之前的false改为true
// ~QMfcApp() 中添加这两句,当析构完成后关闭进程
HANDLE hself = GetCurrentProcess();
TerminateProcess(hself, 0);
测试信号槽
我们可以在MainUI中添加信号槽
MainUI::MainUI(QWidget* parent) :
QWidget(parent)
{
setWindowTitle("Qt Windows");
setFixedSize(800, 720);
QPushButton* pBtn = new QPushButton(QString::fromLocal8Bit("这是一个Qt按钮"), this);
connect(pBtn, &QPushButton::clicked, [=]() {
QMessageBox::information(this, QString::fromLocal8Bit("信号槽"), QString::fromLocal8Bit("这是由信号槽弹出来的"));
});
}
点击按钮之后,消息框也正常弹出来了
使用qt designer 设计界面
使用 qtdesigner 设计这样一个界面
qtdesigner 会生成一个.ui
文件,在qt的开发环境中,会自动使用uic.exe
将这个文件生成一个对应的.h文件。我们先将ui文件导入到项目,并且按照之前的步骤设置自定义生成工具,填入如下命令行
"uic.exe" "%(FullPath)" -o ".\ui_%(Filename).h"
并且填写上输出路径.\ui_%(Filename).h
编译之后会生成一个对应的ui_MainUi.h
文件,修改对应的MainUI.h
头文件,加上关于它的引用,并且添加一个ui的对象指针
//MainUI.h
#pragma once
#include <QWidget>
#include "ui_Main.h"
class MainUI: public QWidget
{
Q_OBJECT
public:
MainUI(QWidget* parent = nullptr);
~MainUI();
private:
Ui::mainUI* m_pUI;
};
在类的构造中,使用ui对象来产生界面元素
//MainUI.cpp
#include "pch.h"
#include "MainUI.h"
#include <QPushButton>
#include <QMessageBox>
MainUI::MainUI(QWidget* parent) :
QWidget(parent),
m_pUI(new Ui::mainUI())
{
m_pUI->setupUi(this);
connect(m_pUI->pushButton, &QPushButton::clicked, [=]() {
QMessageBox::information(this, QString::fromLocal8Bit("qt 信号槽测试"), m_pUI->lineEdit->text());
});
}
MainUI::~MainUI()
{
delete m_pUI;
}
执行效果如下