Tuesday, December 03, 2013

C# - Owner Drawn ComboBox Control

1. Introduction to Owner Drawn Controls

When we place a control on the form, the control’s appearance is usually decided by the control itself. Let us say for example that you placed a List Box control on the form, the appearance of ListBox control is drawn by the dot net frame code written for that control itself. That means, the ListBox class or its parent class can take the responsibility of drawing the control. This is the normal behaviour for almost all the control.

In the same case, let us say that the Form performs the drawing of each ListBox item with a double lined red border around each item. In this case, the Form is called as Parent or Owner of the ListBox and ListBox control is seen as "Owner Drawn Control". In easy words, the Form (Owner of the ListBox) draws the ListBox Items. In this article, we will see how de we create an Owner Drawn ComboBox.



2. About the Example

Below is the Screenshot of the application that we are going to create:


In the above screenshot, the default appearance of the ComboBox is changed to display the color boxes. This ComboBox is partly drawn by the Form. That is, the ComboBox with a Down Arrow is drawn by the control itself. The Form draws the content of the ComboBox when it is dropped down.

The Owner of the ComboBox in the example will perform the Following drawing:


  1. Draws the Color Rectangle
  2. Draws Text String next to the colour rectangle
  3. Draws the Red Border around the selected Item


Once the colour is selected, the text typed in the textbox will appear in the selected colour.


3) About Owner Draw

The control Listbox, Combobox, TabControl and TreeView Control supports Owner Draw. All these controls have the property called "DrawMode", which specifies what kind of owner drawn will be used. The ComboBox as well as ListBox supports three kinds of DrawMode and these modes are:


  1. Normal
  2. OwnerDrawFixed
  3. OwnerDrawVariable

3.1 Normal Mode



This is the default drawing mode set to a ListBox and ComboBox control. In this Mode, windows take the responsibility of drawing the control and each item contained in it.

3.2 OwnerDrawFixed



When this mode is set to the DrawMode property, the owner will draw the control by handling the "DrawItem Event". Each item will have fixed height.

3.3 OwnerDrawVarible



In this mode, the owner will draw as well as measure the individual item. So we can say that each item can have different height. When this Mode is set, the owner of the control should provide "DrawItem" and "MeasureItem" event handlers. In the MeasureItem handler, each item’s height will be measured and in the DrawItem each item will be drawn. The MeasureItem handler will be called only during the control initialization. But, the DrawItem will be called for each item when the drop arrow is clicked.

For this example, we need to set the OwnerDrawFixed to the DrawMode property as shown below:





4. Coding – Arranging the Form

1) In the Form Load handler function, populate the combo box with the strings that represents the colour. Below is the code:

//Snippet 02: Populate the Owner Drawn Combo box
private void frmComboSample_Load(object sender, EventArgs e)
{
    OwnerDCombo.Items.Add("Red");
    OwnerDCombo.Items.Add("Blue");
    OwnerDCombo.Items.Add("Green");
    OwnerDCombo.Items.Add("Chocolate");
    OwnerDCombo.Items.Add("Gold");
    OwnerDCombo.Items.Add("Purple");
    OwnerDCombo.Items.Add("LigntSeaGreen");
}

2) Next, write a function that returns the "System.Drawing.Colour" for a given colour name. Below is the function:

//Snippet 03: Get the Colour selected in the Combo box
public Color GetColor(String ColorName)
{
    Color col = Color.Black;
    if (ColorName == "Red")
    {
        col = Color.Red;
    }
    else if(ColorName == "Blue")
    {
        col = Color.Blue;
    }
    else if(ColorName == "Green")
    {
        col = Color.Green;
    }
    else if(ColorName == "Chocolate")
    {
        col = Color.Chocolate;
    }
    else if(ColorName == "Gold")
    {
        col = Color.Gold;
    }
    else if (ColorName == "Purple")
    {
        col = Color.Purple;
    }
    else
    {
        col = Color.LightSeaGreen;
    }

    return col;
}

3) In the ComboBox Selection change, the selected colour is retrieved to set the "ForeColor Property" of the textbox. To retrieve the colour the previously written function "GetColor() method" is used. Below is the code for that:

//Snippet 03: Set the Textbox colour based on the Combo Selection
private void OwnerDCombo_SelectedIndexChanged(object sender, EventArgs e)
{
    if (OwnerDCombo.SelectedIndex == -1)
        return;

    String strColor = (String)OwnerDCombo.Items[OwnerDCombo.SelectedIndex];
    TxtNotePad.ForeColor = GetColor(strColor);
}

5. Coding – DrawItem Handler

1) As already specified, <controlname>_DrawItem will perform the drawing for each item present in the combo box. This handler will be called for each item present in the combo box (When DrawMode is not normal). In this handler, first, we retrieved the Color Object and colour name by making use the index reported by the "DrawItemEventArgs".

private void OwnerDCombo_DrawItem(object sender, DrawItemEventArgs e)
{

//Snippet 04.1: Get the color currently selected in the Combobox
String strColor = (String) OwnerDCombo.Items[e.Index];
Color Selected_Color = GetColor(strColor);
String color_name = Selected_Color.Name;

2) As the function takes the responsibility of drawing the content of each combo box item, should perform the background drawing. First graphics object called “grp” is retrieved and then "DrawBackGround() method" is called on the DrawItemEventArgs. This will enable current item highlight when you hover the mouse on combo box items. Below is the code:

//Snippet 04.2: Get the Graphics from the Argument
Graphics grp = e.Graphics;
e.DrawBackground();

3) The DrawItemEventArgs supplies bounding box of the current combo box that needs to be drawn through its member called Bounds. A one-pixel indent is applied on top, left and bottom and the Width to be drawn as set as 20 pixels. Based on this, a Rectangle “rct” is formed. Through the already retrieved Graphics object, the rectangle is drawn. Below is the code:

//Snippet 04.3: Get the bounding rectangle to draw the item
int Rect_left = e.Bounds.X + 1;
int Rect_top = e.Bounds.Y + 1;
Rectangle rct = new Rectangle(Rect_left, Rect_top, 20, e.Bounds.Height - 2);
grp.DrawRectangle(Pens.Black, rct);

When we run the application at this stage, it looks like as shown below:



4) Once the bounding rectangle is formed, the rectangle is filled with the colour reported by the event argument. To perform the fill operation on the rectangle, brush object is formed from the colour object associated to the current item that needs to be drawn. Below is code for filling the rectangle using GDI+ brush:

//Snipper 04.4: Create a Solid Brush and Fill the Item Rectangles
SolidBrush brush = new SolidBrush(Selected_Color);
grp.FillRectangle(brush, rct);

Running the application looks like below now:



5) OK. Now we have colour rectangles in the combo box. Next, we should add the text that belongs to the colour rectangle. To draw the text, we should have offset from the left of the combo box as the rectangle is already occupying the left portion. To apply offset for drawing the text, we should perform the summation of, left offset already done to draw the rectangle (Shown as 1), the rectangle width and the new offset from the right of the colour rectangle (Hardcoded as 2). The summation will give from where (On the left side) the drawing of the text should take place. Below picture explains this:




Now in the code, we made a call to "DrawString() method" on the retrieved grp object. While calling the function, the left location of the string to be drawn is calculated based on the color rectangle’s dimensions, which already explained. Below is the code that draws the text next to the color Rectangle:

//Snippet 04.5: Create a Brush for drawing the texts
SolidBrush textb = new SolidBrush(Color.Black);
grp.DrawString(color_name, e.Font, textb, Rect_left + rct.Width + 2, Rect_top);

Running the application at this  stage look like below.



6) At last, we have to draw a bounding rectangle for the selected combo box item. By performing Bitwise AND (&) with “e.state” we can tell that the current item we are drawing is in selected stated or Not. When an item is in Highlighted state, a bounding rectangle around it is drawn in the Red Rectangle colour. To erase previously drawn red bounding rectangle, the drawing in the else portion is performed. Below is the code:

//Snippet 04.6: Draw a Border around the Selected Item
if ((DrawItemState.Selected & e.State) == DrawItemState.Selected)
{
    grp.DrawRectangle(Pens.Red, e.Bounds);
}
else
{
    grp.DrawRectangle(Pens.White, e.Bounds);
}

The below given video shows the completed application:


Source Code: Download

Like this site? Tell it to your Firend :)