当前位置: 首页 > 知识库问答 >
问题:

枚举类型的属性打破了该类型的依赖属性

许亦
2023-03-14

我已经定义了一个枚举类型,详细描述了用于给灰度图像着色的各种调色板,为此我使用了描述属性和TypeConver,以便使用我绑定到该类型的组合框、列表框等枚举值的描述字符串。枚举看起来像这样:

    // available color palettes for colorizing 8 bit grayscale images
    [TypeConverter(typeof(EnumDescriptionTypeConverter))]
    public enum ColorPalette
    {
        [Description("Alarm Blue")]
        AlarmBlue,
        [Description("Alarm Blue High")]
        AlarmBlueHi,
        [Description("Alarm Green")]
        AlarmGreen,
        [Description("Alarm Red")]
        AlarmRed,
        [Description("Fire")]
        Fire,
        [Description("Gray BW")]
        GrayBW,
        [Description("Ice 32")]
        Ice32,
        [Description("Iron")]
        Iron,
        [Description("Iron High")]
        IronHi,
        [Description("Medical 10")]
        Medical10,
        [Description("Rainbow")]
        Rainbow,
        [Description("Rainbow High")]
        RainbowHi,
        [Description("Temperature 256")]
        Temperature256,
        [Description("Nano Green")]
        NanoGreen
    };

EnumDescriptionTypeConverter如下所示:

public class EnumDescriptionTypeConverter : EnumConverter
    {
        public EnumDescriptionTypeConverter(Type type) : base(type) { }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string))
            {
                if (value != null)
                {
                    FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
                    if (fieldInfo != null)
                    {
                        var attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
                        return ((attributes.Length > 0) && (!string.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString();
                    }
                }
                return string.Empty;
            }

            return base.ConvertTo(context, culture, value, destinationType);
        }
    }

使用它,我可以绑定enum类型,比如组合框的ItemsSource属性,并让描述字符串自动用作组合框元素,使用另一个自定义标记扩展类,我认为其代码与此无关。问题是,如果我尝试基于此枚举类型在自定义控件上创建公共依赖属性,它将无法工作。下面是一个自定义控件示例:

    public class TestControl : Control
    {
        public ColorPalette Test1
        {
            get => (ColorPalette)GetValue(Test1Property);
            set => SetValue(Test1Property, value);
        }

        public static readonly DependencyProperty Test1Property = DependencyProperty.Register(nameof(Test1), typeof(ColorPalette),
            typeof(TestControl), new PropertyMetadata
            {
                DefaultValue = ColorPalette.Rainbow
            });
    }

这段代码编译没有错误,我可以把TestControl放入一个窗口,直到我尝试在XAML中设置测试属性的值-然后我没有得到通常的包含枚举值的IntelliSense,并且当我尝试手动设置一个值时,我在运行应用程序时就会得到一个访问违规异常,就在主窗口的初始化组件()方法处:

"在. exe: 0xC000000007FF84723A799(KernelBase.dll)引发的异常:访问冲突读取位置0x0000000000000008."

当我从枚举定义中删除TypeConverter属性时,不会发生这种情况,但是描述字符串绑定当然不再有效。

我对WPF了解不多,不知道到底是什么问题。有没有办法避免这种情况,并且仍然使用TypeConverter使用描述字符串属性进行绑定?

共有2个答案

亢嘉茂
2023-03-14

你必须使用依赖属性吗?

对于这种情况,我在XAML代码中使用了带有Enum对象和IValueConverter的ViewModel

Enum类型的ViewModel示例

public abstract class VM_PropertyChanged : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChange(string propertyName)
    {
        var handler = PropertyChanged;
        if (PropertyChanged != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

public class VM_EnumItem<T> : VM_PropertyChanged
{

    public T Enum { get; }

    public bool IsEnabled
    {
        get { return isEnabled; }
        set { isEnabled = value; OnPropertyChange(nameof(IsEnabled)); }
    }
    private bool isEnabled;

    public VM_EnumItem(T Enum, bool IsEnabled)
    {
        this.Enum = Enum;
        this.IsEnabled = IsEnabled;
    }

    public override int GetHashCode()
    {
        return Enum.GetHashCode();
    }

    public override bool Equals(object obj)
    {
        if (obj != null && obj is VM_EnumItem<T> item)
            return System.Enum.Equals(item.Enum, this.Enum);
        return false;
    }

    public override string ToString()
    {
        return string.Format("{0} | {1}", Enum, IsEnabled);
    }
 
}

用于WPF控件的ViewModel示例

class ViewModel : VM_PropertyChanged
{

    public enum ColorPalette
    {
        [Description("Alarm Blue")]
        AlarmBlue,
        [Description("Alarm Blue High")]
        AlarmBlueHi
    }
    // all options
    public ObservableCollection<VM_EnumItem<ColorPalette>> EnumItems { get; } = new ObservableCollection<VM_EnumItem<ColorPalette>>()
    {
           new VM_EnumItem<ColorPalette>(ColorPalette.AlarmBlue, true),
           new VM_EnumItem<ColorPalette>(ColorPalette.AlarmBlueHi, true)
     };

    public VM_EnumItem<ColorPalette> SelectedEnumItem
    {
        get { return EnumItems.Where(s => s.Enum == SelectedEnum).FirstOrDefault(); }
        set { SelectedEnum = value.Enum; OnPropertyChange(nameof(SelectedEnumItem)); }
    }

    private ColorPalette SelectedEnum; // your selected Enum
}

转换器示例

public class VM_Converter_EnumDescription : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        Type type = value.GetType();
        if (!type.IsEnum)
            return value;

        string name = Enum.GetName(type, value);
        FieldInfo fi = type.GetField(name);
        DescriptionAttribute descriptionAttrib = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));

        return descriptionAttrib == null ? value.ToString() : descriptionAttrib.Description;
    }

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

WPF控件示例

<Window.Resources>
    <ResourceDictionary >
        <local:VM_Converter_EnumDescription x:Key="Converter_EnumDescription"/>
    </ResourceDictionary>
</Window.Resources>

////////////

    <ComboBox 
        ItemsSource="{Binding Path=EnumItems, Mode=OneWay}"
        SelectedItem="{Binding Path=SelectedEnumItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <ComboBox.ItemTemplate>
            <DataTemplate>
                <ContentPresenter Content="{Binding Path=Enum, Converter={StaticResource Converter_EnumDescription}}"/>
            </DataTemplate>
        </ComboBox.ItemTemplate>
        <ComboBox.ItemContainerStyle>
            <Style TargetType="{x:Type ComboBoxItem}">
                <Setter Property="IsEnabled" Value="{Binding Path=IsEnabled}"/>
            </Style>
        </ComboBox.ItemContainerStyle>
    </ComboBox>
东方俊杰
2023-03-14

因此,我找到了一种解决方法,使用另一种MarkupExtension作为枚举类型的绑定源:

    public class EnumDescriptionBindingSourceExtension : MarkupExtension
    {
        public Type EnumType
        {
            get => enumType;
            set
            {
                if (enumType != value)
                {
                    if (value != null)
                    {
                        Type type = Nullable.GetUnderlyingType(value) ?? value;
                        if (!type.IsEnum)
                            throw new ArgumentException("Type must be an enum type");
                    }
                    enumType = value;
                }
            }
        }

        private Type enumType;

        public EnumDescriptionBindingSourceExtension() { }

        public EnumDescriptionBindingSourceExtension(Type enumType) => this.enumType = enumType;

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (enumType == null)
                throw new InvalidOperationException("The enum type must be specified");

            Type actualEnumType = Nullable.GetUnderlyingType(enumType) ?? enumType;
            Array enumValues = Enum.GetValues(actualEnumType);

            if (actualEnumType == enumType)
            {
                List<string> descriptions = new List<string>(enumValues.Length);
                foreach (object value in enumValues)
                {
                    FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
                    if (fieldInfo != null)
                    {
                        DescriptionAttribute[] attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
                        descriptions.Add(((attributes.Length > 0) && !string.IsNullOrEmpty(attributes[0].Description)) ? attributes[0].Description : value.ToString());
                    }
                }
                return descriptions;
            }
            else
            {
                Array tempArray = Array.CreateInstance(actualEnumType, enumValues.Length + 1);
                enumValues.CopyTo(tempArray, 1);
                return tempArray;
            }
        }
    }

这个扩展返回枚举值的描述字符串的数组(如果有的话,否则只返回value. ToString())。当在XAML绑定中使用这个时,我可以让我的组合框直接填充枚举值描述,而以前我会使用一个标记扩展,它只返回枚举值本身的数组,并由TypeConader完成对其描述字符串的转换。

当使用这个新的标记扩展时,我必须使用一个转换器,它可以从描述字符串中确定原始枚举值:

public class EnumDescriptionConverter : IValueConverter
    {
        object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is Enum enumObject)
            {
                FieldInfo fieldInfo = enumObject.GetType().GetField(enumObject.ToString());
                object[] attributes = fieldInfo.GetCustomAttributes(false);

                if (attributes.Length == 0)
                    return enumObject.ToString();
                else
                {
                    DescriptionAttribute attribute = attributes[0] as DescriptionAttribute;
                    return attribute.Description;
                }
            }
            else
                throw new ArgumentException($"Conversion is only defined for enum types");
        }

        object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value is string valString)
            {
                Array enumValues = targetType.GetEnumValues();
                FieldInfo fieldInfo;
                DescriptionAttribute[] attributes;
                string target;
                foreach (object enumValue in enumValues)
                {
                    fieldInfo = enumValue.GetType().GetField(enumValue.ToString());
                    if(fieldInfo != null)
                    {
                        attributes = (DescriptionAttribute[])fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false);
                        target = ((attributes.Length == 1) && !string.IsNullOrEmpty(attributes[0].Description)) ? attributes[0].Description : enumValue.ToString();
                        if (valString == target)
                            return enumValue;
                    }
                }
                throw new ArgumentException($"Back-conversion failed - no enum value corresponding to string");
            }
            else
                throw new ArgumentException($"Back-conversion is only defined for string type");
        }
    }

有了这两者,我可以在XAML中做以下示例:

<ns:EnumDescriptionConverter x:Key="enumDescriptionConverter"/>
(...)
<ComboBox ItemsSource="{Binding Source={ns:EnumDescriptionBindingSource {x:Type ns:MyEnumType}}, Mode=OneTime}" SelectedItem="{Binding MyEnumTypeProperty, Converter={StaticResource enumDescriptionConverter}}"/>

它将自动用描述字符串表示的枚举值填充组合框,并将选定项绑定到该类型的属性。这样就可以在不设置枚举定义的TypeConverter属性的情况下工作,因此不会出现我最初的问题。

我仍然不明白为什么会发生这种事,或者是否有更好的方法来解决它,但嘿,它是有效的。

 类似资料:
  • 您好,我正在建立一个动物园微服务,包括动物、员工、客户和价格表。我的动物微服务可以工作,但在我的员工微服务中,我遇到了一个错误,即没有为类型“EmployeeModel”找到属性“name”。在问这个问题之前,我已经在网上搜索了几个小时,还有一些类似的问题。我在模型中没有“名字”employee_name,我只是感到困惑,不知道如何修复它。任何指导/建议/信息将不胜感激:)第一次发布,所以我希望我

  • 问题内容: 我已经编写了一个枚举类,我想按类型获取属性或按属性获取类型,但这似乎是不可能的。 上面的代码将无法编译。如何上班? 问题答案: 您需要做的就是添加一个默认情况,以便该方法始终返回某些内容或引发异常: 也许更好

  • 1. 前言 上一节,我们演示了如何使用 xml 文件配置,实现属性的依赖注入。但是,注入的依赖类型只是作为演示使用的两种, 而一个类中的属性,可能会多种多样。那么,xml 配置文件如何实现其余类型的属性注入呢? 我们进入本节的学习内容。 2. 多种类型依赖注入 2.1 属性类型分类 基本类型包装类,比如 Integer、Double、Boolean; 字符串类型,比如 String; 类类型,比如

  • Dependencies serve many different purposes. Some dependencies are needed to build your project, others are needed when you’re running your program. As such there are a number of different types of dep

  • 问题内容: 例如,我该怎么做: 结果示例: 问题答案: 迅捷4.2+ 从Swift 4.2(使用Xcode 10)开始,只需添加协议一致性即可从中受益。要添加此协议一致性,您只需要在某处写: 如果枚举是您自己的,则可以直接在声明中指定一致性: 然后,以下代码将打印所有可能的值: 与早期Swift版本(3.x和4.x)的兼容性 如果您需要支持Swift 3.x或4.0,则可以通过添加以下代码来模仿S

  • 枚举类(“新的枚举”/“强类型的枚举”)主要用来解决传统的C++枚举的三个问题: 传统C++枚举会被隐式转换为int,这在那些不应被转换为int的情况下可能导致错误 传统C++枚举的每一枚举值在其作用域范围内都是可见的,容易导致名称冲突(同名冲突) 不可以指定枚举的底层数据类型,这可能会导致代码不容易理解、兼容性问题以及不可以进行前向声明 枚举类(enum)(“强类型枚举”)是强类型的,并且具有类