简体   繁体   中英

Bind UWP ComboBox ItemsSource to Enum

It's possible to use the ObjectDataProvider in a WPF application to bind an enum's string values to a ComboBox's ItemsSource, as evidenced in this question .

However, when using a similar snippet in a UWP application, the ff. error message is displayed:

"ObjectDataProvider is not supported in a Windows Universal project."

Is there a simple alternative to do this in UWP?

Below is a working example from one of my prototypes.

ENUM

public enum GetDetails
{
    test1,
    test2,
    test3,
    test4,
    test5
}

ItemsSource

var _enumval = Enum.GetValues(typeof(GetDetails)).Cast<GetDetails>();
cmbData.ItemsSource = _enumval.ToList();

This will bind combobox to Enum Values.

If you try to set your SelectedItem via xaml and Bindings, make sure that you set the ItemsSource first!

Example:

<ComboBox ItemsSource="{Binding ...}" SelectedItem="{Binding ...}"/>

Trust me, ComboBox and enum in UWP is a bad idea. Save yourself some time, don't use enum on a combobox in UWP. Spent hours trying to make it work. You can try the solutions mentioned in other answers but the problem you're going to get is that the propertychange won't fire when SelectedValue is bound to an enum. So I just convert it to int.

You can create a property in the VM and cast the enum GetDetails to int.

public int Details
{
  get { return (int)Model.Details; }
  set { Model.Details = (GetDetails)value; OnPropertyChanged();}
}

Then you can just work on a list of a class with int and string, not sure if you can use a KeyValuePair

public class DetailItem
{
  public int Value {get;set;}
  public string Text {get;set;}
}

public IEnumerable<DetailItem> Items
{
  get { return GetItems(); }
}

public IEnumerable<DetailItem> GetItems()
{
   yield return new DetailItem() { Text = "Test #1", Value = (int)GetDetails.test1 }; 
   yield return new DetailItem() { Text = "Test #2", Value = (int)GetDetails.test2 }; 
   yield return new DetailItem() { Text = "Test #3", Value = (int)GetDetails.test3 }; 
   // ..something like that
}

Then on the Xaml

<Combobox ItemsSource="{Binding Items, UpdateSourceTrigger=PropertyChanged}"
 SelectedValue="{Binding Details, UpdateSourceTriggerPropertyChanged, Mode=TwoWay}"
 SelectedValuePath="Value" 
 DisplayMemberPath="Text" />

I know this is an old post, but Bind the SelectedIndex of your combobox to the enum property and define your value converter like this,

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        string v = value.ToString();
        string[] vs = Enum.GetNames(typeof(YourEnumType));
        int index = vs.IndexOf(v);
        if (index > -1)
            return index;
        return 0;
    }


    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        int index = (int)value;
        if (index > -1)
            return (YourEnumType)index;
        return YourEnumType.DefaultValue;
    }

Here is the simplest and most generic way of enum binding I have come with for UWP. Maybe this can help someone as this is easy to bind and localize.

/// <summary>
///     Helper class to bind an Enum type as an control's ItemsSource.
/// </summary>
/// <typeparam name="T"></typeparam>
public class EnumItemsSource<T> where T : struct, IConvertible
{
    public string FullTypeString { get; set; }
    public string Name { get; set; }
    public string LocalizedName { get; set; }
    public T Value { get; set; }

    /// <summary>
    /// Constructor.
    /// </summary>
    /// <param name="name"></param>
    /// <param name="value"></param>
    /// <param name="fullString"></param>
    public EnumItemsSource(string name, T value, string fullTypeString)
    {
        if (!typeof(T).IsEnum)
            throw new ArgumentException("EnumItemsSource only accept Enum type.");

        Name = name;
        Value = value;
        FullTypeString = fullTypeString;

        // Retrieve localized name
        LocalizedName = AppResource.GetResource(FullTypeString.Replace('.', '-'));
    }

    /// <summary>
    ///     Create a list of EnumItemsSource from an enum type.
    /// </summary>
    /// <returns></returns>
    public static List<EnumItemsSource<T>> ToList()
    {
        // Put to lists
        var namesList = Enum.GetNames(typeof(T));
        var valuesList = Enum.GetValues(typeof(T)).Cast<T>().ToList();

        // Create EnumItemsSource list
        var enumItemsSourceList = new List<EnumItemsSource<T>>();
        for (int i = 0; i < namesList.Length; i++)
            enumItemsSourceList.Add(new EnumItemsSource<T>(namesList[i], valuesList[i], $"{typeof(T).Name}.{namesList[i]}"));

        return enumItemsSourceList;
    }
}

In ViewModel :

    public List<EnumItemsSource<AccountType>> AccountTypes
    {
        get => EnumItemsSource<AccountType>.ToList();
    }

In View :

<ComboBox ItemsSource="{Binding AccountTypes}" DisplayMemberPath="LocalizedName" SelectedValuePath="Value" SelectedValue="{Binding Path=Account.Type, Mode=TwoWay}" />

ComboBox with ItemSource to Enum, also with SelectedItem . And with option to replace Enum's name with custom string (eg translation). Respecting MVVM pattern.

Enum:

public enum ChannelMark
{
   Undefinned,Left, Right,Front, Back
}

ViewModel

private ChannelMark _ChannelMark = ChannelMark.Undefinned;

public ChannelMark ChannelMark
{
    get => _ChannelMark;
    set => Set(ref _ChannelMark, value);
}

private List<int> _ChannelMarksInts = Enum.GetValues(typeof(ChannelMark)).Cast<ChannelMark>().Cast<int>().ToList();

public List<int> ChannelMarksInts
{
    get => _ChannelMarksInts;
    set => Set(ref _ChannelMarksInts, value);
}

XAML

<ComboBox ItemsSource="{x:Bind ViewModel.ChannelMarksInts}"  SelectedItem="{x:Bind ViewModel.ChannelMark, Converter={StaticResource ChannelMarkToIntConverter}, Mode=TwoWay}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding  Converter={StaticResource IntToChannelMarkConverter}}"/>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Converters:

switch ((ChannelMark)value)
{
    case ChannelMark.Undefinned:
        return "Undefinned mark";
    case ChannelMark.Left:
        //translation
        return Windows.ApplicationModel.Resources.ResourceLoader.GetForCurrentView().GetString("ChannelMarkEnumLeft");
    case ChannelMark.Right:
        return "Right Channel";
    case ChannelMark.Front:
        return "Front Channel";
    case ChannelMark.Back:
        return "Back Channel";
    default:
        throw new NotImplementedException();
}



public class IntToChannelMarkConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language) => ((ChannelMark)value).ToString();
    public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
}

I used Karnaltas answer because it supported easy localisation with resx files. But I had trouble with the two way binding from and back to my entity. I solved this by changing the binding to use the SelectedIndex and adding a Converter. This way the combobox correctly picks up the initial state as well wenn it is displayed.

ViewModel stays like the solution from Karnalta.

In the view change the binding:

<ComboBox ItemsSource="{Binding AccountTypes}" DisplayMemberPath="LocalizedName" SelectedIndex={x:Bind ViewModel.Account.Type, Mode=TwoWay, Converter={StaticResource EnumToIntConverer}" />

New Converter:

public class EnumToIntConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {           
        return (int)value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {            
        return Enum.Parse(targetType, value.ToString());
    }
}

And don't forget to add your converter in your page/control:

<Grid.Resources>
    <ResourceDictionary>
        <converters:EnumToIntConverter x:Key="EnumToIntConverter" />
    </ResourceDictionary>
</Grid.Resources>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM