10

I have been going crazy with binding a combobox to an enum typed property of a class, where the enum itself is declared in that same class.

I am trying to follow the answer provided here (wpf combobox binding to enum what i did wrong?) Specifically I am using the suggested MarkupExtension code and the matching xaml code.

My working code is:

Defining the Enum in a separate file.

namespace EnumTest
{
    public enum TestEnum {one, two, three, four };
}

Class that uses the Enum (Note that the propertyChanged code has been removed to simplify things):

namespace EnumTest
{
    public class Test : INotifyPropertyChanged
    {
        private TestEnum _MyVar;
        public TestEnum MyVar { 
            get { return _MyVar; } 
            set 
            { 
                _MyVar = value;
                OnPropertyChanged("MyVar");
            } 
        }

        public Test()
        {
            _MyVar = TestEnum.three;
        }
    }
}

Program file that uses the class:

namespace EnumTest
{
    public partial class Window1 : Window
    {
        Test _oTest = new Test();

        public Window1()
        {
            InitializeComponent();
            cmbBox.DataContext = _oTest;
        }
    }
 }

Extension method for displaying the Enum

namespace EnumTest
{
    [MarkupExtensionReturnType(typeof(object[]))]
    public class EnumValuesExtension : MarkupExtension
    {
        public EnumValuesExtension()
        {
        }

        public EnumValuesExtension(Type enumType)
        {
            this.EnumType = enumType;
        }

        [ConstructorArgument("enumType")]
        public Type EnumType { get; set; }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            if (this.EnumType == null)
                throw new ArgumentException("The enum type is not set");
            return Enum.GetValues(this.EnumType);
        }
    }
}

And the xaml code that is used to display the data:

<Window x:Class="EnumTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:w="clr-namespace:EnumTest"
    Title="Window1" Height="300" Width="300">
    <Grid>
        <ComboBox Name="cmbBox" 
                  Height="20" 
                  Width="80" 
                  ItemsSource="{Binding Source={w:EnumValues EnumType=w:TestEnum}}" 
                  SelectedItem="{Binding Path=MyVar}"
                  />
    </Grid>
</Window>

The above is all good and dandy, but I want to define the Enum within the Test class and ditch the Enum from being defined at the global scope. Like so:

namespace EnumTest
{
    public class Test : INotifyPropertyChanged
    {
        // Declare Enum **INSIDE** the class
        public enum TestEnum {one, two, three, four };
        private TestEnum _MyVar;
        public TestEnum MyVar { 
            get { return _MyVar; } 
            set 
            { 
                _MyVar = value;
                OnPropertyChanged("MyVar");
            } 
        }

        public Test()
        {
            _MyVar = TestEnum.three;
        }
    }
}

The SO question I referred to alludes to the matching xaml syntax as being:

        <ComboBox Name="cmbBox" 
                  ...
                  ItemsSource="{Binding Source={w:EnumValues EnumType=w:Test+TestEnum}}" 
                  ...
                  />

But this (sort of) does not work for me. When I do a clean build I get a "Build succeeded" message on the VS 2008 status bar, but I also get an error being reported in the xaml

Type 'Test+TestEnum' was not found.  

But the code runs as expected!

However this means that the xaml designer will not load. So I am sort of screwed in doing any more wpf work until I can clear the xaml error.

I am now wondering if this is a VS 2008 SP1 issue, and not a problem on my part.

Edit

  1. Made my problem statement more explicit.
  2. Tried Joel's 1st solution, but I ended up with the code running and 2 xaml errors
  3. Tried Joel's 2nd solution and that worked straight out of the box - so I am going with that one!

Notes The SO question that I got the MarkupExtension code from uses this style of syntax for the xaml:

<ComboBox ItemsSource="{w:EnumValues w:TestEnum}"/>

When I use that I get a compilation error saying that no EnumValues constructor takes 1 parameter. I did some googling and this seems to be an error in VS. I Am using VS 2008 SP1. I did see some comments that alluded to it being in the VS 2010 beta. Anyway, that is why I use the xaml syntax of

<ComboBox ItemsSource="{w:EnumValues EnumType=w:TestEnum}"/>

As this syntax works!

Community
  • 1
  • 1
Peter M
  • 7,309
  • 3
  • 50
  • 91
  • Regarding the error about the constructor, it occurs only in the designer. It should work fine otherwise. To avoid this error, define the markup extension in a separate assembly – Thomas Levesque Aug 03 '09 at 13:16
  • @Thomas: I'd forgotten how the designer can be cantankerous about what types are built when/where and how it instantiates them. – Joel B Fant Aug 03 '09 at 14:41
  • @Joel - And newbie wpf programmers know this sort of thing how? Knowing to put a class in a separate assembly in order to satisfy one part of the build tool is rather obscure IMHO. But I'll keep on slogging at it until I get a reasonable understanding. Thanks again. – Peter M Aug 03 '09 at 14:56
  • @Thomas - thanks for that tip. Thats not something that I would have ever considered at all - even after googling that particular problem. – Peter M Aug 03 '09 at 15:00
  • @Peter: Yes, it's obscure. It didn't pop up very often for me, though. I'm trying to find a link explaining how Cider instantiates things, as all I can remember is something about base classes. – Joel B Fant Aug 03 '09 at 18:59

2 Answers2

3

Another way of getting the enum values for use as a data source:

<Window.Resources>
    <ObjectDataProvider
        MethodName="GetValues"
        ObjectType="{x:Type sys:Enum}"
        x:Key="TestValues">
        <ObjectDataProvider.MethodParameters>
            <w:Type2
                TypeName="w:Test+TestEnum" />
        </ObjectDataProvider.MethodParameters>
    </ObjectDataProvider>
</Window.Resources>

...

ItemsSource="{Binding Source={StaticResource TestValues}}"

Note that you still need the Type2Extension because of weirdness with TypeExtension and nested types. But you wouldn't need the extra custom markup extension. This way is better if you'll be using the list in multiple places, as you can declare it in your App.xaml resources.

Joel B Fant
  • 24,406
  • 4
  • 66
  • 67
1

What about using x:Type markup extension?

{w:EnumValues EnumType={x:Type w:Test+TestEnum}}

Except for implementing INotifyPropertyChanged, I copied your code exactly. I do get the errors that you get, but it seems to run just fine. It is very annoying not to be able to load the designer, though. Nothing I've tried has solved the problem.

I did find this page on MSDN about nested types, and a suggestion in that thread was a custom MarkupExtension for resolving the nested type name. I'm trying to get it to work, but no luck so far. I get similar errors on Type2Extension sometimes, and I get "The enum type is not set" with other tweaks.

Aha! There was a bug in how the original author was calling GetType()! Here's the corrected Type2Extension and how I was using it:

public class Type2Extension : System.Windows.Markup.TypeExtension {
    public Type2Extension() {
    }

    public Type2Extension( string typeName ) {
        base.TypeName = typeName;
    }

    public override object ProvideValue( IServiceProvider serviceProvider ) {
        IXamlTypeResolver typeResolver = (IXamlTypeResolver) serviceProvider.GetService( typeof( IXamlTypeResolver ) );
        int sepindex = TypeName.IndexOf( '+' );
        if ( sepindex < 0 )
            return typeResolver.Resolve( TypeName );
        else {
            Type outerType = typeResolver.Resolve( TypeName.Substring( 0, sepindex ) );
            return outerType.Assembly.GetType( outerType.FullName + "+" + TypeName.Substring( sepindex + 1 ) );
        }
    }
}

And XAML:

ItemsSource="{Binding Source={w:EnumValues {w:Type2 w:Test+TestEnum}}}"

This appears to work fine and the designer loads. I'll be adding Type2Extension to my own little libraries.

Edit: Oddly enough, if I change this in EnumValues:

if ( this.EnumType == null )
    throw new ArgumentException( "The enum type is not set" );

To this:

if ( this.EnumType == null )
    return null;

Then those constructor errors go away. That was the one other thing I changed. However, I am shortly going to post an alternate way of getting enum values.

Joel B Fant
  • 24,406
  • 4
  • 66
  • 67
  • If you mean: ItemsSource="{Binding Source={w:EnumValues EnumType={x:Type w:Test+TestEnum}}}" Then that just gave a big compilation fail – Peter M Aug 03 '09 at 00:28
  • Interesting. After answering, I started putting your example code together to recreate (just to see if it worked). I'll edit if I figure out anything. – Joel B Fant Aug 03 '09 at 00:31
  • Thanks for that. I'm too tired to bother with it right now but I will look t it first thing in the morning. I'm new to wpf and having to jump through these sorts of hoops to do what should be basic stuff is annoying me thoroughly. – Peter M Aug 03 '09 at 02:40
  • @Joel - I have implemented the Ty2Extension class and used our xaml, but it has not got me any further along. IN fact now I have more errors in the xaml "No constructor for type "Tye2extension" has 1 paramters" *and* "No constructor for type "EnumValuesExtension" has 1 parameters". But the code runs as expected and finds the enum. A genuine WTF! – Peter M Aug 03 '09 at 12:59
  • @Joel - Seeing that it works for you I am now wondering if there is an issue with my VS2008 SP1 install? BTW I only got your xaml to work with w:Test+TestEnum – Peter M Aug 03 '09 at 13:01
  • @Peter: Yes, it's supposed to be w:Test+TestEnum. The original used periods and I decided to change it while editing my answer. I do wonder if I changed something else. It wouldn't hurt to run Repair on your installation, just in case. – Joel B Fant Aug 03 '09 at 13:06
  • That's weird, for me it's working fine with "{w:EnumValues w:Test+TestEnum}"... I don't need a Type2Extension or anything like that – Thomas Levesque Aug 03 '09 at 13:44
  • @Thomas I know its weird. Thats why I am suspecting an installation problem on my machine. I'll have to try a repair or clean install and then see what happens. – Peter M Aug 03 '09 at 14:22