Qt嵌入外部程序界面初探

项目需要将一个外部软件嵌入自己编写的qt界面,类似于将外部程序当作自己软件的一个插件,以起到集成的目的。自己刚开始用电脑自带的计算器做例子,但一直没成功,换了其他软件就能成功嵌入。

传统做法
1)首先打开需要的外部程序;
2)通过spy+获取的外部程序类名;
3)将类名当作函数FindWindow的第一个参数(字符串);

代码

    HWND m_pwHwnd = NULL;
    QWidget* m_widget = nullptr;
    while (!m_pwHwnd)
    {
        m_pwHwnd = FindWindow(L"Chrome_WidgetWin_1", NULL);//第一个参数为类名,第二个为标题
    }
    m_window = QWindow::fromWinId((WId)m_pwHwnd);
    m_widget = QWidget::createWindowContainer(m_window, this);
    ui->horizontalLayout_5->addWidget(m_widget);

上面代码中的函数FindWindow第一个参数为外部程序类名,需要借助vs菜单栏 工具——Spy++(+)获取得到。
Qt嵌入外部程序界面初探_第1张图片
最终效果:
Qt嵌入外部程序界面初探_第2张图片

上面的传统做法是利用vs自带工具spy++得到已打开程序的类名或窗口名,进而通过FindWindow函数得到句柄。然而,每次打开的外部程序类名或窗口名都会改变,另外也不可能要求用户通过第三方工具得到这两个值中的一个,所以该方法不大可行。
改进做法
1)根据exe路径启动程序,并得到进程id号;
2)根据进程id号得到主窗口句柄:通常情况下一个进程内有多个窗口句柄,还需要从得到的N个句柄中找到主窗口句柄(此处可以参考这);
3)将HWND转为WId,进而将外部程序嵌入QWindow、widget:

代码:

//启动外部程序
bool _launchExternalSoftware(QString exePath)
{
    /*
    *启动外部程序
    */
    //QString cmd = "D:/Program Files/InnovMetric/PolyWorks MS 2021/bin/polyworks.exe";
    //QString cmd = "D:/Program Files (x86)/Adobe/Reader 11.0/Reader/AcroRd32.exe";
    //QString cmd = "D:/Program Files/MVS/MVS/Applications/Win64/MVS.exe";
    m_process = new QProcess(this);
    connect(m_process, &QProcess::started, this, &MainWindow::on_processStarted);
    m_process->setParent(this);	
	QStringList arg;
	arg << "";
    m_process->start(exePath, arg);//外部程序启动后,将随主程序的退出而退出 
    //m_process->start(exePath);//外部程序启动后,将随主程序的退出而退出  

    return true;
}

//嵌入外部程序
void on_processStarted()
{   
	qint64 id = m_process->processId();//如果程序没有运行,将会返回0
	if (id == 0)
	{
		QMessageBox::information(NULL, "提示", "程序没有启动");
		return;
	}

	Sleep(2500);
	qDebug() << "Status: " << m_process->state();
	HWND mainwindowHwnd = FindMainWindow(id);
	qDebug() << "mainwindowHwnd: " << mainwindowHwnd;
	if (!mainwindowHwnd)
		return;
    

	wid = (WId)mainwindowHwnd;
	m_window = QWindow::fromWinId(wid);
	m_widget = QWidget::createWindowContainer(m_window); //第二个参数是作为window的父类,也可以用layout,此处就不解
    ui->scrollArea->setWidget(m_widget);
}

其中,获取主窗口句柄的函数 FindMainWindow 代码如下:
函数声明

//自定义结构体
struct handle_data {
	unsigned long process_id;
	HWND best_handle;
};

BOOL IsMainWindow(HWND handle);
BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam);
HWND FindMainWindow(unsigned long process_id);//通过进程id号获取主窗口句柄

定义

BOOL IsMainWindow(HWND handle)
{
	return GetWindow(handle, GW_OWNER) == (HWND)0 && IsWindowVisible(handle);
}

BOOL CALLBACK EnumWindowsCallback(HWND handle, LPARAM lParam)
{
	handle_data& data = *(handle_data*)lParam;
	unsigned long process_id = 0;
	GetWindowThreadProcessId(handle, &process_id);
	if (data.process_id != process_id || !IsMainWindow(handle)) {
		return TRUE;
	}
	data.best_handle = handle;
	return FALSE;
}


//通过进程id号获取主窗口句柄
HWND FindMainWindow(unsigned long process_id)
{
	handle_data data;
	data.process_id = process_id;
	data.best_handle = 0;
	EnumWindows(EnumWindowsCallback, (LPARAM)&data);
	return data.best_handle;
}

补充

嵌入后可能还想让被嵌入的窗口脱离(独立)出去,或实现自己的软件退出,但被嵌入的软件不退出的功能。既然嵌入是给窗口一个父窗口,那么,让父窗口为NULL理论上就可以让被嵌入的窗口独立出去。思路有了,这里就不放具体代码了。

总而言之,只要获得进程,嵌入窗口就没有障碍,这里主要有两个思路获取进程:1)启动一个exe并获取对应进程;2)遍历电脑所有进程,找到想要的进程。

你可能感兴趣的:(qt,c++,qt,开发语言)