c# - 动态生成、分组和绑定(bind)单选按钮到枚举

标签 c# wpf mvvm enums radiobuttonlist

我想在我的 ViewModel 上有一个 Enum,在这种情况下,它代表用户可以选择的一组操作。目前,我通过绑定(bind)到各自的枚举成员对 XAML 中的每个单选按钮进行硬编码,使用 IValueConverters 获取显示值并设置选定的枚举。
这很好用,但是,我希望它为 Enum 中的每个成员动态生成和(如果可能)对 RadioButtons 进行分组。理想情况下,我希望根据 Enum 的 GroupName 添加 Enum 成员的单选按钮并将其放置在它所属的组(扩展器)中。
我已经搜索并搜索了一种方法来实现这一点,但到目前为止还没有找到任何合适的东西,而且对我来说理解起来并不复杂。但也许有人在那里有一些意见可以帮助我或一个巧妙的解决方案。
枚举:

using System.ComponentModel.DataAnnotations;

namespace CommonLibrary.SystemSetup.Enums
{
    public enum UtilityOperation
    {
        [Display(Name="Reboot", GroupName ="Client Control")]
        ClientReboot,
        [Display(Name = "Shutdown", GroupName = "Client Control")]
        ClientShutdown,
        [Display(Name = "Remote Desktop", GroupName = "Client Control")]
        ClientRDPControl,
    }
}
看法:
<UserControl.Resources>
    <views:EnumToBoolConverter x:Key="EnumToBoolConverter" />
    <views:EnumToDisplayNameConverter x:Key="EnumToNameConverter" />
</UserControl.Resources>
    
<Expander Foreground="White" VerticalAlignment="Center" Background="Transparent" Margin="10 10 5 5">
                       
    <!-- Sub-menu header. -->
    <Expander.Header>
        <TextBlock Text="Client Control"/>
    </Expander.Header>
                        
    <StackPanel>
                        
        <RadioButton Margin="10 0 0 10"
        Content="{Binding Source={x:Static enum:UtilityOperation.ClientReboot}, Converter={StaticResource EnumToNameConverter}}"
        IsChecked="{Binding Path=E, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static enum:UtilityOperation.ClientReboot}}" />

        <RadioButton Margin="10 0 0 10"
        Content="{Binding Source={x:Static enum:UtilityOperation.ClientShutdown}, Converter={StaticResource EnumToNameConverter}}"
        IsChecked="{Binding Path=E, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static enum:UtilityOperation.ClientShutdown}}" />

        <RadioButton Margin="10 0 0 10"
        Content="{Binding Source={x:Static enum:UtilityOperation.ClientRDP}, Converter={StaticResource EnumToNameConverter}}"
        IsChecked="{Binding Path=E, Converter={StaticResource EnumToBoolConverter}, ConverterParameter={x:Static enum:UtilityOperation.ClientRDP}}" />
                       
    </StackPanel>

</Expander>
IValue 转换器
public class EnumToBoolConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return parameter != null && parameter.Equals(value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value != null && value.Equals(true) ? parameter : DependencyProperty.UnsetValue;
    }
}

public class EnumToDisplayNameConverter : MarkupExtension, IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return ((Enum)value).GetAttributeOfType<DisplayAttribute>().Name;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }

    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return this;
    }
}
}

最佳答案

这是我如何做类似事情的一个例子。基本方法是重新模板 ListBox并使用它来动态创建单选按钮。 viewmodel 包含要显示的值列表,这些值是基于枚举值创建的(一次,在初始化时)。那些绑定(bind)到 ListBox .
此代码是从一个真实的应用程序中截取的,但您可能需要对其进行调整才能编译/运行而不会出现错误。
如前所述,其中一些当然是基于我在 SO 上学到的东西。
首先 - 代表每个单选按钮条目的类:

    public class ListItem
    {
        public string Desc { get; set; }
        public EnumType ID { get; set; }
        public int SortOrder { get; set; }
    }
我们用来将数据附加到枚举的自定义属性:
[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
public class EnumMetadataAttribute : Attribute
{
    public string Description { get; set; }

    public int SortOrder { get; set; }

    public EnumMetadataAttribute(string desc)
    {
        Description = desc;
        SortOrder = 0;
    }

    public EnumMetadataAttribute(string desc, int sortOrder)
    {
        Description = desc;
        SortOrder = sortOrder;
    }
}
用于读取属性值的代码:
// based on https://stackoverflow.com/a/19621488/3195477
public static class EnumerationExtensions
{
    // This extension method is broken out so you can use a similar pattern with
    // other MetaData elements in the future. This is your base method for each.
    static T GetAttribute<T>(this Enum value) where T : Attribute
    {
        var type = value.GetType();
        var memberInfo = type.GetMember(value.ToString());
        var attributes = memberInfo[0].GetCustomAttributes(typeof(T), false);
        return attributes.Length > 0
          ? (T)attributes[0]
          : null;
    }

    static EnumMetadataAttribute GetEnumMetadataAttribute(this Enum value)
    {
        var attr = value.GetAttribute<EnumMetadataAttribute>();
        return attr ?? new EnumMetadataAttribute(value.ToString(), 0);
    }

    public static string GetDescription(this Enum value) => value.GetEnumMetadataAttribute().Description;

    public static int GetSortOrder(this Enum value) => value.GetEnumMetadataAttribute().SortOrder;
}
绝对有争议的是,使用静态对象列表来存储这种数据而不是属性枚举会更好;但这确实运作良好,并且具有我们可以固定到现有枚举上的优势。
示例枚举:
public enum EnumName
{
    [EnumMetadata("None", 1)]
    None,

    [EnumMetadata("Option2", 2)]
    Option2,

    [EnumMetadata("Option3", 3)]
    Option3,
}
自动循环遍历所有枚举值的代码:
    void InitRadioButtonList()
    {
        foreach (EnumType enumval in Enum.GetValues(typeof(EnumName)))
        {
            RadioButtonList.Add(new ListItem
            {
                ID = enumval,
                Item = enumval.GetDescription(),
                SortOrder = enumval.GetSortOrder(),
            });
        }
    }
在 View 模型类内部:
   public List<ListItem> RadioButtonList { get; } = new List<ListItem>();
   public List<ListItem> SortedRadioButtonList { get { return RadioButtonList.OrderBy(x => x.SortOrder); } }
这是绑定(bind)到上面创建的列表的 XAML:
                    <WrapPanel>
                        <Label>Field Name:</Label>

                        <ListBox
                            ItemsSource="{Binding SortedRadioButtonList}"
                            SelectedValuePath="ID"
                            SelectedValue="{Binding SortedRadioButtonList}"
                            Style="{StaticResource RadioButtonListBoxStyle}"
                            >
                            <ListBox.ItemTemplate>
                                <DataTemplate>
                                    <TextBlock Text="{Binding Desc}"/>
                                </DataTemplate>
                            </ListBox.ItemTemplate>

                        </ListBox>
                    </WrapPanel>
最后是ListBox风格:
<!--  https://stackoverflow.com/a/28571411/3195477  -->
<!--  https://stackoverflow.com/a/7487670/3195477  -->
<Style
    x:Key="RadioButtonListBoxStyle"
    TargetType="{x:Type ListBox}"
    >
    <Setter
        Property="BorderBrush"
        Value="Transparent"
        />
    <Setter
        Property="Background"
        Value="Transparent"
        />
    <Setter
        Property="KeyboardNavigation.DirectionalNavigation"
        Value="Cycle"
        />
    <Setter Property="ItemContainerStyle">
        <Setter.Value>
            <Style TargetType="{x:Type ListBoxItem}">
                <Setter
                    Property="Margin"
                    Value="2,2,2,0"
                    />
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate>
                            <Border Background="Transparent">
                                <RadioButton
                                    Content="{TemplateBinding ContentPresenter.Content}"
                                    ContentTemplate="{TemplateBinding ContentPresenter.ContentTemplate}"
                                    ContentTemplateSelector="{TemplateBinding ContentPresenter.ContentTemplateSelector}"
                                    IsChecked="{Binding Path=IsSelected, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
                                    />
                            </Border>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </Style>
        </Setter.Value>
    </Setter>
</Style>

更新 - 我最近阅读了 https://brianlagunas.com/a-better-way-to-data-bind-enums-in-wpf/它有一些相似之处和不同之处,也值得一读。

关于c# - 动态生成、分组和绑定(bind)单选按钮到枚举,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63792146/

相关文章:

.net - 在ASP.NET中生成图像缩略图?

c# - 将带有参数的命令附加到 KeyUp 事件

c# - 抽象 ViewModel 在被继承时是否被视为模型?

C# 将对象转换为十进制

wpf - 在用户将数据放入文本框之前,在文本框的焦点上显示工具提示

c# - WPF 中的命名命令

mvvm - RegisterType <>在Silverlight上不可见

C# 大型 JSON 转字符串导致内存不足异常

c# - 我可以确定有多少数据在传入的 TCP 客户端流上排队(在 C# 中)吗?

c# - 使用文本框上的 OnChange 事件进行简单显示