It is not my first article about .Net combo boxes. The control is really basic and developers need ways to have it fit their needs.
Lately on Experts-Exchange, somebody asked that question. I felt like I should share my answer to a broader audience!
You might wonder yourself why would one would like to show an item that the user wouldn’t be able to select? What if some options were available in the past and are no longer? You might want to show the original value that was saved but not let the user select it if he ever changes it.
This month’s downloadable code
This month solution contains both VB and C# projects. The solution was created using Visual Studio 2017 but the code should work as well in older version of the.Net Framework.
Figure 1: The demo application in action
Creating a ComboBoxItem class
I wanted to have a property on a combo box item to mark is as selectable or not. At first, I thought I would use the Tag property but combo box items don’t have this property.
So I created a small combo box item class with the very basic members:
Public Class ComboboxItem Public Text As String Public Value As Object Public Selectable As Boolean = True Public Overrides Function ToString() As String Return Text End Function End Class
public class ComboboxItem { public string Text; public object Value; public bool Selectable = true; public override string ToString() { return Text; } }
There is nothing really special in that class. Simply 3 public properties and an override of the ToString method.
Filling the combo box
Instead of filling your combo with plain old item, we will use our own ComboBoxItem to add items to the combo. This will let us specify which items are selectable (default) or not.
Here I create dummy items and I have decided arbitrarily that item 4 wouldn’t be selectable. You will need to use your own logic to specify if each item is selectable or not:
Private Sub FillCombo() ComboBox1.DrawMode = DrawMode.OwnerDrawFixed ComboBox1.Items.Add(New ComboboxItem With {.Text = "Test1", .Value = 1}) ComboBox1.Items.Add(New ComboboxItem With {.Text = "Test2", .Value = 2}) ComboBox1.Items.Add(New ComboboxItem With {.Text = "Test3", .Value = 3}) ComboBox1.Items.Add(New ComboboxItem With {.Text = "Test4", .Value = 4, .Selectable = False}) ComboBox1.Items.Add(New ComboboxItem With {.Text = "Test5", .Value = 5}) ComboBox1.Items.Add(New ComboboxItem With {.Text = "Test6", .Value = 6}) ComboBox1.Items.Add(New ComboboxItem With {.Text = "Test7", .Value = 7}) ComboBox1.Items.Add(New ComboboxItem With {.Text = "Test8", .Value = 8}) 'Test manual setting of an unselectable item mblnManualSet = True ComboBox1.SelectedIndex = 3 mblnManualSet = False End Sub
private void FillCombo() { ComboBox1.DrawMode = DrawMode.OwnerDrawFixed; ComboBox1.Items.Add(new ComboboxItem { Text = "Test1", Value = 1 }); ComboBox1.Items.Add(new ComboboxItem { Text = "Test2", Value = 2 }); ComboBox1.Items.Add(new ComboboxItem { Text = "Test3", Value = 3 }); ComboBox1.Items.Add(new ComboboxItem { Text = "Test4", Value = 4, Selectable = false }); ComboBox1.Items.Add(new ComboboxItem { Text = "Test5", Value = 5 }); ComboBox1.Items.Add(new ComboboxItem { Text = "Test6", Value = 6 }); ComboBox1.Items.Add(new ComboboxItem { Text = "Test7", Value = 7 }); ComboBox1.Items.Add(new ComboboxItem { Text = "Test8", Value = 8 }); ComboBox1.DrawItem += ComboBox1_DrawItem; ComboBox1.SelectedIndexChanged += ComboBox1_SelectedIndexChanged; //Test manual setting of an unselectable item _blnManualSet = true; ComboBox1.SelectedIndex = 3; _blnManualSet = false; }
The next section will tell you why the DrawMode has been set.
Finally, I use a way to be able to manually set the selected item to one that shouldn’t by using a private variable that will be reused in the SelectedIndexChanged event.
This method is called when the form loads.
Drawing items
In the FillCombo method, we have set the DrawMode property to OwnerDrawFixed. That will trigger the DrawItem event when items are to be drawn.
That will let us draw non-selectable items differently (italic and strikeout in this demo).
This is the code to draw items depending on the state of the Selectable property:
Private Sub ComboBox1_DrawItem(sender As Object, e As DrawItemEventArgs) Handles ComboBox1.DrawItem Dim objItem As ComboboxItem = CType(ComboBox1.Items(e.Index), ComboboxItem) If Not objItem.Selectable Then 'draw an unselectable item e.Graphics.DrawString(ComboBox1.Items(e.Index).ToString(), myFont2, Brushes.LightSlateGray, e.Bounds) Else 'draw a regular item e.DrawBackground() e.Graphics.DrawString(ComboBox1.Items(e.Index).ToString(), myFont, Brushes.Black, e.Bounds) e.DrawFocusRectangle() End If End Sub
private void ComboBox1_DrawItem(object sender, DrawItemEventArgs e) { ComboboxItem objItem = (ComboboxItem)ComboBox1.Items[e.Index]; if (!objItem.Selectable) { //draw an unselectable item e.Graphics.DrawString(ComboBox1.Items[e.Index].ToString(), _myFont2, Brushes.LightSlateGray, e.Bounds); } else { //draw a regular item e.DrawBackground(); e.Graphics.DrawString(ComboBox1.Items[e.Index].ToString(), _myFont, Brushes.Black, e.Bounds); e.DrawFocusRectangle(); } }
Selecting an item
When an item is selected (manually by code or from a user action), the SelectedIndexChanged is triggered.
We use this event here to just ensure that a non-selectable item will ever be selected except when explicitly asked for:
Private Sub ComboBox1_SelectedIndexChanged(sender As Object, e As EventArgs) Handles ComboBox1.SelectedIndexChanged 'If manual set it true If mblnManualSet Then Return 'prevent setting an unselectable item Dim objItem As ComboboxItem = CType(ComboBox1.SelectedItem, ComboboxItem) If objItem Is Nothing Then Return If Not objItem.Selectable Then ComboBox1.SelectedIndex = -1 End Sub
private void ComboBox1_SelectedIndexChanged(object sender, EventArgs e) { //If manual set it true if (_blnManualSet) return; //prevent setting an unselectable item ComboboxItem objItem = (ComboboxItem)ComboBox1.SelectedItem; if (objItem == null) return; if (!objItem.Selectable) ComboBox1.SelectedIndex = -1; }
Conclusion
Many times, we find ourselves in a situation where a control does not behave exactly like we want but a very little amount of code can do the job.