Export (0) Print
Expand All

How to Create a Microsoft .NET Compact Framework-based Image Button

.NET Compact Framework 1.0
 

Alex Yakhnin
IntelliProg, Inc.

March 2003

Applies to:
    Microsoft® .NET Compact Framework
    Microsoft® Visual Studio® .NET 2003

Summary: Learn how to create a .NET Compact Framework-based image button. (10 printed pages)

Download ImageButton.msi.


Contents

Introduction
What's available?
Best Practices for Creating Graphics
Applying the rules
The usage…
Conclusion

Introduction

The .NET Compact Framework provides a rich set of controls for designing the user interface in your applications. But the first thing to realize as a .NET Framework developer is that not all of the features of the .NET Framework Windows Forms package have been implemented in version 1.0 of the .NET Compact Framework. Demand for memory, CPU, and other resources are at a premium on the Microsoft® Windows® CE platform. This means that you will see a variety of differences between the full .NET Framework and the .NET Compact Framework. For example, most of the standard controls, such as Button, ListBox or Label, don't allow you to override the OnPaint event and add custom drawing to them. The primary example for this whitepaper will be a simple ImageButton control. This will be used to show principal techniques for creating an owner-drawn custom control, and best practice guidelines on achieving optimal graphics performance when dealing with image, text or line drawing in .NET Compact Framework-based applications.

What's available?

Due to the resource constraints, there is no full GDI+ support in the .NET Compact Framework. As you may know, the services of GDI+ fall into the following three broad categories:

  • 2-D vector graphics
  • Imaging
  • Typography

Of the 2-D Vector Graphics in the .NET Compact Framework, only the core drawing primitives like Ellipse, Line, Image, Polygon, Rectangle, String, Fill Ellipse, Fill Polygon, Fill Rectangle, and Fill Region are supported by the Graphics object.

In the Imaging category, the Bitmap class doesn't support image manipulation (at the bit level) or saving the bitmap to file. Additionally, the double-buffering that's supported by the .NET Framework through ControlStyles is not available either.

The Typography category is concerned with the display of text in a variety of fonts, sizes, and styles. GDI in the .NET Compact Framework provides support for this task, except for the usage of any sub-pixel anti-aliasing.

But fear not! The available subset of the GDI functionality still permits us to create quite a graphically-robust application. Just take a look at the System.Windows.Forms.DataGrid control that comes with the .NET Compact Framework. It is a fully-managed, owner-drawn control that yields a really good graphics performance by rendering multiple data rows and cells. In order to achieve this kind of performance, we just need to play by the rules that have been set up for us by the platform.

Best Practices for Creating Graphics

  1. When creating an owner-drawn controls, derive from System.Windows.Forms.Control and override the OnPaint and OnPaintBackground events.
    using System;
    using System.Drawing;
    using System.Windows.Forms;
    
    public class MyButton : Control
    {
       
       protected override void OnPaint(PaintEventArgs e )
       {
          //Some drawing logic
          Graphics gr = e.Graphics;
          gr.DrawImage(image, 0, 0);
          Graphics gr = e.Graphics;
          gr.DrawImage(image, 0, 0);
          gr.DrawRectangle(pen, this.ClientRectangle);
       }
    
       protected override void OnPaintBackground(PaintEventArgs e )
       {
          //Do nothing
       }
    
    }
    
    
  2. Pre-cache Pen, Bitmap, and Brush objects when using them inside OnPaint event. The nature of the OnPaint event is that it can be called as many times as the operating system requires. Losing some time on instantiating and destroying these objects could affect your drawing performance.
  3. Use double-buffering from code to reduce the possible flickering when refreshing the graphics on the screen. In general, the double-buffering logic would look like this:
    protected override void OnPaint(PaintEventArgs e )
    {
    Graphics gxOff; //Offscreen graphics
    
       if (m_bmpOffscreen == null) //Bitmap for doublebuffering
       {
          m_bmpOffscreen = new Bitmap(ClientSize.Width, ClientSize.Height);
       }
             
       gxOff = Graphics.FromImage(m_bmpOffscreen);
    
       gxOff.Clear(this.BackColor);
       //Draw some bitmap
       gxOff.DrawImage(bmpParent, 0, 0, bmpRect, GraphicsUnit.Pixel);
       
       //Boundary rectangle
    Rectangle rc = this.ClientRectangle;
       rc.Width--;
       rc.Height--;
                
       //Draw boundary
       gxOff.DrawRectangle(new Pen(Color.Black), rc);
       //Draw from the memory bitmap
       e.Graphics.DrawImage(m_bmpOffscreen, 0, 0);
    
       base.OnPaint(e);
    }
    
    

In the code above, we create a Graphics object from the empty Bitmap with the size of our control by calling the static FromImage method on the Graphics class. We make all the drawing on the Graphics object in memory, and when we are done, just "blit" the whole prepared bitmap on the controls graphics.

The ability to draw bitmaps transparently though the usage of the ImageAttributes parameter still exists in the .NET Compact Framework, but it's a little bit more work than on the desktop.

ImageAttributes imageAttr = new ImageAttributes();
// Make a transparent key            
imageAttr.SetColorKey(Color.White, Color.White);
//Use the key when drawing         
gxOff.DrawImage(m_bmp, imgRect, 0, 0, img.Width, img.Height, 
                                    GraphicsUnit.Pixel, imageAttr); 
            

Keep in mind that the low and high color-key values in the SetColorKey method should have the same value, mainly because the .NET Compact Framework doesn't support setting transparent colors in any other way.

Applying the rules

As a real-life exercise in utilizing these best practices, we are going to create an owner-drawn ImageButton control. The requirements will be to create a "push button"-style control that will display an image on its surface, and that will provide visual confirmation to the user that the button has been pressed.

We start by creating a new Smart Device Application project in Visual Studio .NET and selecting Windows Application as its type. Let's add the new ImageButton class to the project:

using System;
using System.Drawing;
using System.Windows.Forms;

public class ImageButton : Control
{
   //Private members      
   private Image image;
   //flag to indicate the pressed state
   private bool bPushed; 
   private Bitmap m_bmpOffscreen;

public ImageButton()
   {
      bPushed = false;
      //default minimal size
      this.Size = new Size(21, 21); 
   }

protected override void OnPaint(PaintEventArgs e )
   {
      //Some drawing logic
   }

   protected override void OnPaintBackground(PaintEventArgs e )
   {
      //Do nothing
   }
}

In the code above, we derive an ImageButton class from Control, and override the OnPaint and OnPaintBackground events, as was described earlier. The ImageButton will require an image to be displayed, so we add the Image property:

public Image Image
{
   get
   {
      return image;   
   }
   set
   {
      image = value;
   }
}

And of course, the "meat" of all owner-drawn controls is the drawing code in the OnPaint event:

protected override void OnPaint(System.Windows.Forms.PaintEventArgs e )
{
   Graphics gxOff;      //Offscreen graphics
   Rectangle imgRect; //image rectangle
   Brush backBrush;   //brush for filling a backcolor

   if (m_bmpOffscreen == null) //Bitmap for doublebuffering
   {
      m_bmpOffscreen = new Bitmap(ClientSize.Width, ClientSize.Height);
   }
         
   gxOff = Graphics.FromImage(m_bmpOffscreen);

   gxOff.Clear(this.BackColor);
         
   if (!bPushed) 
      backBrush = new SolidBrush(Parent.BackColor);
   else //change the background when it's pressed
      backBrush = new SolidBrush(Color.LightGray);

   gxOff.FillRectangle(backBrush, this.ClientRectangle);

   if (image != null)
   {
      //Center the image relativelly to the control
      int imageLeft = (this.Width - image.Width) / 2;
      int imageTop = (this.Height - image.Height) / 2;

      if (!bPushed)
      {
         imgRect = new Rectangle(imageLeft, imageTop, image.Width,
 image.Height);
      }
      else //The button was pressed
      {
         //Shift the image by one pixel
         imgRect = new Rectangle(imageLeft + 1, imageTop + 1, image.Width, 
                                                          image.Height);
      }
      //Set transparent key
      ImageAttributes imageAttr = new ImageAttributes();
      imageAttr.SetColorKey(BackgroundImageColor(image),
                                            BackgroundImageColor(image));
      //Draw image
      gxOff.DrawImage(image, imgRect, 0, 0, image.Width, image.Height,
                                          GraphicsUnit.Pixel, imageAttr);    

}

   if (bPushed) //The button was pressed
   {
      //Prepare rectangle
      Rectangle rc = this.ClientRectangle;
      rc.Width--;
      rc.Height--;
      //Draw rectangle
      gxOff.DrawRectangle(new Pen(Color.Black), rc);
   }

   //Draw from the memory bitmap
   e.Graphics.DrawImage(m_bmpOffscreen, 0, 0);

   base.OnPaint(e);
}

When creating this code, I followed the guidelines for successful drawing in the .NET Compact Framework that I outlined in the previous section. I pre-cached drawing objects, where it was possible, and used the double-buffering to reduce flicker. To make an image transparent, we identify its background color by using this helper function:

private Color BackgroundImageColor(Image image)
{
   Bitmap bmp = new Bitmap(image);
   return bmp.GetPixel(0, 0);
}

The task of this function is to retrieve the color of the pixel in the top left corner of the bitmap. When drawing the button, we check the bPushed flag that we need when the user pressed the button down. We can easily achieve that by overriding OnMouseDown and OnMouseUp events:

protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e )
{
      bPushed = true;
      this.Invalidate();
}

protected override void OnMouseUp(System.Windows.Forms.MouseEventArgs e)
{
   bPushed = false;
   this.Invalidate();
}

The usage…

The usage of the newly-created ImageButton should be quite simple. Just add these few lines of code in the form's constructor:

public Form1()
{
   InitializeComponent();

   imageButton1 = new ImageButton();
   imageButton1.Image = new 
Bitmap(Assembly.GetExecutingAssembly().GetManifestResourceStream("ImageButtonProject.doorin2.gif"));
   imageButton1.Location = new Point(30, 50);
   imageButton1.Size = new Size(44, 34);
   //Hook up into click event
   imageButton1.Click+=new EventHandler(imageButton1_Click);
   this.Controls.Add(imageButton1);
}

I've chosen to add the image file, doorin2.gif, as an embedded resource into project that is easy to retrieve by using the GetManifestResourceStream method of the current assembly.

Figure 1. Three ImageButton controls on a form

Figure 2. First ImageButton depressed

Conclusion

The .NET Compact Framework does not provide many of the graphics and drawing functions that are available in the full .NET Framework. This is due to platform resource constraints. But, knowing some of the guidelines for custom drawing, it is possible to create rich and responsive graphics in your applications.

Show:
© 2014 Microsoft