WPF中嵌入普通Win32程序的方法

公司如今在研發基於.Net中WPF技術的產品,因爲要兼容舊有產品,好比一些舊有的Win32程序、第三方的Win32程序等等,還要實現自動登陸這些外部Win32程序,所以必須可以將這些程序整合到咱們的系統中來,讓使用者看起來它們好像是一個程序。 html

在MSDN中有專門的章節提到了在WPF中嵌入Win32控件的辦法,那就是使用 HwndHost ,只要把 Win32控件的句柄傳遞給 HwndHost 就能夠了。MSDN中的例子演示的都是在同一個進程內建立的 Win32控件,我一開始認爲只要經過FindWindow等Win32API獲得外部Win32程序的窗口句柄,而後將窗口句柄交給 HwndHost 就能夠了。實現核心代碼以下: java

         protected   override   HandleRef  BuildWindowCore( HandleRef  hwndParent) python

        { 服務器

            appProc =  new   Process (); app

            appProc.StartInfo.WindowStyle =  ProcessWindowStyle .Hidden; ide

            appProc.StartInfo.FileName =  @"D:\greeninst\netterm\netterm.exe" ; 工具

            appProc.Start(); ui

             //等待初始化完成,實現有點土 this

             Thread .Sleep(1000); spa

            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下須要作進一步的改進。

三、 自動登陸。在NetTerm啓動之後自動登陸到服務器,而且自動輸入用戶名、密碼,而且啓動指定的程序。NetTerm支持在啓動參數中指定要鏈接的服務器地址,這樣能夠解決自動登陸到服務器的問題;使用 SendMessage( handle , Win32Native.WM_CHAR,  ch , IntPtr.Zero) 向NetTerm窗口發送模擬按鍵就能夠實現自動鍵入Linux指令的效果。因爲Linux指令須要必定的處理的時間,因此每發完一條指令就要Sleep一下子以防止鍵入指令速度過快。

運行效果以下:
主要代碼以下,
Win32Native .cs是咱們寫的一個對Win32API的調用聲明,都是簡單的PInvoke聲明,因爲尺寸比較大這裏就不貼出來了,你們能夠查MSDN本身來聲明。

=======================================NetTermHost.cs============================

namespace Client.Pages

{

    class NetTermHost : HwndHost

    {

        public IntPtr hwndHost;

        private IntPtr hookId = new IntPtr(3);

        private HookProc hookProc;

        private Process appProc;

        protected override HandleRef BuildWindowCore(HandleRef hwndParent)

        {

            appProc = new Process();

            appProc.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;

            appProc.StartInfo.FileName = @"D:\greeninst\netterm\netterm.exe";

            //設置要鏈接的主機名,這樣啓動之後就當即鏈接了

            appProc.StartInfo.Arguments = "192.168.88.128";

            appProc.Start();

            //等待初始化完成,實現有點土

            Thread.Sleep(1000);

            hwndHost = Win32Native.FindWindow("NetTermClass", null);

            //設置爲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);

            //將netterm的父窗口設置爲HwndHost,爹地我來了

            Win32Native.SetParent(hwndHost, hwndParent.Handle);

     

      //窗口最大化

            Win32Native.ShowWindow(hwndHost.ToInt32(), Win32Native.SW_MAXIMIZE);

      //隱藏netterm在任務欄上的按鈕

            HideTaskBarButton();

      //隱藏netterm的工具欄

            HideNetTermToolBar();

            //因爲登陸過程很是長,因此不要在這裏等過久,不然界面像死了同樣,因此啓動線程來操做

            ThreadStart ts = new ThreadStart(

                            delegate()

                            {

                //自動登陸telnet

                                AutoLogin();

                            }

                        );

            Thread thread = new Thread(ts);

            thread.Start();       

            hookProc = new HookProc(MyHookHandler);

            //設置鉤子,截獲主窗口界面消息循環

            //對當前的窗口,使用IntPtr.Zero

            HookApi.SetWindowsHookEx(hookId.ToInt32(), hookProc, IntPtr.Zero, 

                HookApi.GetCurrentThreadId());

            return new HandleRef(this, hwndHost);

        }

        private int MyHookHandler(int code, IntPtr wparam, ref MSG msg)

        {

            //若是是當前Host的消息,則將其轉發給netterm程序

            if (msg.hwnd == this.Handle)

            {

                HandleRef handleRef = new HandleRef(this, hwndHost);

                Win32Native.SendMessage(handleRef, (uint)msg.message, msg.wParam, msg.lParam);

            }

            int nextHook = HookApi.CallNextHookEx(hookId, code, wparam, ref msg);

            return nextHook;

        }

        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);

        }

        private void HideNetTermToolBar()

        {

            IntPtr toolBarWin = Win32Native.FindWindowEx(hwndHost, IntPtr.Zero, "ToolbarWindow32", IntPtr.Zero);

            Win32Native.ShowWindow(toolBarWin.ToInt32(), 0);

        }

        private void AutoLogin()

        {      

            Thread.Sleep(10000);

      //輸用戶名

            SendString("yzk\n");

            Thread.Sleep(1000);

      //輸密碼

            SendString("123456\n");

            Thread.Sleep(1000);

      //進入目錄

            SendString("cd /mnt/hgfs/NAHA/src/\n");

            Thread.Sleep(1000);

      //運行字符終端

            SendString("python FrontEnd.py\n");

        }

    //模擬按鍵

        private void SendString(String s)

        {

            foreach(char c in s)

            {

                Win32Native.SendMessage(new HandleRef(this, hwndHost), Win32Native.WM_CHAR, new IntPtr(c), IntPtr.Zero);

            }            

        }

        protected override void DestroyWindowCore(HandleRef hwnd)

        {

            HandleRef handleRef = new HandleRef(this, hwndHost);

            //關閉netterm窗口

            //Win32Native.SendMessage(handleRef, Win32Native.WM_CLOSE, IntPtr.Zero, IntPtr.Zero);

            //很黃很暴力,直接殺死

            appProc.Kill();

            //有bug,若是netterm已經連上遠程主機,那麼若是不退出就close的話會彈出對話框,這就會形成主程序沒法退出

            //幾種策略:殺死netterm、發送模擬鍵點擊「是」按鈕、把netterm釋放出來讓用戶決定、只是demo而已無論它

            //沒有主菜單的bug

            //因爲是攔截消息循環搞的,因此有可能有潛在的bug

            Win32Native.DestroyWindow(hwnd.Handle); 

            HookApi.UnhookWindowsHookEx(hookId); 

        }

    }

}

=======================TradeNetTermHost.cs===========================================

public partial class TradeNetTermHost : UserControl

{

  private NetTermHost ch;

  public TradeNetTermHost()

  {

    Win32Native.InitCommonControls();

    InitializeComponent();

    ch = new NetTermHost();

    this.Win32HosterBorder.Child = ch;

    Loaded += new RoutedEventHandler(TradeNetTermHost_Loaded);

  }

  void TradeNetTermHost_Loaded(object sender, RoutedEventArgs e)

  {

    //設置netterm容器爲焦點,不然消息不會發給它

    Win32Native.SetFocus(ch.Handle);

  }

}

引用:http://www.blogjava.net/huanzhugege/archive/2008/04/24/195516.html

相關文章
相關標籤/搜索