views:

55

answers:

2

While looking at solutions for tying an enum to a group of RadioButtons, I discovered Sam's post from a year and a half ago.

Lars' answer was exactly what I was looking for: simple and effective.

Until I started changing the object tied to the RadioButton group. A simple version follows.

The XAML:

<Window x:Class="RadioEnum.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:re="clr-namespace:RadioEnum"
        Height="200" Width="150">
  <Window.DataContext>
    <re:ViewModel />
  </Window.DataContext>
  <Window.Resources>
    <re:EnumBooleanConverter x:Key="enumBooleanConverter" />
  </Window.Resources>
    <DockPanel>
    <ComboBox DockPanel.Dock="Top" IsSynchronizedWithCurrentItem="True"
              ItemsSource="{Binding Things}" DisplayMemberPath="Name" />
    <GroupBox>
      <StackPanel>
        <RadioButton IsChecked="{Binding Path=Things/Choice, Converter={StaticResource enumBooleanConverter}, ConverterParameter=First}">First</RadioButton>
        <RadioButton IsChecked="{Binding Path=Things/Choice, Converter={StaticResource enumBooleanConverter}, ConverterParameter=Second}">Second</RadioButton>
        <RadioButton IsChecked="{Binding Path=Things/Choice, Converter={StaticResource enumBooleanConverter}, ConverterParameter=Third}">Third</RadioButton>
      </StackPanel>
    </GroupBox>
  </DockPanel>
</Window>

Now, the C#:

namespace RadioEnum
{
    public class ViewModel {
        public ObservableCollection<Thing> Things { get; set; }

        public ViewModel() {
            Things = new ObservableCollection<Thing> {
                new Thing{ Name = "Thing1", Choice = Choice.First, },
                new Thing{ Name = "Thing2", Choice = Choice.Second, },
            };
        }
    }

    public class Thing {
        public string Name { get; set; }
        public Choice Choice { get; set; }
    }

    public enum Choice { None, First, Second, Third, }

    public class EnumBooleanConverter : IValueConverter {
        // Yes, there are slight differences here from Lars' code, but that
        // was to ease debugging. The original version has the same symptom.
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
            object ret = DependencyProperty.UnsetValue;
            var parameterString = parameter as string;

            if (parameterString != null && Enum.IsDefined(value.GetType(), value)) {
                object parameterValue = Enum.Parse(value.GetType(), parameterString);
                ret = parameterValue.Equals(value);
            }

            return ret;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
            object ret = DependencyProperty.UnsetValue;
            var parameterString = parameter as string;

            if (parameterString != null && !value.Equals(false))
                ret = Enum.Parse(targetType, parameterString);

            return ret;
        }
    }
}

When the application loads with Thing1 in the ComboBox, the correct Choice is selected in the radio group. Selecting Thing2 from the ComboBox correctly updates the Choice. But, from this point, switching no longer updates the binding to the Second RadioButton and thus no longer calls the Convert method with parameter set to "Second".

In other words, although Thing2's values have not changed, all of the RadioButtons are cleared from that point forward. Thing1 continues to work, though.

There are no errors seen - neither exceptions nor messages in the Output window. I've tried binding in different ways. I tried making Choice a DependencyProperty, too (and Thing then a DependencyObject).

Any insights out there?

+1  A: 

Original Response: Not sure if this will fix your problem or not... as I think the break in the binding might be somewhere with your combobox... but to improve on your EnumConverter and make sure it's working properly... I suggest taking a look at my response to this question:

http://stackoverflow.com/questions/397556/wpf-how-to-bind-radiobuttons-to-an-enum/2908885#2908885

(Not the selected answer... but my response with the generic converter rather than converting string values)

Edit: I just took your code and tried it and everything seemed to work great! (using visual studio 2010 .net 4)

You have a list of Things (in your combobox) and can set the currently selected Thing's choice via radio button. I can modify each Thing's choice and when I switch between Things it correctly updates the radio button for me!

Correct me if I am wrong on the desired functionality:

App Loads - ComboBox: Thing1 RadioButton: First
Select Thing2 - ComboBox: Thing2 RadioButton: Second
Select Thing1 - ComboBox: Thing1 RadioButton: First
Select Third - ComboBox: Thing1 RadioButton: Third
Select Thing2 - ComboBox: Thing2 RadioButton: Second
Select First - ComboBox: Thing2 RadioButton: First
Select Thing1 - ComboBox: Thing1 RadioButton: Third
Select Thing2 - ComboBox: Thing2 RadioButton: First

Above is the functionality I get when running your app with the code you provided (and with the modified EnumConverter). This also appears to be the desired result. Is the above correct and does that not work that way for you?

Edit 2: I can confirm that the issue is with .NET 3.5

I run .NET 4 Client profile... everything works as desired... running .NET 3.5 Client profile... I get the result you stated.

Scott
Probably not surprising, but that solution suffers from the same symptom. Further, I have now tried it with a simple `bool` property for each `RadioButton` and the same thing occurs.I'm intruiged, Scott, with your comment about the `ComboBox`, but if I expand the example with other properties - for example, adding: <TextBox DockPanel.Dock="Bottom" Text="{Binding Things/Name}" />to the `DockPanel`, that functions perfectly.
RichClaussen
The thing about the combobox that got me was your use of IsSynchronizedWithCurrentItem... and I could be completely wrong about it... but it appears you don't need it based on the code you provided. Now that it's lunch time, though, I can try to mock something up and try to test the scenario that you've got going on and hopefully provide a better response.
Scott
Thanks, Scott... I have also tried doing without the IsSync'd and binding to the `SelectedItem` of the `ComboBox` to no avail.
RichClaussen
Well, how-d-do. Here's the sad thing, Scott: I actually thought I had created a .NET 4 project, but the *last* one I did was 3.5 and the drop down was still on that. The *real* application is still 3.5, though. Maybe this will be the motivator to move it to 4.Thank you **very** much for checking that!
RichClaussen
A: 

For those of you who may be stuck with doing this in .NET 3.5, I do have something working. It's not nearly as elegant as the code above, but it functions.

I'm more than happy to see some feedback from others on alternative methods, too. The example code below is for a ThingB that functions in both .NET 3.5 and 4.

First, change the XAML on the RadioButtons as follows (note that the GroupName must be different for each):

<RadioButton GroupName="One" IsChecked="{Binding Path=Things/ChoiceOne}">First</RadioButton>
<RadioButton GroupName="Two" IsChecked="{Binding Path=Things/ChoiceTwo}">Second</RadioButton>
<RadioButton GroupName="Three" IsChecked="{Binding Path=Things/ChoiceThree}">Third</RadioButton>

Second, the ThingB code:

public class ThingB : INotifyPropertyChanged {
    public string Name { get; set; }

    public Choice Choice {
        get {
            return choiceOne ? Choice.First
                   : choiceTwo ? Choice.Second
                   : choiceThree ? Choice.Third : Choice.None;
        }
        set {
            choiceOne = Choice.First.Equals(value);
            choiceTwo = Choice.Second.Equals(value);
            choiceThree = Choice.Third.Equals(value);
        }
    }

    private bool choiceOne;
    public bool ChoiceOne {
        get { return choiceOne; }
        set {
            if(value) {
                Choice = Choice.First;
                NotifyChoiceChanged();
            }
        }
    }

    private bool choiceTwo;
    public bool ChoiceTwo {
        get { return choiceTwo; }
        set {
            if (value) {
                Choice = Choice.Second;
                NotifyChoiceChanged();
            }
        }
    }

    private bool choiceThree;
    public bool ChoiceThree {
        get { return choiceThree; }
        set {
            if (value) {
                Choice = Choice.Third;
                NotifyChoiceChanged();
            }
        }
    }

    private void NotifyChoiceChanged() {
        OnPropertyChanged("ChoiceOne");
        OnPropertyChanged("ChoiceTwo");
        OnPropertyChanged("ChoiceThree");
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string property) {
        if (PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(property));
        }
    }
}
RichClaussen