WPF調用Win32程序的方法
在MSDN中有專門的章節提到了在WPF中嵌入Win32控件的辦法,那就是使用 HwndHost ,只要把 Win32控件的句柄傳遞給 HwndHost 就可以了。MSDN中的例子演示的都是在同一個進程內創建的 Win32控件,我一開始認為只要通過FindWindow等Win32API得到外部Win32程序的窗口句柄,然后將窗口句柄交給 HwndHost 就可以了。實現核心代碼如下:
protected override HandleRef BuildWindowCore( HandleRef hwndParent)
{
appProc = new Process ();
appProc.StartInfo.WindowStyle = ProcessWindowStyle .Hidden;
appProc.StartInfo.FileName = @"D:/greeninst/netterm/netterm.exe" ;
appProc.Start();
//等待初始化完成,實現有點土
Thread .Sleep(1000);
hwndHost = Win32Native .FindWindow( "NetTermClass" , null );
// 嵌入在HwnHost中的窗口必須要 設置為WS_CHILD風格
uint oldStyle = Win32Native .GetWindowLong(hwndHost, Win32Native .GWL_STYLE);
Win32Native .SetWindowLong(hwndHost, Win32Native .GWL_STYLE, (oldStyle | Win32Native .WS_CHILD));
//將netterm的父窗口設置為HwndHost
Win32Native .SetParent(hwndHost, hwndParent.Handle);
return new HandleRef ( this , hwndHost);
}
這里啟動的是NetTerm這個外部程序。實踐證明我這種想法是可行的,但是唯一的問題就是雖然 外部Win32程序顯示到WPF程序中來了,但是很奇怪的是嵌入的Win32程序再也無法點擊了,點擊按鈕、輸入按鍵都不起作用,程序好像死了一樣。經過分析,我認為由於通過 SetParent 這個 Win32API 將NetTerm的父窗口設置為了 HwndHost ,這樣 NetTerm就不再有自己獨立的窗口消息循環,而是眼巴巴等着 HwndHost 這個爹給他發 消息。可能由於WPF對於消息循環的處理 不同於以前的Win32程序,導致所有的鼠標點擊、按鍵 消息都不能被傳遞給NetTerm這個兒子,這樣NetTerm就得不到任何消息,所以就像死了一樣。
解決這個問題的思路是截獲WPF的窗口消息,然后把它通過 SendMessage 這個Win32API 轉發給NetTerm。但是找了半天也沒找到WPF的消息處理的地方,請教同事以后得知WPF根本不像傳統的Win32程序那樣有窗口消息循環,而是自己搞了一套。郁悶了一會兒,突然靈光一現:管它什么WPF不WPF,它本質上還是Win32程序,只不過是一個內部使用了DirectX技術的Win32程序而已,只要是Win32程序一定有辦法拿到它的窗口消息循環。啥辦法呢?對!就是窗口鈎子。使用 SetWindowsHookEx 這個Win32API可以截獲一個窗口所有的 消息循環,這樣只要挑出來發給 HwndHost 的消息,然后把它轉發給 NetTerm窗口就ok了。經過改造以后NetTerm終於活過來了!!!
解決了最核心的問題就該處理普通問題了,主要問題及對策如下:
1、隱藏NetTerm的窗口邊框,這樣看起來就感覺不出來NetTerm是一個外部程序了。思路很簡單使用 GetWindowLong 得到窗口原來的風格,然后再附加一個 WS_BORDER 風格就ok了。
//設置為WS_CHILD風格
uint oldStyle = Win32Native .GetWindowLong(hwndHost, Win32Native .GWL_STYLE);
//&~WS_BORDER去掉邊框,這樣看起來更像一個內嵌的程序,注意()的作用,改變默認的優先級
Win32Native .SetWindowLong(hwndHost, Win32Native .GWL_STYLE, (oldStyle | Win32Native .WS_CHILD)&~ Win32Native .WS_BORDER);
2、隱藏NetTerm在任務欄上的按鈕
只要找到任務欄的句柄,然后首先向它發送TB_BUTTONCOUNT得到它上邊按鈕的個數,由於NetTerm是剛剛啟動的,可以認為最后一個按鈕就是NetTerm的按鈕,只要向任務欄的句柄發送TB_DELETEBUTTON消息將最后一個按鈕刪掉就ok了。
private void HideTaskBarButton()
{
IntPtr vHandle = Win32Native.FindWindow("Shell_TrayWnd", null);
vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero,
"ReBarWindow32", IntPtr.Zero);
vHandle = Win32Native.FindWindowEx(vHandle,
IntPtr.Zero, "MSTaskSwWClass", IntPtr.Zero);
vHandle = Win32Native.FindWindowEx(vHandle, IntPtr.Zero,
"ToolbarWindow32", IntPtr.Zero);
//得到任務欄中按鈕的數目
int vCount = Win32Native.SendMessage(new HandleRef(this, vHandle),
(uint)Win32Native.TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32();
//認為最后一個按鈕就是被嵌套程序的按鈕,刪除它
Win32Native.SendMessage(new HandleRef(this, vHandle),
Win32Native.TB_DELETEBUTTON, new IntPtr(vCount - 1), IntPtr.Zero);
}
這是在WinXP下的處理。好像Win2000、Vista的任務欄的結構是不同的,如果需要運行在這些OS下需要做進一步的改進。
3、 自動登錄。在NetTerm啟動以后自動登錄到服務器,並且自動輸入用戶名、密碼,並且啟動指定的程序。NetTerm支持在啟動參數中指定要連接的服務器地址,這樣可以解決自動登錄到服務器的問題;使用 SendMessage( handle , Win32Native.WM_CHAR, ch , IntPtr.Zero) 向NetTerm窗口發送模擬按鍵就可以實現自動鍵入Linux指令的效果。由於Linux指令需要一定的處理的時間,所以每發完一條指令就要Sleep一會兒以防止鍵入指令速度過快。