Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Binding Enum Values as ItemsSource in ComboBox not working #3875

Open
Berkunath opened this issue Jan 3, 2021 · 9 comments
Open

Binding Enum Values as ItemsSource in ComboBox not working #3875

Berkunath opened this issue Jan 3, 2021 · 9 comments
Labels
area-ComboBox bug Something isn't working product-winui3 WinUI 3 issues team-Reach Issue for the Reach team

Comments

@Berkunath
Copy link

Berkunath commented Jan 3, 2021

Binding enum Values as ItemsSource in ComboBox not working
I have several enum type in my library and trying to display the enum values in WinUI Desktop ComboBox by get collection using Enum.GetValues by passing Type to EnumValuesExtension class. It works for enum from Microsoft.UI.Xaml, But enum from my library not works.

Observed Items:

  1. Could not send the enum types other than Enum from Framework through Xaml.
  2. After send the enum type FullName as string and parse it as Type, We can get the collection of items using Enum.GetValues. But Opening ComboBox crashes the application.

Steps to reproduce the bug
Comment out the SizeMode and Shape ComboBox and run the sample. Sample run without crash and display the Horizontal Alignment values in ComboBox. But Running the sample with Shape and SizeMode leads to Crash.
Steps to reproduce the behavior:

  1. Run the attached sample
  2. Application crash due to casting to type set value as null in EnumType

Expected behavior
Shape and SizeMode Enum values need to be displayed in ComboBox

NuGet package version:
[Microsoft.WinUI 3.0.0-preview3.201113.0]

Sample :
ComboBoxwithEnumvalues.zip

@ghost ghost added the needs-triage Issue needs to be triaged by the area owners label Jan 3, 2021
@StephenLPeters
Copy link
Contributor

@stevenbrix or @Scottj1s this sounds like a cswinrt issue to me

@StephenLPeters StephenLPeters added product-winui3 WinUI 3 issues area-ComboBox team-Controls Issue for the Controls team and removed needs-triage Issue needs to be triaged by the area owners labels Jan 5, 2021
@Berkunath
Copy link
Author

@StephenLPeters Any work around solution for this?

@StephenLPeters
Copy link
Contributor

@stevenbrix can you weigh in?

@Scottj1s
Copy link
Member

sounds like this issue:
microsoft/CsWinRT#605

@stevenbrix
Copy link
Contributor

@Berkunath, could you convert the enum values to a list of string:

        protected override object ProvideValue()
        {   
            var values = Enum.GetNames(EnumType);
            return values;
        }

@Scottj1s
Copy link
Member

This is not a C#/WinRT issue - it repros with UWP apps as well. Appears that the Xaml compiler doesn't property support local types resolution.

I tried several other syntax variations, like:
ItemsSource="{local:EnumValues EnumType={x:Type local:Shape}}"
and:
ItemsSource="{local:EnumValues EnumType={x:TypeExtension local:Shape}}"
and element syntax:
<ComboBox.ItemsSource>
<local:EnumValues EnumType="{x:Type local:Shape}" />
</ComboBox.ItemsSource>

These all produced "XamlCompiler error WMC0001: Unknown type 'Type' ..."

@RealTommyKlein - any insights here?

@RealTommyKlein
Copy link
Contributor

The syntax in the repro is correct (e.g. ItemsSource="{local:EnumValuesExtension EnumType=local:SizeMode}"). However the Xaml compiler doesn't create type information for SizeMode in its generated XamlMetadataProvider when it only appears as a proeprty assignment, so the framework is unable to get the System.Type for SizeMode and tries to set null instead (see #4161 as well). As a workaround, you can add a dummy Bindable class with the types you set in markup as properties, like this:

    [Microsoft.UI.Xaml.Data.Bindable]
    public class DummyClass
    {
        public Shape DummyShape { get; set; }
        public SizeMode DummySizeMode { get; set; }
    }

Although trying this in the repro project, it looks like there's another issue in the framework where it doesn't handle enums correctly. Using @stevenbrix's suggestion, I was able to get it working.

MainWindow.xaml:

<Window
    x:Class="App2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App2"    
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">


    <StackPanel Orientation="Horizontal"  VerticalAlignment="Center">
        <StackPanel.Resources>
            <local:EnumDisplayNameConverter x:Key="enumDisplayNameConverter" />
        </StackPanel.Resources>
        <Button x:Name="myButton" Click="myButton_Click">Click Me</Button>

        <ComboBox
                            x:Name="Shape"
                            Margin="16,5,5,5"
                            Header="Shape"           
                            ItemsSource="{local:EnumValuesExtension EnumType=local:Shape}"
            SelectedIndex="1">
            <ComboBox.ItemTemplate>
                <DataTemplate x:DataType="x:String">
                    <TextBlock VerticalAlignment="Center" Text="{x:Bind}" />
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>

        <ComboBox
                            x:Name="sizeMode"
                            Margin="16,5,5,5"
                            Header="SizeMode"           
                            ItemsSource="{local:EnumValuesExtension EnumType=local:SizeMode}"
            SelectedIndex="1">

            <ComboBox.ItemTemplate>
                <DataTemplate x:DataType="x:String">
                    <TextBlock VerticalAlignment="Center" Text="{x:Bind}" />
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>

        <ComboBox
                            x:Name="HorizontalAlignment"
                            Margin="16,5,5,5"
                            Header="HorizontalAlignment"           
                            ItemsSource="{local:EnumValuesExtension EnumType=HorizontalAlignment}"
            SelectedIndex="3"
                            >
            <ComboBox.ItemTemplate>
                <DataTemplate x:DataType="x:String">
                    <TextBlock VerticalAlignment="Center" Text="{x:Bind}" />
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </StackPanel>
</Window>

MainWindow.xaml.cs:

using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Markup;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.Foundation.Metadata;

// To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info.

namespace App2
{
    /// <summary>
    /// An empty window that can be used on its own or navigated to within a Frame.
    /// </summary>
    public sealed partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
        }

        private void myButton_Click(object sender, RoutedEventArgs e)
        {
            myButton.Content = "Clicked";
        }
    }

    [MarkupExtensionReturnType(ReturnType = typeof(Array))]
    public sealed class EnumValuesExtension : MarkupExtension
    {
        private Type enumType;
        /// <summary>
        /// Gets or sets the <see cref="System.Type"/> of the target <see langword="enum"/>
        /// </summary>
        public Type EnumType
        {
            get
            {
                return enumType;
            }
            set
            {
                enumType = value;
            }
        }
        /// <inheritdoc/>
        protected override object ProvideValue()
        {
            var values = Enum.GetNames(EnumType);
            return values;
        }
    }

    /// <summary>
    /// A value converter that takes enum value and returns name of the enum.
    /// </summary>
    public class EnumDisplayNameConverter : IValueConverter
    {
        /// <inheritdoc/>
        public object Convert(object value, Type targetType, object parameter, string language)
        {
            if (value == null)
                return string.Empty;

            FieldInfo fieldInfo = value.GetType().GetField(value.ToString());
            if (fieldInfo != null)
            {
                var attributes = fieldInfo.GetCustomAttributes(typeof(DisplayAttribute), false);
                if (attributes.Length > 0)
                {
                    var attribute = attributes[0] as DisplayAttribute;
                    if (!String.IsNullOrEmpty(attribute.Name))
                        return attribute.Name;
                    else if (!String.IsNullOrEmpty(attribute.ShortName))
                        return attribute.ShortName;
                }
            }
            return Enum.GetName(value.GetType(), value);
        }

        /// <inheritdoc/>
        public object ConvertBack(object value, Type targetType, object parameter, string language)
        {
            throw new NotImplementedException();
        }
    }

    public enum Shape
    {
        Square,

        Circle,

        Ellipse
    }

    public enum SizeMode
    {
        Small,

        Medium,

        Large
    }

    [Microsoft.UI.Xaml.Data.Bindable]
    public class DummyClass
    {
        public Shape DummyShape { get; set; }
        public SizeMode DummySizeMode { get; set; }
    }
}

@DRAirey1
Copy link

DRAirey1 commented Oct 28, 2022

RealTommyKlein

This is not a fix. It's not even a useable workaround. Binding to an Enum should be a single attribute in a XAML element, not a final project in a com sci course. I have the same problem with an observable collection of POCOs that uses an Enum property as the "SelectedValue". The same code works great if I replace the Enum with the integer value, but that's not a work-around either as I have a lot of Enums.

What is the status of this issue?

@Marv51
Copy link
Contributor

Marv51 commented Jul 19, 2023

I think this is still important (re #8638)

@duncanmacmichael duncanmacmichael added the bug Something isn't working label Nov 3, 2023
@ranjeshj ranjeshj added team-Reach Issue for the Reach team and removed team-Controls Issue for the Controls team labels Jun 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-ComboBox bug Something isn't working product-winui3 WinUI 3 issues team-Reach Issue for the Reach team
Projects
None yet
Development

No branches or pull requests

9 participants