Introduction
In LOB applications, we have to design many windows, and many entities are bound to entry fields. Also, sometimes, there are enum
s or bool[]
arrays to be used. Therefore, I had a look at the chances I got to bind some user control to an enum data type. There is no CheckListBox
as in WinForms, and WPF shows the whole power when a ListBox
has to be customized to a CheckListBox
. A template-style will do for that more or less. It is more difficult when the CheckListBox
should be bound to a flagged enum or is a RadioListBox
.
What I wanted to achieve
In the case of showing all enum items to the user, I wanted to do the following:
- drop a control from the toolbox on the form
- set a binding to the entity, specifying the property name
With declaring the property in the business object as an enum type, I already do everything beforehand, so I do not have to think again about the enum items and setting the ItemSource
, and so on.
Using the code
Like the specs, you just drop the desired control onto the form and set the binding. In the sample, all the necessary parts are contained to show the functionality:
<clb:RadioListBox Name="listBoxType"
CheckedValue="{Binding PropType, Mode=TwoWay}"/>
<clb:CheckListBox Name="listBoxFeature"
CheckedValue="{Binding PropFeature, Mode=TwoWay}"/>
The CheckedValue
dependency property holds the bound data property from the entity. It is important to specify that the binding is TwoWay
.
Sometimes, you don't have an enum data type but a bool[
] array or anything else. In this case, there is no way to find out what the items to appear in the selection list are. So, you must somehow declare and propagate the collection to the control. You can do this in the code or in XAML. In any case, this has to be done before the first binding occurs. Here is a sample in XAML:
<Window.Resources>
<coll:ArrayList x:Key="actionList">
<clb:CheckItem KeyValue="0" Display="action 0"/>
<clb:CheckItem KeyValue="1" Display="action 1"/>
<clb:CheckItem KeyValue="2" Display="action 2"/>
</coll:ArrayList>
<coll:ArrayList x:Key="arrayList">
<clb:CheckItem KeyValue="0" Display="item 0"/>
<clb:CheckItem KeyValue="1" Display="item 1"/>
<clb:CheckItem KeyValue="2" Display="item 2"/>
<clb:CheckItem KeyValue="3" Display="item 3"/>
</coll:ArrayList>
</Window.Resources>
<clb:RadioListBox Name="listBoxAction"
CheckListArray="{StaticResource actionList}"
CheckedValue="{Binding PropAction, Mode=TwoWay}" />
<clb:CheckListBox Name="listBoxArray"
CheckListArray="{StaticResource arrayList}"
CheckedValue="{Binding PropArray, Mode=TwoWay}"/>
The CheckListArray
holds the collection to be shown in the list. Specify the KeyValue
if you want the chosen value as, e.g., an integer.
What is happening when running
In the underlying base class of the CheckListBox
and RadioListBox
, the DataContextChanged
is used to intercept on a binding to the bound property. In this case, the CheckedValue.OnDataContextChanged
, the type of the bound property, is evaluated, and either a prepared collection, or the items of the enum are bound to the ItemSource
of the ListBox
. To keep track of the changes, a ViewModel (I call it this way, might not meet the name) is created to control the ListBox
and the bound property.
This class is important, the enum could be of various types: byte
, int
, uint
, long
, ulong
... They all work neat because the ViewModel uses a UInt64
array of values to keep track of the enumeration values. The corresponding data types are boxed/unboxed accordingly.
The [Flags]
attribute is very important on an enum data type. Without this, only one choice can be checked, and even a CheckListBox
is used.
If someone wants to do all this before the first binding, the method CreateViewModel
could be called manually by specifying the corresponding type.
public void CreateViewModel(Type boundType)
{
if (boundType==null)
{
return;
}
if (this.CheckViewModel == null || this.CheckViewModel.BoundType != boundType)
{
Type _viewBase = typeof(CheckViewModel<>);
Type _viewType = _viewBase.MakeGenericType(boundType);
this.CheckViewModel = (ICheckViewModel)Activator.CreateInstance(_viewType);
this.CheckViewModel.HostParent = this;
this.CheckViewModel.IsRadioMode = this.isRadioMode;
if (this.CheckListArray != null)
{
List<CheckItem> _itemList = new List<CheckItem>();
foreach (CheckItem _checkItem in this.CheckListArray)
{
_itemList.Add(_checkItem);
}
this.CheckViewModel.CheckItems = _itemList;
}
else
{
this.CheckViewModel.InitDiscovery();
}
}
if (this.CheckViewModel != null && this.ItemsSource == null)
{
this.ItemsSource = this.CheckViewModel.CheckItems;
this.DisplayMemberPath = "Display";
this.SelectedValuePath = "KeyValue";
this.SelectionMode = SelectionMode.Single;
}
}
Overview
The CheckModelView
is encapsulated in the CheckListBoxBase
. From this base class, the CheckListBox
and the RadioListBox
are derived. The main reason for this is the different template they use for the ListBox
:
So maybe, it helps someone, or some real WPF-experts (you name them) might come up with a better solution. Cheers!
History
- 15.10.2008 > Initial publication.
- 31.10.2008 > Update:
CheckComboBox
and RadioComboBox
.