Export (0) Print
Expand All

Hosting a Native Windows Control within a Microsoft® .NET Compact Framework Forms Control

.NET Compact Framework 1.0
 

Author:
Peter Foot
Microsoft Embedded MVP
In The Hand Ltd

Date:
October 2004

Applies to:
Microsoft® Windows® CE
Microsoft® Windows® CE .NET

Download Sample.

Summary: This article will demonstrate a technique for hosting native windows controls, in this example the HTML Web Browser control, within a custom .NET Compact Framework control. The method described supports full two-way communication with the native control so that events can be raised following user interaction. (6 printed pages)

Contents

Introduction
About the ControlEx class
Window Handles
Designer Support
The WebBrowser Control
Communicating with the Native Control
Events
Building For Design Time
Sample Application
Conclusion
Additional Resources

Introduction

If you already have invested in creating a native window control and wish to use this in your .NET Compact Framework code, or you would like to take advantage of some of the intrinsic Windows CE controls which are not directly supported by the .NET Compact Framework, you will need a method of interoperating with native window controls. The OpenNETCF Smart Device Framework features a new custom implementation using the MessageWindow class. This uses Platform Invoke to create a visible window which is capable of hosting native controls. This article will look at how to create a custom control to host a native windows control – my example will be a managed control around the HTML control available in Windows CE.

About the ControlEx class

As with any custom control on the .NET Compact Framework development starts with a class which is derived from System.Windows.Forms.Control. However to handle much of the internal plumbing required to host a native control, I have created an OpenNETCF.Windows.Forms.ControlEx class.

The ControlEx class works by hosting a modified MessageWindow class. Because the MessageWindow is created as a 0 by 0 pixel invisible native window it makes a perfect sink for windows messages but can perform no useful function as part of the user interface. Therefore we use Platform Invoke to alter the properties of this window to make it visible and hence make it possible to host other controls within it.

ControlEx takes care of creating a ControlMessageWindow and creating the native control of your choice. At run time the ControlMessageWindow receives all notification messages from the native control and passes them to the OnNotifyMessage method in the ControlEx derived class, in this case WebBrowser. The ControlEx automatically responds to events such as Resizing, Focus changes etc and resizes the native control automatically.

When a new ControlMessageWindow is created a reference to the parent managed control is assigned. Using Platform Invoke the SetWindowParent API method is called to make the ControlMessageWindow a child of the ControlEx derived class. ControlEx itself creates the native control as a child of the ControlMessageWindow. In this way notification messages from the native control are received by the MessageWindow and passed back into the managed ControlEx derived class.

Figure 1 - How native and managed code interact

Standard Functionality

The ControlEx class has a built in implementation for a number of Properties which will be useful for your derived control including BorderStyle, Handle, Name and Tag.

Window Handles

The .NET Compact Framework doesn't expose the native window handles for controls. The ControlEx class implements the IWin32Window interface which is present in the full .NET framework but added by OpenNETCF to the .NET Compact Framework. This interface defines a Handle property – this is used to expose the window handle of the managed control itself. This is determined by setting Capture on the control and using the API function GetCapture to return the window handle:-

/// <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;
   }
}

Similarly ControlEx exposes a ChildHandle property to derived classes, this provides easy access to the window handle (HWND) of the native control we are hosting. This is used extensively since properties and Methods are invoked on the native control by sending messages to this handle.

Designer Support

The ControlEx code has been written to provide the most basic design-time support. This handles the control being resized on the form and simply fills it in with a rectangle in the controls BackColor. If the BorderStyle is set to either FixedSingle or Fixed3D then a Black box is drawn around the perimeter of the control (there is no support for a 3D look to the border). Individual controls which derive from ControlEx can provide additional design time behavior as is appropriate by overriding the OnPaint method.

The WebBrowser Control

By handling this generic code in the ControlEx class we are reducing the workload required to build an individual managed wrapper control itself. The only extra code required in the constructor for the WebBrowser control is to invoke the InitHtmlControl API function to prepare the control for use. We then set the CreateParams of the ControlEx with the class name "DISPLAYCLASS".

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";
}

The class name for the native control can be found in the header file for the control – you will need the SDK for your target platform which should contain htmlctrl.h. The header file for the native control will also be required as a reference to start hooking up the properties, methods and events for the specific control. In the WebBrowser example I have included all the required constants and structures within the WebBrowser code.

Communicating with the Native Control

With the constructor setup to create the native control, a test project can be created and the control added to it via code (we haven't built a designer assembly yet) and it will display the WebBrowser. There is however not yet any functionality to browse yet or set other properties for the control.

Communication between native controls is achieved via windows messages either sent to the control, or WM_NOTIFY messages received from it. In some cases other settings are altered by setting a specific window style on the control. In the managed world controls have Properties to get or set values, Methods to perform an action, and Events raised when something happens in the control. This is how they generally translate when we are wrapping a native control:-

Managed Native
Property Get Send a specific message to the control and the value is returned. For complex data a pointer to a buffer is sent with the message which is filled with the required data.
Property Set Send a specific message to the control with the value as a parameter. For complex data a native memory pointer is sent which points to a structure containing the data.

Or set a specific window style for the native control.

Method Send a specific message to the control, optionally with parameters sent via a pointer to a structure.
Event WM_NOTIFY message received from control. This contains a pointer to further information describing the event which can be marshaled to a managed structure.

In the case of the WebBrowser control probably the most fundamental message to send is that to navigate to a specific Url. A copy of the current Url is kept in the private field m_url. The string is marshaled to native memory with a null character appended to the end to mark the end of the string, and the pointer is sent to the control with the DTM.NAVIGATE message. The Windows API SendMessage method is used to send the message to the hosted control. Following this the native memory used is freed.

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

The values of the messages used are found in the header file corresponding to the control. To make these accessible in your code you can store them either as an enumeration (as I have in the example code), as private constants, or you could hard-code them into the applicable blocks of code (not recommended). See the DTM enumeration in the code for all the supported HTML Control messages and their values.

All the property and method implementations will follow the same basic pattern. Any data to be sent with the message must be placed in unmanaged memory and this must be freed afterwards. You will find a number of useful methods to allocate and free memory in the OpenNETCF.Runtime.InteropServices.MarshalEx class.

Events

Where the ControlEx implementation really comes into its own is in reacting to events from the native control. Native controls send their notifications back to a parent window. The base Control class does not expose a WndProc method to process incoming messages so you are not able to host native controls directly and receive their events. The ControlMessageWindow is an extra layer between the managed control and the native windows control and is able to receive and process incoming windows messages.

In order to capture events on the native control we need to provide an implementation for the OnNotifyMessage method. This is effectively our WndProc for the control. The SDK defines a number of Notification messages sent by the HTML control to its parent. In the source code to WebBrowser these have been defined in the NM enumeration. The Messages passed to the control will all have the Msg value equal to WM_NOTIFY. The LParam points to a control specific notification structure, this will begin with the NMHDR members identifying the sender, a custom id (not used) and the notification code. We set up a switch statement to check the code field against known notification messages and perform the appropriate actions.

The first task to carry out is to marshal the data attached to this notification into managed memory. This data is specific to the windows control – in the case of the WebBrowser it defines the URL and other information related to the event that has occurred. Once again the structure of this data is defined in the htmlctrl.h header file in the NM_HTMLVIEW structure. Following this we use the "code" field to determine what type of notification this is and what action to take.

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

This section of the code shows the most common event to handle – Navigating which occurs whenever the control starts navigating to a new page. When this event occurs one of the members of the notification data structure points to a string giving the URL of the requested page. This pointer is marshaled to a managed string and used when creating an instance of our custom class WebBrowserNavigatingEventArgs. Finally this is passed to the OnNavigating method which will do the job of raising the event.

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

This method first checks whether there are any subscribers to the event. If there are, the event is raised passing the event arguments created. A consumer of this event then can make use of the Url to determine what action to take. If you are handling an event in the native control which needs to pass back information as done here with the Url, you will need a similar approach in which a custom EventArgs class is passed through with the event.

Building for Design Time

The ability to add your control to a project and layout your user interface using the forms designer in Visual Studio is very important. However we have to provide some additional functionality to implement this. Firstly the desktop framework has no knowledge of the MessageWindow control or our platform specific P/Invokes such as SendMessage and therefore it is necessary to hide the implementation from our design time build. This is done by adding conditional compilation into the source code. With all our OpenNETCF classes we use the constant "DESIGN" when building design time versions of our controls. You will find a link to a great article on making design-time compatible controls by Alex Yakhnin at the end of this article. Therefore in the sections of code where platform specific code is referenced such as in the constructor we add in the #if statement:-

#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 

Effectively the designer is entirely oblivious to the internal workings of our control, although it will still list the public properties and events we have defined. For the WebBrowser I have not implemented any further design time support, it will inherit it's drawing from the ControlEx class. There is little need to provide a fully working HTML rendering control in the forms designer, however getting the positioning of the control correct for runtime is very important.

Sample Application

The Pocket Browser (C#) and Pocket Browser VB projects are identical Pocket PC projects which utilize the WebBrowser control. These show how the control is added to your form in design view just like any other control. The other controls on the form illustrate calling properties and methods on the control to navigate to a specific Url or use the GoBack method. The application handles a number of the events raised by the WebBrowser control.

When the WebBrowser indicates it is starting navigation the WaitCursor is displayed. This cursor is cleared when the DocumentCompleted event is raised. On the DocumentTitleChanged event the Form.Text property is updated to reflect the title of the current HTML document.

[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

The WebBrowser control doesn't have to be used simply for browsing internet sites. Because you can supply HTML source to the control and capture events when links are tapped, you could use it to provide a dynamically generated HTML user interface for your application.

Conclusion

The WebBrowser implements a number of Properties, Methods and Events, all of which follow the basic model described above. The source code for the control is included along with sample web browser projects in C# and VB. It is highly recommended you take a look through the code of both ControlEx and WebBrowser to understand the process and how you can apply it to other native controls. The ControlEx class makes it possible to implement a number of controls previously unavailable in the .NET Compact Framework and allows you to fully support events raised by the native control. Using these same techniques the InkX and MonthCalendar controls have been created for the Smart Device Framework.

Additional Resources

For more details on creating design time controls for the .NET Compact Framework see this article:-http://www.intelliprog.com/articles/index.html

For more details of the Smart Device Framework see http://www.opennetcf.org/sdf/

Show:
© 2014 Microsoft