blue collar

a blog by dan elliott.

Extending .Net Compact Framework Controls

Extending .Net Compact Framework Controls

  • Comments 18

Hello, and welcome to blue collar.

My name is Dan Elliott and I'm a software development engineer in the test organization for the .Net Compact Framework. In my nearly 14 years at Microsoft I've worked in a variety of roles in user education, testing, and development. For the past six years I've tested the execution engine for the Compact Framework (CF). I'm currently starting a new challenge: testing the GUI components of the CF base class libraries.

So why blue collar? I want this blog to address solutions for developers who are in the trenches, attempting to deliver software to end-users on time and under budget. I've worked on end-user applications both inside and outside of Microsoft. In my experience, the single most time consuming task has been determining why the system and framework software that I was relying on didn't work the way I expected. The .NET Framework has provided a great foundation on which developers can build their applications. The .NET Compact Framework has done a good job of providing a useful subset of that functionality for mobile developers. However, to be perfectly honest, no software platform is perfectly designed or engineered, particularly those as ambitious as .Net and .Net CF. I hope to use my experience with software in general and the Compact Framework in particular to provide solutions for those of you doing the real work.

So I'll jump right in. One of the comments I've heard frequently since I began working on the CF GUI base class libraries (BCLs) is "The .Net Framework has an X property (or method or event) on control Y. I really need it, but the CF version doesn't have it. Why?"

When working on devices, memory is limited. The CF provides roughly 28% of the functionality of the .NET Framework in 8% of the space. The only way to achieve that was to leave out everything that wasn't absolutely essential. That's all well and good, but you still need the functionality. Do you have to write your own control from scratch? Thankfully, the answer is usually no.

To take a specific instance, the Compact Framework version of the ListView control (System.Windows.Forms.ListView) doesn't fire the Resize event when the control's size changes. If you want to adjust the width of columns in the control when the control is resized, it would be nice to be notified. It only takes a few lines of code to remedy the problem.

Here's what we need to do:

  1. Add New PocketPC 2003 Control Library project called ListViewThatFiresResizeEvent.
  2. Rename UserControl1.cs to ListViewThatFiresResizeEvent.cs. When prompted, choose to rename all references to the code element UserControl1 .
  3. Change base class of ListViewThatFiresResizeEvent from Control to ListView :
    
      public partial class ListViewThatFiresResizeEvent : ListView
    
  4. In ListViewThatFiresResizeEvent.InitializeComponent method remove the following line because System.Windows.Forms.ListView doesn't contain an AutoScaleMode property:
     
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
    
    
  5. Remove the OnResize event handler in UserControl1.Designer.cs if one was generated.
  6. In the the ListViewThatFiresResizeEvent namespace, declare an event handler delegate:
    
      public delegate void ResizeEventHandler(object sender, EventArgs e);
    
  7. In the ListViewThatFiresResizeEvent class, declare the Resize event:
    
      public new event ResizeEventHandler Resize;
    

    The new keyword is required because System.Control.Resize is not virtual.
  8. Override the ListView.OnResize event handler in the ListViewThatFiresResizeEvent class:
    
      protected override void OnResize(EventArgs e) 
      { 
        if (null != this.Resize) 
        { 
          this.Resize(this, new EventArgs()); 
        }
      }
    
  9. Implement new handlers for the properties that change the size of the ListView control ( Size , Width , Height ). In each of the property setters, fire the Resize event. (Again the new keyword is required because the properties are not declared virtual in the base class.)
           
      public new Size Size 
      {
        get { return base.Size; }
        set 
        {
          base.Size = value; 
          this.OnResize(new EventArgs()); 
        }
      }
    
      public new int Width 
      {
        get { return base.Width; }
        set 
        {
          base.Width = value; 
          this.OnResize(new EventArgs()); 
        }
      }
    
      public new int Height 
      {
        get { return base.Height; }
        set 
        {
          base.Height = value; 
          this.OnResize(new EventArgs()); 
        }
      }
        
    
  10. Finally, drag a ListViewThatFiresResizeEvent object from the toolbox to your form and add an event handler function in your application.

That's it. A similar process can be used to extend any CF control. Let me know you find this useful.

~Dan

[edited to fix formatting]

  • I have worked with Dan for several years now (we both joined the .NET Compact Framework team at almost...
  • Hi Dan,

    is there a pratical need for Resize-event on a ListView? The event will not be fired when the windows form comes from landscape to portrait modus and vise versa.

    Regards!

    Torsten

    The person that originally asked this question wanted to resize columns within the ListView when the Resize event was fired.  I don't remember what was triggering the Resize event in that particular case.

    ~Dan

  • Do you have any idea what happens, when a windows forms comes from portrait to landscape modus and vise versa? The properties like height, width, bounds etc. pp. there are not involved. The question is: Who resize the controls? And how?

    Regards!

    Torsten

    [Edited]

    When a device switches orientation, a Resize event is fired for the form.  In addition, Resize events are fired for controls that support the Resize event if the control moves due to anchoring.  So, in most cases, you will need to hook the Form.Resize event and move your controls manually.

    Here are a couple of articles that explain further:

    How to: Handle Screen Rotation 

    Programming Windows Mobile 5.0 Applications Using the .NET Compact Framework

    ~Dan

  • Hi Dan,

    there is another problem.

    If you inherit a user control class from ListView the solution can be compiled but Visual Studio means that there are no property Items and Columns and show errors.

    The type 'Microsoft.CompactFramework.Design.UnsafeUserControl' has no property named 'Items'... bla bla

    Regards!

    Torsten
  • Hi Dan,

    I got it! Damn. It occurs by using the SnApi. Comment private SystemState..., recompile and there is no error in an another project, which the user control is used. Uncomment it, recompile and Visual Studio comes with bla bla. Can you explain the behavior? (I also send this to my friend Frank Prengel from the Dev Group from Microsoft Germany.)

    //using Microsoft.WindowsCE.Forms;
    using Microsoft.WindowsMobile;
    using Microsoft.WindowsMobile.Status;
    using System;
    using System.Drawing;
    using System.Windows.Forms;

    namespace ControlLibrary
    {
       public delegate void ResizeEventHandler(object sender, EventArgs e);

       [System.ComponentModel.DesignerCategory("code")]
       public partial class ListViewResize : ListView
       {
           private SystemState displayRotation = new SystemState(SystemProperty.DisplayRotation);
           public new event ResizeEventHandler Resize;

           public new ListViewItemCollection Items
           {
               get { return base.Items; }
           }

           public new Size Size
           {
               get { return base.Size; }
               set
               {
                   base.Size = value;
                   this.OnResize(new EventArgs());
               }
           }

           public ListViewResize() : base()
           {
               InitializeComponent();
               //displayRotation.Changed += new ChangeEventHandler(displayRotation_Changed);
           }

           //private void displayRotation_Changed(object sender, ChangeEventArgs args)
           //{
           //    for (int i = 0; i < Columns.Count; i++)
           //    {
           //        Columns[i].Width = -2;
           //    }
           //}

           protected override void OnResize(EventArgs e)
           {
               if (null != Resize)
               {
                   Resize(this, new EventArgs());
               }
           }
       }
    }

    I don't know off the top of my head, but I'll look into it. I'm not sure I understand what you mean by SnApi.

    ~Dan

  • Hi Dan,

    just download http://www.dotnet-leipzig.de/download/1/DeviceApplicationLVScrollbars2.zip, then compile all in the debug-mode. Then open MainForm.cs in the design-mode - every looks fine.

    Switch to the release-mode, recompile all, open MainForms.cs in the design-mode and Visual Studio comes with bla bla. The error comes from/with the State and Notification Broker (SnApi) (SystemState(SystemProperty.DisplayRotation and so on...)

    Regards! Your feedback is very welcome!

    Torsten

    Torsten,

    The reason that debug builds worked and release didn't is because you have the SystemState object is #if'd out in ListViewResize.cs for debug builds.

    
    #if !DEBUG
            private SystemState displayRotation = new SystemState(SystemProperty.DisplayRotation);
    #endif
    

    After removing the #if !DEBUG, there are errors displaying the form in both debug and release builds. I'm looking into why the SystemState object would cause display problems in the designer.

    ~Dan

  • I ran into that exact same problem (UnsafeUserControl) on a completely unrelated project I made on my own.  I have yet to find a resolution for it besides #if !debug.  Maybe the description of my problem can help you track down the cause.

    I originally posted about it here:
    http://www.opennetcf.org/forums/topic.asp?TOPIC_ID=8116

    I'll be checking both places for any more information.
  • >>> After removing the #if !DEBUG, there are errors displaying the form in both debug and release builds.<<<

    Hi Dan!

    Yes... so I can use the designer from Visual Studio in the debug mode. In the release mode the SnApi will be included. That's just only a workaround.

    >>> I'm looking into why the SystemState object would cause display problems in the designer. <<<

    I'm curious about it. :)

    Regards!

    Torsten
  • Your example although fine, does not address the need that many people have which requires subclassing an existing control to extend it's functionality and generally capture and handle windows messages.  Life would have been so much easier if the .NET CF team simply exposed the WnProc() function as public virtual function such that we could capture various windows messages and then modify or pass on the restults to the base class as desired.  A perfect example is to extend the messages such that the custom  drawing messages were easily available so that we could create custom drawn listview controls, header columns and more without a great deal of work.  Same with the ability to override the Paint method for custom button drawing, etc.

    This sounds like a good topic for a future post. I'll look into it.

    ~Dan

  • Hi Dan,

    I agree with everything you said! It's just a sample for an custom control and I run into the unsafe user control problem. Window messages, WnProc() function? I know about it. :)

    2 Samples:
    Just write your own code like Alex, see http://blog.opennetcf.org/ayakhnin/CategoryView.aspx?category=ListBox%20extender,

    or use a wrapper like the .NET CF Team http://blogs.msdn.com/netcfteam/archive/2005/07/24/442616.aspx

    ...
    const int SB_LINEUP = 0;
    WindowsAPIs.SendMessage(lvMain.Handle, WindowsAPIs.WM_VSCROLL, SB_LINEUP, 0);
    ...

    and the ListView scrolls one line (without selection) up. I'm convinced that people build custom controls without any window messages, WnProc() and so on. And there is still nowhere an explanation why the unsafe control problem comes or how we can resolve this problem.

    Regards!

    Torsten
  • One of the comments I've heard frequently since I began working on the CF GUI base class libraries (BCLs)

  • i heavily invested in custom user control but all of the designer consept become corrupted because i'm using generics inside my code. i don't want to take the generics out do you know whether microsoft intend to address this issue in any farther SP?
  • Dan,

    I am very new to .NET CF.  My problem is I need to extend the ContextMenu control so that when a submenu is selected, it is displayed on top of the parent menu - the user should only see the submenu.  The parent menu should be completely hidden. What do I need to do to achieve that?  Thanks.

    Tuan

    Tuan,

    If I understand you correctly, you want to take something that looks like this:

    Display Context Menu 2 > Item 1
      Item 2
    and overlap the flyout menu on top of the original context menu.

    To do this you would need to get the window handle of the flyout menu and adjust its position. Since the flyout menu is not exposed by the ContextMenu, you would have to hook the ContextMenu's window procedure and handle the messages like you would in native code. You can get an idea of how to do this in Tim Gerken's article Subclassing Controls in .NET CF 2.0.

    As an alternative you might consider simply changing context menus when an item on the first context menu is clicked? For instance, if Form1 had the following context menus:

    ContextMenu1 ContextMenu2
    Display Context Menu 2 Item 1
      Item 2
    The handler for Display Context Menu 2 could set Form1.ContextMenu to ContextMenu2 like this:
    
    private void DisplayContextMenu2_Click(object sender, EventArgs e)
    {
        this.ContextMenu = this.ContextMenu2;
        this.ContextMenu2.Show(this, Control.MousePosition);
    }
    
    

    HTH

    ~Dan

  • Dan,

    Thank you for such a prompt reply. I think I have enough information to go from here.  I really appreciate your time.  Thanks.

    Tuan

  • Dan,

    I need to listen to key events while the ContextMenu is visible - I want to implement menuitem accelerator key feature.  I tried to use the technique in the

    "Subclassing Controls in .NET CF 2.0." article but my program would not compile because ContextMenu is not a control.  Can you give me some directions.  Thanks.

    Tuan

Page 1 of 2 (18 items) 12