C#?WPF調(diào)用QT窗口的方法
WPF 程序內(nèi)嵌 QT 窗體
1、目標(biāo):將QT控件(Qwiget)(或則基于QWiget的控件)(或則任何第三方C++控件)封裝為WPF可調(diào)用的用戶控件。簡單來說就是WPF程序調(diào)用QT窗體控件。
本人需要使用3D控件顯示一些3D點云等功能。但是又找不到好的兼容WPF的控件(大點云和效率原因)。最后選用CloudConpare作為點云顯示控件(該控件基于QT的QWiget)。
目前網(wǎng)絡(luò)上這方面的內(nèi)容并不是太多,而且多數(shù)還都是雷同。
個人感覺 C# 跨平臺并不是太友好(或則我水平不夠),特別是3D方面。
本人對QT不是很熟悉,但是CloudCompare是基于QT寫的界面,所以沒辦法只能慢慢摸索,好在以前有MFC編程經(jīng)驗,對C++還有點積累,過程中關(guān)于配置方面的問題我盡量詳細(xì)說明(對于C#編程者來說還是挺惡心的,QT的配置和CloudCompare的配置挺麻煩的)。
實現(xiàn)過程如下(可能需要稍微有點基礎(chǔ)才能看懂,我認(rèn)為有意思的會寫多點 ,寫的不是太系統(tǒng)),可能有點亂,有點啰嗦,想到哪里寫到哪,見諒。
心路:
一開始查詢資料想通過中間庫 CLR鏈接C#和C++,簡單demo也測試OK,后期碰到控件句柄傳遞問題調(diào)試也比較麻煩,遂放棄(現(xiàn)在想來應(yīng)該也是可以實現(xiàn)的)。
最終直接使用 C形式函數(shù)導(dǎo)出 的形式來創(chuàng)建DLL,也挺方便的。專業(yè)點叫P/Invoke(推薦大家一本書《精通.NET互操作:P/Invoke,C++Interop和COM Interop》)。
大概邏輯:WPF控件句柄–>傳遞給C++生成的QT庫–>動態(tài)庫根據(jù)傳遞過來的句柄創(chuàng)建一個QWiget–>根據(jù)這個QWiget生成一個GLWindow(真實的3D顯示窗體)–>C#保存這個指針方便下次訪問。
數(shù)據(jù)傳邏輯:主要是C#這邊發(fā)給C++ Dll數(shù)據(jù),主要涉及到托管和非托管資源的問題、字節(jié)對齊等。
實現(xiàn)過程中可能有幾個主要問題:
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。網(wǎng)上資料很多,不詳細(xì)描述(大概流程:修改輸出類型為dll,將沒必要的文件刪除即可(Main)(所有文件都可以刪除,然后新增一個QtWidgets class也行))。如下:
我們這里界面就不需要設(shè)置(因為我們不是直接用QT界面),只需要一個空白的Qwidget。(同志們只需要用Qwiget就可以在這里繪制了)
右鍵 編譯這個.ui文件。會生成一個ui_xxxx.h文件。添加這個文件到項目中,(可能在生成目錄的uic文件夾下,如有必要還需要將這個文件路徑加到包含目錄中。若是不添加可能會生成失?。?br />首次生成失敗很正常,各種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對象是我封裝的一個調(diào)用動態(tài)庫的接口類(只是封裝了一層),功能上直接調(diào)用C形式的函數(shù)接口也是可以的。上代碼
這里是封裝的3D對象的部分接口,C#這邊就很簡單了,都封裝成用戶控件了,那不是想怎么玩怎么玩。(關(guān)于C#定義函數(shù)接口就不描述了,C++那邊導(dǎo)出函數(shù)接口也不描述了)。上代碼
還有一點很重要(我感覺)如何顯示QT窗口正好覆蓋WPF中句柄對應(yīng)的控件(可能會碰到直接顯示QT窗口在主界面的左上角)。有兩步很重要,通過如上圖,可以看到我們將控件句柄和對應(yīng)的尺寸傳遞過去,并且將生成的QT窗口的實例對應(yīng)的指針傳回來,為了下次能夠直接拿到這個QT窗口??梢钥聪挛业膭?chuàng)建窗口函數(shù)是如何實現(xiàn)的,上代碼
圖中標(biāo)記部分,很重要 。(忘記從哪個大神哪里偷學(xué)來的)
另外為了C# 那邊能拿到C++這邊的對象指針。使用了二級指針概念(這個不太好解釋,要自己細(xì)細(xì)理解,這個不是偷學(xué)的,在下耗盡腦子想的)。
還有一個問題QT事件循環(huán),(若是你調(diào)用過QT的dll,可能就會發(fā)現(xiàn)這個問題),我這邊解決辦法就是,在程序開始(你認(rèn)為的合適的地方)調(diào)用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);//設(shè)置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是偷學(xué)某個大佬)。
OK,其實到這里整個流程已經(jīng)基本打通了,路走通了,其余的都好辦了。
關(guān)于數(shù)據(jù)傳遞問題,這里也說一下。C#和C++的基礎(chǔ)數(shù)據(jù)類型在內(nèi)存中占位不盡相同,有興趣可以看下對照表(網(wǎng)上一大堆)。
若是使用CLR包裝C++dll,可以解決類型不一致問題(個人感覺CLR就是一次形參類型轉(zhuǎn)換而已,可能是我太淺薄了)。
若是我們這種直接調(diào)用C形式的函數(shù)接口。需要傳遞參數(shù)需要注意幾點。
1、基礎(chǔ)類型也需要注意內(nèi)存占位是否一致。
2、結(jié)構(gòu)體類型,需要考慮字節(jié)對齊和托管資源問題。思路:兩邊定義相同類型的結(jié)構(gòu)體。直接傳遞結(jié)構(gòu)體指針(首地址)本質(zhì)上還是內(nèi)存要一樣,不能亂。先上代碼(C++)
C#中數(shù)據(jù)結(jié)構(gòu)
仔細(xì)對比(字節(jié)對齊,還要考慮編譯器可能優(yōu)化)。
數(shù)組必須確定傳遞長度(內(nèi)存分配才能連續(xù)),并且C#這邊申請的內(nèi)存必須是非托管內(nèi)存(使用Marsh申請)。關(guān)于C++傳遞數(shù)據(jù)到C#,上文也提到了二級指針,基礎(chǔ)類型或確定內(nèi)存長度的可以直接傳遞,不確定的對象使用二級指針。關(guān)于內(nèi)存釋放問題。大家可以網(wǎng)上找下,有幾種常用方法,此處不贅述。
按照慣例,上效果圖。
遺留問題:
玩過WPF的都知道,WPF有一個隱藏的UI線程,我們的屬性綁定控件和刷線界面都是
這個線程來更新的。QT也有一個類似的界面線程。因為我這邊主程序是WPF,QWiget只是作為一個控件來用的。
目前發(fā)現(xiàn)他們兩個界面線程會出現(xiàn)搶資源情況(我也是猜的,因為出現(xiàn)WPF的源已經(jīng)改變了但是目標(biāo)控件上沒有刷新(及時刷新)的現(xiàn)象)。各位大佬若有這方面的見解,請一定聯(lián)系我。抱拳?。?!
到此這篇關(guān)于C# WPF調(diào)用QT窗口的方法的文章就介紹到這了,更多相關(guān)C# WPF調(diào)用QT窗口內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C#中Dictionary<TKey,TValue>排序方式的實現(xiàn)
這篇文章主要介紹了C#中Dictionary<TKey,TValue>排序方式的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02C#中Byte[]和String之間轉(zhuǎn)換的方法
很多朋友不清楚如何在Byte[]和String之間進(jìn)行轉(zhuǎn)換?下面小編給大家?guī)砹薭yte與string轉(zhuǎn)換的方法,感興趣的朋友參考下吧2016-08-08C#/VB.NET實現(xiàn)將Html轉(zhuǎn)為Word的示例詳解
本文分享以C#程序代碼為例,實現(xiàn)將Html文件轉(zhuǎn)換Word文檔的方法(附VB.NET代碼)。在實際轉(zhuǎn)換場景中可參考本文的方法,感興趣的可以了解一下2022-07-07如何使用LinQ To Object把數(shù)組或DataTable中的數(shù)據(jù)進(jìn)行向上匯總
這篇文章主要介紹了如何使用LinQ To Object把數(shù)組或DataTable中的數(shù)據(jù)進(jìn)行向上匯總,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12