26.5 Using the MessageWindow Class

 <  Day Day Up  >  

26.5 Using the MessageWindow Class

You want to communicate with unmanaged applications by using Window Messages.


Technique

Because COM interop was not included in the .NET Compact Framework, a class was specifically designed for Smart Device Applications for communicating with unmanaged applications rather than relying on PInvoke . The result was the creation of two .NET Compact Framework classes called MessageWindow and Message . These classes allow you to send and receive window messages. A window message consists of a 32-bit value specifying the actual message itself and two values named WPARAM and LPARAM , which contain extra information about the window message.

The typical use of a MessageWindow is to communicate with an unmanaged application by sending and receiving Window messages. If you're new to Win32 API programming, the concepts presented here might seem a little foreign because the .NET Framework kindly shields the developer from working with window handles and messages. This recipe builds an application that displays all the window titles currently active on the system with the option to programmatically close a window by selecting its name in a ListBox control and clicking on a Close button. You build it by creating an unmanaged C++ application that uses the EnumWindows Win32 API method to enumerate all the windows on the system. For each window it finds, it posts a window message back to the managed C# application, which then places the window title into the ListBox control.

The first step to create this application is to create the unmanaged C++ application. Recall that you cannot create a Visual C++ Smart Device Application within Visual Studio .NET 2003 but must instead use the Embedded Visual Tools application available as a free download on Microsoft's Web site. If you don't have the application and don't want to download it, the final binary files are included in the code for this book, which is available at http://www.samspublishing.com. Also, because this book is written for C# developers, the C++ explanation is rather terse.

For the unmanaged portion, we create a Windows CE (WCE) Dynamic Link Library project with the option to export sample variables , methods , and classes turned on. After the project is created, open the main C++ source file and add an EnumerateDeviceWindows method, which accepts a HWND parameter but does not return anything. Additionally, wrap the entire method with an extern "C" block. This step prevents the compiler from mangling the method name, a concept known as name decoration , so that it's easier to use from your C# application. The method body, shown in Listing 26.1, simply starts the window enumeration by calling the Win32 API method EnumWindows , passing a pointer to the callback function and the window handle, which is passed from the C# application. Finally, the callback method named EnumWindowsProc first checks whether the current window being enumerated is a child and then posts a message to the callback window, which ultimately ends up in the C# application created next . The unmanaged C++ code should appear similar to the code in Listing 26.1.

Listing 26.1 Enumerating Windows with C++
 #include "stdafx.h" #include "MessageWindowNative.h" #ifdef MESSAGEWINDOWNATIVE_EXPORTS #define MESSAGEWINDOWNATIVE_API __declspec(dllexport) #else #define MESSAGEWINDOWNATIVE_API __declspec(dllimport) #endif // entry point method for DLL's BOOL APIENTRY DllMain( HANDLE hModule,                        DWORD  ul_reason_for_call,                        LPVOID lpReserved ) {     switch (ul_reason_for_call)     {         case DLL_PROCESS_ATTACH:         case DLL_THREAD_ATTACH:         case DLL_THREAD_DETACH:         case DLL_PROCESS_DETACH:             break;     }     return TRUE; } // Window message value to send #define WM_USER_ADDWINDOW WM_USER + 1 BOOL CALLBACK EnumWindowsProc( HWND hwnd, LPARAM lParam ) {     WPARAM wParam = 0;     if( GetParent( hwnd ) != NULL )         wParam = 1;     // post message to C# application     PostMessage( (HWND) lParam, WM_USER_ADDWINDOW, wParam, (LPARAM) hwnd );     return TRUE; } extern "C" {     MESSAGEWINDOWNATIVE_API void EnumerateDeviceWindows( HWND hCallbackWnd )     {         EnumWindows( EnumWindowsProc, (LPARAM) hCallbackWnd );     } } 

The next step is to create a C# Smart Device Application that communicates with the DLL just shown using PInvoke methods and window messages. After creating a Smart Device Windows Application, drag and drop a ListBox control and a Button from the toolbox onto the form and add an event handler for the button. After this step, you add a new class that will perform the communication with the unmanaged DLL.

Create a new class derived from MessageWindow . You have to add a reference to the Microsoft.WindowsCE.Forms assembly in addition to a using declaration to avoid having to namespace-qualify the message classes. To simplify things, this class reports back to the Windows Form by obtaining a reference to it and calling a public method. Therefore, create a field within the class whose data type is the main form type, and assign it by creating a custom constructor that the main form will pass itself as a parameter to. The code for the C# application should appear similar to Listing 26.2.

Listing 26.2 Communication Using Window Messages
 using System; using System.Drawing; using System.Collections; using System.Windows.Forms; using System.Data; using Microsoft.WindowsCE.Forms; using System.Runtime.InteropServices; using System.Text; namespace _6_MessageWindow {     public class DeviceWindowsForm : System.Windows.Forms.Form     {         private System.Windows.Forms.ListBox listBox1;         private System.Windows.Forms.MainMenu mainMenu1;         private System.Windows.Forms.Button btnClose;         private MessageHandler handler;         public DeviceWindowsForm()         {             InitializeComponent();             handler = new MessageHandler( this );         }         protected override void Dispose( bool disposing )         {             base.Dispose( disposing );         }         private void InitializeComponent()         {             this.mainMenu1 = new System.Windows.Forms.MainMenu();             this.listBox1 = new System.Windows.Forms.ListBox();             this.btnClose = new System.Windows.Forms.Button();             //             // listBox1             //             this.listBox1.Size = new System.Drawing.Size(240, 226);             //             // btnClose             //             this.btnClose.Location = new System.Drawing.Point(80, 232);             this.btnClose.Text = "Close";             this.btnClose.Click += new System.EventHandler(this.btnClose_Click);             //             // DeviceWindowsForm             //             this.Controls.Add(this.btnClose);             this.Controls.Add(this.listBox1);             this.Menu = this.mainMenu1;             this.Text = "Form1";         }         static void Main()         {             Application.Run(new DeviceWindowsForm());         }         private void btnClose_Click(object sender, System.EventArgs e)         {         }     }     public class MessageHandler : MessageWindow     {         // the main application form         DeviceWindowsForm mainForm;         public MessageHandler(DeviceWindowsForm mainForm)         {             // save the form to post back to             this.mainForm = mainForm;         }     } } 

Two lines of communication occur within the application. The first is between the MessageWindow class and the unmanaged DLL, and the second is between the MessageWindow class and the Window Form. We tackle the first technique first. Window messages are 32-bit integers and can be anything you want to define as long as you don't use a window message reserved for the system. In the unmanaged C++ code, the window message was named WM_USER_ADDWINDOW , which was defined using the WM_USER window message. You can use any value higher than WM_USER because it is guaranteed not to be reserved by the operating system. Therefore, in the MessageWindow class, create a static constant for that value noting that the C++ expression WM_USER + 1 evaluates to the number 0x0401 :

 
 public const int WM_USER_ADDWINDOW = 0x0401; 

Next, you need to use the PInvoke methods described in Chapter 21 to import the proper DLL methods from the unmanaged project. Additionally, this application calls the GetWindowText API function to get the window title. The DllImport statements should appear as follow within the MessageWindow class:

 
 [DllImport(@"\Windows\MessageWindowNative.dll")] public extern static void EnumerateDeviceWindows(IntPtr hWnd); [DllImport("coredll.dll")] static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count); 

The line of communication between the MessageWindow and unmanaged DLL is almost complete. The last step is to create the method that listens for the window message posted by the unmanaged method. You override the base class method WndProc . A WndProc is the main method the operating system calls in your application when a window message needs to be delivered to you. This method uses the Message class as a parameter that packages up all the necessary components of a Windows message. The actual window message is accessed using the Msg property defined in the Message parameter. If this value is the same as the WM_USER_ADDWINDOW constant created earlier, then you know that the message originated from the unmanaged DLL and you can therefore unpack the rest of the message to send to the Windows Form for display. Listing 26.3 demonstrates the WndProc method. The WParam and LParam properties of the Message parameter correspond to values passed in the PostMessage method from the unmanaged DLL. Because these values can be anything you want because it is a user defined message, we arbitrarily used the WParam to denote whether the window being enumerated is a child and LParam to be the actual window handle value. This handle is used as a parameter to the GetWindowText API call to get the window's associated title. Additionally, to get the unmanaged DLL to start enumerating windows, create a method named EnumWindows , which will be called by the main Windows Form and will call the unmanaged DLL method using PInvoke .

Listing 26.3 Overriding WndProc to Handle Window Messages
 public void EnumWindows() {     // call the native dll function     EnumerateDeviceWindows( this.Hwnd ); } // Override the default WndProc behavior to monitor messages. protected override void WndProc(ref Message msg) {     switch(msg.Msg)     {         case WM_USER_ADDWINDOW:         {             // get the window title using GetWindowText             StringBuilder windowText = new StringBuilder(1024);             GetWindowText( msg.LParam.ToInt32(), windowText, 1024 );             if( windowText.ToString() == "" )                 break;             // if wParam is 1 then window is a child of someone             if( msg.WParam.ToInt32() == 1 )                 windowText.Insert( 0, "    " );             // add it to the listbox of the main form             mainForm.AddWindow( msg.LParam, windowText.ToString() );             break;         }         default:         {             break;         }     }     // Call the base class WndProc for default message handling.     base.WndProc(ref msg); } 

At this point, the MessageWindow and unmanaged DLL are able to communicate using window messages. The last step is to simply enable the last line of communication between the MessageWindow class and the main Windows form. In the code just shown, you can see a method being called named AddWindow . This method is defined in the form class and accepts an IntPtr and string parameter, denoting the window handle and window title of the window currently being enumerated. A Hashtable object stores the window handles with the window title being the key . Although this method isn't the best method to use because some window titles can theoretically be the same, it works for our purposes now without complicating things further. Additionally, you need a method to start the MessageWindow class, so within the constructor of the main form, initialize the Hashtable object and add a method call to the MessageWindow method named EnumWindows , which was defined earlier. The AddWindow function and the updated constructor for the form should be similar to the following code:

 
 public DeviceWindowsForm() {     InitializeComponent();     hWndTable = new Hashtable();     handler = new MessageHandler( this );     // fill the listbox     handler.EnumWindows(); } public void AddWindow( IntPtr hWnd, string WindowTitle ) {     // save window handle     hWndTable[WindowTitle] = hWnd;     // add to listbox     listBox1.Items.Add( WindowTitle ); } 

Last but not least is the button handler. At this point in the application, a user sees a large list of windows within the ListBox . The EnumWindows API method does not distinguish between visible and hidden windows, so even though no windows are visible, you still see a large amount of hidden windows within the ListBox . The button handler for the application allows the user to select a window title from the ListBox and force it to close by sending it an operating-system “defined message called WM_CLOSE . Instead of receiving messages with the MessageWindow and Message classes, you can send them also. To do so, create a new Message object by calling the static method Message.Create and passing a window handle, the message to send, and the associated WParam and LParam parameters of which both are for the WM_CLOSE message. The window handle to send it to, if you recall, was stored in the Hashtable using the window title as the key. Therefore, pass the selected ListBox item as the key to the Hashtable to retrieve its associated window handle. Finally, call the static method MessageWindow.Send , which sends the WM_CLOSE message to the target window:

 
 private void btnClose_Click(object sender, System.EventArgs e) {     Message msg;     // get selected hwnd     IntPtr hwnd = (IntPtr) hWndTable[listBox1.SelectedItem.ToString()];     // create the message to send     msg = Message.Create( hwnd, WM_CLOSE, (IntPtr) 0, (IntPtr) 0 );     // close the window     MessageWindow.SendMessage( ref msg );     // refresh listbox     listBox1.Items.Clear();     handler.EnumWindows(); } 

Comments

Window messages drive the user interfaces of Windows. Although Windows Form applications shield you from having to learn all the intricacies, messages are still sent to your application and handled within the .NET Framework just like the code in this recipe. Another item that a developer using the .NET Framework rarely manages is Window handles. Each window created in the operating system is assigned a unique 32-bit value. The operating system uses this value as a pointer into its internally maintained handle table, which contains information about each window. A developer uses a window handle to interact with other windows within the system whenever information needs to be extracted from it, as in the GetWindowText method shown in this recipe, or a message needs to be sent to it, as in the WM_CLOSE message. Both the window handle and window message are packaged up with corresponding WParam and LParam values containing additional information into a Message class used by the .NET Compact Framework.

There is one additional thing to note about the code for this recipe dealing with the GetWindowText method called using PInvoke . In a desktop version of Windows such as Windows XP, the GetWindowsText method is contained within user32.dll library. For Pocket PC applications, there is no user32.dll . Instead, a lot of the methods are contained in the coredll.dll library contained within the device's ROM. Again, although the differences are small when changing from a desktop to a mobile-device environment, they are still worthy of pointing out.

 <  Day Day Up  >  


Microsoft Visual C# .Net 2003
Microsoft Visual C *. NET 2003 development skills Daquan
ISBN: 7508427505
EAN: 2147483647
Year: 2003
Pages: 440

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net