Chinaunix首页 | 论坛 | 博客
  • 博客访问: 49539
  • 博文数量: 21
  • 博客积分: 10
  • 博客等级: 民兵
  • 技术积分: 190
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-24 15:25
文章分类

全部博文(21)

文章存档

2013年(21)

我的朋友

分类: C#/.net

2013-08-13 10:33:39

WPF – Walkthrough: Hosting win32 control in WPF

Topic: WPF – Walkthrough: Hosting win32 control in WPF

标题: WPF – 教程:在wpf托管一个win32的控件

QUOTE: “
Windows Presentation Foundation (WPF) provides a rich environment for creating applications. However, when you have a substantial investment in Win32 code, it may be more effective to reuse at least some of that code in your WPF application rather than rewrite it completely. WPF provides a straightforward mechanism for hosting a Win32 window, on a WPF page.”

引:

WPF有丰富的创建应用程序的环境,但是如果你在win32代码上有很多投资的话,能够做到复用这些 win32代码的话,可以节省很多的人力和物力。Wpf提供了在wpf中host win32 的方法。

So, this walkthrough is mainly about creating in-process a win32 control (probably with the P/Invoke method) and host it inside an HwndHost (we have to do some overriding in order to get message/child-parenting relationship right), this example also setup a window hook handler to enable interaction (receive messages from/send notification to the hosted control).

本教程是告诉你如何创建一个win32控件(可能使用了P/Invoke方法)。在Hwnd里我们host了一个win32控件(我们做了一些override来实现消息 /-父关系)。本方法中叶创建了一个windows hook处理方法 (接受信息/发送消息)

Quote: the basic Procedures

This section outlines the basic procedure for hosting a Win32 window on a WPF page. The remaining sections go through the details of each step.

The basic hosting procedure is:

1.       Implement a WPF page to host the window. One technique is to create a  element to reserve a section of the page for the hosted window.

2.       Implement a class to host the control that inherits from .

3.       In that class, override the  class member .

4.       Create the hosted window as a child of the window that contains the WPF page. Although conventional WPF programming does not need to explicitly make use of it, the hosting page is a window with a handle (HWND). You receive the page HWND through thehwndParent parameter of the  method. The hosted window should be created as a child of this HWND.

5.       Once you have created the host window, return the HWND of the hosted window. If you want to host one or more Win32 controls, you typically create a host window as a child of the HWND and make the controls children of that host window. Wrapping the controls in a host window provides a simple way for your WPF page to receive notifications from the controls, which deals with some particular Win32 issues with notifications across the HWND boundary.

6.       Handle selected messages sent to the host window, such as notifications from child controls. There are two ways to do this.

o    If you prefer to handle messages in your hosting class, override the  method of the  class.

o    If you prefer to have the WPF handle the messages, handle the  class  event in your code-behind. This event occurs for every message that is received by the hosted window. If you choose this option, you must still override, but you only need a minimal implementation.

7.       Override the  and  methods of . You must override these methods to satisfy the  contract, but you may only need to provide a minimal implementation.

8.       In your code-behind file, create an instance of the control hosting class and make it a child of the  element that is intended to host the window.

9.       Communicate with the hosted window by sending it Microsoft Windows messages and handling messages from its child windows, such as notifications sent by controls.

 

基本流程如下:

·         实现了一个wpf页来host。一个常用的方法是使用border预留一个给控件的空间。

·         实现一个继承于Hwnd的类。

·         在Hwnd的继承类中重载一些方法,buildeWindowCore

·         创建一个window对象作为wpf 页的子元素,虽然wpf并不要求这么做。

·         创建好了该host window后,把它的Hwnd传递返回。将你的控件作为该window的子元素提供了一个 简单的让wpf页接受控件消息的方法。这个方法有利于我们处理一些跨Hwnd边界的问题。

·         处理传递给Host window的事件,比如说来自子节点的事件,可以作如下的事情

o   如果希望在 hosting class中处理事件,重载wndproc方法

o   如果希望WPF处理事件,处理HwndMessageHook事件,这么做还是要处理wndproc方法,但是,实现的代码很小。

·         创建一个ControlHost的对象,使其成为Border的子元素。

·         建立hosting windowwin32 控件的事件,接受控件的notification

首先,我们来看看Page的布局 (该页面是一个window.

First, let’s see page layout: (the page is a window)

    x:Class="Walkthrough_HostingWin32ControlInWpf.Windowing.HostWindow"

    xmlns=""

    xmlns:x=""

    Title="HostWindow"

    Name="mainWindow"

    Height="300"

    Width="300"

    Loaded="On_UIReady">

   

       

    Width="200"

    Height="200"

    HorizontalAlignment="Right"

    VerticalAlignment="Top"

    BorderBrush="LightGray"

    BorderThickness="3"

    DockPanel.Dock="Right"/>

       

            

        Margin="0,10,0,0"

        FontSize="14"

        FontWeight="Bold">Control the Control

            Selected Text:

            Number of Items:

 

           

        Stroke="LightYellow"

        StrokeThickness="2"

        HorizontalAlignment="Center"

        Margin="0,20,0,0"/>

 

           

        Margin="10,10,10,10">Append an Item to the List

           

               

          Margin="10,10,10,10">Item Text

               

          Name="txtAppend"

          Width="200"

          Margin="10,10,10,10">

           

 

           

 

           

        Stroke="LightYellow"

        StrokeThickness="2"

        HorizontalAlignment="Center"

        Margin="0,20,0,0"/>

 

           

        Margin="10,10,10,10">Delete the Selected Item

 

           

       

    

 

我们需要一个host win32 控件的类

Then we need a class to host the Microsoft Win32 Control.

    public class ControlHost : HwndHost

    {

        IntPtr hwndControl;

        IntPtr hwndHost;

        int hostHeight, hostWidth;

 

        public ControlHost(double height, double width)

        {

            hostHeight = (int)height;

            hostWidth = (int) width;

        }

   }

 

我们需要重载BuildWindowCore创建一个Microsoft Win32 Window。并且,我们需要创建一个包含ListBoxhost window.

We need to override the BuildWindowCore to create the Microsoft Win32 Window. Also, we need to create a host window and ListBox control.

关于host window ,它被wpf page control 加载,这是因为HwndHost允许你处理送到给window的消息。如果你直接加载win32 控件,你可以接到送到给控件的internal 消息 处理环路,你可以显示该控件,也可以给他送消息。但是你不能接受到送给它父元素的消息。

A special note on the design where a host window is created and hosted by the page, this is because the HwndHost class HwndHost class allows you to process messages sent to the window that is hosting, if you directly host a win32 control, you receive the messages sent to the internal message loop of the control. You can display the message and send it message , but you don’t receive the notification that the control sends to its parent window.

下面的代码创建host window,并把ListBoxhwnd暴露出来的方法。

Below is the code that create host window and ListBox contro and expose the ListBox control so we can further use it.

 

        #region Properties

 

        public IntPtr hwndListBox

        {

            get { return hwndControl; }

        }

 

 

        #endregion

 

 

 

        #region HwndHost overrides

        protected override HandleRef BuildWindowCore(HandleRef hwndParent)

        {

            hwndControl =IntPtr.Zero;

            hwndHost = IntPtr.Zero;

 

            hwndHost = CreateWindowEx(0, "static", "",

                            WS_CHILD | WS_VISIBLE,

                            0, 0,

                            hostWidth, hostHeight,                  // the width and the height of the host window, why we need to pass that in, why we cannot get the height, width implicitly?

                            hwndParent.Handle,

                            (IntPtr)HOST_ID,

                            IntPtr.Zero,

                            0);

 

            hwndControl = CreateWindowEx(0, "listbox", "",

                                WS_CHILD | WS_VISIBLE | LBS_NOTIFY

                                  | WS_VSCROLL | WS_BORDER,

                                0, 0,

                                hostWidth, hostHeight,

                                hwndHost,                            // this means that hwndHost is the host of hwndControl

                                (IntPtr) LISTBOX_ID,

                                IntPtr.Zero,

                                0);

 

            return new HandleRef(this, hwndHost);

        }

 

We need to implement BuildWindowCore nad DestroyWindowCore, as part of the contract.handled set to false, and return IntPtr.Zero means messaqge not handled (the MessageHook that we will introduce later will take over message handling), and DestroyWindowCore is to ensure proper destroy the window.

        // Implement DestroyWindow and WndProc

        // In addition to BuildWindowCore, you must also override the WndProc and DestroyWindowCore methods of the HwndHost.

        // In this example, the messages for the control are handled by the MessageHook handler, thus the implementation of WndProc and DestroyWindowCore is minimal.

        // In the case of WndProc, set handled to false to indicate that the message was not handled and return 0. For DestroyWindowCore, simply destroy the window.

        //

        protected override IntPtr  WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)

        {

                handled = false;

            return IntPtr.Zero;

        }

 

        protected override void  DestroyWindowCore(HandleRef hwnd)

        {

            // See the P/Invoke methods

              DestroyWindow(hwnd.Handle);

 

        }

当我们使用P/Invoke以后,总的ControlHost的代码如下:

We have P/Invoke and other code, so the overall ControlHost code is as follow.

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    using System.Windows.Interop;

    using System.Runtime.InteropServices;

 

    ///

    /// TODO: Update summary.

    ///

    public class ControlHost : HwndHost

    {

        IntPtr hwndControl;

        IntPtr hwndHost;

        int hostHeight, hostWidth;

 

        public ControlHost(double height, double width)

        {

            hostHeight = (int)height;

            hostWidth = (int) width;

        }

 

 

        #region Properties

 

        public IntPtr hwndListBox

        {

            get { return hwndControl; }

        }

 

 

        #endregion

 

 

 

        #region HwndHost overrides

        protected override HandleRef BuildWindowCore(HandleRef hwndParent)

        {

            hwndControl =IntPtr.Zero;

            hwndHost = IntPtr.Zero;

 

            hwndHost = CreateWindowEx(0, "static", "",

                            WS_CHILD | WS_VISIBLE,

                            0, 0,

                            hostWidth, hostHeight,                  // the width and the height of the host window, why we need to pass that in, why we cannot get the height, width implicitly?

                            hwndParent.Handle,

                            (IntPtr)HOST_ID,

                            IntPtr.Zero,

                            0);

 

            hwndControl = CreateWindowEx(0, "listbox", "",

                                WS_CHILD | WS_VISIBLE | LBS_NOTIFY

                                  | WS_VSCROLL | WS_BORDER,

                                0, 0,

                                hostWidth, hostHeight,

                                hwndHost,                            // this means that hwndHost is the host of hwndControl

                                (IntPtr) LISTBOX_ID,

                                IntPtr.Zero,

                                0);

 

            return new HandleRef(this, hwndHost);

        }

 

 

        // Implement DestroyWindow and WndProc

        // In addition to BuildWindowCore, you must also override the WndProc and DestroyWindowCore methods of the HwndHost.

        // In this example, the messages for the control are handled by the MessageHook handler, thus the implementation of WndProc and DestroyWindowCore is minimal.

        // In the case of WndProc, set handled to false to indicate that the message was not handled and return 0. For DestroyWindowCore, simply destroy the window.

        //

        protected override IntPtr  WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)

        {

                handled = false;

            return IntPtr.Zero;

        }

 

        protected override void  DestroyWindowCore(HandleRef hwnd)

        {

            // See the P/Invoke methods

              DestroyWindow(hwnd.Handle);

 

        }

        #endregion

 

 

        #region Constants

 

        // you can find the Constants from the Winuser.h

        // 

        //  There is also a set of constants. These constants are largely taken from Winuser.h, and allow you to use conventional names when calling Win32 functions.

        //

        internal const int

              WS_CHILD = 0x40000000,

              WS_VISIBLE = 0x10000000,

              LBS_NOTIFY = 0x00000001,

              HOST_ID = 0x00000002,

              LISTBOX_ID = 0x00000001,

              WS_VSCROLL = 0x00200000,

              WS_BORDER = 0x00800000;

 

        #endregion

 

 

        #region P/Invoke Declaration

 

        //PInvoke declarations

        [DllImport("user32.dll", EntryPoint = "CreateWindowEx", CharSet = CharSet.Unicode)]

        internal static extern IntPtr CreateWindowEx(int dwExStyle,

                                                      string lpszClassName,

                                                      string lpszWindowName,

                                                      int style,

                                                      int x, int y,

                                                      int width, int height,

                                                      IntPtr hwndParent,

                                                      IntPtr hMenu,

                                                      IntPtr hInst,

                                                      [MarshalAs(UnmanagedType.AsAny)] object pvParam);

 

        [DllImport("user32.dll", EntryPoint = "DestroyWindow", CharSet = CharSet.Unicode)]

        internal static extern bool DestroyWindow(IntPtr hwnd);

 

        #endregion

 

    }

我们需要创建连接,我们将创建好一个MessageHook接受事件。

Now, we need to setup the connection, we will host the content control and setup the MessageHook to receive event from the control.

    public partial class HostWindow : Window

    {

        int selectedItem;

        IntPtr hwndListBox;

        ControlHost listControl;

        Application app;

        Window myWindow;

        int itemCount;

 

        #region Handler

        private void On_UIReady(object sender, EventArgs e)

        {

            app = System.Windows.Application.Current;

            myWindow = app.MainWindow;

            myWindow.SizeToContent = SizeToContent.WidthAndHeight;

            listControl = new ControlHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth);

            ControlHostElement.Child = listControl;                             // add the control as the Child of the WPF control (in this case, this is ControlHostElement is a child).

            listControl.MessageHook += new HwndSourceHook(ControlMsgFilter);    // Setup the MessageHook on the HwndHost, ? Is this to setup windows message handler?

            hwndListBox = listControl.hwndListBox;

            for (int i = 0; i < 15; i++) //populate listbox

            {

                string itemText = "Item" + i.ToString();

                SendMessage(hwndListBox, LB_ADDSTRING, IntPtr.Zero, itemText);

            }

            itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);

            numItems.Text = "" + itemCount.ToString();

        }

     }

 

ControlMsgFilter代码如下:

The ControlMsgFilter method is as follow.

        private IntPtr ControlMsgFilter(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)

        {

            int textLength;

 

            handled = false;

            if (msg == WM_COMMAND)

            {

                switch ((uint)wParam.ToInt32() >> 16 & 0xFFFF) // extract the HIWORD

                {

                    case LBN_SELCHANGE: //Get the item text and display it

                        selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);

                        textLength = SendMessage(listControl.hwndListBox, LB_GETTEXTLEN, IntPtr.Zero, IntPtr.Zero);

                        StringBuilder itemText = new StringBuilder();

                        SendMessage(hwndListBox, LB_GETTEXT, selectedItem, itemText);

                        selectedText.Text = itemText.ToString();

                        handled = true;

                        break;

 

                }

            }

 

            return IntPtr.Zero;

        }

我们将连接Wpf和hosted ListBox的消息。处理代码消息如下:

We also route/connect the Wpf message with the hosted ListBox, the handler code is as follow.

        // Events that bridges the WPF controls to the Hosted contents.

        private void AppendText(object sender, EventArgs args)

        {

            if (txtAppend.Text != string.Empty)

            {

                SendMessage(hwndListBox, LB_ADDSTRING, IntPtr.Zero, txtAppend.Text);

            }

            itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero); // Interaction to the Hosted W32 control to get the count of items

            numItems.Text = "" + itemCount.ToString();

 

        }

 

        private void DeleteText(object sender, EventArgs args)

        {

            selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);

            if (selectedItem != -1) //check for selected item

            {

                SendMessage(hwndListBox, LB_DELETESTRING, (IntPtr)selectedItem, IntPtr.Zero);

            }

            itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);

            numItems.Text = "" + itemCount.ToString();

        }


总的代码如下:

The overall code of the HostWindow is as follow.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Data;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Imaging;

using System.Windows.Shapes;

using Walkthrough_HostingWin32ControlInWpf.Host;

using System.Windows.Interop;

using System.Runtime.InteropServices;

 

namespace Walkthrough_HostingWin32ControlInWpf.Windowing

{

    ///

    /// Interaction logic for HostWindow.xaml

    ///

    public partial class HostWindow : Window

    {

        int selectedItem;

        IntPtr hwndListBox;

        ControlHost listControl;

        Application app;

        Window myWindow;

        int itemCount;

 

        #region Handler

        private void On_UIReady(object sender, EventArgs e)

        {

            app = System.Windows.Application.Current;

            myWindow = app.MainWindow;

            myWindow.SizeToContent = SizeToContent.WidthAndHeight;

            listControl = new ControlHost(ControlHostElement.ActualHeight, ControlHostElement.ActualWidth);

            ControlHostElement.Child = listControl;                             // add the control as the Child of the WPF control (in this case, this is ControlHostElement is a child).

            listControl.MessageHook += new HwndSourceHook(ControlMsgFilter);    // Setup the MessageHook on the HwndHost, ? Is this to setup windows message handler?

            hwndListBox = listControl.hwndListBox;

            for (int i = 0; i < 15; i++) //populate listbox

            {

                string itemText = "Item" + i.ToString();

                SendMessage(hwndListBox, LB_ADDSTRING, IntPtr.Zero, itemText);

            }

            itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);

            numItems.Text = "" + itemCount.ToString();

        }

 

        // Events that bridges the WPF controls to the Hosted contents.

        private void AppendText(object sender, EventArgs args)

        {

            if (txtAppend.Text != string.Empty)

            {

                SendMessage(hwndListBox, LB_ADDSTRING, IntPtr.Zero, txtAppend.Text);

            }

            itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero); // Interaction to the Hosted W32 control to get the count of items

            numItems.Text = "" + itemCount.ToString();

 

        }

 

        private void DeleteText(object sender, EventArgs args)

        {

            selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);

            if (selectedItem != -1) //check for selected item

            {

                SendMessage(hwndListBox, LB_DELETESTRING, (IntPtr)selectedItem, IntPtr.Zero);

            }

            itemCount = SendMessage(hwndListBox, LB_GETCOUNT, IntPtr.Zero, IntPtr.Zero);

            numItems.Text = "" + itemCount.ToString();

        }

       

        #endregion

 

        private IntPtr ControlMsgFilter(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)

        {

            int textLength;

 

            handled = false;

            if (msg == WM_COMMAND)

            {

                switch ((uint)wParam.ToInt32() >> 16 & 0xFFFF) // extract the HIWORD

                {

                    case LBN_SELCHANGE: //Get the item text and display it

                        selectedItem = SendMessage(listControl.hwndListBox, LB_GETCURSEL, IntPtr.Zero, IntPtr.Zero);

                        textLength = SendMessage(listControl.hwndListBox, LB_GETTEXTLEN, IntPtr.Zero, IntPtr.Zero);

                        StringBuilder itemText = new StringBuilder();

                        SendMessage(hwndListBox, LB_GETTEXT, selectedItem, itemText);

                        selectedText.Text = itemText.ToString();

                        handled = true;

                        break;

 

                }

            }

 

            return IntPtr.Zero;

        }

 

        #region Constants

 

        internal const int

          LBN_SELCHANGE = 0x00000001,

          WM_COMMAND = 0x00000111,

          LB_GETCURSEL = 0x00000188,

          LB_GETTEXTLEN = 0x0000018A,

          LB_ADDSTRING = 0x00000180,

          LB_GETTEXT = 0x00000189,

          LB_DELETESTRING = 0x00000182,

          LB_GETCOUNT = 0x0000018B;

       

        #endregion

 

        #region P/Invoke

        #endregion

 

        public HostWindow()

        {

            InitializeComponent();

        }

 

        #region P/Invoke

 

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]

        internal static extern int SendMessage(IntPtr hwnd,

                                               int msg,

                                               IntPtr wParam,

                                               IntPtr lParam);

 

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]

        internal static extern int SendMessage(IntPtr hwnd,

                                               int msg,

                                               int wParam,

                                               [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lParam);

 

        [DllImport("user32.dll", EntryPoint = "SendMessage", CharSet = CharSet.Unicode)]

        internal static extern IntPtr SendMessage(IntPtr hwnd,

                                                  int msg,

                                                  IntPtr wParam,

                                                  String lParam);

        #endregion

    }

}

 

引用:

References:


阅读(1182) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~