Combobox with dynamically created items for enum type with text coming from Attributes won't support binding of SelectedItem

Will Pittenger 306 Reputation points
2020-03-18T17:42:52.56+00:00

I have a custom WPF combobox that generates its contents based on an enum type it's told about via a XAML attribute. When that attribute sets the corresponding property, the control calls the function below.
private void OnEnumTypePropChanged(string strNewVal)
{
System.Type typeProposed = System.Type.GetType(strNewVal);

    if(typeProposed == typeOfEnum)
        return;

    if(typeProposed == null || !typeProposed.IsEnum)
        return;

    typeOfEnum = typeProposed;

    foreach(object objCurValInEnumType in typeOfEnum.GetEnumValues())
    {
        System.Windows.Controls.Label labelForCurValInEnumType = new System.Windows.Controls.Label();

        GetEnumDesc(objCurValInEnumType, out string strText, out string strTooltip);

        labelForCurValInEnumType.Content = strText;
        labelForCurValInEnumType.ToolTip = strTooltip;
        labelForCurValInEnumType.Tag = objCurValInEnumType;
        labelForCurValInEnumType.HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch;
        labelForCurValInEnumType.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Center;
        labelForCurValInEnumType.FontFamily = FontFamily;
        labelForCurValInEnumType.FontSize = FontSize;
        labelForCurValInEnumType.FontStretch = FontStretch;
        labelForCurValInEnumType.FontStyle = FontStyle;
        labelForCurValInEnumType.FontWeight = FontWeight;
        labelForCurValInEnumType.UpdateLayout();

        Items.Add(labelForCurValInEnumType);
    }

    UpdateLayout();
}

GetEnumDesc looks up the attributes on each value in the enum type. That then is used to configure the label that's actually shown. But now binding to the selected value doesn't work. Is there a way I can tell the underlying control how to get the value for the label?

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,784 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Alex Li-MSFT 1,096 Reputation points
    2020-03-19T05:40:40.82+00:00

    Hi,

    Welcome to our Microsoft Q&A platform!

    You can see my code,use Lable to bind SelectedValue:

    Xaml:

    <StackPanel>  
            <ComboBox Name="comboBox1" Width="150" ItemsSource="{Binding SelectedTypeDictionary}" DisplayMemberPath="Value" SelectedValuePath="Key" SelectedValue="{Binding ComboBoxSelectedValue}"/>  
              
            <Label Width="150" Content="{Binding ComboBoxSelectedValue}" Margin="30"></Label>  
        </StackPanel>  
    

    C# code:

    public partial class MainWindow : Window,INotifyPropertyChanged  
    {  
        public event PropertyChangedEventHandler PropertyChanged;  
        public object _comboBoxSelectedValue;  
        public object ComboBoxSelectedValue  
        {  
            get { return _comboBoxSelectedValue; }  
            set { _comboBoxSelectedValue = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("ComboBoxSelectedValue")); }  
        }  
        public Dictionary<Enum_Type, string> SelectedTypeDictionary { get; set; }     
        public MainWindow()  
        {  
            InitializeComponent();  
            SelectedTypeDictionary = new Dictionary<Enum_Type, string>();  
            SelectedTypeDictionary.Add(Enum_Type.SelectedOne, GetDescription(Enum_Type.SelectedOne));  
            SelectedTypeDictionary.Add(Enum_Type.SelectedTwo, GetDescription(Enum_Type.SelectedTwo));  
            SelectedTypeDictionary.Add(Enum_Type.SelectedThree,GetDescription(Enum_Type.SelectedThree));  
      
            this.DataContext = this;  
        }  
      
        public static T GetEnumAttribute<T>(Enum source) where T : Attribute  
        {  
            Type type = source.GetType();  
            var sourceName = Enum.GetName(type, source);  
            FieldInfo field = type.GetField(sourceName);  
            object[] attributes = field.GetCustomAttributes(typeof(T), false);  
            foreach (var o in attributes)  
            {  
                if (o is T)  
                    return (T)o;  
            }  
            return null;  
        }  
      
        public static string GetDescription(Enum source)  
        {  
            var str = GetEnumAttribute<DescriptionAttribute>(source);  
            if (str == null)  
                return null;  
            return str.Description;  
        }  
      
      
    }  
      
    public enum Enum_Type  
    {  
        [Description("test1")]  
        SelectedOne,  
        [Description("test2")]  
        SelectedTwo,  
        [Description("test3")]  
        SelectedThree,  
    }  
    

    4991-1.gif

    Thanks.


  2. Will Pittenger 306 Reputation points
    2020-03-21T10:03:41.937+00:00

    Well, I thought of building a list of a custom type that would be an intermediary type. So I rewrote my class to look like https://pastebin.com/x7cyaJmu . This doesn't do any better than what I had before. Still won't select. I tried changing entries member to be a HashTable with the key being the value. That caused the labels to be blank. The XAML code in the second sample is required for this to work.

    namespace Org.WillPittenger.YouTubeDownloader.Ctrls.ForEnums
    {
        public partial class ComboBox : System.Windows.Controls.ComboBox, System.ComponentModel.INotifyPropertyChanged
        {
            #region Constructors & Destructors
                public ComboBox()
                {
                    ItemsSource = entries = new System.Collections.Generic.List<OneEntry>();
    
                    InitializeComponent();
                }
            #endregion
    
            #region Events
                public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
            #endregion
    
            #region Constants
                private static class PropNames
                {
                    public const string strEnumType = "EnumType";
                }
            #endregion
    
            #region Helper Types
                private class OneEntry
                {
                    #region Constructors & Deconstructors
                        public OneEntry(object enumVal, string strDisplayText, string strTooltipText)
                        {
                            System.Diagnostics.Debug.Assert(enumVal.GetType().IsEnum);
    
                            this.enumVal = enumVal;
                            this.strDisplayText = strDisplayText;
                            this.strTooltipText = strTooltipText;
                        }
                    #endregion
    
                    #region Members
                        private readonly object enumVal;
                        private readonly string strDisplayText;
                        private readonly string strTooltipText;
                    #endregion
    
                    #region Properties
                        public object EnumVal => enumVal;
                        public string DisplayText => strDisplayText;
                        public string TooltipText => strTooltipText;
                    #endregion
                }
            #endregion
    
            #region Members
                #region Dependency Properties
                    public static readonly System.Windows.DependencyProperty EnumTypeProperty = System.Windows.DependencyProperty.Register(PropNames.strEnumType,
                        typeof(string), typeof(ComboBox), new System.Windows.PropertyMetadata(OnEnumTypePropChanged));
                #endregion
    
                private System.Type typeOfEnum = null;
    
                private System.Collections.Generic.List<OneEntry> entries;
            #endregion
    
            #region Properties
                [System.ComponentModel.Description("This is the enum type that the items are based on.  Each one must have Org.WillPittenger.AdBlockPlusIniParserWPF.Core.Attr"
                    + ".LocalizedDescAttribute applied to it.")]
                [System.ComponentModel.Category("Common")]
                public string EnumType
                {
                    get => (string)GetValue(EnumTypeProperty);
    
                    set => SetValue(EnumTypeProperty, value);
                }
            #endregion
    
            #region Methods
                private void GetEnumDesc(object objEnumValue, out string strDesc, out string strExtendedDesc)
                {
                    System.Reflection.FieldInfo fieldInfo = objEnumValue.GetType().GetField(objEnumValue.ToString());
    
                    object[] attribArray = fieldInfo.GetCustomAttributes(typeof(Attr.LocalizedDescAttribute), false);
    
                    if(attribArray.Length == 0)
                    {
                        strDesc = objEnumValue.ToString();
                        strExtendedDesc = "";
                    }
                    else
                    {
                        Attr.LocalizedDescAttribute attrib = attribArray[0] as Attr.LocalizedDescAttribute;
    
                        strDesc = attrib.Desc;
                        strExtendedDesc = attrib.ExtendedDesc;
                    }
                }
            #endregion
    
            #region Event Handlers
                private void OnEnumTypePropChanged(string strNewVal)
                {
                    System.Type typeProposed = System.Type.GetType(strNewVal);
    
                    if(typeProposed == typeOfEnum)
                        return;
    
                    if(typeProposed == null || !typeProposed.IsEnum)
                        return;
    
                    typeOfEnum = typeProposed;
    
                    entries.Capacity = typeOfEnum.GetEnumValues().Length;
    
                    foreach(object objCurValInEnumType in typeOfEnum.GetEnumValues())
                    {
                        GetEnumDesc(objCurValInEnumType, out string strText, out string strTooltip);
    
                        entries.Add(new OneEntry(objCurValInEnumType, strText, strTooltip));
                    }
    
                    UpdateLayout();
                }
    
                private static void OnEnumTypePropChanged(System.Windows.DependencyObject ctrlParent, System.Windows.DependencyPropertyChangedEventArgs e) =>
                    ((ComboBox)ctrlParent).OnEnumTypePropChanged((string)e.NewValue);
    
                protected override void OnSelectionChanged(System.Windows.Controls.SelectionChangedEventArgs e) => base.OnSelectionChanged(e);
            #endregion
        }
    }
    
    <ComboBox
        x:Class="Org.WillPittenger.YouTubeDownloader.Ctrls.ForEnums.ComboBox"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="clr-namespace:Org.WillPittenger.YouTubeDownloader.Ctrls.ForEnums"
        mc:Ignorable="d"
        d:DesignHeight="450"
        d:DesignWidth="800"
        ItemTemplate="{DynamicResource OurItemTemplate}">
        <ComboBox.Resources>
            <DataTemplate x:Key="OurItemTemplate">
                <Label
                    Content="{Binding DisplayText}"
                    ToolTip="{Binding TooltipText}" />
            </DataTemplate>
        </ComboBox.Resources>
    </ComboBox>
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.