Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile

Custom ProgressBar for .NET Compact Framework

4.90/5 (44 votes)
19 Dec 2007CPOL15 min read 1   7.4K  
An article on creating a better looking ProgressBar for Windows Mobile 5
Screenshot - CustomProgressBar.png

Introduction

This article describes how to create a ProgressBar control which, by having an appearance that can be customized, is a better looking and (to some extent) a more functional progress bar than what is provided as standard on the Windows Mobile 5 platform. There are already some good articles on creating good looking progress bars (such as this article), but this one will focus on making a progress bar that can take on virtually any appearance and run on a mobile device. I will also provide some tips on how to set up a Visual Studio project to reduce development time when implementing for Windows Mobile 5.

Updated: this update contains a performance fix. The fix is described in the Performance chapter.

Using the Code

The source code ZIP file that can be downloaded for this article contains one Visual Studio solution in a folder called Bornander UI. This solution contains the code for the progress bar and some code that tests it; all of it has Windows as the target platform. This project can be used when the .NET Compact Framework is not installed to try out the progress bar in a desktop environment. The downloadable ZIP also includes a ZIP file called Bornander UI (Cross platform).zip which contains the solution I used when building this progress bar. This also has projects that build the source code for a Device environment.

The code for the progress bar control is all in the file ProgressBar.cs. This file holds a class called ProgressBar that extends from System.Windows.Panel. Since it extends from a standard Windows Forms control, it is possible to lay it out on a form or panel using the visual designer.

Requirements

When creating this control, I decided on a set of requirements that the control should implement:

  1. The appearance of the progress bar should be configurable.
  2. The progress bar should be able to mimic already existing progress bars such as the ones found on XP, Vista and Mac.
  3. The progress bar must function as a normal Windows Forms control, meaning that it should be possible to use the form designer to add it to panels or forms.
  4. The source code for the progress bar must be fully portable between a Windows environment and a Windows Mobile environment, meaning that no changes to the source code should be required to compile it for either platform.

So, there are four rather straightforward requirements to implement. So, how did it go?

Catering for Custom Appearance

Rendering Using Primitives

One way to do custom rendering of a control is to override the paint methods and call methods such as DrawRect or FillRect to draw the desired graphics, for example:

C#
...

protected override void OnPaint(PaintEventArgs e)
{
    e.Graphics.FillRectangle(new SolidBrush(backgroundColor), 0, 0,
                             this.Width, this.Height);
    e.Graphics.FillRectangle(new SolidBrush(foregroundColor), 0, 0,
                             scrollbarValue, this.Height);
}

...

This would first render a solid rectangle with a background in backgroundColor color and then render another, possibly shorter, rectangle over that in foregroundColor. This is basically how I think they've implemented the standard progress bar in the .NET Compact Framework.

However, it would be quite hard (or at least very time-consuming) to draw a progress bar such as the one in Windows Vista this way since that uses gradient transitions between colours. Luckily, System.Drawing.Drawing2D has brushes that do gradient fill (such as System.Drawing.Drawing2D.LinearGradientBrush). Excellent! We've got the tools we need to do the job. Or do we?

Everyone who has done a bit of .NET programming and then started doing .NET Compact Framework programming has realized just how compact the Compact Framework really is. Not only are some of the methods on some classes missing, but entire classes have also disappeared. For example, there is no System.Drawing.Drawing2D.LinearGradientBrush in the Compact Framework.

Rendering Using Images

After looking at the level of customization I was going for, I decided that even if there had been a System.Drawing.Drawing2D.LinearGradientBrush in the Compact Framework, it still wouldn't have met my requirements. I want my progress bar to allow for a greater level of customization. I would like to be able to have text or images in the background, kind of what you can sometimes see on progress bars found in games.

Custom ProgressBar different styles both on Windows and Windows Mobile

I decided to go with a solution where my progress bar, instead of rendering itself using primitives, uses a set of images that I can provide using the visual form designer in Visual Studio. This has three big benefits:

  1. It is a solution that is generic enough to allow for most appearances I can think of.
  2. It will allow me to "rip" other progress bars, as I can do a Print Screen and then cut and paste the parts I want. This is important to me because I've got no artistic skills whatsoever.
  3. The implementation time will only be a fraction of that of a primitive based rendering approach.

So images it is then.

Images

Types of Images

I started out trying to reproduce an XP kind of style and realized that I would need three images to do this:

  1. Background: this is, of course, the background image. This is drawn first.
  2. Foreground: this is stuff that "fills in" the progress bar as the value of it reaches its maximum. This is the second thing drawn.
  3. Overlay: this one is theoretically not necessary, but it is so much easier to use one than to skip it. This one is drawn last and this image should most often be more or less completely transparent. I use it to add nice looking borders.

Sizes of Images

How big should the images be? Do they all need to be the same size? What if I want my progress bar to be wider than the images used? Obviously, I want the progress bar to be able to take on more or less any dimension regardless of the images used, so how should the progress bar render, for example, the background if the background image isn't as wide as I want my progress bar to be? There are two ways to fix such a case:

  1. Draw the image more than once, one after the other until the width of the progress bar is filled, tiling the image
  2. Stretch the image whilst drawing it, making it as wide as the progress bar

I realized that both of these approaches have pros and cons. You can't stretch an image to create an XP-like look where the progress is indicated by green blocks. In this case, you have to tile the images. On the other hand, with a look such as Vista's it is more convenient to just stretch the image. In the end, I decided that I could get a cool effect from both of these approaches and that is why I left the choice up to the developer using the control instead. I did that by exposing a property called [imageType]DrawMethod. I wanted to have the option to tile or stretch the images differently for background, foreground and overlay:

C#
public class ProgressBar : Panel
{
    public enum DrawMethod
    {
        Tile, 
        Stretch
    }
    
    private DrawMethod foregroundDrawMethod = DrawMethod.Stretch;
    
    ...
    
    public DrawMethod ForegroundDrawMethod
    {
        get { return foregroundDrawMethod; }
        set { foregroundDrawMethod = value; }
    }
    
    ...
}

By exposing it as a public property, the visual form designer will allow me to change it using the property pages for the control. Perfect.

Image Segments

Ok, so we have the types of images we need and we have ways to make sure that any length of image will still cover the entire width of the progress bar. Great, that means that we can make small images and save memory and resources that way. The XP-style progress bar in my example above is made up of these three images:

  • XP Style background image Background image
  • XP Style foreground image Foreground image
  • XP Style overlay image Overlay image (this is mostly transparent)

However, what if we wanted to draw, for example, the background image stretched to 200 pixels? That would mess up the proportions of the image in the corner, like this:

  • XP Style background image, stretched Background image (stretched from 19 pixels to 200 pixels in width)

That does not look good, so I came up with the concept of image segments. I expose three segment related properties per image and then only stretch or tile the center segment. The three segments are defined by two properties of the ProgressBar class:

  1. [imageType]LeadingSize: this defines the left-most area that will not be stretched.
  2. [imageType]TrailingSize: this defines the right-most area that will not be stretched.

The center segment is implicitly defined as the segment between the leading and trailing segments. Again, public properties expose the segment values to the form designer:

C#
public class ProgressBar : Panel
{
    ...
    
    private int backgroundLeadingSize = 0;
    private int backgroundTrailingSize = 0;
    
    ...

    public int BackgroundLeadingSize
    {
        get { return backgroundLeadingSize; }
        set { backgroundLeadingSize = value; }
    }

    public int BackgroundTrailingSize
    {
        get { return backgroundTrailingSize; }
        set { backgroundTrailingSize = value; }
    }
}

Now, finally, all the properties we need are defined.

Rendering

It's now time to render the images. We do that by overloading OnPaint:

C#
protected override void OnPaintBackground(PaintEventArgs e)
{
    // Do nothing in here as all the painting is done in OnPaint    


}

protected override void OnPaint(PaintEventArgs e)
{
    //


    // An offscreen is a must have so we make sure one is always 


    // created if it does not exists, 


    // a resize of the progressbar sets offscreenImage to null and 


    // this will then automatically 


    // create a new one with the correct dimensions.


    //


    if (offscreenImage == null)
        CreateOffscreen();

    // Render the background first, here we pass the entire width of 


    // the progressbar as the distance value because we


    // always want the entire background to be drawn.


    Render(offscreen, 
           backgroundImage, 
           backgroundDrawMethod,
           backgroundLeadingSize, 
           backgroundTrailingSize, 
           this.Width);

    // We only need to render the foreground if the 


    // current value is above the minimum


    if (value > minimum)
    {
        // Calculate the amount of pixels (the distance) to draw.


        int distance = 
            (int)(((float)this.Width) * ((float)(value - minimum)) / 
            ((float)(maximum - minimum)));

        Render(offscreen, 
               foregroundImage, 
               foregroundDrawMethod, 
               foregroundLeadingSize, 
               foregroundTrailingSize, 
               distance);
    }

    // Render the overlay, this way we can get neat border 


    // on our progress bar (for example)


    Render(offscreen, 
           overlayImage, 
           overlayDrawMethod, 
           overlayLeadingSize, 
           overlayTrailingSize, 
           this.Width);

    // Finally, draw we our offscreen onto the Graphics in the event.


    e.Graphics.DrawImage(offscreenImage, 0, 0);
}

A few things to note here: first, we not only override OnPaint, but also OnPaintBackground to make sure the panel is not trying to render its default background. This is important because failing to do this can cause the progress bar to flicker.

Further, I do not render directly to the Graphics object that is passed to the OnPaint method, as that would also lead to flicker. Instead, I create an image in memory (this is done in the CreateOffscreen method) and render the progress bar to that. Then I render the off-screen image to e.Graphics at the end of the method. This way, the Graphics object that is visible on-screen only gets one update. No flicker!

A method called Render in the progress bar class is used to render an image up to a certain width. This is called three times, one for each type of image: background, foreground and overlay. For background and overlay, the width of the control is passed as a width parameter, making the background and overlay always being drawn to fill the entire progress bar. The foreground is drawn with a width parameter representing the amount of "progress" on the progress bar. The Render method looks like this:

C#
protected void Render(Graphics graphics, 
                      Image sourceImage, 
                      DrawMethod drawMethod, 
                      int leadingSize, 
                      int trailingSize, 
                      int distance)
{
    // If we don't have an image to render just bug out, this allows us 


    // to call Render without checking sourceImage first.


    if (sourceImage == null)
        return;

    //


    // Draw the first segment of the image as defined by leadingSize, 


    // this is always drawn at (0, 0).


    // 


    ProgressBar.DrawImage(
        graphics, 
        sourceImage,
        new Rectangle(0, 0, leadingSize, this.Height),
        new Rectangle(0, 0, leadingSize, sourceImage.Height));
    
    //


    // Figure out where the last segment of the image should be drawn, 


    // this is always to the right of the first segment 


    // and then at the given distance minus the width of the last segment.


    //


    int trailerLeftPosition = Math.Max(leadingSize, distance - trailingSize);

    ProgressBar.DrawImage(
        graphics, 
        sourceImage,
        new Rectangle(trailerLeftPosition, 0, trailingSize, this.Height),
        new Rectangle(sourceImage.Width - trailingSize, 
                      0, 
                      trailingSize, 
                       sourceImage.Height));

    //


    // We only draw the middle segment if the width of the first and last 


    // are less than what we need to display.


    //


    if (distance > leadingSize + trailingSize)
    {
        RenderCenterSegment(graphics, 
                            sourceImage, 
                            drawMethod, 
                            leadingSize, 
                            trailingSize, 
                            distance, 
                            trailerLeftPosition);
    }
}

By passing in a source rectangle (specifying which area of the image being drawn is going to be used) and the destination rectangle (the area on the graphics object the image is drawn onto), it is easy to draw the leading, trailing and center segments. At the end of the Render, and only if the width parameter is greater than the leading segment width plus the trailing segment width (this check is here to make sure that the "ends" for the progress bar are always drawn). the center segment is rendered. This is the part that takes into account the current DrawMode:

C#
private void RenderCenterSegment(Graphics graphics, 
                                 Image sourceImage, 
                                 DrawMethod drawMethod, 
                                 int leadingSize, 
                                 int trailingSize, 
                                 int distance, 
                                 int trailerLeftPosition)
{
    switch (drawMethod)
    {
        // This draws the middle segment stretched to fill the area
        // between the first and last segment.
        case DrawMethod.Stretch: 
            ProgressBar.DrawImage(
                graphics, 
                sourceImage,
                new Rectangle(leadingSize, 
                              0, 
                              distance - (leadingSize + trailingSize), 
                              this.Height),
                new Rectangle(leadingSize, 
                              0, 
                              sourceImage.Width - 
                              (
                                  leadingSize + trailingSize
                              ), 
                              sourceImage.Height));
            break;
        // This draws the middle segment un-stretched as many times 
        // as required to fill the area between the first and last segment.
        case DrawMethod.Tile: 
            {
                Region clipRegion = graphics.Clip;
                int tileLeft = leadingSize;
                int tileWidth = sourceImage.Width - 
                               (leadingSize + trailingSize);

                // By setting clip we don't have to change the size 
                // of either the source rectangle or the destination 
                // rectangle, the clip will make sure the 
                //overflow is cropped away.
                graphics.Clip = new Region(
                     new Rectangle(tileLeft, 
                                   0, 
                                   trailerLeftPosition - tileLeft, 
                                   this.Height + 1));
                while (tileLeft < trailerLeftPosition)
                {

                    ProgressBar.DrawImage(
                        graphics,
                        sourceImage,
                        new Rectangle(tileLeft, 
                                      0, 
                                      tileWidth, 
                                      this.Height),
                        new Rectangle(leadingSize, 
                                      0, 
                                      tileWidth, 
                                      sourceImage.Height));

                    tileLeft += tileWidth;
                }
                graphics.Clip = clipRegion;
            }
            break;
    }
}

The observant reader might have reacted to the use of ProgressBar.DrawImage rather than graphics.DrawImage. This is because of a portability issue between .NET and .NET Compact Framework. I want the same code base to run both platforms, desktop and device. This leads to the use of some pre-processor directives, since we must change the code slightly between platforms. The .NET Compact Framework ignores transparent pixels in PNG files and renders them as white. This won't do, so I fix that by using a "chroma key" defined in an ImageAttribute class to achieve transparency.

This would have worked for the desktop as well, but it is so much nicer being able to draw the images the way I want them to look, without the use of a "green screen" colour. This is why I decided to keep the original behaviour on the desktop.

C#
protected static void DrawImage(Graphics graphics, 
                                Image image, 
                                Rectangle destinationRectangle, 
                                Rectangle sourceRectangle)
{
    /*
     * The only place where some porting issues arises in when 
     * drawing images, because of this the ProgressBar code does not 
     * draw using Graphics.DrawImage directly. It instead uses this 
     * wrapper method that takes care of any porting issues using pre-
     * processor directives.
     */

#if PocketPC
    //
    // The .NET Compact Framework can not handle transparent pngs 
    // (or any images), so to achieve transparancy we need to set 
    // the image attributes when drawing the image. 
    // I've decided to hard code the "chroma key" value to
    // Color.Magenta but that can easily
    // be set by a property instead.
    // 

    if (imageAttributes == null)
    {
        imageAttributes = new ImageAttributes();
        imageAttributes.SetColorKey(Color.Magenta, Color.Magenta);
    }

    graphics.DrawImage(image, 
                       destinationRectangle, 
                       sourceRectangle.X, 
                       sourceRectangle.Y, 
                       sourceRectangle.Width, 
                       sourceRectangle.Height, 
                       GraphicsUnit.Pixel, 
                       imageAttributes);
#else
    graphics.DrawImage(image, 
                       destinationRectangle, 
                       sourceRectangle, 
                       GraphicsUnit.Pixel);
#endif
}

I went with magenta as a hard-coded chroma key, making it impossible to use that colour in the progress bar as it will not be rendered. This is a good thing because magenta is an ugly color. And that's it. Custom progress bars that can take on any appearance!

Rendering Marquee Bars

(This chapter was added in version 3 of this article.)

As was correctly pointed out to me in this article's discussion, my implementation lacks a marquee mode. I decided to add that and implement the same type of customization possibilities. For those of you who are unfamiliar with marquee progress bars, this is what it's called when a progress bar is used to indicate processing rather than progress. It is normally used to show the user that the application is doing something, but does not know how much work there is left to do. The first thing my progress bar needed was a way to indicate what type of bar it was, so I added an enumeration:

C#
public class ProgressBar : Panel
{
    ...
    
    public enum BarType
    {
        Progress,
        Marquee
    }
    
    ...
}

Using a member of this enumeration that is exposed by a get/set property, it is then easy to set the type of bar using the property pages in the visual designer:

C#
public class ProgressBar : Panel
{
    ...
    
    public enum BarType
    {
        Progress,
        Marquee
    }
    
    private BarType barType = BarType.Progress;

#if !PocketPC
    [Category("Progressbar")]
#endif
    public BarType Type
    {
        get { return barType; }
        set { barType = value; }
    }    
    
    ...
}

You might wonder what the pre-processor directive around an attribute on the property is for:

C#
#if !PocketPC
    [Category("Progressbar")]
#endif
    public BarType Type

By adding a Category attribute to the property, property pages in the visual designer can group related properties together. However, the .NET Compact Framework does not include the Category attribute, so the code would not compile without the pre-processor directive. This means that if we're using the progress bar in a desktop environment, the properties will be neatly grouped together. Unfortunately, this wont happen on the Device version. I also added another enumeration and member/property pair:

C#
public class ProgressBar : Panel
{
    ...
    
    public enum MarqueeStyle
    {
        TileWrap,
        BlockWrap,
        Wave
    }
    
    ...
}

This gives the option to select a type of marquee rendering. There are three different rendering types to choose from in my implementation:

  • TileWrap: this renders a single tile that travels over the bar and starts again at the beginning when reaching the end.
  • BlockWrap: this is kind of like TileWrap, but the width of the block traveling over the bar is configurable.
  • Wave: this renders a block like BlockWrap, but it moves back and forth over the bar in a sinuous wave pattern.

Since the background and overlay part of the progress bar do not change with BarType, most of the ProgressBar.OnPaint method remains the same as in the previous implementation. The only change is the addition of a switch statement that renders the foreground based on the BarType. The rendering of the marquee foreground is delegated to another method, as there are three options and I want to keep the ProgressBar.OnPaint method tidy:

C#
protected override void OnPaint(PaintEventArgs e)
{
    // An offscreen is a must have so we make sure one is always created
    // if it does not exists, a resize of the progressbar sets 
    // offscreenImage to null and this will then automatically 
    // create a new one with the correct dimensions.
    if (offscreenImage == null)
        CreateOffscreen();

    // Render the background first, here we pass the entire width of the
    // progressbar as the distance value because we always want the entire
    // background to be drawn.
    Render(offscreen, backgroundImage, backgroundDrawMethod,
        backgroundLeadingSize, backgroundTrailingSize, this.Width);

    switch (barType)
    {
        case BarType.Progress:
            // We only need to render the foreground if the current value
            // is above the minimum
            if (value > minimum)
            {
                // Calculate the amount of pixels (the distance) to draw.
                int distance = (int)(((float)this.Width) * ((float)(
                    value - minimum)) / ((float)(maximum - minimum)));
                Render(offscreen, foregroundImage, foregroundDrawMethod,
                    foregroundLeadingSize, foregroundTrailingSize, distance);
            }
            break;
        case BarType.Marquee:
            // There are a couple of ways to render the marquee foreground
            // so this is delegated to a method
            RenderMarqueeForeground();
            break;
    }

    // Render the overlay, this way we can get neat border on our progress
    // bar (for example)
    Render(offscreen, overlayImage, overlayDrawMethod, overlayLeadingSize,
        overlayTrailingSize, this.Width);

    // Finally, draw we our offscreen onto the Graphics in the event.
    e.Graphics.DrawImage(offscreenImage, 0, 0);
}

ProgressBar.RenderMarqueeForeground() is then responsible for rendering the foreground using the correct method:

C#
private void RenderMarqueeForeground()
{
    switch (marqueeStyle)
    {
        case MarqueeStyle.TileWrap:
            RenderMarqueeTileWrap();
            break;
        case MarqueeStyle.Wave:
            RenderMaqueeWave();
            break;
        case MarqueeStyle.BlockWrap:
            RenderMarqueeBlockWrap();
            break;
    }
}

The three methods are then used to render the foreground. These methods are all basically the same: they all render the foreground leading part, a center part and, finally, the trailing part. The only difference between them is how they calculate where the drawing should begin.

Performance

As Windows Mobile Devices are a lot less powerful than desktop computers, I found that I needed to improve the rendering performance of the progress bar so that it would work smoother in cases where there are frequent updates to the progress value.

In this case, it was not very difficult to find an area where some time could be saved. I know that the progress bar is basically not doing anything but rendering itself. So, to optimize it, I needed to optimize the methods that render the background, foreground and overlay. When I say that it would not be very difficult, I mean that it would be easy to find a compromise where speed was gained at the cost of something else. This is usually the case when optimizing, unless the code is really poorly written to start with, in which case optimizations can be made by removing redundant things or just do thing correctly.

I decided to gain speed at the cost of memory used by storing the "calculated" graphics in cache images. By "calculated" I mean that the background, foreground and overlay as the way they appear on-screen is calculated using their leading, trailing properties. By rendering them to a cache image on Resize and then using the cache images when the progress bar needed repainting, no calculation would have to be done for a normal repaint.

This approach is all right for background and overlay, but for foreground, which changes as the progress value changes, it becomes more complicated. I could have created a complete set of cache images for the foreground, one for each amount of progress, and then rendered the correct one based on the progress value. This would have either made the progress bar appear as if it where snapping between values, if I had used too few cache images, or consumed too much memory if I had created one cache image for each possible state (equal to the width in pixels of the progress bar).

I decided to go for optimizing just the background and the overlay. The first thing needed then is a method that renders the images in their correct size, not to the off-screen, but to their cache images. This method is called from the method that handles resizes:

C#
protected void RenderCacheImages()
{
    ProgressBar.DisposeToNull(backgroundCacheImage);
    ProgressBar.DisposeToNull(overlayCacheImage);

    backgroundCacheImage = new Bitmap(Width, Height);
    Graphics backgroundCacheGraphics = Graphics.FromImage(backgroundCacheImage);

    // Render the background, here we pass the entire
    // width of the progressbar as the distance value because we
    // always want the entire background to be drawn.
    Render(backgroundCacheGraphics, backgroundImage, backgroundDrawMethod,
        backgroundLeadingSize, backgroundTrailingSize, this.Width);

    overlayCacheImage = new Bitmap(Width, Height);
    Graphics overlayCacheGraphics = Graphics.FromImage(overlayCacheImage);
    // Make sure that we retain our chroma key value by starting with a
    // fully transparent overlay cache image
    overlayCacheGraphics.FillRectangle(new SolidBrush(Color.Magenta),
        ClientRectangle);
    // Render the overlay, this way we can get neat border on our
    // progress bar (for example)
    Render(overlayCacheGraphics, overlayImage, overlayDrawMethod,
        overlayLeadingSize, overlayTrailingSize, this.Width);
}

I can still re-use the existing ProgressBar.Render method, as that was created to render to an off-screen. However, instead of supplying the off-screen graphics as the first argument, the graphics for the cache images are provided. Note that in order to maintain any transparency, the overlay image is drawn onto a magenta area, as this will be left by the transparent (magenta) pixels on this pass and made transparent on the render to off-screen. The next step is then to replace the calls to ProgressBar.Render for background and overlay in the OnPaint method with calls to ProgressBar.DrawImage instead:

C#
protected override void OnPaint(PaintEventArgs e)
{
    //
    // An offscreen is a must have so we make sure one is
    // always created if it does not exists,
    // a resize of the progressbar sets offscreenImage to
    // null and this will then automatically
    // create a new one with the correct dimensions.
    //
    if (offscreenImage == null)
        CreateOffscreen();

    // Render the background first using the cached image
    ProgressBar.DrawImage(offscreen, backgroundCacheImage,
        ClientRectangle, ClientRectangle);

    switch (barType)
    {
        // Render foreground here...

        ...
    }

    // Render the overlay using the cached image
    ProgressBar.DrawImage(offscreen, overlayCacheImage,
        ClientRectangle, ClientRectangle);

    // Finally, draw we our offscreen onto the Graphics in the event.
    e.Graphics.DrawImage(offscreenImage, 0, 0);
}

And that's it for the performance fix. When running the application, it is hard to see the difference on Windows Mobile 5 devices, but it's visible on PocketPC 2003 devices. Although this implementation is intended for use on Mobile 5 or later, I still try to make my code work on as wide a range of platforms as possible.

Final Result

All the requirements defined in the beginning of this article are implemented and the only thing that needs improvement is the performance when running on Mobile 5 devices. Overall, I'm pretty happy with the result.

Points of Interest

The ZIP file inside the downloadable shows how to set up a solution so that two projects can reference the same source files. This is good when developing for the .NET Compact Framework because it can take a little while to launch the test application on the emulator. It is therefore convenient to try it out in a desktop environment, but at the same time, I want instant feedback on API conformance. This is so that I do not spend days implementing something that then is useless because I've been using stuff that is unsupported by the Compact Framework.

All are comments welcome, both on the code and the article.

History

  • 2007-11-14: First version
  • 2007-11-15: Second version (fixed some spelling and grammar)
  • 2007-11-26: Added support for Marquee progress bars
  • 2007-12-14: Updated with a performance fix

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)