C#的WinForm中嵌入Qt界面庫

C#的WinForm中嵌入Qt界面庫

爲何會有如此奇葩需求

  • 一處代碼,多處運行

C#有兩種寫界面的框架,winFrom和WPF,這二者寫的界面可以相互調用,但C#編寫的界面組件在非託管C++雖然理論上可以,但是這種類似反射的機制使用其他來說特別不舒服。而對於編寫組件式SDK而言,核心的界面組件可以在C++、C#、JAVA中使用而無需修改太多的代碼是我們的終級目標。

  • ocx 編寫難度太大

使用MFC編寫的OCX控件可以嵌套在各種高級語言甚至瀏覽器中,但對於新時代的開發人員而言,會MFC開發的越來越少,開發難度也比較大,開發MFC的性價比並不高。對於使用C++編寫界面的人員來說,用Qt是大多數人的選擇。

  • 跨平臺

ocx 不能跨平臺,導致了無法在除windows操作系統下使用ocx控件,但是Qt的界面可以在跨平臺環境下使用。

嵌入原理

界面的可視化本質上是使用繪圖引擎繪製出來的,對於MFC、WinForm、Qt而言在編寫界面庫的本質上是沒有太大的區別的,即都是基於windows的窗口句柄的繪製,windows SDK的很多函數都是需要指定窗口句柄(如控制窗口的消息循環、Hook鉤子等)。其次解決消息發送問題。MFC、WinForm使用的是windows的消息機制,而Qt使用的信號槽機制(幸運的是Qt也可以接收windows的消息)

也就是說基於Qt編寫界面dll理論上可以在MFC、Qt、JAVA、C# WinFrom以及WPF(這個還是要嵌入winfrom外殼)中使用,誇大點來說是可以在任何語言中調用

實現步驟

使用qtwinmigrate開源庫

正常情況下,qt的界面庫必須是qt的主程序調用纔行,但是qtwinmigrate庫在內部解決了qt主程問題。我們只需要使用即可.
qtwinmigrate 庫的下載地址 https://github.com/qtproject/qt-solutions.git

使用步驟

  • 新建一個C++的dll,將qtwinmigrate庫的三個類(qwinhost、qmfcapp、qwinwidget三個類對應的頭文件和cpp文件)文件拷貝到自己的工程目錄下,並加入到工程中
  • 在dll入口函數dllmain 中加入如下代碼
BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpvReserved*/ )
{
    static bool ownApplication = FALSE;

    if ( dwReason == DLL_PROCESS_ATTACH )
    {
        //在dll加載的時候完成qapplication的創建,在內部通過SetWindowsHookEx這個函數解決消息的發送問題
	   ownApplication = QMfcApp::pluginInstance( hInstance ); 
    }
	if (dwReason == DLL_PROCESS_DETACH && ownApplication)
	{
		delete qApp;//退出的時候delete qApp;
	}
    return TRUE;
}
  • 編寫一個C++類來管理一個界面
class SampleWidget
{
public:
    SampleWidget(void* ptr)
    {
        _win_widget=new QWinWidget((HWND)ptr);//通過句柄創建一個QWinWidget類,這個
        QToolBar* tool_bar = new QToolBar(win);
       	tool_bar->addAction("open");
	    tool_bar->addAction("close");
	    QComboBox* combo_box = new QComboBox();
	    combo_box->addItem("zhang san");
	    combo_box->addItem("li si");
        QVBoxLayout* box_layout = new QVBoxLayout(win);
        box_layout->addWidget(tool_bar);
        box_layout->addWidget(combo_box);
        QListView* list_view = new QListView(win);
        box_layout->addWidget(list_view);
        win->setLayout(box_layout);
        win->move(0, 0);
        win->show();
    }
    ~ SampleWidget()
    {
        delete _win_widget;
    }
   private:
     QWinWidget* _win_widget;
}
  • 使用Swig生成對應的cxx文件和相應的C#文件

notes:在前面的一系列文章中,對swig有相應的使用說明

//## 建立對應的sample.i文件
%module(directors="1") sampledll
%{
    #include "SampleWidget.h"   
%}

/* turn on director wrapping Callback */
%apply void *VOID_INT_PTR { void * }
%include "SampleWidget.h"

建立好swig的i文件之後執行命令

swig -c++ -csharp  example.i //最簡單的swig使用

執行之後將生成sample_wrap.cxx加入到dll工程中一起編譯(smapledll是dll的名字),注意名字要跟你sample_wrap.cxx所在的工程名一致,負責會出現調用錯誤.

  • 新建C# winFrom程序,將生成C#文件納入工程中
public partial class Form1 : Form
    {
        SampleWidget _sample_widget;
        public Form1()
        {
            InitializeComponent();
            _sample_widget=new  SampleWidget(this.Handle);
        }
    }

測試結果

image.png
可以看到窗體風格之類的也沒有什麼太大的問題,一個簡單的類com組件的控件就完成了(窗口大小這樣不會自動佈滿屏幕,需要重寫winfrom中的大小改變的方法,還有一種方法是在C++截獲From1這個的句柄消息如以下方式(這樣對C#研發人員來說就只要寫一句代碼即可)

static LRESULT CALLBACK WindowsProc(HWND win_hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case  WM_SIZE:
	{
		int nHeight = HIWORD(lParam);
		int nWidth = LOWORD(lParam);
		if (_win_widget)
		{
			_win_widget->setFixedSize(nWidth, nHeight);
		}
		break;
	}
	default:
		return DefWindowProc(win_hwnd, message, wParam, lParam);
	}
	return 0;
}

SampleWidget(void* ptr,bool full_screen) //之前的Sample構造函數
{
    _win_widget=new QWinWidget((HWND)ptr);//通過句柄創建一個QWinWidget類,這個
    QToolBar* tool_bar = new QToolBar(win);
    tool_bar->addAction("open");
    tool_bar->addAction("close");
    if(full_screen)
    {
        SetWindowLongPtr((HWND)parent, -4, (LONG_PTR)WindowsProc);//parent是之前傳入的窗口句柄
    }
    QComboBox* combo_box = new QComboBox();
    combo_box->addItem("zhang san");
    combo_box->addItem("li si");
    QVBoxLayout* box_layout = new QVBoxLayout(win);
    box_layout->addWidget(tool_bar);
    box_layout->addWidget(combo_box);
    QListView* list_view = new QListView(win);
    box_layout->addWidget(list_view);
    win->setLayout(box_layout);
    win->move(0, 0);
    win->show();
}

image.png
喜歡我就關注我吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章