Bind UWP ComboBox ItemsSource to Enum
Asked Answered
H

8

13

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?

Homoiousian answered 29/8, 2016 at 15:25 Comment(0)
N
16

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.

Nut answered 29/8, 2016 at 16:8 Comment(2)
Thank you for the input. Do you know if there is a XAML equivalent to this that i can make use of?Homoiousian
Pretty simple. Create a view model which returns the strings. And on your UI set the data context and Binding in XAML.Nut
L
4

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" />
Lab answered 27/3, 2020 at 22:7 Comment(0)
A
3

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 ...}"/>
Armillia answered 5/9, 2017 at 15:51 Comment(0)
S
3

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}" />
Sorn answered 28/11, 2020 at 11:58 Comment(0)
K
2

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;
    }
Kliman answered 4/9, 2020 at 11:14 Comment(0)
J
1

ComboBox with ItemSource to Enum, also with SelectedItem. And with option to replace Enum's name with custom string (e.g. 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();
}

Jarrett answered 29/9, 2019 at 8:12 Comment(0)
T
1

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>
Triadelphous answered 12/3, 2021 at 9:37 Comment(0)
L
0

Let me post another option.

Let's say we want a ComboBox populated with the NavigationViewPaneDisplayMode enum.

This class will hold the items in the target enum.

public class EnumOptions<T> : ReadOnlyCollection<T> where T : Enum
{
    public EnumOptions() : this(Enum.GetValues(typeof(T)).Cast<T>().ToArray())
    {
    }

    private EnumOptions(IList<T> list) : base(list)
    {
    }
}

We need this class to use EnumOptions<T> in XAML.

public class NavigationViewPaneDisplayModeOptions : EnumOptions<NavigationViewPaneDisplayMode>
{
}

This class is optional.

public partial class EnumCamelCaseToSpacedStringConverter : IValueConverter
{
    public bool OnlyTheFirstWordFirstCharacterIsUpperCase { get; set; }

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value?.ToString() is string stringValue)
        {
            if (stringValue.Length is 0)
            {
                return string.Empty;
            }

            string spacedString = Regex.Replace(stringValue, "(\\B[A-Z])", " $1");

            if (OnlyTheFirstWordFirstCharacterIsUpperCase is false)
            {
                return spacedString;
            }

            string onlyTheFirstCharacterUpperCase = $"{spacedString[0].ToString()?.ToUpper()}{spacedString.Substring(1).ToLower()}";
            return onlyTheFirstCharacterUpperCase;
        }

        throw new ArgumentException("Value must be a string", nameof(value));
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language) => throw new NotImplementedException();
}

And we use it like this:

<Page.Resources>
    <local:NavigationViewPaneDisplayModeOptions x:Key="NavigationViewPaneDisplayModeOptions" />
    <local:EnumCamelCaseToSpacedStringConverter
        x:Key="CamelCaseStringConverter"
        OnlyTheFirstWordFirstCharacterIsUpperCase="True" />
</Page.Resources>

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <ComboBox
        x:Name="NavigationViewPaneDisplayModeComboBox"
        Grid.Row="0"
        ItemsSource="{StaticResource NavigationViewPaneDisplayModeOptions}"
        SelectedIndex="0">
        <ComboBox.ItemTemplate>
            <DataTemplate x:DataType="NavigationViewPaneDisplayMode">
                <TextBlock Text="{x:Bind Converter={StaticResource CamelCaseStringConverter}}" />
            </DataTemplate>
        </ComboBox.ItemTemplate>
    </ComboBox>
    <NavigationView
        Grid.Row="1"
        PaneDisplayMode="{x:Bind (NavigationViewPaneDisplayMode)NavigationViewPaneDisplayModeComboBox.SelectedValue, Mode=OneWay}">
        <NavigationView.MenuItems>
            <NavigationViewItem Content="Menu 1" />
            <NavigationViewItem Content="Menu 2" />
            <NavigationViewItem Content="Menu 3" />
        </NavigationView.MenuItems>
    </NavigationView>
</Grid>
Licit answered 28/7, 2023 at 2:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.