0

I have a list of objects of different types. Some of them are of type RadioProperty.
Each object has some properties. The ones of interest are the following:

string PropName; // property name
string Value; // current property value
Dictionary<string, string> Values; // possible values - name, value

Value should at any time have one of the values available in the dictionary. What I want to do, is to somehow bind this to a radio button group, so I can select the value of the property from the available values in the dictionary.
For the moment this is what I have:
I have simplified the code for presentation purposes. Hope I didn't missed anything important.

XAML

<StackPanel>
  <StackPanel.Resources>
    <local:RadioPropertyConverter x:Key="radioPropertyConverter" />
  </StackPanel.Resources>
  <ItemsControl x:Name="PropertyList" ItemsSource="{Binding PropList}">
    <ItemsControl.Resources>
      <DataTemplate DataType="{x:Type proptool:RadioProperty}">
        <StackPanel>
          <Grid>
            <StackPanel>
              <ItemsControl ItemsSource="{Binding Values}">
                <ItemsControl.ItemTemplate>
                  <DataTemplate>
                    <RadioButton Content="{Binding Path=Key}">
                      <!-- BEGIN: GroupName is equal to PropName -->
                      <RadioButton.GroupName>
                        <Binding Path="DataContext.PropName">
                          <Binding.RelativeSource>
                            <RelativeSource Mode="FindAncestor"
                                            AncestorType="{x:Type TypeName=StackPanel}" />
                          </Binding.RelativeSource>
                        </Binding>
                      </RadioButton.GroupName>
                      <!-- END -->
                      <RadioButton.IsChecked>
                        <!-- specifying only `Value` for Path will fail at
                        runtime with the following error:
                        System.Windows.Markup.XamlParseException: 'A TwoWay or
                        OneWayToSource binding cannot work on the read-only
                        property 'Value' of type
                        'System.Collections.Generic.KeyValuePair`2[System.String,System.String]'.' -->
                        <Binding Converter="{StaticResource radioPropertyConverter}"
                                 Path="/Value" ConverterParameter="Y" />
                      </RadioButton.IsChecked>
                    </RadioButton>
                  </DataTemplate>
                </ItemsControl.ItemTemplate>
              </ItemsControl>
            </StackPanel>
          </Grid>
        </StackPanel>
      </DataTemplate>
    </ItemsControl.Resources>
  </ItemsControl>
</StackPanel>

PropList is of type List<RadioProperty>.
The value for the ConverterParameter should be equal to the current value in the dictionary. It doesn't seem to be possible to specify a binding for the ConverterParameter:
System.Windows.Markup.XamlParseException: 'A 'Binding' cannot be set on the 'ConverterParameter' property of type 'Binding'. A 'Binding' can only be set on a DependencyProperty of a DependencyObject.'

C#

public class RadioPropertyConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        return value?.Equals(parameter);
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        return value?.Equals(true) == true ? parameter : Binding.DoNothing;
    }
}

I don't think that the converter should look like this but the current issue I'm having is that the Convert/ConvertBack methods are never called.

What I want to achieve is having a list of properties, each with some details in different controls. Each property might have on its own a list (Values) which should be presented to the user as a list of radio buttons. Depending on the value of Value a particular radio button should be selected. If a different radio button is selected, the value should change accordingly. So I think that the binding should be done with the Value and the radio buttons rendered depending on the dictionary's content (I already have this working).

I kindly ask you not to point me to other SO questions as I'm pretty confident that I went through most of the ones that are related to my question. Unless, of course, if you understood my question and you think that I might've missed some details in some of the already answered questions.

Some of the, presumably, more relevant SO questions, for my particular scenario, which I already went through:

  1. Binding RadioButton to Dictionary
  2. How to bind RadioButtons to an enum?
Iulian Paun
  • 3
  • 1
  • 2
  • 1
    Any reason you specified your path as `Path="/Value"` instead of `Path="Value"` ? – Rand Random Nov 07 '18 at 15:44
  • @RandRandom, I mentioned the reason in the XAML comments after the `RadioButton.IsChecked` property element. I still don't know why it doesn't work if I simply specify `Value`. – Iulian Paun Nov 08 '18 at 08:05
  • I saw that later to my comment - but you do realise the only thing you did was destroying your valid input it would have been the same if you wrote `Path="WTF_IsWrong"` now your runtime error about wpf complaining two way binging is gone aswell since you aren't binding anymore to the readonly `Value` Property. - So, yeah your error is gone but just because you destroyed your valid input. – Rand Random Nov 08 '18 at 10:51
  • I still don't get why it's not working if I specify `Value` for a Binding's Path property but it's working with MultiBinding. I'll try to google it a bit and see if I find anything... – Iulian Paun Nov 08 '18 at 11:34

1 Answers1

0

That error that you're getting binding to "Value" is there for a reason, you can't fudge it with a "/Value" and expect it to go away. Also there are plenty of SO questions about this, although it may be a bit tricky finding and making sense of them without considerable effort.

To answer your question, I don't know the exact reasons as to why, but radio buttons have never just "worked" out of the box with WPF, presumably because there are just too many different ways people need to wire them up. In your case there are two things you need to do:

1) Get the OneWay binding from the RadioProperty to the radio button control working. You've already noticed that converter parameters can't use bindings, the way around this is to use a multi-converter instead:

public class RadioPropertyConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        if (values == null)
            return false;
        if (values.Length != 2)
            return false;
        return Object.Equals(values[0], values[1]);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Forget about ConvertBack, it's meaningless in this context and you won't need it. Back in your XAML you now use this in a OneWay MultiBinding tag in RadioButton.IsChecked, passing in the RadioProperty Value and the currrent Dictionary Value:

<RadioButton.IsChecked>
    <MultiBinding Converter="{StaticResource radioPropertyConverter}" Mode="OneWay">
        <Binding Path="Value" />
        <Binding Path="DataContext.Value" RelativeSource="{RelativeSource AncestorType={x:Type StackPanel}}" />
    </MultiBinding>
</RadioButton.IsChecked>

2) Get the binding working in the other direction, i.e. so that clicking the radio buttons updates the corresponding RadioProperty Value. There are actually a few different ways to do this, but the one I prefer takes advantage of the fact that radio buttons, like all other buttons, generate Commands when clicked (something you've obviously already cottoned onto given the presence of an empty Command handler in the <RadioButton> tag). All you need to do is add a binding to a command handler in your RadioProperty class and for the CommandParameter pass in the Key value for the button pressed:

<RadioButton Content="{Binding Path=Key}"
     Command="{Binding Path=DataContext.SetValueCommand, RelativeSource={RelativeSource AncestorType={x:Type StackPanel}}}"
     CommandParameter="{Binding Key}">

And then you just add an ICommand handler to your RadioProperty class which accepts the key and uses it to set both the PropName and Value:

    private ICommand _SetValueCommand;
    public ICommand SetValueCommand => this._SetValueCommand ?? (this._SetValueCommand = new RelayCommand<string>(OnSetValue));

    private void OnSetValue(string key)
    {
        this.PropName = key;
        this.Value = this.Values[key];
    }

If you've got other controls binding to the same RadioProperty instances then you may also need to give PropName and Value property change notification, but otherwise this should work fine.

Mark Feldman
  • 15,731
  • 3
  • 31
  • 58
  • This solution worked out of the box for me. I didn't need to change `PropName` value, though. After giving property change notifications to `Value` everyting worked as expected. Could you please specify what you're referring to with _empty Command handler_? I did at some point stumbled upon commands but didn't really took the time to study them as I didn't need them at the time. I've just started learning C# and WPF last month so the answer might be quite obvious for others. Thanks a lot. – Iulian Paun Nov 08 '18 at 11:22
  • Sorry @Julian, for some reason I thought I saw a `Command=""` in your code (it was late and Iwas very tired). Commands are essential to MVVM, instead of things like button presses etc being directed to code-behind via events they instead allow you to direct them to your view models, so they're important for maintaining good seperation-of-concerns and clean architectural design. Glad to hear it's now working for you. – Mark Feldman Nov 08 '18 at 19:58