Search code examples
wpfbindingradio-button

Simple WPF RadioButton Binding?


What is the simplest way to bind a group of 3 radiobuttons to a property of type int for values 1, 2, or 3?


Solution

  • Updated Answer with a Simpler Approach

    Please see my new answer posted on this same page for a completely different, and much simpler approach to solving this issue. That new approach uses custom IValueConverter and Binding subclasses which let you go back to using a true RadioButton instead of a heavily-styled ListBox, as shown here.

    Again, unless there are other benefits you'd personally gain when using a ListBox subclass, the other approach is what I now recommend.

    Original Answer

    Actually, using the converter like that breaks two-way binding, plus as I said above, you can't use that with enumerations either. The better way to do this is with a simple style against a ListBox, like this:

    Note: Contrary to what DrWPF.com stated in their example, do not put the ContentPresenter inside the RadioButton or else if you add an item with content such as a button or something else, you will not be able to set focus or interact with it. This technique solves that. Also, you need to handle the graying of the text as well as removing of margins on labels or else it will not render correctly. This style handles both for you as well.

    <Style x:Key="RadioButtonListItem" TargetType="{x:Type ListBoxItem}" >
    
        <Setter Property="Template">
            <Setter.Value>
    
                <ControlTemplate TargetType="ListBoxItem">
    
                    <DockPanel LastChildFill="True" Background="{TemplateBinding Background}" HorizontalAlignment="Stretch" VerticalAlignment="Center" >
    
                        <RadioButton IsChecked="{TemplateBinding IsSelected}" Focusable="False" IsHitTestVisible="False" VerticalAlignment="Center" Margin="0,0,4,0" />
    
                        <ContentPresenter
                            Content             = "{TemplateBinding ContentControl.Content}"
                            ContentTemplate     = "{TemplateBinding ContentControl.ContentTemplate}"
                            ContentStringFormat = "{TemplateBinding ContentControl.ContentStringFormat}"
                            HorizontalAlignment = "{TemplateBinding Control.HorizontalContentAlignment}"
                            VerticalAlignment   = "{TemplateBinding Control.VerticalContentAlignment}"
                            SnapsToDevicePixels = "{TemplateBinding UIElement.SnapsToDevicePixels}" />
    
                    </DockPanel>
    
                </ControlTemplate>
    
            </Setter.Value>
    
        </Setter>
    
    </Style>
    
    <Style x:Key="RadioButtonList" TargetType="ListBox">
    
        <Style.Resources>
            <Style TargetType="Label">
                <Setter Property="Padding" Value="0" />
            </Style>
        </Style.Resources>
    
        <Setter Property="BorderThickness" Value="0" />
        <Setter Property="Background"      Value="Transparent" />
    
        <Setter Property="ItemContainerStyle" Value="{StaticResource RadioButtonListItem}" />
    
        <Setter Property="Control.Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListBox}">
                    <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    
        <Style.Triggers>
            <Trigger Property="IsEnabled" Value="False">
                <Setter Property="TextBlock.Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
            </Trigger>
        </Style.Triggers>
    
    </Style>
    
    <Style x:Key="HorizontalRadioButtonList" BasedOn="{StaticResource RadioButtonList}" TargetType="ListBox">
        <Setter Property="ItemsPanel">
            <Setter.Value>
                <ItemsPanelTemplate>
                    <VirtualizingStackPanel Background="Transparent" Orientation="Horizontal" />
                </ItemsPanelTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    

    You now have the look and feel of radio buttons, but you can do two-way binding, and you can use an enumeration. Here's how...

    <ListBox Style="{StaticResource RadioButtonList}"
        SelectedValue="{Binding SomeVal}"
        SelectedValuePath="Tag">
    
        <ListBoxItem Tag="{x:Static l:MyEnum.SomeOption}"     >Some option</ListBoxItem>
        <ListBoxItem Tag="{x:Static l:MyEnum.SomeOtherOption}">Some other option</ListBoxItem>
        <ListBoxItem Tag="{x:Static l:MyEnum.YetAnother}"     >Yet another option</ListBoxItem>
    
    </ListBox>
    

    Also, since we explicitly separated out the style that tragets the ListBoxItem rather than putting it inline, again as the other examples have shown, you can now create a new style off of it to customize things on a per-item basis such as spacing. (This will not work if you simply try to target ListBoxItem as the keyed style overrides generic control targets.)

    Here's an example of putting a margin of 6 above and below each item. (Note how you have to explicitly apply the style via the ItemContainerStyle property and not simply targeting ListBoxItem in the ListBox's resource section for the reason stated above.)

    <Window.Resources>
        <Style x:Key="SpacedRadioButtonListItem" TargetType="ListBoxItem" BasedOn="{StaticResource RadioButtonListItem}">
            <Setter Property="Margin" Value="0,6" />
        </Style>
    </Window.Resources>
    
    <ListBox Style="{StaticResource RadioButtonList}"
        ItemContainerStyle="{StaticResource SpacedRadioButtonListItem}"
        SelectedValue="{Binding SomeVal}"
        SelectedValuePath="Tag">
    
        <ListBoxItem Tag="{x:Static l:MyEnum.SomeOption}"     >Some option</ListBoxItem>
        <ListBoxItem Tag="{x:Static l:MyEnum.SomeOtherOption}">Some other option</ListBoxItem>
        <ListBoxItem Tag="{x:Static l:MyEnum.YetAnother}"     >Ter another option</ListBoxItem>
    
    </ListBox>