65.9K
CodeProject 正在变化。 阅读更多。
Home

绑定 TextBlock、ListBox、RadioButtons 到枚举

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.73/5 (12投票s)

2010年11月23日

CPOL

7分钟阅读

viewsIcon

72838

downloadIcon

1786

将 WPF 控件绑定到枚举属性。

ScreenShot_small.PNG

引言

本文档演示了将 WPF 控件绑定到 enum 属性的不同方法,以便更改控件上的选定项可以自动更新属性的值。

示例包括 ListBoxComboBox、一组 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 事件。

因此,我们这样做:

  1. 添加 System.ComponentModel 命名空间
  2. using System.ComponentModel;
  3. INotifyPropertyChanged 接口添加到我们的 UserControl 中,并通过添加 PropertyChanged 事件来实现该接口:
  4. public partial class UserControl1 : UserControl, INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
  5. 更改在值更改时引发 PropertyChanged 事件的属性:
  6. private States _state;
    public States State
    {
        get { return _state; }
        set
        {
            if ( _state != value )
            {
                _state = value;
    
                if ( PropertyChanged != null )
                {
                     PropertyChanged( this, 
    		new PropertyChangedEventArgs( "State" ));
                }
            }
        }
    }
  7. 使其美观。
  8. 如果您有多个属性,这段代码会需要大量的编码。我们可以通过创建一个便捷函数来检查并调用 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 规则,并将 UserControlDataContext 设置为它自身。这实际上将使 UserControl 同时成为 ViewViewModel

public UserControl1()
{
    this.InitializeComponent();

    this.DataContext = this;
}

3 - 将 TextBlock 绑定到 Enum 属性

首先,我们将添加一个 TextBlock 来显示属性的选定值。

绑定到 TextBlock 非常简单,只需将绑定添加到 State 属性。由于控件是只读的,我们只需要一个单向绑定(这是 TextBlock 的默认值)。对属性的更改将引发 PropertyChanged 事件,并且文本块将使用新属性值进行更新。

<TextBlock Text="{Binding State}" />

4 - 将 ListBox 绑定到 Enum 属性

创建 ListBox 时,ListBox 中有两个属性需要设置:ItemsSourceSelectedItemItemsSource 将是一个 enum 的列表。SelectedItem 将绑定到我们的属性。

有多种方法可以创建和绑定 ListBox 上的 ItemsSource 属性。我将列出几种。

  1. 创建一个返回 enum 列表的属性。
  2. 一种方法是在 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}" />
  3. enum 获取值列表。
  4. System.Enum 类包含一个 static 函数 GetValues,它返回所有 enum 值的列表。您需要将 enum 类型作为参数传递给该函数。

    public System.Array ListOfStates
    {
        get
        {
            return Enum.GetValues( States.Virginia.GetType() );
        }
    }

    请注意,当您这样做时,您无法控制顺序。

  5. 使用 ObjectDataProvider 在 XAML 中创建列表。
  6. 为了在 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}" />
  7. 使用 ValueConverter 在 XAML 中创建列表。
  8. 创建 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 资源中,并在绑定中引用它。请注意,我们将 ItemsSourceSelectedItem 都绑定到 State 属性。

    <UserControl.Resources>
        <local:EnumToValuesConverter x:Key="EnumToValuesConverter" />
    </UserControl.Resources>
    
    <ListBox
        ItemsSource="{Binding State, Converter={StaticResource EnumToValuesConverter}}"
        SelectedItem="{Binding Path=State, Mode=TwoWay}" />
  9. 使用 Singleton ValueConverter 在 XAML 中创建列表。
  10. 将转换器制作为 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 相同,使用 ItemsSourceSelectedItem 属性。

<ComboBox
    ItemsSource="{Binding State, 
	Converter={x:Static local:EnumToValuesConverter.Instance}}"
    SelectedItem="{Binding State, Mode=TwoWay}" />

6 - 将 Radio Box Group 绑定到 Enum 值

如果您想要一个包含一组单选按钮的组框,每个按钮对应一个 enum 值,一种方法是为每个 enum 值创建一个 RadioButton,然后使用一个转换器将 RadioButtonIsChecked 属性绑定到 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 属性。此外,RadioButtonContent 也通过绑定设置。

<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,并将 listboxItemsSource 设置为 enum(使用我们的 EnumToValuesConveter),并将 ListBoxSelectedItem 绑定到 State 属性。

<ListBox
    ItemsSource="{Binding State, 
	Converter={x:Static local:EnumToValuesConverter.Instance}}"
    SelectedItem="{Binding State, Mode=TwoWay}" />

完成后,我们为 ListBoxItem 创建一个模板,在 ControlTemplate 中指定一个 RadioButton。在模板中,我们将 RadioButtonIsChecked 属性绑定到 ListBoxItemIsSelected 属性(模式为双向)。

<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}}" />

ListBoxComboBox 变为:

<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 - 初始发布
© . All rights reserved.