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

在 Silverlight 中将 RadioButtons 绑定到枚举

starIconstarIconstarIconstarIconstarIcon

5.00/5 (30投票s)

2010 年 5 月 18 日

CPOL

7分钟阅读

viewsIcon

107292

downloadIcon

1277

如何在 Silverlight/WPF 中将枚举绑定到单选按钮

引言

今天早些时候,我正在为我的 Silverlight 应用程序开发一个新表单,我意识到我需要将一个 enum 绑定到一组 RadioButton。作为一名技术高超、经验丰富的软件工程师,我卷起袖子,戴上思考帽,然后……尝试用 Google 搜索解决方案。我知道基本的解决方案应该是什么样子,我确信会涉及一个有创意的转换器解决方案,但我希望节省一些时间和精力。然而,我在网上发现的结果非常令人失望。我不是第一个遇到这种情况并提出问题的人,但我阅读的论坛消息似乎都遵循一个共同的主题……“这办不到”。每个人都建议你只需通过使用代码隐藏并根据需要手动调整控件状态来处理这种情况。我不知道你怎么想,但我知道牛粪的甜味,当我闻到它们时,这种建议简直就是胡说八道。所以我又一次卷起旧袖子,这次是准备做一些实际工作,并决定为所有寻求快速解决此问题的未来开发人员解决这个问题。

背景

我只想花点时间为 Silverlight 新手强调一下显而易见的事情。如果你来自 ASP.NET 或其他背景,你很可能花了很多年时间在代码隐藏中管理 UI 控件的状态。如果是这样,那么在某个时候,你很可能学到了一个惨痛的教训,即这种方法是多么容易出错。当然,在一个没有错误代码的完美世界中,它工作得很好,但在开发人员的世界里,事情很少会如此顺利。而且,当对象值意外更改时,你的控件可能无法反映正确的值。这在任何情况下都是令人恼火的,但在关键的医疗或金融应用程序中,结果可能是灾难性的。将控件绑定到数据是确保你的 UI 准确表示所附加数据(并且很少受 bug 影响)的唯一方法(不是 100%,但一个编写良好的转换器通常是一小段非常可测试的代码,你将会看到)。

Using the Code

每个好的演示都需要一个虚构的公司示例(至少,微软是这样说的),所以我们开始吧。我们的客户是一家名为 SmallMediumLarge.com 的公司,简称 SML.com。SML.com 销售 T 恤。他们的“卖点”是他们每天只销售一种 T 恤,你唯一的选择就是选择衬衫尺寸,限于小号、中号或大号。作为 SML.com 的承包商,你被要求编写尺寸页面。作为一家初创公司,他们还没有钱支付你,并向你提供一件免费 T 恤作为补偿(仅限小号、中号或大号)。他们对你期望不高,他们只想要一个页面,其中包含三个代表各种衬衫尺寸的单选按钮,并且他们希望这些按钮绑定到他们的 Shirt 对象中的一个 enum。(如果你像我一样开发了这么长时间,那么你会悲伤地意识到这绝不是一个不切实际的场景)。

幸运的是,上一位获得免费 T 恤的承包商被要求编写 Shirt 对象,并且对 Silverlight 有一点经验。他足够聪明,创建了一个表示衬衫尺寸的简单 enum,并将其添加到一个公开了 enumShirt 类中。更好的是,他知道什么是 PropertyChanged 通知以及如何使用它,并确保每当设置衬衫尺寸时,Shirt 对象都会向绑定到它的任何控件发送适当的通知。谢谢你,另一位承包商先生,你得到了你的免费 T 恤伙伴。他的代码看起来是这样的:

public class Shirt : INotifyPropertyChanged
{
    private Sizes _size;

    public Shirt()
    {
        Size = Sizes.Small;
    }

    public Sizes Size
    {
        get { return _size; }
        set
        {
             _size = value;
             RaisePropertyChanged("Size");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
     }
}

public enum Sizes
{
    Small,
    Medium,
    Large
}

让我们像所有好项目一样开始这个项目——一瓶健怡激浪和 iPod 上播放的奥兹·奥斯本。哦,启动 Visual Studio 可能也有帮助。我使用的是 VS 2010 和 Silverlight 4,但你可以做任何你想做的事情。添加一个 LabelTextBlock 来描述我们想从客户那里得到什么,三个单选按钮来代表我们的三个尺寸 enum,以及一个 TextBox,我们将用它来帮助监控绑定 Shirt 对象的值。我将在这里发布我自己的获奖设计,以节省你一些时间……

<UserControl x:Class="DemoBoundRadioButtons.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:DemoBoundRadioButtons="clr-namespace:DemoBoundRadioButtons"
    mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <DemoBoundRadioButtons:RadioButtonConverter x:Key="RadioButtonConverter" />
    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White" Margin="30">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <TextBlock Text="Choose a t-shirt size:" 
               Margin="0 0 0 10" HorizontalAlignment="Center" />
        <StackPanel Orientation="Horizontal" 
                 Grid.Row="1" HorizontalAlignment="Center">
            <RadioButton Content="Small" Margin="0 0 10 0" 
                 GroupName="Sizes" IsChecked="True" />
            <RadioButton Content="Medium" 
                 Margin="0 0 10 0" GroupName="Sizes" />
            <RadioButton Content="Large" 
                 Margin="0 0 10 0" GroupName="Sizes" />
         </StackPanel>
         <TextBox Text="{Binding Size, Mode=TwoWay}" 
           Grid.Row="2" Margin="0 10 0 0" 
           Width="100" HorizontalAlignment="Center" 
           TextAlignment="Center" />
    </Grid>
</UserControl>

从功能上讲,这里唯一需要注意的是 TextBox 已绑定到 Shirt 对象的 Size 属性,因此它可以作为当前选定 enum 的清晰指示器。(将其标记为 Mode=TwoWay 并不是必需的,但我们稍后会讨论)。此外,所有 RadioButton 都已添加到一个组中,以便当其中一个被选中时,其他所有都会取消选中。我将一个新的 Shirt 对象绑定到整个页面的 DataContext,如下所示:

public MainPage()
{
    InitializeComponent();
    Loaded += MainPage_Loaded;
}

private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    DataContext = new Shirt();
}

现在让我们来看看那些 CheckBox。我们需要做的第一件事是连接一个转换器。我只是把它添加到代码隐藏文件中,但如果你愿意,你可以把它作为一个单独的文件。我的基本代码是这样的:

public class RadioButtonConverter : IValueConverter
{
    public object Convert(object value, Type targetType, 
           object parameter, CultureInfo culture)
    {
       
    }

    public object ConvertBack(object value, Type targetType, 
           object parameter, CultureInfo culture)
    {
      
    }
}

并且不要忘记添加对转换器作为 Resource 的引用:

<UserControl.Resources>
    <DemoBoundRadioButtons:RadioButtonConverter x:Key="RadioButtonConverter" />
</UserControl.Resources>

好了,现在我们完成了所有基本框架,让我们来填写细节。这里真正的挑战是 RadioButton 只能处理布尔值。TruefalseOnoff。在某些情况下,第三方控件允许第三种“部分”状态,但为了本次讨论的目的,我们假设我们正在使用标准的 RadioButton。我们到底如何将布尔值转换为枚举值?嗯,答案在于转换器签名允许我们传递一些额外的信息,除了 RadioButton 的值之外。这就是我们隧道尽头的曙光。考虑以下代码,然后我们对其进行分解。

<RadioButton Content="Small" Margin="0 0 10 0" 
  GroupName="Sizes" 
  IsChecked="{Binding Size, Mode=TwoWay, ConverterParameter=Small, 
             Converter={StaticResource RadioButtonConverter}}" />
<RadioButton Content="Medium" Margin="0 0 10 0" 
  GroupName="Sizes" 
  IsChecked="{Binding Size, Mode=TwoWay, ConverterParameter=Medium, 
             Converter={StaticResource RadioButtonConverter}}" />
<RadioButton Content="Large" Margin="0 0 10 0" 
  GroupName="Sizes" 
  IsChecked="{Binding Size, Mode=TwoWay, ConverterParameter=Large, 
             Converter={StaticResource RadioButtonConverter}}" />

每个 RadioButtonIsChecked 值都绑定到 Shirt.Size 值,但它还包含另一个参数 - ConverterParameterConverterParameter 使我们能够向转换器传递一些额外的信息;在本例中,它标识了哪个 RadioButton 代表我们想要订购的哪个 Shirt.Size。再加上我们将 RadioButton 放在一个组中,以便每次只有一个 RadioButton 会被选中,现在我们可以明确地识别正在选择哪个 Shirt.Size。我们想要的那个具有 IsChecked == true,而 ConverterParameter 告诉我们我们想要哪个 Shirt.Size。所以现在,我们只需要编写转换器本身。

public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
    return (value.ToString() == parameter.ToString());
}

public object ConvertBack(object value, Type targetType, 
       object parameter, CultureInfo culture)
{
    return (bool) value ? Enum.Parse(targetType, parameter.ToString(), true) : null;
}

当单击 RadioButton 时,会调用 ConvertBack() 函数。实际上,在这种情况下,它会被调用三次,因为组中所有 RadioButton 的值都会被评估。如果正在评估的 RadioButton 未选中 (IsChecked == false),则返回 null,基本上什么都不会发生。但是,如果该值评估为 true,那么奇迹就会发生。此时 ConverterParameter 开始发挥作用,我们使用 Enum.Parse 来评估 Size 中的每个 Size,当我们找到与我们的参数匹配的那个时,我们就赢了,并返回该 enum 值。简单轻松。

大部分工作我们已经完成。但我讨厌半途而废,所以让我们完成它。还记得我说我们将 TextBox 设置为 Mode=TwoWay 吗?好吧,让我们发挥它的作用。运行示例,选择“小” RadioButton,然后将“Medium”输入到 TextBox 中。完成后务必按 Tab 键,使其“生效”。看!“中” RadioButton 现在被选中了!我喜欢转换器。让我们看看发生了什么。

当您在 TextBox 中输入“Medium”时,每个绑定到相同 Shirt.Size 值的 RadioButton 都会被评估(因此在本例中再次评估了三次)。计算很简单——如果您输入到 TextBox 中的 string 与正在评估的 RadioButtonConverterParameter 匹配,则返回 true 值并选中 RadioButton。甚至更容易。

总结

我希望您阅读本文的乐趣不亚于我撰写本文的乐趣。如果这个故事有什么寓意,那就是永远不要将“这办不到”作为答案。有时,它只是意味着“还没有”人做到。弄清楚这一点当然不是什么了不起的壮举。在撰写本文时,没有脑细胞受到伤害。如果您觉得本文对您有价值,那么请通过在未来撰写您解决某个问题的方案来回报它。

历史

  • 2010年5月18日:初始版本
© . All rights reserved.