Extending the ComboBox with C#

Ever needed to do something with the ComboBox, but found that the normal ComboBox is quite limited? The ComboBox, as we know it, shows a list of items. What if you wanted to show colours, fonts, drawn lines, or images with the ComboBox? Wouldn't that be nice? Yes, it would. Not only that, but why not change the appearance and normal behaviour of the normal ComboBox? In this article, I will demonstrate how to do all these and more with the ComboBox.

Your Project: CrazyCombos

As the name implies, you are really going to go a bit crazy with extending the ComboBox's uses.

Create a new Windows Forms project, named CazyCombos.

Fonts Combo

The first thing that I will cover is creating a Font ComboBox. A Font ComboBox displays a list of all the available fonts on the user's PC, similar to what you use to change the Font in MS Word or MS Excel, for example. Apart from listing the available fonts, you also can display a small sample of each particular font by writing the current font name with the current font. For example, if you want to display Comic Sans MS, you must use the Comic Sans MS font to write Comic sans MS; in other words, the name of the font.

To create a Font ComboBox, follow these steps:

  1. Click Project.
  2. Click Add Class...
  3. Name the class FontCbo.cs.

This class will create a new font.

Import the Drawing Namespace at the top of FontCbo.cs.

using System.Drawing;    //Import Drawing NameSpace

Create a Font object:

public Font FCFont;      //Font Used

Edit the Class constructor as follows:

public FontCbo(Font FCCurrFont)
{
   //Set This Font Equal To Font Supplied
   FCFont = FCCurrFont;
}

Override the built-in ToString method to display the current font's name:

   /// <summary>
   /// Override ToString Method To Display Current Font's Name
   /// </summary>
   /// <returns></returns>
public override string ToString()
{ 
    return FCFont.Name;    //Display Font Name
}

Build your project.

Go to frmCrazy's Design view and add a ComboBox named cboFontsCrazy to it. Set cboFontsCrazy's DrawMode property to OwnerDrawVariable. This enables you to display custom items in the list. Add the following variable to frmCrazy:

   private SolidBrush FontForeColour;    //Font's Colour

Load All the Fonts

In frmCrazy_Load add the following:

//FontCombo
//Obtain & Store System fonts Into Array
FontFamily[] families = FontFamily.Families;

//Loop Through System Fonts
foreach (FontFamily family in families)
{
//Set Current Font's Style To bold
   FontStyle style = FontStyle.Bold;

   //These Are Only Available In Italic, Not In "Regular",
   //So Test For Them, Else, Exception!!
   if (family.Name   == "Monotype Corsiva"
      || family.Name == "Brush Script MT" 
      || family.Name == "Harlow Solid Italic"
      || family.Name == "Palace Script MT" || 
      family.Name    == "Vivaldi") 
   { 
      //Set Style To Italic, To Overt "Regular" & Exception
      style = style | FontStyle.Italic;
   }
   //Display The Font Combo Items
   cboFontsCrazy.Items.Add(new FontCbo(new Font(family.Name, 12,
                           style, GraphicsUnit.Point)));
}

Here, you created an array to store all the system's fonts, and then you looped through all of them. As you loop, you set each font's style to Bold, so that it appears Bold in FontCbo. The interesting thing here to note is that not all of the fonts support the Bold style, not even the Regular font style. What I had to do was to wait for the program to give me errors on the particular font(s) name(s). On each of the fonts in the if block, I received an error that they don't support the regular or Bold styles. This meant that each of those fonts only support italic; that is why I had to identify them, tand hen change their styles to italic. You could have set the style just to italic in the first place, but still you may have received errors on fonts not supporting Bold. So, it is basically trial and error.

The physical drawing of each font happens in the cboFontsCrazy_DrawItem event:

/// <summary>
/// Drawing Of Fonts
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cboFontsCrazy_DrawItem(object sender,
                                    DrawItemEventArgs e)
{
   Brush FontBrush;    //Brush To Be used

   //If No Current Colour
   if (FontForeColour == null)
   {
      //Set ForeColour
      FontForeColour = new SolidBrush(e.ForeColor);
   }
   else
   {
      //Fore Colour Changed, Create New Brush
      if (!FontForeColour.Color.Equals(e.ForeColor))
      {
         FontForeColour.Dispose();   //Dispose Old Brush
         //Create New Brush
         FontForeColour = new SolidBrush(e.ForeColor);
     }
   }

   //Set Appropriate Brush
   if ((e.State & DrawItemState.Selected) ==
        DrawItemState.Selected)
   {
      FontBrush = SystemBrushes.HighlightText;
   }
   else
   {
      FontBrush = FontForeColour;
   }

   //Current item's Font
   Font font = ((FontCbo)cboFontsCrazy.Items[e.Index]).FCFont;
    
   e.DrawBackground();    //Redraw Item Background

   //Draw Current Font
   e.Graphics.DrawString(font.Name, font, FontBrush, e.Bounds.X,
                         e.Bounds.Y);

   e.DrawFocusRectangle();    //Draw Focus Rectangle Around It

}

Edit the cboFontsCrazy_MeasureItem event to look like:

/// <summary>
/// Item Height Measurements
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cboFontsCrazy_MeasureItem(object sender,
                                       MeasureItemEventArgs e)
{
   //Get Current Font In ComboBox
    Font font = ((FontCbo)cboFontsCrazy.Items[e.Index]).FCFont;

   //determine Its Size
   SizeF stringSize = e.Graphics.MeasureString(font.Name, font);

   //Set Appropriate Height
   e.ItemHeight = (int)stringSize.Height;
   //Set Appropriate Width
   e.ItemWidth  = (int)stringSize.Width;

}

You can build and run your program now, and test your Fonts ComboBox. If everything went well, you should be able to display and select Fonts, as displayed in Figure 1.

Figure 1: The Working Fonts ComboBox

Extending the ComboBox with C#

Displaying Different Drawn Lines in a ComboBox

If you have seen the Underline Styles ComboBox in MS Word, you know that it displays various line drawings to be used as underlines. You will attempt this next, although it won't be as advanced as the Line ComboBox from MS Word, it stukk will give you the platform to work from. The first step is to add a new class, called LineCbo.cs, to your project.

Make sure you have all the necessary NameSpaces in your class:

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

Make sure your LineCbo class inherits from ComboBox:

namespace System.Windows.Forms
{
   class LineCbo : ComboBox
   {

Declare the following:

//Get Current System.Drawing.Drawing2D.DashStyles
private string[] DashStyles = Enum.GetNames(typeof(DashStyle));

private int i = 0;    //Counter

You need to create an array to store all available Dash styles; you get this from System.Drawing.Drawing2D.DashStyles. This will give you dashed, dotted, and thick lines. Then, you create a variable to be used just in a loop to loop through each dash style.

Modify the Constructor to look like:

/// <summary>
/// Constructor Setting Startup Settings
/// </summary>
public LineCbo()
{
   this.DrawMode = DrawMode.OwnerDrawFixed;    //DrawMode

   //Style Of ComboBox
   this.DropDownStyle = ComboBoxStyle.DropDownList;

   LCFillLineTypes();    //Call FillLineTypes Method
}

You set the ComboBox's style and DrawMode properties and called the LCFillLineTypes procedure. It looks like this:

/// <summary>
/// Adds All DashStyles To List
/// </summary>
private void LCFillLineTypes()
{
   this.Items.Clear();    //Clear All Items

   //Loop Through All DashStyles
   for (int i = 0; i <= DashStyles.Length - 1; i++)
   { 

   this.Items.Add(DashStyles[i]);    //Add Each DashStyle

   }
}

The LCFillLineTypes procedure simply loops through the DashStyles enumeration, and adds each item found to the list. Without the aid of overriding the OnDrawItem event, the actual showing of the lines will not happen. Add the following to your class:

/// <summary>
/// Override OnDrawItem, To Draw The Lines
/// </summary>
/// <param name="e"></param>
protected override void
   OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)
{
   base.OnDrawItem(e);

   if (e.Index >= 0)    //If Valid List
   {
      //Create New rectangle For New Item
      Rectangle LCCurrItemRect = e.Bounds;

      Pen LCDrawPen = new Pen(Color.Black, 2);    //Set Drawing Pen

      if (i <= DashStyles.Length - 1)    //If Valid DashStyle
      {
         //Set Pen's DashStyle To Current DashStyle
         LCDrawPen.DashStyle =
            (DashStyle)System.Enum.Parse(typeof(DashStyle),
            DashStyles[i]);

         //Draw The Current Line With Appropriate DashStyle
         e.Graphics.DrawLine(LCDrawPen,
            Convert.ToSingle(LCCurrItemRect.X),
         Convert.ToSingle(LCCurrItemRect.Y +
            LCCurrItemRect.Height / 2),
         Convert.ToSingle(LCCurrItemRect.X + LCCurrItemRect.Width),
         Convert.ToSingle(LCCurrItemRect.Y +
            LCCurrItemRect.Height / 2));

            i++;    //Increment Counter
      }
      if (i >= DashStyles.Length - 1)    //If Out of Range
            i = 0;    //Reset to 0, To Loop the Drawing
   }
}

In the OnDrawItem event, you created a Rectangle to draw onto, and also a Pen to draw with. You loop through the DashStyle array, and as you loop, you cast the current found dash style so that you can set the Pen's DrawStyle property to the correct current dashstyle present in the array. After you have cast the pen, you simply draw the line with the current dashstyle. I have opted to continue looping through the arrays to keep drawing as needed; you can, of course, opt not to.

Build your program.

Once built (and without errors), you will notice that the LineCbo component was added to your toolbox as shown in Figure 2. You can now add it from the toolbox, to your form and name it lineCboCrazy, or anything else descriptive. If you run your program now, you will find that the LineComboBox functions as intended, as shown in Figure 3.

[CrazyComboCompontents.png]

Figure2: LineCbo Added To The Toolbox

[Lines.png]

Figure 3: The Working Lines ComboBox

Extending the ComboBox with C#

Image Combo

Since I began working with .NET, I've always been disappointed with .NET not having a ImageComboBox. That is why I decided to make my own. Create a new class named ImgCbo.cs.

The only NameSpace you need is the System.Drawing NameSpace, and ImgCbo also needs to inherit ComboBox, as listed next:

using System.Drawing;

namespace System.Windows.Forms
{
   class ImgCbo : ComboBox    //Inherits ComboBox
   {

Create an ImageList object. Use it to store your images:

//Create New ImageList
private ImageList ICImagList = new ImageList();

Set the DrawMode in the constructor:

      public ImgCbo()
      {
         //Set DrawMode
         this.DrawMode = DrawMode.OwnerDrawFixed;
      }

Create an ImageList property:

      /// <summary>
      /// ImageList Property
      /// </summary>
      public ImageList ICImageList
      {
         get
         {
            return ICImagList;     //Get value
         }
         set
         {
            ICImagList = value;    //Set Value
         }
      }

Override OnDrawItem, to be able to draw images, text, and font formatting:

/// <summary>
/// Override OnDrawItem, To Be able To Draw Images, Text,
/// And Font Formatting
/// </summary>
/// <param name="e"></param>
protected override void OnDrawItem(DrawItemEventArgs e)
{
   e.DrawBackground();        //Draw Background Of Item
   e.DrawFocusRectangle();    //Draw Its rectangle

   if (e.Index < 0)           //Do We Have A Valid List?

   //Just Draw Indented Text
   e.Graphics.DrawString(this.Text, e.Font,
      new SolidBrush(e.ForeColor), e.Bounds.Left +
      ICImagList.ImageSize.Width, e.Bounds.Top);

   else    //We Have A List
   {

      //Is It A ImageCombo Item?
      if (this.Items[e.Index].GetType() == typeof(ICItem))
      {

         //Get Current Item
         ICItem ICCurrItem = (ICItem)this.Items[e.Index];

         //Obtain Current Item's ForeColour
         Color ICCurrForeColour = (ICCurrItem.ICForeColour !=
            Color.FromKnownColor(KnownColor.Transparent))
         ? ICCurrItem.ICForeColour : e.ForeColor;

         //Obtain Current Item's Font
         Font ICCurrFont = ICCurrItem.ICHighLight ?
            new Font(e.Font, FontStyle.Bold) : e.Font;

         //If In Actual List (Which Needs Images)
         if (ICCurrItem.ICImageIndex != -1)
            {
               //Draw Image
               this.ICImageList.Draw(e.Graphics, e.Bounds.Left,
                  e.Bounds.Top, ICCurrItem.ICImageIndex);

               //Then, Draw Text In Specified Bounds
               e.Graphics.DrawString(ICCurrItem.ICText,
               ICCurrFont, new SolidBrush(ICCurrForeColour),
               e.Bounds.Left + ICImagList.ImageSize.Width,
               e.Bounds.Top);
            }
         else    //No Image Needed, Index = -1

         //Just Draw The Indented Text
         e.Graphics.DrawString(ICCurrItem.ICText, ICCurrFont,
            new SolidBrush(ICCurrForeColour),
         e.Bounds.Left + ICImagList.ImageSize.Width, e.Bounds.Top);

         }
         else    //Not An ImageCombo Box Item

         //Just Draw The Text
         e.Graphics.DrawString(this.Items[e.Index].ToString(),
         e.Font, new SolidBrush(e.ForeColor),
         e.Bounds.Left + ICImagList.ImageSize.Width, e.Bounds.Top);

      }

      base.OnDrawItem (e);
   }

As with LineCbo, you need to override the OnDrawItem event, so that, in this case, you will be able to display pictures inside the ComboBox, just as a normal ImageComboBox. You have to establish whether or not you have decided to include an image with the item text. If you are displaying text as well as pictures, you need to draw the picture, and then write the text. If you only want to display text, you simply have to draw the text. You can, of course, change the colours of the Item text and make the text Bold or not. You are only half way now, because you still need a class for your ImageCombo's text. Inside ImgCbo, create another class called ICItem. This class will be used to display the actual text and picture(s) in a format you want. Add the following variables to ICItem:

private string ICIText = null        //ImageCombo Item Text

private int ICIImageIndex = -1;      //Image Combo Item Image Index

private bool ICIHighlight = false;   //Highlighted?

//ImageCombo Item ForeColour
private Color ICIForeColour =
   Color.FromKnownColor(KnownColor.Transparent);

This is your text, current image index, Boldness setting, and forecolour of your ImgCbo item. Now, create their associated Properties:

/// <summary>
/// ImageCombo Item Text
/// </summary>
public string ICText
{
   get
   {
      return ICIText;     //Get Value
   }
   set
   {
      ICIText = value;    //Set Value
   }
}

/// <summary>
/// Image Index
/// </summary>
public int ICImageIndex
{
   get
   {
      return ICIImageIndex;     //Get Value
   }
   set
   {
      ICIImageIndex = value;    //Set Value
   }
}

/// <summary>
/// Highlighted ?
/// </summary>
public bool ICHighLight
{
   get
   {
       return ICIHighlight;    //Get Value
   }
   set
   {
      ICIHighlight = value;    //Set Value
   }
}
/// <summary>
/// ForeColour
/// </summary>
public Color ICForeColour
{
   get
   {
      return ICIForeColour;     //Get Value
   }
   set
   {
      ICIForeColour = value;    //Set Value
   }
}

If you can remember, in the ImgCbo's OnDrawItem event you established whether or not you want pictures along with text. This decision you create via the use of ICItem's constructor. You can overload it to display text only, to display text and the picture, to be able to change the text to bold, and the forecolour of the current item. Add the following:

public ICItem()
   {
   }

/// <summary>
/// Text & Image Index Only
/// </summary>
/// <param name="ICIItemText"></param>
/// <param name="ICImageIndex"></param>
//First Overload
public ICItem(string ICIItemText, int ICImageIndex)
{
   ICIText = ICIItemText;           //Text
   ICIImageIndex = ICImageIndex;    //Image Index
}

/// <summary>
/// Text, Image Index, Highlight, ForeColour
/// </summary>
/// <param name="ICIItemText"></param>
/// <param name="ICImageIndex"></param>
/// <param name="ICHighLight"></param>
/// <param name="ICForeColour"></param>
//Second Overload
public ICItem(string ICIItemText, int ICImageIndex,
              bool ICHighLight, Color ICForeColour)
{
   ICIText       = ICIItemText;     //Text
   ICIImageIndex = ICImageIndex;    //Image Index
   ICIHighlight  = ICHighLight;     //Highlighted?
   ICIForeColour = ICForeColour;    //ForeColour
}

Finally, you need to override the ToString method again (as with FontCbo):

/// <summary>
/// Override ToString To Return Item Text
/// </summary>
/// <returns></returns>
public override string ToString()
{
   return ICIText;
}

Once built, the ImgCbo component will appear inside your Toolbox. Add this new component to your form and name it imgCboCrazy. You need to add the physical items to your new component in frmCrazy_Load:

private void frmCrazy_Load(object sender, EventArgs e)
{
   //Insert Text Only In ImageCombo
   imgCboCrazy.Items.Add(new ImgCbo.ICItem("No Icon", -1));
   //First Real Item In ImageCombo
   imgCboCrazy.Items.Add(new ImgCbo.ICItem("Blue Hills", 0, true,
      Color.Green));
   //Second ImageCombo Item
   imgCboCrazy.Items.Add(new ImgCbo.ICItem("Sunset", 1, false,
      Color.Blue));
   //Third ImageCombo Item
   imgCboCrazy.Items.Add(new ImgCbo.ICItem("Water Lillies", 2,
      false, Color.Gray));
   //Fourth ImageCombo Item
   imgCboCrazy.Items.Add(new ImgCbo.ICItem("Winter", 3, false,
      Color.Red));

As you can see with the above "creation methods", the ImageCombo is quite versatile. Test your project now, and you should be greeted with a screen similar to Figure 4:

[Image.png]

Figure 4: The Working Image Combo

Extending the ComboBox with C#

Displaying Colours

Add a normal combobox to your form. Name it cboColourCrazy, set its DrawMode property to OwnerDrawVariable, and set its DropDownStyle property to DropDownList. In frmCrazy_Load, add the following:

//Colour Combo
//Add Each Known Colour Into Colour ComboBox

//Get System's Known Colours
foreach (string CurrColourName in
   System.Enum.GetNames(typeof(System.Drawing.KnownColor)))
{
   //Add Known Colours
   cboColourCrazy.Items.Add(Color.FromName(CurrColourName));
}

Modify the cboColourCrazy_MeasureItem event to look like this:

/// <summary>
/// Make Sure All Items Will Fit
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void cboColourCrazy_MeasureItem(object sender,
   System.Windows.Forms.MeasureItemEventArgs e)
{
   Random ColourRnd = new Random();    //Set A Random value
   e.ItemHeight = ColourRnd.Next(20, 40);

}

Modify the cboColourCrazy_DrawItem event to the following:

/// <summary>
/// Paint Background, etc.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
protected void cboColourCrazy_DrawItem(object sender,
   System.Windows.Forms.DrawItemEventArgs e)
{
   if (e.Index < 0)    //Don't We Have A List?
   {
      e.DrawBackground();
      e.DrawFocusRectangle();
      return; 
   }
   //Get Colour Object From Items List
   Color CurrColour = (Color)cboColourCrazy.Items[e.Index];

   //Create A Rectangle To Fit New Item
   Rectangle ColourSize = new Rectangle(2, e.Bounds.Top + 2,
      e.Bounds.Width, e.Bounds.Height - 2);

   Brush ColourBrush;         //New Colour Brush To Draw With

   e.DrawBackground();        //Draw Item's Background
   e.DrawFocusRectangle();    //Draw Item's Focus Rectangle

   //If Item Selected
   if (e.State == System.Windows.Forms.DrawItemState.Selected)
   {
       ColourBrush = Brushes.White;    //Change To White
   }
   else
   {
       ColourBrush = Brushes.Black;    //Change Back to Black
   }
   //Draw New Item Rectangle With Current Colour
   e.Graphics.DrawRectangle(new Pen(CurrColour), ColourSize);
   //Fill New Item rectangle With Current Colour
   e.Graphics.FillRectangle(new SolidBrush(CurrColour),
      ColourSize);

   //Add Border Around Rectangle
   ColourSize.Inflate(1, 1);    //Border Size
   //Draw New Border
   e.Graphics.DrawRectangle(Pens.Black, ColourSize);

   //Draw Current Colour Name, In The Middle
   e.Graphics.DrawString(CurrColour.Name, cboColourCrazy.Font,
      ColourBrush, e.Bounds.Height + 5, ((e.Bounds.Height -
      cboColourCrazy.Font.Height) / 2) + e.Bounds.Top);
}

In frmCrazy_Load, you simply got a list of all available colours. In the DrawItem event, you gave each item a background colour of the current colour, and wrote the current colour's name in the middle.

Extending the ComboBox with C#

Aligning ComoBox Objects

If you look at a ComboBox, you will notice that it consists of a List, a Drop down button, and optionally a Scrollbar. Wouldn't it be nice to be able to move these parts around? You can play around with it now. Add a new class named ComboAlignSettings.cs, and add the following NameSpace:

//Necessary For APIs
using System.Runtime.InteropServices;

namespace System.Windows.Forms
{
   //Inherits ComboBox
   public class ComboAlignSettings : ComboBox
   {

As you probably know, the System.Runtime.InteropServices NameSpace means only one thing, and that is APIs. Without the use of the Windows API moving the ComboBox Scrollbar to the left, right-aligning the text, and moving the ComboBox, a dropdown button would not be possible. The API helps you get an appropriate handle to the ComboBox "control" you want to move, and then move it. Add all the APIs, Constants, and Structures needed:

//Retrieve Info About Specified Window
[DllImport("user32", CharSet = CharSet.Auto)]
public extern static int GetWindowLong(IntPtr hwnd, int nIndex);

//Change An Attribute Of Specified Window
[DllImport("user32")]
public static extern int SetWindowLong(IntPtr hwnd, int nIndex,
   int dwNewLong); 

//Retrieve Info About Specified Combo Box
[DllImport("user32.dll")]
public static extern int GetComboBoxInfo(IntPtr hWnd,
   ref ComboBoxINFO pcbi); 


[StructLayout(LayoutKind.Sequential)]
public struct ComboBoxINFO    //Contains ComboBox Status Info
{
    public Int32 cbSize;
    public RECT rcItem;
    public RECT rcButton;
    public CASComboBoxButtonState caState;
    public IntPtr hwndCombo;
    public IntPtr hwndEdit;
    public IntPtr hwndList;
}

//Describes Width, Height, And Location Of A Rectangle
[StructLayout(LayoutKind.Sequential)] 
public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

//Determines Current State Of ComboBox
public enum CASComboBoxButtonState
{
    STATE_SYSTEM_NONE = 0,
    STATE_SYSTEM_INVISIBLE = 0x00008000,
    STATE_SYSTEM_PRESSED   = 0x00000008
}

/// <summary>
/// Alignment Enum For Left & Right
/// </summary>
public enum CASAlignment
{
   CASLeft  = 0,
   CASRight = 1
}

//ComboBox Style
private const int GWL_EXSTYLE         = -20;
//Right Align Text
private const int WS_EX_RIGHT         = 4096;
//Left ScrollBar
private const int WS_EX_LEFTSCROLLBAR = 16384;
//Show Drop Down
private const int CB_SHOWDROPDOWN     = 335;

private IntPtr CASHandle; //Handle Of ComboBox

//Alignment Options For Text
private CASAlignment CASList;
//Alignment Options For Button
private CASAlignment CASButton;
//Alignment Options For ScrollBar
private CASAlignment CASScroll;

Edit the Class constructor to look like this:

public ComboAlignSettings()
{
   CASHandle = CASGetHandle(this);    //Get Handle Of ComboBox

   //Set Alignments
   CASButton = CASAlignment.CASRight;
   CASScroll = CASAlignment.CASRight;
   CASList   = CASAlignment.CASLeft;
}

In the constructor, I got a Handle to this component, with the use of the CASGetHandle method (which is covered later), and I set the default alignment options for this component. The drop-down button is displayed on the right, the scrollbar is right, and the text is aligned left, like an ordinary ComboBox.

Add the CASGetHandle method:

/// <summary>
/// Retrieves ComboBox Handle
/// </summary>
/// <param name="CASCombo"></param>
/// <returns></returns>
public IntPtr CASGetHandle(ComboBox CASCombo)
{
   //New ComboBox Settings Object
   ComboBoxINFO CASCBI = new ComboBoxINFO();
   //Call In Correct Size
   CASCBI.cbSize =
      System.Runtime.InteropServices.Marshal.SizeOf(CASCBI);
   GetComboBoxInfo(CASCombo.Handle, ref CASCBI);    //Obtain Handle
   return CASCBI.hwndList;                          //Return Handle
}

This method uses the ComboBoxINFO structure with the GetComboBoxInfo API to return a valid handle to your ComboBox.

Aligning the Objects

Add the following methods:

/// <summary>
/// Align The ComboBox List
/// </summary>
private void CASAlignList()
{
   if (CASHandle != IntPtr.Zero)    //If Valid Handle
   {
      //Get ComboBox Style
      int CASStyle = GetWindowLong(CASHandle, GWL_EXSTYLE);
      switch (CASList)
      {
         case CASAlignment.CASRight:
         //Align Text To The Right
         CASStyle = CASStyle | WS_EX_RIGHT;
         break;
      }
         //Apply On ComboBox
         SetWindowLong(CASHandle, GWL_EXSTYLE, CASStyle);
   }
}
/// <summary>
/// Align The ComboBox ScrollBar
/// </summary>
private void CASAlignScroll()
{
   if (CASHandle != IntPtr.Zero)    //If Valid Handle
   {
      //Get ComboBox Style
      int CASStyle = GetWindowLong(CASHandle, GWL_EXSTYLE);
      switch (CASScroll)
      {
         case CASAlignment.CASLeft:
         //Align ScrollBare To The Left
         CASStyle = CASStyle | WS_EX_LEFTSCROLLBAR;
         break;
      }
      //Apply On ComboBox
      SetWindowLong(CASHandle, GWL_EXSTYLE, CASStyle);
   }
}

/// <summary>
/// Align The ComboBox Button
/// </summary>
private void CASAlignButton()
{
   if (CASHandle != IntPtr.Zero)    //If Valid Handle
   {
      //Get ComboBox Style
      int CASStyle = GetWindowLong(this.Handle, GWL_EXSTYLE);

      switch (CASButton)
      {
         case CASAlignment.CASLeft:
         //Align ComboBox Button To The Left
         CASStyle = CASStyle | WS_EX_RIGHT;
         break;

   }

   //Apply On ComboBox
   SetWindowLong(this.Handle, GWL_EXSTYLE, CASStyle);
   }

}

Extending the ComboBox with C#

With the use of the SetWindowLong API, you are aligning each object as you want. Add these methods' associated properties:

/// <summary>
/// Set Text Alignment
/// </summary>
public CASAlignment CASDropListAlignment
{
   get
   {
      return CASList;          //Get Value
   }
   set
   {
      if (CASList == value)    //If Not Valid Value
      return; 

   CASList = value;            //Set Value
   CASAlignList();             //Call AlignList Method
   }
}


/// <summary>
/// Align ScrollBar
/// </summary>
public CASAlignment CASScrollAlignment
{
   get
   {
       return CASScroll;         //Get Value
   }
   set
   {
      if (CASScroll == value)    //If Not Valid Value
      return;  

      CASScroll = value;         //Set Value
      CASAlignScroll();          //Call AlignScroll Method
   }
}

/// <summary>
/// Align ComboBox Button
/// </summary>
public CASAlignment CASDropButtonAlignment
{
   get
   {
       return CASButton;         //Get Value
   }
   set
   {
      if (CASButton == value)    //If Not Valid Value
      return; 

      CASButton = value;         //Set Value
      CASAlignButton();          //Call AlignButton Method
   }
}

Build your project, and once successfully built, your component should appear in the Toolbox, as displayed in Figure 2.

Making the Alignment ComboBox Work

On your form, add this new component and give it a proper name, such as cboAlignAllCrazy, and add three buttons as well. These buttons will be used to align each part of your custom ComboBox you've just added. Once you have added all these controls, you can switch to code view. Currently, cboAlignAllCrazy does not contain any items, so quickly add some items in frmCrazy_Load:

string[] AllAlignArr = new string[10];    //Array For Combo Items

int intAllLoop;    //Loop Counter
//Initiate Loop
for (intAllLoop = 0; intAllLoop <= 9; intAllLoop++)
{
   //Fill Each Array Element With Appropriate Text
   AllAlignArr[intAllLoop] = "Item " + intAllLoop;
   //Display Items In Alignment Combo
   cboAlignAllCrazy.Items.Add(AllAlignArr[intAllLoop]);

   //Set Alignment Combo's Text To Left (Default)
   cboAlignAllCrazy.CASDropListAlignment =
      ComboAlignSettings.CASAlignment.CASLeft;
}

Add the alignment code for each button:

/// <summary>
/// Button To Right Align Alignment Combo's Text
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnRightText_Click(object sender, EventArgs e)
{
//If Left
   if (cboAlignAllCrazy.CASDropListAlignment ==
      ComboAlignSettings.CASAlignment.CASLeft) 
   {
      //Set To Right
      cboAlignAllCrazy.CASDropListAlignment =
         ComboAlignSettings.CASAlignment.CASRight;
   }

}

/// <summary>
/// Button To Left Align Alignment Combo's ScrollBar
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLeftAlignScroll_Click(object sender, EventArgs e)
{
   //If Right Aligned
   if (cboAlignAllCrazy.CASScrollAlignment ==
      ComboAlignSettings.CASAlignment.CASRight)
   {
       //Set To Left
       cboAlignAllCrazy.CASScrollAlignment =
          ComboAlignSettings.CASAlignment.CASLeft;
   }
}

/// <summary>
/// Button To Left Align Alignment Combo's Drop Button
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnLeftAlignButton_Click(object sender, EventArgs e)
{
   //If Right
   if (cboAlignAllCrazy.CASDropButtonAlignment ==
      ComboAlignSettings.CASAlignment.CASRight)
   {
       //Set To Left
       cboAlignAllCrazy.CASDropButtonAlignment =
          ComboAlignSettings.CASAlignment.CASLeft;
   }
}

Run it, and you should be able to align your ComboBox objects as displayed in the next sequence of pictures.

[RightAligned.png]

Figure 5: The Text is Right Aligned

[ScrollLeft.png]

Figure 6: A ScrollBar appears on the Left

[ButtonLeft.png]

Figure 7: The Drop-Down button appears on the Left

Centering ComboBox Text

Luckily, centering the text inside a ComboBox is not that complicated. Add a normal ComboBox to your form, and name it cboCenterCrazy. Edit the cboCenterCrazy_DropDown event to resemble the following:

/// <summary>
/// Center The Text Again
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void cboCenterCrazy_DropDown(object sender, EventArgs e)
{
   cboCenterCrazy.Items.Clear();           //Clear ComboBox
   string[] stringArr = new string[10];    //New Items

   int intLoop;    //Loop Counter

   for (intLoop = 0; intLoop <= 9; intLoop++)    //Start Loop
   {
      //Add Items To Array
      stringArr[intLoop] = "Item " + intLoop;

      //Center Align Items Again
      cboCenterCrazy.Items.Add(stringArr[intLoop].
         PadLeft(((cboCenterCrazy.DropDownWidth / 3)
         - (stringArr[intLoop].Length)) / 2));

   }

}

Here, I added all its items in the DropDown event, and calculated the exact centered position based on the ComboBox's Dropdown width.

Conclusion

Obviously, you can do so much more with the combobox, but hopefully, now, you are able to do special things with your ComboBoxes. Until next time!



About the Author

Hannes du Preez

Hannes du Preez is a Microsoft MVP for Visual Basic. He is a trainer at a South African-based company. He is the co-founder of hmsmp.co.za, a community for South African developers.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: May 7, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT This eSeminar will explore three popular games engines and how they empower developers to create exciting, graphically rich, and high-performance games for Android® on Intel® Architecture. Join us for a deep dive as experts describe the features, tools, and common challenges using Marmalade, App Game Kit, and Havok game engines, as well as a discussion of the pros and cons of each engine and how they fit into your development …

  • The exponential growth of data, along with virtualization, is bringing a disruptive level of complexity to your IT infrastructure. Having multiple point solutions for data protection is not the answer, as it adds to the chaos and impedes on your ability to deliver consistent SLAs. Read this white paper to learn how a more holistic view of the infrastructure can help you to unify the data protection schemas by properly evaluating your business needs in order to gain a thorough understanding of the applications …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds