question

WillPittenger avatar image
0 Votes"
WillPittenger asked WillPittenger commented

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

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-wpf
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

I'm unable to find SelectedTypeDictionary in https://docs.microsoft.com/en-us/dotnet/api/system.windows.controls.combobox?view=netframework-4.8#properties (ComboBox Class (System.Windows.Controls) | Microsoft Docs).


0 Votes 0 ·

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.


0 Votes 0 ·
AlexLi-MSFT avatar image
0 Votes"
AlexLi-MSFT answered WillPittenger commented

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.


1.gif (53.2 KiB)
· 2
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

@AlexLi-MSFT, I now understand more of your code. Please reexamine my code. I not only set the content of the label, but the tooltip too. Is that possible?


0 Votes 0 ·

@AlexLi-MSFT, can I put controls in the map you showed? So enumType.x would map to a specific label instance.


0 Votes 0 ·
WillPittenger avatar image
0 Votes"
WillPittenger answered

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>
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.