C#?WPF調用QT窗口的方法
WPF 程序內嵌 QT 窗體
1、目標:將QT控件(Qwiget)(或則基于QWiget的控件)(或則任何第三方C++控件)封裝為WPF可調用的用戶控件。簡單來說就是WPF程序調用QT窗體控件。
本人需要使用3D控件顯示一些3D點云等功能。但是又找不到好的兼容WPF的控件(大點云和效率原因)。最后選用CloudConpare作為點云顯示控件(該控件基于QT的QWiget)。
目前網絡上這方面的內容并不是太多,而且多數還都是雷同。
個人感覺 C# 跨平臺并不是太友好(或則我水平不夠),特別是3D方面。
本人對QT不是很熟悉,但是CloudCompare是基于QT寫的界面,所以沒辦法只能慢慢摸索,好在以前有MFC編程經驗,對C++還有點積累,過程中關于配置方面的問題我盡量詳細說明(對于C#編程者來說還是挺惡心的,QT的配置和CloudCompare的配置挺麻煩的)。
實現過程如下(可能需要稍微有點基礎才能看懂,我認為有意思的會寫多點 ,寫的不是太系統(tǒng)),可能有點亂,有點啰嗦,想到哪里寫到哪,見諒。
心路:
一開始查詢資料想通過中間庫 CLR鏈接C#和C++,簡單demo也測試OK,后期碰到控件句柄傳遞問題調試也比較麻煩,遂放棄(現在想來應該也是可以實現的)。
最終直接使用 C形式函數導出 的形式來創(chuàng)建DLL,也挺方便的。專業(yè)點叫P/Invoke(推薦大家一本書《精通.NET互操作:P/Invoke,C++Interop和COM Interop》)。
大概邏輯:WPF控件句柄–>傳遞給C++生成的QT庫–>動態(tài)庫根據傳遞過來的句柄創(chuàng)建一個QWiget–>根據這個QWiget生成一個GLWindow(真實的3D顯示窗體)–>C#保存這個指針方便下次訪問。
數據傳邏輯:主要是C#這邊發(fā)給C++ Dll數據,主要涉及到托管和非托管資源的問題、字節(jié)對齊等。
實現過程中可能有幾個主要問題:
1、C#和C++混合編程;
2、QT程序(QT事件循環(huán)QApplication.exec())如何封裝為Dll;
3、QT的UI線程和WPF的UI線程沖突問題(目前本人也沒完全弄懂,歡迎溝通);
工具:VS2019(Qt Visual Studio Tool 2.7.0)、QT5.14.2、CloudCompare2.6.12源碼。。。。
上代碼:
1、創(chuàng)建一個VS-QT的動態(tài)庫。

選擇Qtwidgets application,然后修改配置生成為dll。網上資料很多,不詳細描述(大概流程:修改輸出類型為dll,將沒必要的文件刪除即可(Main)(所有文件都可以刪除,然后新增一個QtWidgets class也行))。如下:

我們這里界面就不需要設置(因為我們不是直接用QT界面),只需要一個空白的Qwidget。(同志們只需要用Qwiget就可以在這里繪制了)
右鍵 編譯這個.ui文件。會生成一個ui_xxxx.h文件。添加這個文件到項目中,(可能在生成目錄的uic文件夾下,如有必要還需要將這個文件路徑加到包含目錄中。若是不添加可能會生成失?。?。
首次生成失敗很正常,各種QT環(huán)境問題。不要心急。

這里我們就獲得了一個帶QT的界面的C++庫。
2、WPF中引用它:
若是你們用的是Winform,那就比較方便了,直接拿到控件句柄(我這里使用的panel控件)傳遞給動態(tài)庫就行了。
若是你用的是WPF,會麻煩一點(因為WPF控件獲取不到“控件”句柄),這里在下試了很多,獲得的都是窗體句柄。所以只能使用WindowsFormsHost。上代碼

這里是本人在WPF創(chuàng)建了一個用戶控件的xaml文件。上代碼
public partial class Window3DControl : UserControl
{
public Window3DControl()
{
InitializeComponent();
}
/// <summary>
/// 3D顯示 窗口
/// </summary>
public Window3DControl_Net Control3D = new Window3DControl_Net();
/// <summary>
/// 創(chuàng)建真實的3D窗體,GlWindow
/// </summary>
/// <returns></returns>
public bool Create3DWindow()
{
int res = Control3D.Creat3DWindow_Net((long)Panel3D.Handle, Panel3D.Width, Panel3D.Height);
return res == 0;
}
/// <summary>
/// 控件發(fā)生變化
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window3DControl_SizeChanged(object sender, SizeChangedEventArgs e)
{
if(Control3D.IsInitWindow)
{
int width = (int)(WindowForm3D.ActualWidth * 1.25);
int Heigth = (int)(WindowForm3D.ActualHeight * 1.25);
int res = Control3D.WindowSizeChanged_Net(width, Heigth);
}
}
/// <summary>
/// 3D窗體切換
/// </summary>
public void ChangeWindowModel()
{
if(WindowForm3D.Visibility == Visibility.Visible)
{
WindowForm3D.Visibility = Visibility.Collapsed;
Button2D.Visibility = Visibility.Visible;
}
else
{
WindowForm3D.Visibility = Visibility.Visible;
Button2D.Visibility = Visibility.Collapsed;
}
}
}
上面是用戶控件的代碼部分。也很簡單只有幾個簡單功能。其中Control3D對象是我封裝的一個調用動態(tài)庫的接口類(只是封裝了一層),功能上直接調用C形式的函數接口也是可以的。上代碼

這里是封裝的3D對象的部分接口,C#這邊就很簡單了,都封裝成用戶控件了,那不是想怎么玩怎么玩。(關于C#定義函數接口就不描述了,C++那邊導出函數接口也不描述了)。上代碼

還有一點很重要(我感覺)如何顯示QT窗口正好覆蓋WPF中句柄對應的控件(可能會碰到直接顯示QT窗口在主界面的左上角)。有兩步很重要,通過如上圖,可以看到我們將控件句柄和對應的尺寸傳遞過去,并且將生成的QT窗口的實例對應的指針傳回來,為了下次能夠直接拿到這個QT窗口。可以看下我的創(chuàng)建窗口函數是如何實現的,上代碼

圖中標記部分,很重要 。(忘記從哪個大神哪里偷學來的)
另外為了C# 那邊能拿到C++這邊的對象指針。使用了二級指針概念(這個不太好解釋,要自己細細理解,這個不是偷學的,在下耗盡腦子想的)。
還有一個問題QT事件循環(huán),(若是你調用過QT的dll,可能就會發(fā)現這個問題),我這邊解決辦法就是,在程序開始(你認為的合適的地方)調用InitQTEnvironment接口,上代碼。
#include "qmfcapp.h"
int InitQtEnvironment()
{
try
{
//if (QApplication::instance() == NULL)
//{
// int argc = 0;
// g_app = new QApplication(argc, NULL);
// //QWidget* GroudWidget = new QWidget(NULL);
// //GroudWidget->setGeometry(0, 0,0, 1);//設置widget的大小
// //GroudWidget->showMinimized();
// //g_app->exec();
//}
if (QApplication::instance() == NULL)
{
return QMfcApp::pluginInstance();
}
return 0;
}
catch(QException ex )
{
printf("Init Error");
printf(ex.what());
return -1;
}
}
兩種方式都行,目的是創(chuàng)建出來QApplication這個靜態(tài)對象即可。(其中QMfcApp是偷學某個大佬)。
OK,其實到這里整個流程已經基本打通了,路走通了,其余的都好辦了。
關于數據傳遞問題,這里也說一下。C#和C++的基礎數據類型在內存中占位不盡相同,有興趣可以看下對照表(網上一大堆)。
若是使用CLR包裝C++dll,可以解決類型不一致問題(個人感覺CLR就是一次形參類型轉換而已,可能是我太淺薄了)。
若是我們這種直接調用C形式的函數接口。需要傳遞參數需要注意幾點。
1、基礎類型也需要注意內存占位是否一致。
2、結構體類型,需要考慮字節(jié)對齊和托管資源問題。思路:兩邊定義相同類型的結構體。直接傳遞結構體指針(首地址)本質上還是內存要一樣,不能亂。先上代碼(C++)

C#中數據結構

仔細對比(字節(jié)對齊,還要考慮編譯器可能優(yōu)化)。
數組必須確定傳遞長度(內存分配才能連續(xù)),并且C#這邊申請的內存必須是非托管內存(使用Marsh申請)。關于C++傳遞數據到C#,上文也提到了二級指針,基礎類型或確定內存長度的可以直接傳遞,不確定的對象使用二級指針。關于內存釋放問題。大家可以網上找下,有幾種常用方法,此處不贅述。
按照慣例,上效果圖。

遺留問題:
玩過WPF的都知道,WPF有一個隱藏的UI線程,我們的屬性綁定控件和刷線界面都是
這個線程來更新的。QT也有一個類似的界面線程。因為我這邊主程序是WPF,QWiget只是作為一個控件來用的。
目前發(fā)現他們兩個界面線程會出現搶資源情況(我也是猜的,因為出現WPF的源已經改變了但是目標控件上沒有刷新(及時刷新)的現象)。各位大佬若有這方面的見解,請一定聯系我。抱拳?。?!
到此這篇關于C# WPF調用QT窗口的方法的文章就介紹到這了,更多相關C# WPF調用QT窗口內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C#中Dictionary<TKey,TValue>排序方式的實現
這篇文章主要介紹了C#中Dictionary<TKey,TValue>排序方式的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02
如何使用LinQ To Object把數組或DataTable中的數據進行向上匯總
這篇文章主要介紹了如何使用LinQ To Object把數組或DataTable中的數據進行向上匯總,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-12-12

