在 Microsoft? .NET Compact Framework 窗体控件中宿主本机 Windows 控件

翻译|其它|编辑:郝浩|2007-02-27 09:34:33.000|阅读 1996 次

概述:

# 界面/图表报表/文档/IDE等千款热门软控件火热销售中 >>

 

适用于:
Microsoft® Windows® CE
Microsoft® Windows® CE .NET

下载示例。

摘要:
本文将演示在自定义 .NET Compact Framework 控件中宿主本机 Windows 控件(在本例中为 HTML Web 浏览器控件)的技术。本文所描述的方法支持与本机控件进行完全双向通信,这样,就可以按照用户交互来引发事件。
 

本页内容
 

  • 简介
    如果您已经在创建本机窗口控件上投入了精力并希望在 .NET Compact Framework 代码中使用该控件,或者您想利用一些 .NET Compact Framework 不直接支持的 Windows CE 内部控件,那么您需要一种与本机窗口控件互操作的方法。OpenNETCF Smart Device Framework 具有使用 MessageWindow 类进行新的自定义实现的功能。该实现使用平台调用来创建能够宿主本机控件的可见窗体。本文将介绍如何创建自定义控件以宿主本机 Windows 控件 — 我的示例是有关 Windows CE 中可用的 HTML 控件的一个托管控件。


     
  • 关于 ControlEx 类
    与 .NET Compact Framework 上的其他自定义控件一样,开发工作从派生于 System.Windows.Forms.Control 的类开始。然而,处理大量的内部管线 (plumbing) 需要宿主本机控件,我创建了一个 OpenNETCF.Windows.Forms.ControlEx 类。

    ControlEx 类通过宿主一个已修改的 MessageWindow 类来工作。因为 MessageWindow 被创建为 0 乘 0 像素的不可见本机窗口,所以它是极好的 Windows 接收器,但是作为用户界面的一部分,它无法执行什么有用的功能。所以我们使用平台调用来改变该窗口的属性,使它可见,并因此允许在其中宿主其他控件。

    ControlEx 负责创建 ControlMessageWindow 以及创建所选择的本机控件。在运行时,ControlMessageWindow 接收来自本机控件的所有通知消息,并将它们传递给 ControlEx 派生类(在本例中为 WebBrowser)中的 OnNotifyMessage 方法。ControlEx 自动响应事件,如大小调整、焦点改变等,并自动调整本机控件的大小。

    在创建新的 ControlMessageWindow 时,将分配一项对父托管控件的引用。通过使用平台调用,调用 SetWindowParent API 方法以使 ControlMessageWindow 成为 ControlEx 派生类的子类。ControlEx 自身创建本机控件作为 ControlMessageWindow 的子类。按照这种方式,来自本机控件的通知消息由 MessageWindow 接收,并且传递回托管 ControlEx 派生类中。



    图 1 - 本机代码和托管代码如何交互


     
  • 标准功能

    ControlEx 类具有用于派生控件的许多属性(包括 BorderStyle、Handle、Name 和 Tag)的内置实现。


     
  • 窗口句柄
    .NET Compact Framework 没有公开控件的本机窗口句柄。ControlEx 类实现了 IWin32Window 接口,该接口存在于完整的 .NET Framework 中,但通过 OpenNETCF 添加到 .NET Compact Framework 中。此接口定义了一个 Handle 属性 — 用于公开托管控件自身的窗口句柄。这通过设置控件上的 Capture 和使用 API 函数 GetCapture 返回窗口句柄来确定:

    /// <summary>
    /// Native Window Handle
    /// </summary>
    public IntPtr Handle
    {
        get
         {
             if(m_handle==IntPtr.Zero)
              {
                 this.Capture = true;
                 m_handle = OpenNETCF.Win32.Win32Window.GetCapture();
                 this.Capture = false;
              }
             return m_handle;
         }
    }

    同样,ControlEx 对派生类公开了 ChildHandle 属性,这为宿主的本机控件的窗口句柄 (HWND) 提供了简单的访问方法。因为可以通过向此句柄发送消息来调用本机控件的属性和方法,所以这种方法使用得非常广泛。


     
  • 设计器支持
    ControlEx 代码提供了最基本的设计时支持。它处理控件在窗体上的大小调整,并简单地使用控件 BackColor 中的矩形来填充它。如果 BorderStyle 设置为 FixedSingle 或 Fixed3D,那么将在控件的周围绘制 Black 框(不支持边框的三维效果)。从 ControlEx 派生的单个控件可以通过重写 OnPaint 方法来提供适当的附加设计时行为。


     
  • WebBrowser 控件
    通过处理 ControlEx 类中的这种常规代码,可以减少构建单个托管包装控件自身所需的工作量。WebBrowser 控件构造函数中唯一所需的额外代码调用 InitHtmlControl API 函数来为控件的使用做准备。然后,使用类名“DISPLAYCLASS”来设置 ControlEx 的 CreateParams。

    public WebBrowser() : base(true)
    {
         //new stack (for history urls)
         m_history = new Stack();

        //load htmlview module
        IntPtr module = Core.LoadLibrary("htmlview.dll");
        //init htmlcontrol
        int result = InitHTMLControl(Instance);
        //set the html specific class name of the native control
        this.CreateParams.ClassName = "DISPLAYCLASS";
    }

    可以在控件的头文件中找到本机控件的类名 — 需要目标平台的 SDK(应该包含 htmlctrl.h 文件)。还需要本机控件的头文件来作为对特定控件的开始挂钩属性、方法和事件的引用。在 WebBrowser 示例中,我将所有需要的常量和结构包括在 WebBrowser 代码中。


     
  • 与本机控件通信
    在设置了用于创建本机控件的构造函数后,可以创建一个测试项目并通过代码将控件添加到其中(我们尚未构建设计器程序集),它将显示 WebBrowser。然而,还没有任何用于浏览的功能,也没有为控件设置其他属性。

    通过发送到控件的窗口消息或从其接收的 WM_NOTIFY 消息来实现本机控件之间的通信。在某些情况下,通过设置控件上的特定窗口样式来更改其他设置。在托管世界中,控件具有用于获取或设置值的属性、用于执行操作的方法和控件中发生事情时引发的事件。这是在包装一个本机控件时它们通常的转换方式:
     
    托管 本机
    属性获取 向控件发送特定消息并返回值。对于复杂数据,指向缓冲区的指针将与用所需数据填充的消息一起发送。
    属性设置 使用值作为参数,向控件发送特定消息。对于复杂数据,将发送指向包含数据的结构的本机内存指针。或者为本机控件设置特定窗口样式。
    方法 向控件发送特定消息(可以选择使用指向结构的指针发送的参数)。
    事件 从控件接收到的 WM_NOTIFY 消息。该消息包含指向描述事件更详细信息的指针,可将其封送处理为托管结构。
  • 对于 WebBrowser 控件,可能要发送的最基本的消息是定位到特定 URL 的消息。当前 URL 的副本保存在私有字段 m_url 中。该字符串被封送到本机内存,在字符串结尾附加空字符以标记字符串的结束,并将该指针与 DTM.NAVIGATE 消息一同发送到控件。Windows API SendMessage 方法用于向宿主控件发送消息。接下来,释放所用的本机内存。

    public void Navigate(string url)
    {
         //allocate temporary native buffer
         IntPtr stringptr = MarshalEx.StringToHGlobalUni(url + '\0');

         //send message to native control
         Win32Window.SendMessage(ChildHandle,(int)DTM.NAVIGATE, 0, (int)stringptr);
         //free native memory
         MarshalEx.FreeHGlobal(stringptr);
    }

    所使用消息的值可以在与控件对应的头文件中找到。要使这些值在代码中可访问,可以将其存储为枚举(与示例代码一样)、私有常量,也可以将其硬编码 (hard-code) 为可用的代码块(不推荐)。请参见所有支持的 HTML 控件消息的代码中的DTM 枚举及其值。

    所有属性和方法实现将遵循相同的基本模式。任何随消息一同发送的数据必须放置在非托管内存中,并且必须在事后释放。在 OpenNETCF.Runtime.InteropServices.MarshalEx 类中可以找到许多有用的方法来分配和释放内存。

     
  • 事件
    ControlEx 实现真正受欢迎的地方在于对来自本机控件事件的响应。本机控件将它们的通知发送回父窗口。基控件类没有为处理传入的消息公开 WndProc 方法,因此不能直接宿主本机控件和接收它们的事件。ControlMessageWindow 是托管控件和本机窗口控件之间的额外层,它能够接收和处理传入的窗口消息。

    为了捕获本机控件上的事件,需要为 OnNotifyMessage 方法提供实现。这实际上是控件的 WndProc。SDK 定义了许多由 HTML 控件发送到其父控件的通知消息。在 WebBrowser 的源代码中,这些消息定义在 NM 枚举中。传递到控件的消息都有等于 WM_NOTIFY 的 Msg 值。LParam 指向控件特定的通知结构,该结构以标识发送者的 NMHDR 成员、自定义 ID(没有使用)和通知代码开始。我们设置了 switch 语句来根据已知的通知消息检查代码字段并执行适当的操作。

    要执行的第一项任务是将附加于该通知的数据封送到托管内存。该数据对窗口控件来说是特定的 — 在 WebBrowser 的情况下,它定义了 URL 和与所发生的事件相关的其他信息。该数据的结构再一次定义在 NM_HTMLVIEW 结构的 htmlctrl.h 头文件中。接下来,我们使用“code”字段来确定这是什么类型的通知以及采取什么操作。

    //get html viewer specific data from the notification message
    NM_HTMLVIEW nmhtml = (NM_HTMLVIEW)Marshal.PtrToStructure(m.LParam,typeof(NM_HTMLVIEW));
    //marshal the Target string
    string target = MarshalEx.PtrToStringAuto(nmhtml.szTarget);
    //check the incoming message code and process as required
    switch(nmhtml.code)
    {
        //hotspot click
        case (int)NM.HOTSPOT:
        case (int)NM.BEFORENAVIGATE:
        OnNavigating(new WebBrowserNavigatingEventArgs(target));
        break;
    }

    这段代码显示了要处理的最常见事件 — Navigating — 只要控件开始定位到一个新的页面该事件就会发生。当该事件发生时,通知数据结构的一个成员将指向提供所请求页面的 URL 的字符串。该指针被封送为托管字符串并在创建自定义类 WebBrowserNavigatingEventArgs 的实例时使用。最后,该指针被传递到将引发 Navigating 事件的 OnNavigating 方法。

    protected virtual void OnNavigating(WebBrowserNavigatingEventArgs e)
    {
        if(Navigating!=null)
         {
            Navigating(this,e);
         }
    }

    此方法首先检查是否有该事件的订户。如果有,则引发传递所创建的事件参数的事件。然后,该事件的使用者可以使用 URL 来确定采取什么操作。如果在需要传送回信息的本机控件中处理事件(正如此处通过 URL 所做的),则需要一种类似的方法与事件一同传递自定义 EventArgs 类。


     
  • 为设计时而构建
    将控件添加到项目中和在 Visual Studio 中使用窗体设计器进行用户界面布局的能力非常重要。然而,要实现这种功能,您必须提供某种附加功能。首先,桌面框架并不知道 MessageWindow 控件或平台特定的 P/Invokes(例如 SendMessage),因此需要对设计时构建隐藏实现。可以通过向源代码中添加条件编译来完成此任务。对于所有的 OpenNETCF 类,我们在构建控件的设计时版本时都使用常量“DESIGN”。在本文的结尾处,可以找到由 Alex Yakhnin 撰写的关于构建设计时兼容控件的一篇很有价值的文章。因此,在代码部分中引用了平台特定的代码(例如在构造函数中添加了 #if 语句):

    #if !DESIGN
    //load htmlview module
    IntPtr module = Core.LoadLibrary("htmlview.dll");

    //init htmlcontrol

    int result = InitHTMLControl(Instance);

    //set the html specific classname of the native control
    this.CreateParams.ClassName = "DISPLAYCLASS";
    #endif

    实际上设计器完全不理会控件的内部工作,尽管它仍将列出已定义的公共属性和事件。对于 WebBrowser,我还没有实现任何更进一步的设计时支持,它将从 ControlEx 类继承其绘图。完全不需要提供包括所有功能在内的 HTML 来在窗体设计器中呈现控件,然而获取适合于运行时的控件位置是非常重要的。

     
  • 示例应用程序
    Pocket Browser (C#) 和 Pocket Browser VB 项目与利用 WebBrowser 控件的 Pocket PC 项目是等同的。这些项目显示了如何像任何其他控件那样在设计视图中的窗体上添加控件。窗体上的其他控件演示了调用控件上的属性和方法,以定位到特定的 URL 或使用 GoBack 方法。该应用程序处理由 WebBrowser 控件引发的许多事件。

    当 WebBrowser 指示它开始导航时,将显示 WaitCursor。此光标将在引发 DocumentCompleted 事件时被清除。在 DocumentTitleChanged 事件中,更新 Form.Text 属性以反映当前 HTML 文档的标题。

    [C#]
    //update the page title
    private void webBrowser1_DocumentTitleChanged(object sender, EventArgs e)
    {
        this.Text = webBrowser1.DocumentTitle;
    }

    [VB]
    Private Sub WebBrowser1_DocumentTitleChanged(ByVal sender As Object,
    ByVal e As System.EventArgs) Handles WebBrowser1.DocumentTitleChanged

    'update title to document title
    Me.Text = WebBrowser1.DocumentTitle

    End Sub

    在简单浏览 Internet 站点时,不必使用 WebBrowser 控件。因为可以向控件提供 HTML 源文件并在点击链接时捕获事件,所以可以用该控件来为应用程序提供动态生成的 HTML 用户界面。

     
  • 小结
    WebBrowser 实现了许多属性、方法和事件,所有这些都遵循上面描述的基本模型。该控件的源代码附加在用 C# 和 VB 编写的示例 web 浏览器项目中。强烈建议您看一看 ControlEx 和 WebBrowser 的代码,以便理解该过程以及如何将其应用到其他本机控件。ControlEx 类使得实现许多以前在 .NET Compact Framework 中不可用的控件成为可能,并允许您完全支持本机控件引发的事件。使用这些相同的技术为 Smart Device Framework 创建了 InkX 和 MonthCalendar 控件。
     

标签:

本站文章除注明转载外,均为本站原创或翻译。欢迎任何形式的转载,但请务必注明出处、不得修改原文相关链接,如果存在内容上的异议请邮件反馈至chenjj@evget.com


为你推荐

  • 推荐视频
  • 推荐活动
  • 推荐产品
  • 推荐文章
  • 慧都慧问
扫码咨询


添加微信 立即咨询

电话咨询

客服热线
023-68661681

TOP