绑定 TextBlock、ListBox、RadioButtons 到枚举






4.73/5 (12投票s)
将 WPF 控件绑定到枚举属性。

引言
本文档演示了将 WPF 控件绑定到 enum
属性的不同方法,以便更改控件上的选定项可以自动更新属性的值。
示例包括 ListBox
、ComboBox
、一组 RadioButtons
和一个 ListBox
包含 RadioButtons
。
还展示了一种将 enum
值转换为用户友好的 string
的方法。
Using the Code
摘要
您有一个 enum
属性,如下所示:
private enum State
{
Virginia,
WestVirginia,
NorthCarolina,
SouthCarolina
};
以及一个包含属性的 UserControl
,如下所示:
private States _state;
public States State
{
get { return _state; }
set { _state = value; }
}
并且您想将组合框 (combo box)、列表框 (list box) 或一组单选按钮 (radio buttons) 连接到该属性,以便用户可以选择特定值。
1 - 创建用户控件
我们希望绑定到 UserControl
中的属性,因此我们需要使其触发 PropertyChanged
事件。
因此,我们这样做:
- 添加
System.ComponentModel
命名空间 - 将
INotifyPropertyChanged
接口添加到我们的UserControl
中,并通过添加PropertyChanged
事件来实现该接口: - 更改在值更改时引发
PropertyChanged
事件的属性: - 使其美观。
using System.ComponentModel;
public partial class UserControl1 : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private States _state;
public States State
{
get { return _state; }
set
{
if ( _state != value )
{
_state = value;
if ( PropertyChanged != null )
{
PropertyChanged( this,
new PropertyChangedEventArgs( "State" ));
}
}
}
}
如果您有多个属性,这段代码会需要大量的编码。我们可以通过创建一个便捷函数来检查并调用 PropertyChanged
事件来将其分离出来。
private void RaisePropertyChanged( string name )
{
if ( PropertyChanged != null )
{
PropertyChanged( this, new PropertyChangedEventArgs( name ));
}
}
更好的是,我们可以创建一个通用函数来处理几乎所有事情:
public void SetWithNotify<T>( string name, ref T currValue, T newValue )
where T : struct, IComparable
{
if ( currValue.CompareTo( newValue ) != 0 )
{
currValue = newValue;
RaisePropertyChanged( name );
}
}
现在,属性的代码如下所示:
private States _state;
public States State
{
get { return _state; }
set { SetWithNotify( "State", ref _state, value ); }
}
2 - 设置 UserControl 的 DataContext
绑定使用 DataContext
来定位要绑定的源。这通常是 UserControl
(通常是对 ViewModel
的引用)。由于这是一个简单的测试程序,我将打破所有 MVVM 规则,并将 UserControl
的 DataContext
设置为它自身。这实际上将使 UserControl
同时成为 View
和 ViewModel
。
public UserControl1()
{
this.InitializeComponent();
this.DataContext = this;
}
3 - 将 TextBlock 绑定到 Enum 属性
首先,我们将添加一个 TextBlock
来显示属性的选定值。
绑定到 TextBlock
非常简单,只需将绑定添加到 State
属性。由于控件是只读的,我们只需要一个单向绑定(这是 TextBlock
的默认值)。对属性的更改将引发 PropertyChanged
事件,并且文本块将使用新属性值进行更新。
<TextBlock Text="{Binding State}" />
4 - 将 ListBox 绑定到 Enum 属性
创建 ListBox
时,ListBox
中有两个属性需要设置:ItemsSource
和 SelectedItem
。ItemsSource
将是一个 enum
的列表。SelectedItem
将绑定到我们的属性。
有多种方法可以创建和绑定 ListBox
上的 ItemsSource
属性。我将列出几种。
- 创建一个返回
enum
列表的属性。 - 从
enum
获取值列表。 - 使用
ObjectDataProvider
在 XAML 中创建列表。 - 使用
ValueConverter
在 XAML 中创建列表。 - 使用 Singleton
ValueConverter
在 XAML 中创建列表。
一种方法是在 UserControl
中创建一个返回 enum
列表的属性。由于此列表不会更改,因此它可以是一个只读属性。
public List<States> ListOfStates
{
get
{
List<States> list = new List<States>();
list.Add( States.NorthCarolina );
list.Add( States.SoundCarolina );
list.Add( States.Virginia );
list.Add( States.WestVirginia );
return list;
}
}
然后,ListBox
中的绑定是:
<ListBox ItemsSource="{Binding ListOfStates}"
SelectedItem="{Binding State, Mode=TwoWay}" />
System.Enum
类包含一个 static
函数 GetValues
,它返回所有 enum
值的列表。您需要将 enum
类型作为参数传递给该函数。
public System.Array ListOfStates
{
get
{
return Enum.GetValues( States.Virginia.GetType() );
}
}
请注意,当您这样做时,您无法控制顺序。
为了在 XAML 中创建 enum
列表,我们将创建列表并将其放入 UserControl
的 Resources 中。然后,我们将使用 StaticResource
绑定调用来绑定到该列表。
首先,我们需要通过添加 System
命名空间在 XAML 中定义 System.Enum
类型:
xmlns:sys="clr-namespace:System;assembly=mscorlib"
然后,我们可以使用 ObjectDataProvider
类来调用 System.Enum
类型的 GetValues
函数。
<UserControl.Resources>
<ObjectDataProvider MethodName="GetValues"
ObjectType="{x:Type sys:Enum}" x:Key="States">
<ObjectDataProvider.MethodParameters>
<x:Type TypeName="local:States" />
</ObjectDataProvider.MethodParameters>
</ObjectDataProvider>
</UserControl.Resources>
现在,绑定如下所示:
<ListBox ItemsSource="{Binding Source={StaticResource ListOfStates}}"
SelectedItem="{Binding State, Mode=TwoWay}" />
创建 enum
列表的另一种方法是使用 ValueConverter
。该转换器将转换 enum
类型的对象,并返回指定 enum
的值列表。
[ValueConversion(typeof(System.Enum), typeof(System.Array))]
public class EnumToValuesConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return Enum.GetNames(value.GetType());
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
现在我们将转换器放入 UserControl
资源中,并在绑定中引用它。请注意,我们将 ItemsSource
和 SelectedItem
都绑定到 State
属性。
<UserControl.Resources>
<local:EnumToValuesConverter x:Key="EnumToValuesConverter" />
</UserControl.Resources>
<ListBox
ItemsSource="{Binding State, Converter={StaticResource EnumToValuesConverter}}"
SelectedItem="{Binding Path=State, Mode=TwoWay}" />
将转换器制作为 Singleton(实现 Singleton 模式),方法是将以下 2 行添加到转换器中:
private static EnumToValuesConverter _Instance = new EnumToValuesConverter();
public static EnumToValuesConverter Instance { get { return _Instance; }}
一旦转换器成为 Singleton,就将其从资源中删除,并使用 x:Static
表达式来获取实例:
<ListBox
ItemsSource="{Binding State,
Converter={x:Static local:EnumToValuesConverter.Instance}}"
SelectedItem="{Binding Path=State, Mode=TwoWay}" />
5 - 将 Combo Box 绑定到 Enum 属性
绑定到组合框与绑定到 ListBox
相同,使用 ItemsSource
和 SelectedItem
属性。
<ComboBox
ItemsSource="{Binding State,
Converter={x:Static local:EnumToValuesConverter.Instance}}"
SelectedItem="{Binding State, Mode=TwoWay}" />
6 - 将 Radio Box Group 绑定到 Enum 值
如果您想要一个包含一组单选按钮的组框,每个按钮对应一个 enum
值,一种方法是为每个 enum
值创建一个 RadioButton
,然后使用一个转换器将 RadioButton
的 IsChecked
属性绑定到 enum
值。该转换器在 enum
匹配属性值时返回 True
,在不匹配时返回 False
。
使用 Enum To Boolean Converter 绑定 Radio Box Group
首先,我们需要一个使用 ConverterParameter
属性来指定 enum
值的转换器。如果绑定属性与 ConverterParameter
匹配,则转换器返回 true
,否则返回 false
。
[ValueConversion( typeof( System.Enum ), typeof( bool ) )]
public class EnumToBooleanConverter : IValueConverter
{
public object Convert( object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture )
{
return value.Equals( parameter );
}
public object ConvertBack( object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture )
{
if ( value.Equals( false ) )
return DependencyProperty.UnsetValue;
else
return parameter;
}
}
拥有 EnumToBooleanConveter
后,我们在 UserControl
资源中创建一个实例,并将 IsChecked
属性绑定到 State
属性。此外,RadioButton
的 Content
也通过绑定设置。
<RadioButton
Content="{x:Static local:States.Virginia}"
IsChecked="{Binding Path=State,
Converter={StaticResource EnumToBooleanConverter},
ConverterParameter={x:Static local:States.Virginia}}" />
请注意,您需要为每个 enum
值创建一个 RadioButton
。
7 - 将 ListBox of RadioButtons 绑定到 Enum 属性
当然,您不希望为每个 enum
值创建一个 Radio 按钮。您希望 WPF 自动完成此操作。处理此问题的方法是创建一个 ListBox
,并将 listbox
的 ItemsSource
设置为 enum
(使用我们的 EnumToValuesConveter
),并将 ListBox
的 SelectedItem
绑定到 State
属性。
<ListBox
ItemsSource="{Binding State,
Converter={x:Static local:EnumToValuesConverter.Instance}}"
SelectedItem="{Binding State, Mode=TwoWay}" />
完成后,我们为 ListBoxItem
创建一个模板,在 ControlTemplate
中指定一个 RadioButton
。在模板中,我们将 RadioButton
的 IsChecked
属性绑定到 ListBoxItem
的 IsSelected
属性(模式为双向)。
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}"
IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
这将为每个 ListBoxItem
自动创建一个 RadioButton
。
8 - 使用不同的转换器更改 Enum 的显示值
在上面列出的所有情况下,显示给用户的列表是 enum
的值(如 ToString()
所显示的)。通常情况下,enum
值并不是我们想要显示给用户的值。例如,我们希望显示“West Virginia”(带空格),而不是“WestVirginia”(无空格)。
为了显示这种用户友好的版本,我们需要一个转换器,一个将 enum
值转换为 string
值的转换器。例如,在我们的情况下,我们可以创建一个 EnumToSpacedStringConverter
,它只在每个大写字母(第一个除外)前插入一个空格。此外,我们还需要另一个转换器来创建 string
列表。因此,组合框、列表框、单选按钮都显示由我们的转换器提供的字符串值。
首先,我们编写一个快速的 InsertSpaces
函数:
private string InsertSpaces( string inString )
{
string outString = "";
foreach( char x in inString )
{
if ( outString.Length > 0 && x >= 'A' && x <= 'Z' )
{
outString += ' ';
}
outString += x;
}
return outString;
}
然后,我们创建创建带空格字符串列表的转换器(同样,这只是一次性的):
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
Array array = Enum.GetValues(value.GetType());
List<string> strings = new List<string>();
foreach( object o in array )
{
strings.Add( InsertSpaces( o.ToString() ));
}
return strings;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
最后,我们创建处理带空格 string
的转换器。这里最棘手的部分是 ConvertBack
例程,它将提供的 string
转换为 enum
值。
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
return InsertSpaces( value.ToString() );
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
Array array = Enum.GetValues(targetType);
foreach( object o in array )
{
if ( InsertSpaces( o.ToString() ) == (string) value )
{
return o as System.Enum;
}
}
return DependencyProperty.UnsetValue;
}
一旦编写了我们的转换器,我们就将它们添加到 resources
部分。
<UserControl.Resources>
<local:EnumToSpacedStringConverter x:Key="SpacedStringConverter" />
<local:EnumToSpacedStringsConverter x:Key="SpacedStringsConverter" />
文本块变为:
<TextBlock Text="{Binding State, Converter={StaticResource SpacedStringConverter}}" />
ListBox
和 ComboBox
变为:
<ListBox
ItemsSource="{Binding State, Converter={StaticResource SpacedStringsConverter}}"
SelectedItem="{Binding State, Mode=TwoWay,
Converter={StaticResource SpacedStringConverter}}" />
组中的每个 RadioButton
都变为(这有点棘手):
<RadioButton
Content="{Binding
Source={x:Static local:States.NorthCarolina},
Converter={StaticResource SpacedStringConverter}}"
IsChecked="{Binding Path=State,
Converter={StaticResource EnumToBooleanConverter},
ConverterParameter={x:Static local:States.NorthCarolina}}"/>
然后是 RadioButton
ListBox
:
<ListBox Background="{x:Null}"
ItemsSource="{Binding State, Converter={StaticResource SpacedStringsConverter}}"
SelectedItem="{Binding Path=State, Mode=TwoWay,
Converter={StaticResource SpacedStringConverter}}">
<ListBox.Resources>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}"
IsChecked="{Binding Path=IsSelected,
RelativeSource={RelativeSource TemplatedParent},
Mode=TwoWay}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources
</ListBox>
关注点
当用户使用鼠标选择项目时,程序可以正常工作。但是,使用键盘在控件之间切换会导致程序行为异常。我不知道原因。
历史
- 2010/11/21 - 初始发布