0

I'm trying to express an enumeration property in my view-model as a set of radio buttons in my view. So far, so good; I can express that with a two-way MultiBinding:

(rb1.IsChecked, rb2.IsChecked, rb3.IsChecked) <-> vm.Value

The multi-binding used here would feature a multi-converter that converts between (bool, bool, bool) <-> MyValue; obviously, one of the (three) allowable values of the MyValue type is chosen based on which bool is true, and vice-versa.

This is already a bit inconvenient, though: I cannot define that binding in my view's Xaml, as multi-bindings have to be defined from the side of the single value. Hence, I have to define the multi-binding in code-behind and use SetBinding set it on my view model's Value property.

Now, the issue that I'm stuck at is that I'm not just binding one set of radio buttons to that value, but two. Hence, my bindings would have to look like this:

(rbA1.IsChecked, rbA2.IsChecked, rbA3.IsChecked) <-> vm.Value <-> (rbB1.IsChecked, rbB2.IsChecked, rbB3.IsChecked)

The problem is that I cannot use SetBinding to connect several bindings to vm.Value at a time.

Solutions that I have tried so far are:

  • Use one big multi-binding, binding to all radio buttons at a time. This would mean a binding of the form (rbA1.IsChecked, rbA2.IsChecked, rbA3.IsChecked, rbB1.IsChecked, rbB2.IsChecked, rbB3.IsChecked) <-> vm.Value. The problem with this solution is that if one of the radio buttons (say, rbB2) is checked, I have no way of telling whether rbA2 (unchecked) or rbB2 (checked) has the "new, correct" value.
  • Abstract the radio button groups first by defining a radio group control that exposes only one SelectedIndex property. This property can then be conveniently bound to my vm.Value property from all instances of my radio group control. While feasible, it requires writing a new control class and I wonder whether this is the only way in WPF.
  • Bind one set of radio buttons to another one: By two-way-binding rbB1 to rbA1, rbB2 to rbA2, and so on, and using a multi-binding between my vm.Value and the first set of radio buttons only, I would achieve the desired effect, but I don't like the notion of having a "master radio group". It would be abusing GUI elements for data transfer.
  • Do everything in code-behind and manually update radio buttons and view-model value. Of course this is a viable fallback solution, but this feels like it should be feasible in Xaml/with bindings.
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
O. R. Mapper
  • 20,083
  • 9
  • 69
  • 114
  • the "Use one big multi-binding", I didn't quite understand. YOu have two radioButton groups. You can pick one item from group1 and you can pick one item from group2. I dont understand the 'has the new correct value'. – Erti-Chris Eelmaa Sep 22 '12 at 09:05
  • @ChrisDD: The "big mult-binding" would bind `vm.Value` with all six radio buttons at a time. The six radio buttons could have an initial state such as `(1, 0, 0, 1, 0, 0)`. Now, let's say someone clicks the 2nd radio button in the 2nd set, so my multi-converter gets called with `(1, 0, 0, 0, 1, 0)`. How can I determine whether the first set or the second set is correct, i.e., whether the first enum value or the second enum value should be returned? As far as I can see, that's not conveniently possible. – O. R. Mapper Sep 22 '12 at 09:08
  • Why dont you use single binding for each RadioButton? Use converter and commandparameter too. Such as: IsChecked={Binding VMEnum, Converter={StaticResource toEnum}, ConverterParameter={YourEnum:FirstButtonChecked}}. You need to implement both-way converter. Either way IsChecked = VMEnum == (CommandParameter as Enum) [for one way] – Erti-Chris Eelmaa Sep 22 '12 at 09:22
  • that is for controlling RadioBoxes from ViewMOdel. For opposite way, the logic would be like: IsChecked==true then return CommandParameter. Bindings have to be two-way – Erti-Chris Eelmaa Sep 22 '12 at 09:24
  • @ChrisDD: Fine, as long as the converter is called when `IsChecked` changes to `true`. When that happens, however, all the other radio buttons' `IsChecked` properties change to `false`; what should their converter return? – O. R. Mapper Sep 22 '12 at 09:25
  • You could return Binding.DoNothing in converter. Or you could also make new enum value such as Enum.NotValid + return it in converter, and in your property setter, you only change enum when value != Enum.NotValid – Erti-Chris Eelmaa Sep 22 '12 at 09:31
  • @ChrisDD: Interesting solution with the `Binding.DoNothing`. If you add this as an answer, I'll accept it. – O. R. Mapper Sep 22 '12 at 09:52

2 Answers2

0

Using complex multibinding with converters and codebehind will not only make your code harder to debug but even harder to test. In my opinion it's better to express each set of radio buttons (flags) as a view model. Evaluate your value when any of the radio buttons is checked/unchecked.

RadioButtonGroup

public class RadioButtonGroup : ViewModel {

    public RadioButtonGroup(string groupName, int count, Action<bool[]> whenAnyChanaged = null) {

        RadioButtons = Enumerable.Range(0, count).Select(_ => {
            var button = new RadioButton { GroupName = groupName };
            button.PropertyChanged += (s, e) => {
                if (e.PropertyName == "IsChecked")
                    whenAnyChanaged(Flags);
            };
            return button;
        }).ToList();           
    }

    public List<RadioButton> RadioButtons { get; private set; }

    public bool[] Flags { get { return RadioButtons.Select(rb => rb.IsChecked).ToArray(); } }
}

RadioButton

 public class RadioButton : ViewModel {

    private bool isChecked;

    public bool IsChecked {
        get { return isChecked; }
        set { SetProperty(ref this.isChecked, value); }
    }

    public string GroupName { get; set; }
}

MainViewModel

public class MainViewModel : ViewModel {
    public MainViewModel() {
        GroupA = new RadioButtonGroup("A", 10, flags => GroupToggle(flags, GroupB.Flags));
        GroupB = new RadioButtonGroup("B", 10, flags => GroupToggle(GroupA.Flags, flags));
    }

    public RadioButtonGroup GroupA { get; private set; }

    public RadioButtonGroup GroupB { get; private set; }

    void GroupToggle(bool[] groupA, bool[] groupB) {
        MyValue = Evaluate(groupA, groupB);
    }
}

View

<Window x:Class="WpfLab.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="{Binding Title}" Height="350" Width="525">
<Window.Resources>
    <DataTemplate x:Key="RadioButton">
        <RadioButton IsChecked="{Binding IsChecked, Mode=OneWayToSource}" GroupName="{Binding GroupName}"/>
    </DataTemplate>
</Window.Resources>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="30"/>
        <RowDefinition Height="30"/>
    </Grid.RowDefinitions>

    <ListBox Grid.Row="0" ItemsSource="{Binding GroupA.RadioButtons}" ItemTemplate="{StaticResource ResourceKey=RadioButton}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>

    <ListBox Grid.Row="1" ItemsSource="{Binding GroupB.RadioButtons}" ItemTemplate="{StaticResource ResourceKey=RadioButton}">
        <ListBox.ItemsPanel>
            <ItemsPanelTemplate>
                <StackPanel Orientation="Horizontal"/>
            </ItemsPanelTemplate>
        </ListBox.ItemsPanel>
    </ListBox>
</Grid>

Per
  • 1,061
  • 7
  • 11
  • What does the `Evaluate` method do you're invoking in your `MainViewModel` class? In particular, when the states of the `RadioButtonGroup`s are e.g. `(1, 0, 0); (0, 1, 0)`, how does it know whether `(1, 0, 0)` or `(0, 1, 0)` is the intended new state? – O. R. Mapper Sep 22 '12 at 11:12
  • Right now it probably wont, but you can add Enum, eg "Which Group was changed" and add it to GroupToggle & modify delegates(MainiewModel()) + pass it to Evaluate. – Erti-Chris Eelmaa Sep 22 '12 at 18:36
  • @ChrisDD: I think the problem is that at the time where `Evaluate` is called, it's already unknown/too late to find out which group was changed. – O. R. Mapper Sep 23 '12 at 21:34
0

Bind VMEnum to each RadioButton seperately(use single two-way binding). Each binding should have CommandParameter it's enum. Like:

<RadioButton IsChecked="{Binding VMEnum, Converter={StaticResource EnumConverter}, ConverterParameter={Enums:VMEnums.FirstRadioButtonGroupA}}" />

In the converter,

  • Convert should return correct value(true/false) depending of the VMEnum and COmmandParameter. Essentially the logic is VMEnum == (YourEnum)CommandParameter.
  • ConvertBack should return correct Enum based on IsChecked. If IsChecked is true, return the correct enum. Otherwise return Binding.DoNothing which will abort the binding for that specific case.
Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78