zoukankan      html  css  js  c++  java
  • WPF中嵌入普通Win32程序的方法

    公司现在在研发基于.Net中WPF技术的产品,由于要兼容旧有产品,比如一些旧有的Win32程序、第三方的Win32程序等等,还要实现自动登录这些外部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 etterm etterm.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一会儿以防止键入指令速度过快。

    运行效果如下: 主要代码如下, 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 etterm etterm.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 ");

                Thread.Sleep(1000);

          //输密码

                SendString("123456 ");

                Thread.Sleep(1000);

          //进入目录

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

                Thread.Sleep(1000);

          //运行字符终端

                SendString("python FrontEnd.py ");

            }

     

        //模拟按键

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

      }

    }

     

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

  • 相关阅读:
    『实践』Yalmip建模+Cplex类求解(文末附程序、文章和算例)
    『实践』Matlab实现Flyod求最短距离及存储最优路径
    『转载』Matlab中fmincon函数获取乘子
    『实践』Yalmip获取对偶函数乘子
    『转载』hadoop 1.X到2.X的变化
    『转载』hadoop2.x常用端口、定义方法及默认端口
    『实践』VirtualBox 5.1.18+Centos 6.8+hadoop 2.7.3搭建hadoop完全分布式集群及基于HDFS的网盘实现
    C#设计模式学习笔记:(6)适配器模式
    ASP.NET 开源导入导出库Magicodes.IE 导出Pdf教程
    Rx基础
  • 原文地址:https://www.cnblogs.com/dotfun/p/4282702.html
Copyright © 2011-2022 走看看