Introduction
I worked once on a case where I had to show on a System.Windows.Forms.ListBox
control some items that end user cannot select, but still they have to be shown anyway since the end user was expecting them. The point is that System.Windows.Forms.ListBox
does not provide such a feature, so I decided to create my own user control and share it.
Control at Runtime
Here are some snapshots of the control at run time: [You can download the sample project build and run it].
after disabling Item3 [with Index 2] we will get:
Now Item3 cannot be selected, we will do the same with Item5 [Index4] and get:
Enabling Item3 [Index2] will revert the item at its default state:
Using the Code
Using this control is fairly straightforward. It is used in the same way the built-in ListBox
control is used.
private Netdev.Windows.Forms.ListBox listBox1;
this.listBox1 = new Netdev.Windows.Forms.ListBox();
listBox1.DisableItem(index);
listBox1.EnableItem(index);
this.listBox1.DisabledItemSelected += new System.EventHandler
<Netdev.Windows.Forms.IndexEventArgs>(this.listBox1_DisabledItemSelected);
Describing the Code
The core of the customization happens on the OnDrawItem
method override. However the runtime will call this method only if DrawMode = DrawMode.OwnerDrawFixed;
which is the default value of the control property at creation and a list of disabled Indices [items] as shown in this constructor.
public ListBox()
{
DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed;
disabledIndices = new DisabledIndexCollection(this);
}
DisabledIndexCollection
: This is a custom collection that is identical to the one holding SelectedIndices
property. This collection tracks the indices that are currently disabled in the list which helps OnDrawItem
method override to display those items as grayed items and prevent the end user from selecting them.
Here is the code of OnDrawItem
method:
protected override void OnDrawItem(System.Windows.Forms.DrawItemEventArgs e)
{
base.OnDrawItem(e);
if (DesignMode && Items.Count == 0)
{
if (e.Index == 0)
{
e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
e.Graphics.DrawString(this.Name, e.Font, SystemBrushes.WindowText, e.Bounds);
}
return;
}
if (e.Index != ListBox.NoMatches)
{
object item = this.Items[e.Index];
string displayValue = GetItemText(item);
if (disabledIndices.Contains(e.Index))
{
e.Graphics.FillRectangle(SystemBrushes.InactiveBorder, e.Bounds);
e.Graphics.DrawString(displayValue, e.Font, SystemBrushes.GrayText, e.Bounds);
}
else
{
if (SelectionMode == System.Windows.Forms.SelectionMode.None)
{
e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
e.Graphics.DrawString(displayValue, e.Font,
SystemBrushes.WindowText, e.Bounds);
}
else
{
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
e.Graphics.FillRectangle(SystemBrushes.Highlight, e.Bounds);
e.DrawFocusRectangle();
e.Graphics.DrawString(displayValue, e.Font,
SystemBrushes.HighlightText, e.Bounds);
}
else
{
e.Graphics.FillRectangle(SystemBrushes.Window, e.Bounds);
e.Graphics.DrawString(displayValue, e.Font,
SystemBrushes.WindowText, e.Bounds);
}
}
}
}
}
If an item is held in DisabledIndices
collection, then display it as grayed.
And finally make sure to intercept SelectedIndexChanged
event and swallow it if the selected item is grayed by overriding OnSelectedIndexChanged
method.
protected override void OnSelectedIndexChanged(EventArgs e)
{
int currentSelectedIndex = SelectedIndex;
List<int> selectedDisabledIndices = new List<int>();
for (int i = 0; i < SelectedIndices.Count; i++)
{
if (disabledIndices.Contains(SelectedIndices[i]))
{
selectedDisabledIndices.Add(SelectedIndices[i]);
SelectedIndices.Remove(SelectedIndices[i]);
}
}
foreach (int index in selectedDisabledIndices)
{
IndexEventArgs args = new IndexEventArgs(index);
OnDisabledItemSelected(this, args);
}
if (currentSelectedIndex == SelectedIndex)
base.OnSelectedIndexChanged(e);
}
History
- Updated version - Fixed an issue related to the items display value when the list is bound to data, or
DisplayMember
, EnableFormating
and FormatString
are set