About Me

My photo
I'm a colonist who has declared war on machines and intend to conquer them some day. You'll often find me deep in the trenches fighting off bugs and ugly defects in code. When I'm not tappity-tapping at my WMD (also, known as keyboard), you'll find me chatting with friends, reading comics or playing a PC game.

Tuesday, November 24, 2009

LWUIT and WPF Gradients Mash-up #2

"On the last episode of Angelo's Stuff..."
We saw how to create a multi-coloured linear gradient. We also learnt the concept and purpose of gradient stops. This time around, we're going to dive into radial gradients.

What's so hard about radial gradients?
First off, there are a couple of things that radically differentiate linear and radial gradients.
  1. Linear radients are memory efficient. Only a small strip of an image needs to be made and that same image is tiled either horizontally or vertically across the area to be painted. Radial gradients are not tegular. We need an image that is as large as the area to be painted. A lot of memory is going to be used for a fancy effect. The good news is that the memory will be used up at run-time. So, the downloadable application isn't bloated.
  2. Radial gradients have a lot of customization options. Since we're using up a large image, we might as well get our memory's worth.
Let's paint the town red!

The GradientStop class.
See previous post.

The RadialGradient class.
public class RadialGradient
{
private Vector vGradientStops;
private int radiusX, radiusY;
private int marginTop, marginLeft;

public final static int FILL = 0xffff;
public final static int CENTER = 0xffff;

public RadialGradient( Vector gradientStops,
int radiusX, int radiusY,
int marginLeft, int marginTop )
{
vGradientStops = new Vector();
if(gradientStops != null)
{
//Ensure the gradient stops are ordered by offset in the member Vector.
for(int i = 0; i < gradientStops.size(); ++i)
{
GradientStop newGradientStop =
(GradientStop) gradientStops.elementAt(i);
int insertPos = 0;
for(insertPos = 0; insertPos < vGradientStops.size(); ++insertPos)
{
GradientStop existingGradientStop =
(GradientStop) vGradientStops.elementAt(insertPos);
if(newGradientStop.getOffset() < existingGradientStop.getOffset())
{
break;
}
}

vGradientStops.insertElementAt(newGradientStop, insertPos);
}
}

this.radiusX = radiusX;
this.radiusY = radiusY;
this.marginTop = marginTop;
this.marginLeft = marginLeft;
}

//Getters.
public Vector getGradientStops()
{ return vGradientStops; }
public int getRadiusX()
{ return radiusX; }
public int getRadiusY()
{ return radiusY; }
public int getMarginLeft()
{ return marginLeft; }
public int getMarginTop()
{ return marginTop; }
}

The constructor of this class takes the following parameters:
  1. A Vector of GradientStop. This allows us to use multiple colours within a single gradient.
  2. An X radius. This is the width of the radial gradient. Specify RadialGradient.FILL in order to have the radial gradient fill the entire width of the painted area.
  3. A Y radius. This is the height of the radial gradient. Specify RadialGradient.FILL in order to have the radial gradient fill the entire height of the painted area.
  4. A left margin. This is the distance of the center of the gradient from the left-most point of the area to be painted. Specify RadialGradient.CENTER to have the radial gradient show up in the horizontal center of the painted area.
  5. A top margin. This is the distance of the center of the gradient from the top-most point of the area to be painted. Specify RadialGradient.CENTER to have the radial gradient show up in the vertical center of the painted area.
What are these margins for?
The margins are basically used to space the radial gradient in the area to be painted. Assume that we have a radial gradient with an X radius and Y radius of 20 each. Now let's say that we want to paint this radial gradient at the top left of the painted area. Then all we need to do is specify left and top margins of 10 (half the respective radii). The illustration below should clarify things.

The Painter.
We're going to cache an image that is as large as the area to be painted. So, why restrict ourselves to a single radial gradient? Let's call our custom Painter - MultiRadialGradientPainter. The user of our Painter should be able to overlay multiple radial gradients over each other.
public class MultiRadialGradientPainter implements Painter
{
private byte opacity;
private Image cache;
private Vector vRadialGradients;
private int backFillColor;

public MultiRadialGradientPainter( Vector radialGradients,
byte opacity, int backFillColor )
{
vRadialGradients = radialGradients;
this.opacity = opacity;
this.backFillColor = backFillColor;
}

//Special case overload for single Radial Gradients only.
public MultiRadialGradientPainter( RadialGradient radialGradient,
byte opacity, int backFillColor )
{
vRadialGradients = new Vector();
vRadialGradients.addElement(radialGradient);

this.opacity = opacity;
this.backFillColor = backFillColor;
}

public void paint(Graphics g, Rectangle rect)
{
final Dimension d = rect.getSize();
final int x = rect.getX();
final int y = rect.getY();
final int width = d.getWidth();
final int height = d.getHeight();

final int size = Math.max(width, height);
if(cache == null || size != cache.getWidth())
{
cache = Image.createImage(size, size);
Graphics dc = cache.getGraphics();

//Set the backFillColor before drawing the gradients.
if(backFillColor >= 0)
{
dc.setColor(backFillColor);
dc.fillRect(0, 0, width, height);
}

for(int i = 0; i < vRadialGradients.size(); ++i)
{
final RadialGradient radialGradient =
(RadialGradient)vRadialGradients.elementAt(i);
int radiusX = radialGradient.getRadiusX();
int radiusY = radialGradient.getRadiusY();
int marginLeft = radialGradient.getMarginLeft();
int marginTop = radialGradient.getMarginTop();

if(radiusX >= RadialGradient.FILL)
radiusX = size;
if(radiusY >= RadialGradient.FILL)
radiusY = size;

if(marginTop >= RadialGradient.CENTER)
marginTop = height / 2;
if(marginLeft >= RadialGradient.CENTER)
marginLeft = width / 2;

final Vector vGradientStops = radialGradient.getGradientStops();
for(int j = vGradientStops.size() - 2; j > -1; --j)
{
GradientStop thisGradientStop =
((GradientStop)vGradientStops.elementAt(j));
GradientStop nextGradientStop =
((GradientStop)vGradientStops.elementAt(j + 1));

drawRadialGradient(dc,
thisGradientStop.getColor(),
nextGradientStop.getColor(),
(int)(radiusX * (thisGradientStop.getOffset() / 100.0)),
(int)(radiusX * (nextGradientStop.getOffset() / 100.0)),
(int)(radiusY * (thisGradientStop.getOffset() / 100.0)),
(int)(radiusY * (nextGradientStop.getOffset() / 100.0)),
marginLeft, marginTop
);
}
}

if(opacity < 255)
{
cache = cache.modifyAlpha(opacity);
}
}

g.drawImage(cache, x, y);
}

//This routine does the job of drawing a radial gradient.
private void drawRadialGradient(Graphics dc, int srcColor, int destColor,
int startRadiusX, int endRadiusX,
int startRadiusY, int endRadiusY,
int marginLeft, int marginTop)
{
final int srcR = srcColor >> 16;
final int srcG = srcColor >> 8 & 0xff;
final int srcB = srcColor & 0xff;

final int destR = destColor >> 16;
final int destG = destColor >> 8 & 0xff;
final int destB = destColor & 0xff;

final int dx = (endRadiusX - startRadiusX);
final int dy = (endRadiusY - startRadiusY);
final int biggerDiff = (dx > dy) ? dx : dy;

for(int i = biggerDiff; i > 0; --i)
{
final int interpolatedR =
(int)(srcR + ((destR - srcR) * ((float)i / biggerDiff)));
final int interpolatedG =
(int)(srcG + ((destG - srcG) * ((float)i / biggerDiff)));
final int interpolatedB =
(int)(srcB + ((destB - srcB) * ((float)i / biggerDiff)));

final int interpolatedColor =
interpolatedB | (interpolatedG << 8) | (interpolatedR << 16);

dc.setColor(interpolatedColor);

final int currentRadiusX = startRadiusX + (i * dx / biggerDiff);
final int currentRadiusY = startRadiusY + (i * dy / biggerDiff);

dc.fillArc(
marginLeft - (currentRadiusX / 2),
marginTop - (currentRadiusY / 2),
currentRadiusX, currentRadiusY, 0, 360);
}
}
}
The constructor of MultiRadialGradientPainter takes the following arguments:
  1. A Vector of RadialGradient. This allows us to have multiple radial gradients drawn by the painter.
  2. Opacity. A byte value that controls the transparency of the entire painted image.
  3. A back fill color. This colour will be used to fill the remainder of the space in the image where there is no gradient.
How about an example?
We have a simple LWUIT Form with a Label at the top (BorderLayout.NORTH).






















The screenshot on the left shows the Form without the Label. The Form has two radial gradients - one on the top and the other in the center. The screenshot on the right shows the barebones Form with the Label. The Label has a linear gradient.

This is what the code for setting these gradients looks like:
{
Vector vGradientStops = new Vector();
vGradientStops.addElement(new GradientStop(0xffffff, (byte)0));
vGradientStops.addElement(new GradientStop(0x434343, (byte)50));
vGradientStops.addElement(new GradientStop(0xffffff, (byte)60));

theLabel.getStyle().setBgPainter(
new LinearGradientPainter(vGradientStops, (byte)127, false));
}
{
Vector vGradientStops = new Vector();
vGradientStops.addElement(new GradientStop(0x704700, (byte)0));
vGradientStops.addElement(new GradientStop(0x251801, (byte)100));

Vector vRadialGradients = new Vector();
vRadialGradients.addElement(
new RadialGradient(vGradientStops,
RadialGradient.FILL, RadialGradient.FILL,
RadialGradient.CENTER, RadialGradient.CENTER));

Vector vSmallGradientStops = new Vector();
vSmallGradientStops.addElement(new GradientStop(0xffa300, (byte)0));
vSmallGradientStops.addElement(new GradientStop(0xffa300, (byte)50));
vSmallGradientStops.addElement(new GradientStop(0x251801, (byte)100));

vRadialGradients.addElement(
new RadialGradient(vSmallGradientStops,
RadialGradient.FILL, 50,
RadialGradient.CENTER, 0));

theForm.getStyle().setBgPainter(
new MultiRadialGradientPainter(vRadialGradients, (byte)255, 0x251801));

}
Note that the linear gradient is at 50% opacity. When combining the two together, we get the effect as seen below.
Pretty neat, huh?

Is that all?
Well, yes. That's it. A word of caution: Use radial gradients sparingly. Remember that they require memory allocated for an image that is as large as the area to be painted. If we were to start using radial gradients for all our components then we would run into Out Of Memory exceptions. Also, expect the unexpected. If we do run into an OOM exception, we need to gracefully handle it and fall back on a less fancy UI.

Whew! That was a pretty long-winded post but I hope the MultiRadialGradientPainter proves useful to LWUIT designer/developers out there. If you find an issue with the code feel free to inform me and I'll be happy to look into it. Happy coding!

Sunday, November 15, 2009

LWUIT and WPF Gradients Mash-up #1

Shai Almog's blog is the one-stop-shop for all things LWUIT. I recently read his entry on GradientPainters and it floored me! I thought I could enhance it a bit by adding GradientStops to the LinearGradientPainter.

What is a GradientStop?
If you come from a WPF background, then you know exactly what a GradientStop is. The fillLinearGradient() routine in LWUIT allows us to smoothly transition between two colours. So, we could go from White to Black and end up with this.

However, if we want to smoothly transition between multiple colours then that's where GradientStops can help us out. Let's say we want to vertically transition from White to Red and then from Red to Black. We start with White at the top and transition to red halfway through. Then we go from red at the half position, all the way to black at the bottom. Your mind's eye should have a picture like the one below.

A GradientStop has an offset and colour. The offset is the percentage of the position at which the colour is the strongest. For example, in our multiple colour gradient example above, we would need three GradientStops.
  1. GradientStop with offset 0 and colour White (0xffffff in LWUIT terms).
  2. GradientStop with offset 50 and colour Red (0xff0000).
  3. GradientStop with offset 100 and colour Black (0x000000).
Where's the code?
First, the implementation of the GradientStop class.

public class GradientStop
{
   private int color;
   private byte offset;

   public GradientStop()
   {
       color = 0x000000;   //Default Black at offset 0.
       offset = 0;
   }

   public GradientStop(int colorVal, byte offsetVal)
   {
       color = colorVal;
       offset = offsetVal;
   }

   public int getColor()
   {
       return color;
   }

   public int getOffset()
   {
       return offset;
   }

   public void setColor(int value)
   {
       color = value;
   }

   public void setOffset(byte value)
   {
       offset = value;
   }
}

Now, comes the implementation of the enhanced LinearGradientPainter.

public class LinearGradientPainter implements Painter
{
   private boolean horizontal;
   private byte opacity;
   private Image cache;
   private Vector vGradientStops;

   public LinearGradientPainter(   Vector gradientStops,
                                   byte opacity, boolean horizontal)
   {
       vGradientStops = new Vector();

       if(gradientStops != null)
       {
           //Ensure the gradient stops are ordered by offset in the Vector.
           for(int i = 0; i < gradientStops.size(); ++i)
           {
               GradientStop newGradientStop =
                       (GradientStop) gradientStops.elementAt(i);
               int insertPos = 0;
               for(insertPos = 0; insertPos < vGradientStops.size(); ++insertPos)
               {
                   GradientStop existingGradientStop =
                           (GradientStop) vGradientStops.elementAt(insertPos);
                   if(newGradientStop.getOffset() < existingGradientStop.getOffset())
                   {
                       break;
                   }
               }

               vGradientStops.insertElementAt(newGradientStop, insertPos);
           }
       }

       this.horizontal = horizontal;
       this.opacity = opacity;
   }

   public void paint(Graphics g, Rectangle rect)
   {
       final Dimension d   = rect.getSize();
       final int x         = rect.getX();
       final int y         = rect.getY();
       final int height    = d.getHeight();
       final int width     = d.getWidth();

       //Horizontal painting of gradient.
       if (horizontal)
       {
           if (cache == null || width != cache.getWidth())
           {
               cache = Image.createImage(width, 1);               
               Graphics dc = cache.getGraphics();

               for(int i = 0; i < vGradientStops.size() - 1; ++i)
               {
                   GradientStop thisGradientStop =
                           ((GradientStop)vGradientStops.elementAt(i));
                   GradientStop nextGradientStop =
                           ((GradientStop)vGradientStops.elementAt(i + 1));

                   dc.fillLinearGradient(thisGradientStop.getColor(),
                          nextGradientStop.getColor() ,
                          (int)(width * (thisGradientStop.getOffset() / 100.0)),
                          0,
                          (int)(width * (nextGradientStop.getOffset() / 100.0)),
                          1, horizontal);
               }

               if(opacity < 255)
               {
                   cache = cache.modifyAlpha(opacity);
               }
           }
           for (int iter = 0; iter < height; ++iter)
           {
               g.drawImage(cache, x, y + iter);
           }           
       }
       //Vertical painting of gradient.
       else
       {
           if (cache == null || height != cache.getHeight())
           {
               cache = Image.createImage(1, height);               
               Graphics dc = cache.getGraphics();

               for(int i = 0; i < vGradientStops.size() - 1; ++i)
               {
                   GradientStop thisGradientStop =
                           ((GradientStop)vGradientStops.elementAt(i));
                   GradientStop nextGradientStop =
                           ((GradientStop)vGradientStops.elementAt(i + 1));

                   dc.fillLinearGradient(thisGradientStop.getColor(),
                           nextGradientStop.getColor(),
                           0,
                           (int)(height * (thisGradientStop.getOffset() / 100.0)),
                           1,
                           (int)(height * (nextGradientStop.getOffset() / 100.0)),
                           horizontal);
               }

               if(opacity < 255)
               {
                   cache = cache.modifyAlpha(opacity);
               }
           }
          
           for (int iter = 0; iter < width; ++iter)
           {
               g.drawImage(cache, x + iter, y);
           }           
       }
   }
}

How do I use it?
Well usage is pretty simple. Here's an example for our previous multi-gradient case.

Vector vGradientStops = new Vector();
vGradientStops.addElement(new GradientStop(0xffffff, (byte)0));     
vGradientStops.addElement(new GradientStop(0xff0000, (byte)50));     
vGradientStops.addElement(new GradientStop(0x000000, (byte)100));

myComponent.getStyle().setBgPainter(
             new LinearGradientPainter(vGradientStops, (byte)255, false));    
     

Where do I go from here?
Let your imagination run wild!
What about the RadialGradientPainter?
I haven't forgotten about that. I thought I'd cover just one GradientPainter in this post so as not to bog the reader down. A multi-colour RadialGradientPainter will be covered in a future post.

Have fun!

Tuesday, September 22, 2009

Take Control of your Scroll

I recently ran into a specific issue when developing for a Windows Mobile environment. I had a lot of controls inside a Windows Form and by setting the Form's AutoScroll property to true, I could automatically scroll the contents of my form. This was quick and easy but then I found that the scrolling happened one pixel at a time which was just too slow. It would not really matter in most cases but in this case, I had to support the TouchFlo scrolling gesture. Thus began a 2-day hunt for an appropriate solution.

Attempt #1:
The first thing I looked for was some sort of property or method which could increase the scroll step. Nothing.

Attempt #2:
The next thing to try was take complete control of the scrolling. I put in my own scrollbar and handled the Scroll event of the scrollbar. This was a reasonable approach but then I realized that the Touch Flo scroll gesture did not work for me. I slapped my forehead and proceeded to the next attempt.

Attempt #3 (Final Solution):
The only other real solution was to take control of the Windows pumping messages. It turns out that you can override the WndProc function for Windows Forms. When it comes to the .NET Compact Framework, though, you can’t do that because its just not supported. I googled for an alternative and found this. I really must thank Mike Underhill for bailing me out here.
Third time’s a charm, they say and it seemed true in my case. This is what I ended up with.


private const int WM_VSCROLL = 0x115;
private const int GWL_WNDPROC = -4;
private const int SB_LINEUP = 0;
private const int SB_LINEDOWN = 1;

delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg,
IntPtr wParam, IntPtr lParam);

//Win32 APIs that I need.
[DllImport("coredll.dll", EntryPoint = "GetWindowLong")]
private static extern IntPtr GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport("coredll.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, IntPtr newWndProc);

[DllImport("coredll.dll")]
static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd,
uint Msg, IntPtr wParam, IntPtr lParam);

private static IntPtr oldWndProc = IntPtr.Zero;
private static WndProcDelegate newWndProc;
private const long lowOrderMask = 0xF;

public IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg == WM_VSCROLL)
{
long val = ((long)wParam & lowOrderMask);

if (val == SB_LINEDOWN)
{
//Haven’t really found out why the Y position
//was going negative but this seemed an easy fix. :p
AutoScrollPosition =
new Point(0, - AutoScrollPosition.Y + step);
}
else if (val == SB_LINEUP)
{
AutoScrollPosition =
new Point(0, - AutoScrollPosition.Y - step);
}


return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
}
}

//Constructor
public Form1()
{
InitializeComponent();

newWndProc = new WndProcDelegate(WndProc);
oldWndProc = GetWindowLong(pnlContainer.Handle, GWL_WNDPROC);
SetWindowLong(pnlContainer.Handle, GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newWndProc));
}


Hope this helps someone who ever comes across this problem.

Tuesday, July 28, 2009

My take on a Glass Button in WPF.

The following video tutorial shows how to create a nifty glass button in WPF using Microsoft Expression Blend 2. This is the first time I've actually made a screencast and it didn't turn out too bad. I'm sure it could be better. So, feel free to comment on anything that you like or don't like.

Saturday, July 25, 2009

SMSBalcony on LWUIT Featured Apps Gallery

I've been working on this J2ME based mobile application named SMSBalcony and its been featured on the LWUIT Featured Apps Gallery. Check it out here. Some of the other apps in the gallery are much better though. :) Perhaps someday, I'll be skilled enough to do stuff like that.

Sunday, July 19, 2009

Fate: Another Diablo Clone.

Fate is a dungeon crawler released in 2005 by Wild Tangent for the PC. Prima Facie, it seems like just another Diablo clone but there's a lot more to it. Ever since I completed Diablo II (about 3 times with different warrior classes :) ), I lay in wait for another Diablo Clone to come my way. Titan Quest (released in 2006) was great and maybe I'll blog about that someday. Diablo III is still in the works. So, in the meantime, let's talk about Fate.

Here are some of the nice things that I noted about Fate:
  • The game isn't as dark and gritty as Diablo II. The graphics are a lot more kiddish. Its 3D though but I find that sometimes when 2D artwork is done well in can look a lot better than a 3D game.
  • You have a pet (a dog or a cat) that fights on your behalf and can run back to town to sell items. The pet can also be transformed into some of the monsters that we encounter by feeding it fish!
  • Death isn't as frustrating as in Diablo. If the player dies, Fate itself appears and offers to resurrect the player (at the cost of experience, gold or both).
  • The dungeon level layouts and monsters and treasures are randomized.
  • You don't have different character classes as in Diablo II. The decision to be groomed as a brawler, sorceror or something in between is made while playing the game (Like purchasing sorceror items, giving more priority to Magic points instead of Strength).
Hope you enjoy some of the screenies I've posted of Fate and check out the links below to know more about the game.
("Don't bite the townsfolk." My pet may look like a Widow Spider but its still a dog at heart.)

(Me and the boys taking care of business.)

("Neat particle effects, guys but that won't stop me from taking you OUT!")

Monday, July 6, 2009

Cppcheck - An open source C++ static analysis tool

How would you identify potential flaws in your code? Conducting a code review would make sure that:
1. Possible errors/exceptions are dealt with.
2. Proper design patterns and good coding idioms have been used.
3. Common logical errors are eliminated and more...

However if bugs were introduced by a human in the first place, then those self-same bugs could be missed during a code review. Human program-comprehension is not very reliable.

Now, what if there were a tool to conduct the code review? That's what Static Code Analysis is for. It involves the analysis of program code without actually executing it. Of course, unlike a human, a tool that automates the process of static analysis can't really consider design issues on a large scale. It can offer advice about certain basic design patterns and good programming practices but it can't go beyond that and look at the big picture.

There are plenty of great static analysis tools around. You can get a complete list here. If you're a C/C++ programmer, you might want to try out Cppcheck. Its been GPL licensed and has a QT application front end for those who don't want to get their hands dirty with the command line. :)

Wiki entry on Cppcheck
SourceForge Project site of CppCheck

Saturday, June 27, 2009

AIMP2: A free Audio Player

AIMP2 is a free audio player. While the player appears free for now, there might possibly be a paid version in the future. The look and feel of AIMP2 lies somewhere between players like VLC (not so great looking...) and WinAmp (overkill!). I found this player to be pretty feature rich and less buggy too.

Check it out at http://www.aimp2.us

Wednesday, April 15, 2009

Customizing the items in a WinForms ComboBox

The combo box is one of the favoured means by which users can select one from a group of pre-defined options. It provides a neat interface and reduces on-screen clutter. The items in a combo box are strings by default and for most cases, string items are sufficient for presenting data to the user. For example, a list of cities like "Wayanad", "Trivandrum" and "Ernakulam". However, there may be cases where strings are not enough.

What do we have?
We have an application that displays a demographic map of civil conflict. Areas on this map are categorised as high, medium and low risk. High risk areas are characterized by a red colour, medium risk areas are yellow and low risk areas are grey.
Now, these legend colours need to be presented to the user (There are only three colours here but there could be more). Further more, the user might want to select an area of risk and additional data will be presented to the user based on his/her selection.

What do we need?
We need more than just a standard combo box, we need a customized combo box! Let's call this type of combo box a LegendComboBox. A LegendComboBox will display a rectangle with a colour inside it along with a string. This can be accomplished by manually drawing the items inside the combo box instead of letting it be drawn for us.

How do we get what we need?
Before we dive into creating our LegendComboBox, we need to realize the item(s) that this combo box will hold. Obviously, a simple string won't do because we also need to specify the legend colour for that string. In order to store this information, let's create a new class named LegendItem.

//The type of item that can be added to a LegendComboBox.
public class LegendItem
{
public object Data { get; set; }
public Color Color { get; set; }
}

Now, that we know what type of items will be added to the LegendComboBox, let's create the combo box itself.

Step1: We start by deriving our LegendComboBox from System.Windows.Forms.ComboBox.
That way it looks and smells just like a regular combo box.

Step2: In the constructor of LegendComboBox we do two things.
First, we set the DrawMode of this combo box to OwnerDrawVariable. This indicates that we want to manually draw the items in the combo box. Secondly, we register a function with the DrawItem event. This function will be invoked whenever a particular item in the combo box is to be drawn on the screen.

public class LegendComboBox : System.Windows.Forms.ComboBox
{
public LegendComboBox()
{
this.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawVariable;
this.DrawItem += new DrawItemEventHandler(LegendComboBox_DrawItem);
...
}
}

Step3: The next step is to actually define the function that we registered with the DrawItem event of our combo box. This function will have a prototype of:
void LegendComboBox_DrawItem(object sender, DrawItemEventArgs e);

DrawItemEventArgs provides data about the item to be drawn.
This data includes the index of the item to be drawn, the graphics surface on which to do the drawing, and the area within which we can do the drawing.

void LegendComboBox_DrawItem(object sender, DrawItemEventArgs e)
{
e.DrawBackground();
LegendItem currentItem = null;
Rectangle rect = new Rectangle(2, e.Bounds.Top + 2,
e.Bounds.Height, e.Bounds.Height - 4);
try
{
currentItem = (LegendItem)this.Items[e.Index];
}
catch (InvalidCastException)
{
//If the item in the combo box is not of type LegendItem,
//then we just draw the item without the legend colour.
e.Graphics.DrawString(this.Items[e.Index].ToString(), this.Font,
new SolidBrush(this.ForeColor), e.Bounds);

return;
}

//Draw rectangle with legend colour.
e.Graphics.FillRectangle(new SolidBrush(currentItem.Color), rect);
e.Graphics.DrawString(currentItem.Data.ToString(), this.Font,
new SolidBrush(this.ForeColor),
new Rectangle(e.Bounds.X + rect.Width + 2,
e.Bounds.Y, e.Bounds.Width, e.Bounds.Height));
}

That's it. We're done.

How do we use what we've got?
Using the LegendComboBox is pretty straightforward. Create an instance of this class and then add LegendItems to it.

LegendComboBox cboLegend = new LegendComboBox();
cboLegend.Items.Add(new LegendItem { Data = "High Risk", Color = Color.Red });
cboLegend.Items.Add(new LegendItem { Data = "Medium Risk", Color = Color.Yellow });
cboLegend.Items.Add(new LegendItem { Data = "Low Risk", Color = Color.Gray });

A better solution would be to make this LegendComboBox a control that can be used in any WinForms application.

What have we learnt?
Through an example, we've seen that a WinForms combo box can be customized to display more than just string data. As a matter of fact, the DrawItem event is available for the ListBox and TabControl controls as well. Also, we aren't restricted to just drawing shapes for our items, we could even draw images and more.

Thursday, February 12, 2009

Creating an uninstall shortcut for your MSI package

Microsoft, as a general rule, expect every user to uninstall applications from the Add/Remove Programs tool in the Control Panel. However, there might be cases where one might want to add an 'Uninstall Program' shortcut to the Start->Programs Menu. The steps below are a walkthrough for creating a simple MSI package using Visual Studio 2008 and adding an 'Uninstall Program' shortcut to the Programs Menu as well.

Basic Walkthrough for creating an MSI Installation package in Visual Studio 2008.
  1. Open the project for which an MSI Installation package is to be added in Visual Studio 2008.
  2. Right click the Solution of the Project in the Solution Explorer and select Add->New Project from the context menu.
  3. Expand the 'Other Project Types' category and choose 'Setup Project' from the 'Setup and Deployment category'. Enter a name and location for the MSI package to be created and click OK.
  4. A Setup project will be added to the project solution. Right Click the Setup Project in the Solution Explorer and select View->File System from the context menu.
  5. Right click 'Application Folder' in the new window and select Add->Project Output. Now, select 'Primary Output' from the dialog box that pops up and click OK. A 'Primary Output from xxx' entry should appear in the Application Folder. This is the main executable of the project.
  6. Right click the 'Primary Output from xxx' entry and select 'Create Shortcut to Primary Output from xxx'. Repeat this step one more time to create two shortcuts.
  7. Cut one of the shortcuts and paste it in the User's Desktop folder. Similarly cut the other shortcut and paste it in the User's Program Menu folder. Rename each of these shortcuts to something more appropriate (such as the application name).
Creating an Uninstall Program shortcut.
  1. Browse to the MSI project folder (using Windows Explorer), right click and select New->Shortcut from the context menu. In the Create Shortcut Wizard dialog that pops up type '%windir%\system32\msiexec.exe -x {prodCode} ' as the location of the shortcut, where prodCode is the Product Code of the MSI package. This Product Code can be identified from the Project Properties of the MSI Project in Visual Studio. Also, provide a proper name for the shortcut (such as Uninstall xxx, where xxx is the name of the application) and click Finish.
  2. The next step involves adding this shortcut to the User's Programs Menu folder of the MSI project in Visual Studio. The problem is that files with extension .lnk (extension of the shortcut) cannot be added to the Project. So, first we need to rename the shortcut extension from .lnk to .txt. Open up a DOS command window and browse to the location of the shortcut using the 'cd' command. Now type 'ren xxx.lnk xxx.txt' where xxx is the name of the shortcut item.
  3. Now, simply drag the renamed shortcut into the User's Programs Menu folder of the MSI project.
  4. Rename the shortcut from .txt back to .lnk.
  5. Build the MSI project and the necessary setup files will be created in the bin folder of the project.
Hope this helps someone out. Have a good day.