在上一文中本人嘗試將Ue4嵌入到Qt中,但依然有一些問題沒有去嘗試解決。今天因為幫助知乎專欄作者@大釗的關系,順便進行補完。
2018.7.18更新:
正好在參加杭州UnrealCircle的時候見到了EPIC上海的工程師李鋒,之后我通過郵件詢問了他這個問題,以下是他給我的回復:
問題所在原因是當你把虛幻引擎的窗口作為子窗口掛在Qt后,SWindow::GetPositionInScreen()中返回的坐標是錯誤的。
當你單獨啟動虛幻引擎的窗口,這里返回的是當前窗口左上角在屏幕中的位置。而當你把虛幻引擎掛在為Qt子窗口后,SWindow::GetPositionInScreen()中的ScreenPosition的值會由於WM_MOVE更新為0.
我看了你的代碼中 SetParent后:
MoveWindow(hwnWindow,rect.x(),rect.y(), rect.width(), rect.height(), true);
::SetWindowPos( hwnWindow, nullptr,rect.x(), rect.y(), rect.width(), rect.height(), SWP_NOZORDER | SWP_FRAMECHANGED| SWP_NOCOPYBITS );
但是這兩句執行后,最后系統還是會收到WM_MOVE消息進而把窗口的坐標設置為0. 說明Qt在后面某一時刻把你的窗口重新Move回(0,0). 這個需要你去Qt中查看了.
(其實這里你的MoveWindow和SetWindowPos 在后面都被覆蓋掉了,沒有生效。原因是當你單獨創建兩個Windows窗口,不適用Qt框架,你會看到子窗口相對於父窗口左上角的位置就是你MoveWindow中設置的 rect.x(),rect.y()的值,然而在你寫的Qt代碼運行后,子窗口>這里不管寫什么值,最終都是貼着父窗口Client Area左上角的)
總結原因是因為SetParent后,SWindow就無法接受到WM_MOVE消息了。突破思路有這么幾條,但限於本人技術的問題暫時不會去嘗試:
1.在Qt中手動發送WM_MOVE消息給Ue4窗口
2.在Qt中通過Socket之類的方法更新ScreenPosition的數值
3.修改UE4源代碼,將SWindow::GetPositionInScreen()的獲取方式進行修改。
2018.6.16更新:
1、更新了一下代碼,刪除了一些錯誤的東西
小貼士
Ue4項目設置中的Use Borderless Window
這個選項會讓Ue4窗口變成沒有外框與標題欄的狀態,這樣在嵌入Qt的時候就不要設置WindowStyle了,這樣看起來效果會更好(設置WindowStyle會讓窗口短暫顯現出來,這樣或許就不需要做靜默啟動了)
解除Ue4對鼠標的限制
Set Input Game And UI 來解除Ue4對鼠標限制
讓Ue4重新獲得焦點
BringWindowToTop (HWND);
已知的坑
1、因為啟動的exe進程並非游戲進程,所以通過QProcess的狀態來判斷Ue4是否啟動是不對的,推薦使用WINAPI來獲取對應線程。
2、可以在項目設置中修改窗口顯示標題,可以把討厭的(32-bit, PCD3D_SM5)去掉,強烈推薦使用窗口句柄查看工具,我是網上下了句柄精靈。(窗口標題后面都是有空格的)
3、使用嵌入方法回導致Ue4客戶區(不太確定這個叫什么)坐標異常(固定在左上角),會導致Umg按鈕無法點擊,最大化窗口后可以點擊到一部分。使用原生的win32窗口項目嵌入也是會有這個問題的,如果有對winapi了解人麻煩告知我解決方案。
本人嘗試了許多方法,都失敗了:
1、在Ue4中使用AdjustWindowRectEx
2、SetWindowPos與MoveWindow
3、在Ue4中手動調用Ue4包裝的MoveWindow函數
4、先移動Ue4窗口再嵌入Qt
5、向ue4發送Resize消息
目前的補救方案就是,在Ue4中計算鼠標坐標,再使用虛擬點擊來點擊按鈕。
關於靜默啟動外部程序與枚舉進程與窗體句柄
因為本人對WINAPI不熟所以只找了一些資料
打開一個外部程序,一般是通過ShellExecute/Ex或CreateProcess或WinExec等API。
至於隱藏主窗口以及控制窗口上的控件,這個根據不同的程序會有一定的復雜程度,首先是找到所謂的主窗口,然后再枚舉窗口上的子窗口,通過一系列API模擬點擊或讀取/設置文本內容等。
參考:http://bbs.csdn.net/topics/240049935
https://www.cnblogs.com/zjutlitao/p/3889900.html
代碼
這里我直接上代碼了,一看就懂。
#include "calculateandmove.h"
#include "ui_calculateandmove.h"
#include <QDebug>
#include <QtConcurrent>
#include <QThread>
#include <QWindow>
#include <QTimer>
CalculateAndMove::CalculateAndMove(QWidget *parent) :
QWidget(parent),
ui(new Ui::CalculateAndMove),process(nullptr)
{
ui->setupUi(this);
//setAttribute(Qt::WA_NativeWindow, true);
}
CalculateAndMove::~CalculateAndMove()
{
if(hwnWindow!=0)
SendMessage(hwnWindow,WM_CLOSE,0,0);
if(process){
if(process->state()==QProcess::Running){
process->terminate();
process->waitForFinished(30000);
}
delete process;
}
delete ui;
}
void CalculateAndMove::on_insetUe4_clicked()
{
startUe4();
}
void CalculateAndMove::on_deleteUe4_clicked()
{
if(hwnWindow==0)
return;
SendMessage(hwnWindow,WM_CLOSE,0,0);
}
void CalculateAndMove::startUe4(){
//啟動程序
QString unreal4Path{"D:/QtProject/QtWithUnreal4/WindowsNoEditor/DemoGame.exe"};
QStringList arguments;
arguments << "-WINDOWED";
process=new QProcess;
process->start(unreal4Path,arguments);
QtConcurrent::run([this]{
while (true) {
//通過窗口名稱取得窗口句柄
//hwnWindow=FindWindow(NULL,L"DemoGame ");
hwnWindow=FindWindow(NULL,L"DemoGame (32-bit, PCD3D_SM5) ");
qDebug()<<hwnWindow;
if(hwnWindow!=0)
{
connect(this,SIGNAL(insetUe4Complete()),this,SLOT(insetUe4()));
emit insetUe4Complete();
break;
}
}
});
}
void CalculateAndMove::insetUe4(){
////----------------------------------------------
////方法一,進qt后需要調用解除客戶區鎖定函數(Ue4
// ue4Window=QWindow::fromWinId(WId(hwnWindow));
// ue4Window->setParent( this->windowHandle());
// ue4Window->setGeometry(0,0,ui->label->width(),ui->label->height());
// ue4Window->show();
//----------------------------------------------
//方法二,進qt后需要調用解除客戶區鎖定函數(Ue4
QRect rect=ui->label->geometry();
QPoint pos=ui->label->mapToGlobal(ui->label->pos());
qDebug()<<"rect:"<<rect;
qDebug()<<"worldPos:"<<pos;
QRect worldRect={rect.x()+pos.x(),rect.y()+pos.y(),rect.width()+pos.x(),rect.height()+pos.y()};
SetParent(hwnWindow,(HWND)QWidget::winId());
//MoveWindow(hwnWindow,rect.x(),rect.y(), rect.width(), rect.height(), true);
::SetWindowPos( hwnWindow, nullptr,rect.x(), rect.y(), rect.width(), rect.height(), SWP_NOZORDER | SWP_FRAMECHANGED| SWP_NOCOPYBITS );
//----------------------------------------------
LPRECT lprect;
GetClientRect(hwnWindow,lprect);
qDebug()<<(int)lprect->left<<(int)lprect->top<<(int)lprect->right<<(int)lprect->bottom;
this->repaint();
}
void CalculateAndMove::on_pushButton_clicked()
{
BringWindowToTop (hwnWindow);
SetForegroundWindow(hwnWindow);
}