Microsoft-ui-xaml: Proposal: Add a RadioButton group control

Created on 21 Feb 2019  路  10Comments  路  Source: microsoft/microsoft-ui-xaml

Proposal: Add a RadioButton Group Control

Summary

Enable the developer to create a group or collection of RadioButtons that do not use/need a StackPanel.

Rationale

The behavior for grouping RadioButtons today (via using StackPanels and labeling RadioButtons individually through GroupNames) on UWP differs greatly from our Win32 version - both in narrator experience and expected keyboarding behavior. This means it is causing a lot of confusion for our narrator users when they attempt to navigate it.

This proposal is to a RadioButton group container control that will address these accessibility issues and optimize the narrator and keyboarding experience.

Functional Requirements

| # | Feature | Priority |
|:--:|:--|--:|
| 1 | Create a control that can group RadioButtons | Must |
| 2 | It's clear and easy for a user to understand which item is focused and has selection, in the context of the other items in the group, when navigating with narrator|Must|
| 3 | Focus navigation moves as expect for keyboard and gamepad | Must |
| 4 | Intuitive navigation support for both narrator and keyboard dependent users when entering and existing the RadioButton group| Must |
| 5 | Changing selection within the group using keyboard is easy, and narrator clearly reflects these changes if enabled/assisting| Must |
| 6 | RadioButton group displays a consistent and easy-to-setup visual experience for both row and column RadioButton grouping scenarios| Must |

Important Notes

Small example of what this feature might look like in markup.

<RadioButtons Header="App Mode" SelectedIndex="2">
    <x:String>Dark</x:String>
    <x:String>Light</x:String>
    <x:String>Default</x:String>
</RadioButtons>

image

feature proposal team-Controls

Most helpful comment

Thanks for reading my comment, sorry I phrased it in a convoluted way. What I had in mind was something closer to @michael-hawker 's second example: binding each radio button to a different enum value, where it might show up as a longer descriptive string that's different from the name of the enum value in code. The common way to do this since WPF has been to write an EnumToBoolean converter that takes a parameter and compares it to the value, then apply it to each RadioButton with a different parameter. e.g.,

  <RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=FirstSelection}">first selection</RadioButton>
  <RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=TheOtherSelection}">the other selection</RadioButton>
  <RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=YetAnotherOne}">yet another one</RadioButton>

(I took this from a Stack Overflow answer: https://stackoverflow.com/questions/397556/how-to-bind-radiobuttons-to-an-enum). Function binding makes one-way binding easier but doesn't really help with two-way binding (since you'd have to define a separate ConvertBack method for every property you wanted to bind this way). It's not especially difficult once you know how to do it, but I think it's unintuitive - the number of tutorials, blog posts and Stack Overflow answers I found on this sort of testifies to that imo, for example
https://blogs.u2u.be/diederik/post/Databinding-to-an-enumeration-in-WPF
https://www.codecisions.com/wpf-binding-boolean-to-an-enum-value/
https://wpf.2000things.com/2011/08/05/358-binding-a-radiobutton-to-an-enumerated-type/

and since the Converter/ConverterParameter has to be applied to every radio button bound this way, it also contributes to XAML bloat that makes the codebase incrementally harder to read and understand over time (look how much it's repeated in this XAML file I found on github for example)

Unfortunately if the API for the new RadioButton group control closely matches ListView, I'm not sure it will be much of an improvement for this scenario (but I could very well be missing something obvious). The two ways I can think of to accomplish this with a ListView would be

  1. Make a new type in code which bundles a choice's enum value with its display text. e.g.,

     public class ThemeOptionViewModel {
          public Theme Value {get; set;}
          public string DisplayText {get; set;}
     }
    
    
    <ListView SelectedValue="{x:Bind SelectedTheme, Mode=TwoWay}" SelectedValuePath="Value">
                <ListView.Items>
                    <local:ThemeOptionViewModel Value="DarkMode" DisplayText="Dark theme"/>
                    <local:ThemeOptionViewModel Value="LightMode" DisplayText="Light theme"/>
                    <local:ThemeOptionViewModel Value="CustomMode" DisplayText="Your own custom theme"/>
                 </ListView.Items>
                 <ListView.ItemTemplate>
                     <DataTemplate x:DataType="local:ThemeOptionViewModel">
                          <TextBlock Text="{x:Bind DisplayText}"/>
                      </DataTemplate>
                  </ListView.ItemTemplate>
      </ListView>
    

The problems with this I see are

  • some code and XAML that feels extraneous (the ThemeOptionViewModel, the ItemTemplate)
  • requires adding C#/C++ code just to support display text, which feels like it should be implementable purely in XAML
  • like the EnumBooleanConverter approach above, seems like a fairly roundabout and unintuitive way to accomplish a very basic task.
  1. Set the ContentTemplate of the ListViewItem for each option individually, e.g.,

        <ListView SelectedValue="{x:Bind SelectedTheme, Mode=TwoWay}" SelectedValuePath="Content">
                <ListViewItem>
                    <ListViewItem.ContentTemplate>
                        <DataTemplate>
                            <TextBlock>Dark theme</TextBlock>
                        </DataTemplate>
                    </ListViewItem.ContentTemplate>
                    <local:Theme>DarkMode</local:Theme>
                </ListViewItem>
                <ListViewItem>
                    <ListViewItem.ContentTemplate>
                        <DataTemplate>
                            <TextBlock>Light theme</TextBlock>
                        </DataTemplate>
                    </ListViewItem.ContentTemplate>
                    <local:Theme>LightMode</local:Theme>
                </ListViewItem>
            </ListView>
    

I think this is conceptually better, the remaining problems are just verbosity and nonobviousness.

It seems to me though that for many uses of radio buttons, what we really care about configuring is just a mapping of enum (or whatever type) values to labels. In other words I really sort of wish I could just write something like

    <RadioButtons SelectedValue={x:Bind SelectedTheme, Mode=TwoWay}>
       <RadioButton Value="DarkMode">Dark theme</RadioButton>
       <RadioButton Value="LightMode">Light theme</RadioButton>
     </RadioButtons>`

I'm not proposing this exact API (not sure if it's workable), just hoping for something that feels more like this last one to write and read, and less like the earlier examples.

All 10 comments

Some questions/observations

  • If this is an extension of ListView, will all inherited properties be available also? The proposal is currently vague on this.
  • Is there any way to modify the individual styling of elements in the group? Either on a collective, or individual basis.
  • What happens if "non-RadioButton elements" are specified? Do you just take the text from them and use only that? If so, how-.ToString()? What if it's a complex object? What if I want an accompanying image or a help hyperlink?

This was actually a spec that was approved before the repo was created, and I was asked to put in an issue surrounding it - apologies if some details were missed, this was previously in a different format.

If this is an extension of ListView, will all inherited properties be available also? The proposal is currently vague on this.

No, the properties available on this control are a portion of ListView's and an added one for organizing the RadioButton group into columns:

Here's the list:
``` c#
static Windows.UI.Xaml.DependencyProperty ItemsSourceProperty{ get; };
static Windows.UI.Xaml.DependencyProperty ItemsProperty{ get; };
static Windows.UI.Xaml.DependencyProperty ItemTemplateProperty{ get; };
static Windows.UI.Xaml.DependencyProperty SelectedIndexProperty{ get; };
static Windows.UI.Xaml.DependencyProperty SelectedItemProperty{ get; };
static Windows.UI.Xaml.DependencyProperty MaxColumnsProperty{ get; };
static Windows.UI.Xaml.DependencyProperty HeaderProperty{ get; };

>Is there any way to modify the individual styling of elements in the group? Either on a collective, or individual basis.

Only to the same extent that you can do it today with ListViewItems. The supported type within this group is RadioButtonListViewItem.

>What happens if "non-RadioButton elements" are specified? Do you just take the text from them and use only that? If so, how-.ToString()? What if it's a complex object? What if I want an accompanying image or a help hyperlink?

This was a bit of a typo. This group takes only non-RadioButton elements. Meaning you can customize your data template in any way you like, and get that exact definition represented in the RadioButton group, but prefaced with a RadioButton icon.

Like so:
```xaml
<muxc:RadioButtons Header="Test Group" x:Name="RadioButtonGroup">
    <muxc:RadioButtons.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock Text="{Binding}" Margin="5"/>
                <HyperlinkButton Content="My Link" Margin="5"/>
                <Rectangle Fill="Orange" Width="10" Height="10" Margin="5"/>
            </StackPanel>
        </DataTemplate>
    </muxc:RadioButtons.ItemTemplate>
</muxc:RadioButtons>

image

in code behind I had this specified:
```c#
public class OptionDataModel
{
public string Label;

public override string ToString()
{
    return Label;
}

}

List radioButtonItems;

public MainPage()
{
this.InitializeComponent();

radioButtonItems = new List<OptionDataModel>();
radioButtonItems.Add(new OptionDataModel() { label = "Item 1" });
radioButtonItems.Add(new OptionDataModel() { label = "Item 2" });
radioButtonItems.Add(new OptionDataModel() { label = "Item 3" });
radioButtonItems.Add(new OptionDataModel() { label = "Item 4" });
radioButtonItems.Add(new OptionDataModel() { label = "Item 5" });

RadioButtonGroup.ItemsSource = radioButtonItems;

}
```

I can see certain folks wanting to restyle the radio button look itself, would that be possible somehow?

Will there be an Orientation property to allow showing the buttons horizontally vs. vertically? (Or would they change the ItemsPanel like a regular ListView?)

Assuming SelectedItem and Binding would work like ListView as well?

@michael-hawker yes SelectedItem and Binding work like the ListView does.

There is no orientation property. The closest is the MaxColumns property, where you can tell it the # of columns you want and the control will auto fill them in an accessibility-friendly format:

It would look like this:

<muxc:RadioButtons Header="App Mode" MaxColumns="3">
    <x:String>Column 1</x:String>
    <x:String>Column 2</x:String>
    <x:String>Column 3</x:String>
    <x:String>Column 1</x:String>
    <x:String>Column 2</x:String>
    <x:String>Column 3</x:String>
</muxc:RadioButtons>

image

This definitely seems valuable, I'd like to highlight one question that @mrlacey raised:

Is there any way to modify the individual styling of elements in the group? Either on a collective, or individual basis.

Being able to easily individually style elements (or just provide individual labels for each item that are specified in the view) seems especially important for a RadioButton group, relative to a ListView. I would think the common use cases are different in that a ListView is typically used to display a dynamic collection of data - where there's often a natural display format for each item that can be straightforwardly mapped from its model/viewmodel object - while a group of radio buttons would be more often used to present a static, finite list of options, that would logically map to a static, finite set of values of some data type (like an enum or bool) - but with each one assigned a text label, where the labels chosen are purely a UI design decision and "arbitrary" from the perspective of the model.

Unless I'm missing something (and someone please tell me and make me feel stupid if I am 馃槉) , binding the choice of a static set of values to a static set of arbitrarily-labeled controls is somewhat cumbersome and unintuitive today, either with RadioButtons (where you have to write something like a BooleanNegationConverter or EnumToBoolConverter with ConverterParameter etc.) or with ListView (where you have to write a ViewModel type that includes both the label and value, then bind to SelectedValuePath - this is both awkward and feels like it's making the view model worry about something that should be purely a View concern). So patterning the API on ListView wouldn't by itself fix this common scenario.

Would it be possible to allow setting labels individually on each item in the group, independent of the item's value - using an attached property for example?

binding the choice of a static set of values to a static set of arbitrarily-labeled controls is somewhat cumbersome and unintuitive today

Could you elaborate on what your data model looks like that you think data binding would be cumbersome? I want to make sure we build something easy to use, but it's hard for me to picture what is difficult/unintuitive in your mind. Maybe give an example of what you think the "hard to achieve" scenario would look like?

@jevansaks maybe @contextfree's talking about a scenario where he has a data model like:

class Person {
  string Name;
  string Address;
  ...
}

List<Person> Friends;

How would you XAML bind the RadioButtons to be the set of Name or Addresses from the Friends group?

Or if you have an enum for different values like LightMode, DarkMode, CustomMode but want them to show up differently within the choices? e.g. Light, Dark, and Custom?

Thanks for reading my comment, sorry I phrased it in a convoluted way. What I had in mind was something closer to @michael-hawker 's second example: binding each radio button to a different enum value, where it might show up as a longer descriptive string that's different from the name of the enum value in code. The common way to do this since WPF has been to write an EnumToBoolean converter that takes a parameter and compares it to the value, then apply it to each RadioButton with a different parameter. e.g.,

  <RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=FirstSelection}">first selection</RadioButton>
  <RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=TheOtherSelection}">the other selection</RadioButton>
  <RadioButton IsChecked="{Binding Path=VeryLovelyEnum, Converter={StaticResource enumBooleanConverter}, ConverterParameter=YetAnotherOne}">yet another one</RadioButton>

(I took this from a Stack Overflow answer: https://stackoverflow.com/questions/397556/how-to-bind-radiobuttons-to-an-enum). Function binding makes one-way binding easier but doesn't really help with two-way binding (since you'd have to define a separate ConvertBack method for every property you wanted to bind this way). It's not especially difficult once you know how to do it, but I think it's unintuitive - the number of tutorials, blog posts and Stack Overflow answers I found on this sort of testifies to that imo, for example
https://blogs.u2u.be/diederik/post/Databinding-to-an-enumeration-in-WPF
https://www.codecisions.com/wpf-binding-boolean-to-an-enum-value/
https://wpf.2000things.com/2011/08/05/358-binding-a-radiobutton-to-an-enumerated-type/

and since the Converter/ConverterParameter has to be applied to every radio button bound this way, it also contributes to XAML bloat that makes the codebase incrementally harder to read and understand over time (look how much it's repeated in this XAML file I found on github for example)

Unfortunately if the API for the new RadioButton group control closely matches ListView, I'm not sure it will be much of an improvement for this scenario (but I could very well be missing something obvious). The two ways I can think of to accomplish this with a ListView would be

  1. Make a new type in code which bundles a choice's enum value with its display text. e.g.,

     public class ThemeOptionViewModel {
          public Theme Value {get; set;}
          public string DisplayText {get; set;}
     }
    
    
    <ListView SelectedValue="{x:Bind SelectedTheme, Mode=TwoWay}" SelectedValuePath="Value">
                <ListView.Items>
                    <local:ThemeOptionViewModel Value="DarkMode" DisplayText="Dark theme"/>
                    <local:ThemeOptionViewModel Value="LightMode" DisplayText="Light theme"/>
                    <local:ThemeOptionViewModel Value="CustomMode" DisplayText="Your own custom theme"/>
                 </ListView.Items>
                 <ListView.ItemTemplate>
                     <DataTemplate x:DataType="local:ThemeOptionViewModel">
                          <TextBlock Text="{x:Bind DisplayText}"/>
                      </DataTemplate>
                  </ListView.ItemTemplate>
      </ListView>
    

The problems with this I see are

  • some code and XAML that feels extraneous (the ThemeOptionViewModel, the ItemTemplate)
  • requires adding C#/C++ code just to support display text, which feels like it should be implementable purely in XAML
  • like the EnumBooleanConverter approach above, seems like a fairly roundabout and unintuitive way to accomplish a very basic task.
  1. Set the ContentTemplate of the ListViewItem for each option individually, e.g.,

        <ListView SelectedValue="{x:Bind SelectedTheme, Mode=TwoWay}" SelectedValuePath="Content">
                <ListViewItem>
                    <ListViewItem.ContentTemplate>
                        <DataTemplate>
                            <TextBlock>Dark theme</TextBlock>
                        </DataTemplate>
                    </ListViewItem.ContentTemplate>
                    <local:Theme>DarkMode</local:Theme>
                </ListViewItem>
                <ListViewItem>
                    <ListViewItem.ContentTemplate>
                        <DataTemplate>
                            <TextBlock>Light theme</TextBlock>
                        </DataTemplate>
                    </ListViewItem.ContentTemplate>
                    <local:Theme>LightMode</local:Theme>
                </ListViewItem>
            </ListView>
    

I think this is conceptually better, the remaining problems are just verbosity and nonobviousness.

It seems to me though that for many uses of radio buttons, what we really care about configuring is just a mapping of enum (or whatever type) values to labels. In other words I really sort of wish I could just write something like

    <RadioButtons SelectedValue={x:Bind SelectedTheme, Mode=TwoWay}>
       <RadioButton Value="DarkMode">Dark theme</RadioButton>
       <RadioButton Value="LightMode">Light theme</RadioButton>
     </RadioButtons>`

I'm not proposing this exact API (not sure if it's workable), just hoping for something that feels more like this last one to write and read, and less like the earlier examples.

One idea for improving this that seems promising to me is just having a compact string format for simple text-based DataTemplates. If you could write

 <RadioButtons SelectedValue="{x:Bind SelectedTheme, Mode=TwoWay}" SelectedValuePath="Content">
         <RadioButtonsItem ContentTemplate="Dark theme">
             <local:Theme>DarkMode</local:Theme>
         </RadioButtonsItem>
         <RadioButtonsItem ContentTemplate="Light theme">
             <local:Theme>LightMode</local:Theme>
         </RadioButtonsItem>
  </RadioButtons>

it would cut down on the noise, while still following existing patterns like ListView, ContentTemplate etc., and not introducing new concepts. It seems this should be possible by implementing CreateFromString on DataTemplate, returning a template with a TextBlock around the given string. This could be useful not only for RadioButtons but for similar controls such as ComboBox, or ContentControls in general.

A problem I do see here is that the implicit conversion from string would be applicable to any property typed as DataTemplate, but using it for ItemTemplates would show every item as the same constant string which doesn't make sense. It might be useful to implement an syntax for simple inline property binding, similar to C# $"" ($"My name is {Name}"), but generating bindings instead of simply substituting. I could imagine this helping with some uses of ItemsControl though I still need to think through and research the use cases here.

I'm going to write this up as a separate proposal, and also prototype it in WPF (unfortunately I don't think it's possible for me to do this in WinUI without resorting to Detours or something, since CreateFromString must be applied to the DataTemplate class itself and that part of the platform isn't open source (yet?))

I'd appreciate any feedback on whether this makes sense to other people.

Does this proposal include fixing the current bug with radio buttons reported here - https://wpdev.uservoice.com/forums/110705-universal-windows-platform/suggestions/36047965--uwp-radiobutton-ischecked-in-xaml-does-not-work? Also, it's sometimes necessary to place radio buttons in the same group in different parents. Does this proposal cover that scenario? If not, would you consider either changing the behaviour of GroupName so it's scoped to the current naming container and not the whole app, or adding an alternatively named property that does this?

Was this page helpful?
0 / 5 - 0 ratings