0

My goal is to present to the user the list of available enum's in a user friendly string format rather than the code value, and display said property when required. Given that the DataAnnotations namespace doesn't exist in 8.1 universal apps, I can't simply apply [Display(Name="Nice display string")].

I have instead, used IValueConverter to run a switch statement on the incoming enum and return the string I want, and same in reverse, and will throw an exception if unable to convert. In the viewmodel, I am returning a List<Enum> as the bindable data source (for the combobox), for which the selected item is bound to the relative property in the viewmodel.

When testing, this works - the strings display in user friendly format, the property is changing and correctly setting the enum value. However, the Visual Studio designer (randomly) throws a xaml parse exception (Which I'm pretty sure is thrown due to the binding markup on the combobox.itemtemplate), which is never ideal.

So, my question - Is there a better way achieve my goal without throwing exceptions? Is the xaml error something to be concerned about even though the app compiles and runs?

My converter

public class FactToStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        Fact input = (Fact)value;
        switch (input)
        {
            case Fact.Wrong:
                return "Your Fact is Incorect";
            case Fact.Right:
                return "Your Fact is Correct";
            default:
                throw new ArgumentOutOfRangeException("value", "Fact to string conversion failed as selected enum value has no corresponding string output set up.");
        }
    }
}

My Bindable Itemssource

public List<Fact> FactList
{
    get
    {
        return new List<Fact>
        {
            Fact.Wrong,
            Fact.Right,
        };
    }
}

and my xaml

<ComboBox Header="Sample Fact:" Margin="0,10" ItemsSource="{Binding FactList}"
          SelectedItem="{Binding CurrentFact, Mode=TwoWay}">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <Grid DataContext="{Binding}">
                <TextBlock Text="{Binding Converter={StaticResource FactConv}}"/>
            </Grid>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>
poke
  • 369,085
  • 72
  • 557
  • 602
Lindsay
  • 575
  • 8
  • 18
  • If the `Display` attribute class doesn't exist, then create your own: http://stackoverflow.com/a/17826168/7122 – David Arno May 21 '15 at 06:05
  • @DavidArno thanks for that, however I was unable to get it to work. Perhaps my understanding of the display attribute is wrong? I created the Display system.attribute class as per that post (although I set it to `AttributeTargets.All` instead of .Class). I then added the attribute to the enum values and changed the binding on the combobox so there wasn't an item template. By my understanding, this would then automatically display the string entered as the name, rather than `.EnumValue` of the `Enum.EnumValue`. No converters needed, no textblock etc. Is my understanding wrong? – Lindsay May 22 '15 at 00:56
  • 1
    How about using a list of items that have both the enum value and its friendly name? Something like a Tuple. And then just set the `DisplayMemberPath` property of the `ComboBox`, so that it would display the name property. – yasen May 22 '15 at 13:49
  • I believe that would work for the combobox (and lists of enums), so I'll certainly keep that for future reference. But then I still need to implement the converter for each enum That I'm binding the view to individually (I think). – Lindsay May 25 '15 at 02:29
  • 1
    @Lindsay To expand on (explain) David's suggestion and answer your question for him: once you create the attribute and attach it to the enum values, you would need to make a converter that actually reads the value of that attribute and returns it. It wouldn't happen automatically. It might get tricky if you want to have multiple translations though. Not unsolvable, but tricky. – yasen May 25 '15 at 22:21
  • @Yasen that clarification was just what I needed to get over the line. Using a combo of the link by David, your comment, [this](http://stackoverflow.com/questions/12814723/what-is-an-equivalent-method-to-getcustomattributes-for-netcore-windows-8-fr) post and [this](http://stackoverflow.com/questions/10732105/get-custom-attributes-of-enum-value) post, I was able to implement the `Display` attribute and a single converter. I'll put the final solution below. – Lindsay May 26 '15 at 07:02

3 Answers3

2

Display Attribute Implementation

[System.AttributeUsage(System.AttributeTargets.All)]
public class Display : System.Attribute
{
    private string _name;
    public Display(string name)
    { _name = name; }
    public string GetName()
    { return _name; }
}

Converter

public class EnumWithDisplayConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        try
        {
            string output = value.GetType()
                .GetTypeInfo()
                .GetDeclaredField(((Enum)value).ToString())
                .GetCustomAttribute<Display>()
                .GetName();
            return output;
        }
        catch (NullReferenceException)
        {
            return ((Enum)value).ToString();
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}
Lindsay
  • 575
  • 8
  • 18
1

Just because the DisplayAnnotations namespace is missing, doesn't mean you can't use Display. Simply create your own attribute class. Please see this answer to another question for how to do that.

Community
  • 1
  • 1
David Arno
  • 42,717
  • 16
  • 86
  • 131
0

The VS designer does throw some exceptions randomly for no reason, so it's not really all that important. Now, if the exception is thrown at runtime - that's a serious problem and you need to resolve it!

On a side note: you don't need that DataContext={Binding} on the Grid in the DataTemplate.

yasen
  • 3,580
  • 13
  • 25