IanG on Tap

Ian Griffiths in Weblog Form (RSS 2.0)

Blog Navigation

August (2014)

(1 item)

July (2014)

(5 items)

April (2014)

(1 item)

March (2014)

(1 item)

January (2014)

(2 items)

November (2013)

(2 items)

July (2013)

(4 items)

April (2013)

(1 item)

February (2013)

(6 items)

September (2011)

(2 items)

November (2010)

(4 items)

September (2010)

(1 item)

August (2010)

(4 items)

July (2010)

(2 items)

September (2009)

(1 item)

June (2009)

(1 item)

April (2009)

(1 item)

November (2008)

(1 item)

October (2008)

(1 item)

September (2008)

(1 item)

July (2008)

(1 item)

June (2008)

(1 item)

May (2008)

(2 items)

April (2008)

(2 items)

March (2008)

(5 items)

January (2008)

(3 items)

December (2007)

(1 item)

November (2007)

(1 item)

October (2007)

(1 item)

September (2007)

(3 items)

August (2007)

(1 item)

July (2007)

(1 item)

June (2007)

(2 items)

May (2007)

(8 items)

April (2007)

(2 items)

March (2007)

(7 items)

February (2007)

(2 items)

January (2007)

(2 items)

November (2006)

(1 item)

October (2006)

(2 items)

September (2006)

(1 item)

June (2006)

(2 items)

May (2006)

(4 items)

April (2006)

(1 item)

March (2006)

(5 items)

January (2006)

(1 item)

December (2005)

(3 items)

November (2005)

(2 items)

October (2005)

(2 items)

September (2005)

(8 items)

August (2005)

(7 items)

June (2005)

(3 items)

May (2005)

(7 items)

April (2005)

(6 items)

March (2005)

(1 item)

February (2005)

(2 items)

January (2005)

(5 items)

December (2004)

(5 items)

November (2004)

(7 items)

October (2004)

(3 items)

September (2004)

(7 items)

August (2004)

(16 items)

July (2004)

(10 items)

June (2004)

(27 items)

May (2004)

(15 items)

April (2004)

(15 items)

March (2004)

(13 items)

February (2004)

(16 items)

January (2004)

(15 items)

Blog Home

RSS 2.0

Writing

Programming C# 5.0

Programming WPF

Other Sites

Interact Software

WPF RadioButton GroupName Scope

Monday 15 November, 2010, 11:36 AM

I recently had an email from a Pluralsight On-Demand! customer about a problem with WPF’s RadioButton. (Yes, the italics and the exclamation mark are part of the brand, apparently…) I thought I’d post my response here, because it might be useful to a wider audience.

Here’s the problem: if you use the GroupName property on some radio buttons in a user control, and you use that user control more than once in the same window, the radio buttons form a single group across the whole window—they are not scoped to the user control as you might expect. For example, suppose your user control contains this rather contrived Xaml:

...
<GroupBox Header="Normal GroupName">
    <StackPanel>
        <RadioButton GroupName="MyGroup" Content="One" IsChecked="True" />
        <RadioButton GroupName="MyGroup" Content="Two" />
        <RadioButton GroupName="MyGroup" Content="Three" />
    </StackPanel>
</GroupBox>
...

(It’s contrived because in practice, you probably wouldn’t set a GroupName at all here. I’m just showing it as a simple example that demonstrates the issue.) If you try to use this user control several times in the same window, e.g.:

<StackPanel>
    <my:WithRadios />
    <my:WithRadios />
    <my:WithRadios />
</StackPanel>

you will find that the radio buttons don’t group by user control. They form one group that spans all the user controls. So you’ll only be able to select one item at a time, even though the layout implies three separate groups:

Three GroupBoxes containing three RadioButtons each, but with only one RadioButton being selected out of all nine.

From WPF’s perspective, this behaviour is by design—the whole point of GroupName is to allow you to define radio buttons that act as a group without having to be contained by the same parent panel. But from our application’s perspective, it’s not likely to be what we want—if a user control contains some radio buttons, the grouping should probably remain within the user control.

In this particular example, it’s easily solved: don’t specify a GroupName. The buttons are all children of the same StackPanel, so they will automatically form a group that’s independent of any other radio button groups. However, that won’t always work in real examples. The GroupName property exists for a reason: sometimes it’s useful to split logically related radio buttons across multiple panels for layout purposes. If we want to do that inside a user control, we’ll have a problem if we try to use multiple instances of that user control inside a single window.

This is a somewhat obscure combination of circumstances—I’ve been working with WPF since its first preview went public around 7 years ago, and I’ve not run into this myself, nor do I recall ever having been asked about it before. However, it seems like a reasonable thing to want to do, unusual though it may be.

Unfortunately, there’s nothing built into WPF to help. Ideally, WPF would provide something like an attachable RadioButton.IsGroupNameScope property that we could set to limit the scope of our names, just like we can with grid size groups. Unfortunately, group names are always scoped to the containing root visual, which is usually your window, and there’s no way to change that. So we need to build some extra stuff to make this work. I’ll start by showing you how the finished result looks in use:

<GroupBox Header="Unique GroupName" Grid.Column="2">
    <StackPanel nn:LocalName.BaseName="{nn:UniqueName}">
        <RadioButton GroupName="{nn:LocalName}"
                     Content="One" IsChecked="True" />
        <RadioButton GroupName="{nn:LocalName}"
                     Content="Two" />
        <RadioButton GroupName="{nn:LocalName}"
                     Content="Three" />

        <RadioButton GroupName="{nn:LocalName Letter}"
                     Content="A" IsChecked="True" />
        <RadioButton GroupName="{nn:LocalName Letter}"
                     Content="B" />

        <RadioButton GroupName="{nn:LocalName Colour}"
                     Content="Red" IsChecked="True" />
        <RadioButton GroupName="{nn:LocalName Colour}"
                     Content="Green" />
        <RadioButton GroupName="{nn:LocalName Colour}"
                     Content="Blue" />
    </StackPanel>
</GroupBox>

That actually defines three different name groups, so this single StackPanel contains three distinct sets of radio buttons.

This example relies on some code that defines some custom features I’ve added to my Xaml. (The nn: prefix in front of all of them is just an XML namespace prefix mapped to my example project’s default namespace by the way.) The first feature is an attachable, inherited property called LocalName.BaseName:

<StackPanel nn:LocalName.BaseName="{nn:UniqueName}">

This property holds a text string that can be used as a name, or as the basis for a name. The property doesn’t do anything itself—its value will be picked up by elements inside the panel, as we’ll see shortly.

This same line of Xaml also uses the second feature, a custom markup extension called UniqueName. This generates a unique string at runtime. (It relies on the Guid type.) The string is generated in the extension’s constructor, so every instance of this extension will return a different string. If you use the extension multiple times in a single Xaml file, each usage will return a different name. But more importantly, a single use of this markup extension will return a different name each time the Xaml is loaded. Since each instance of a user control loads its own copy of its Xaml, that means that the value returned by this UniqueName markup extension will be different for each instance of any user control that uses it.

That solves the problem of getting a different group name in each user control. However, we still need to use the same name across multiple radio buttons within the control—if we just use this UniqueName markup extension on every radio button, each button will end up in its own group, which would be useless.

To deal with that, the final feature is a custom markup extension that uses the inherited LocalName.BaseName. You can see it here:

<RadioButton GroupName="{nn:LocalName}" Content="Two" />

This sets the radio button’s GroupName property to be whatever the value of LocalName.BaseName is on this element. And since my custom attachable property is inherited, the value will come from the containing StackPanel in this case. Or you could put it higher up—you could set LocalName.BaseName on, say, the Grid at the root of your user control’s content, and then any time you use the LocalName markup extension anywhere in the control, it’ll pick up that name.

This gives us what we need: we get a common name within our user control, meaning radio buttons function as a group within the user control, but the name will be different for each instance of that user control. So the upshot is that each user control gets its own distinct group.

This markup extension supports an optional argument: a string that is simply appended to the BaseName. (That’s why I called it base name—it’s not necessarily the name that actually gets used.) Here’s a radio button that uses the argument:

<RadioButton GroupName="{nn:LocalName Letter}" Content="B" />

For example, I ran the program just now, and looking in the debugger, on a particular instance of the user control the base name ended up as “a6b9345b660a4f248b5b5232a48ab653” and so we’d get a name of “a6b9345b660a4f248b5b5232a48ab653Letter” on this particular radio button. This is how the example above gets three distinct groups of radio buttons.

The code for all three features is pretty simple. Here’s the attachable property:

public class LocalName
{
    public static string GetBaseName(FrameworkElement obj)
    {
        return (string) obj.GetValue(BaseNameProperty);
    }
 
    public static void SetBaseName(FrameworkElement obj, string value)
    {
        obj.SetValue(BaseNameProperty, value);
    }
 
    public static readonly DependencyProperty BaseNameProperty =
        DependencyProperty.RegisterAttached("BaseName", typeof(string),
        typeof(LocalName),
        new FrameworkPropertyMetadata(null,
            FrameworkPropertyMetadataOptions.Inherits));
}

That’s about as simple as a dependency property gets—a pair of accessors, and the property registration. The only thing I’ve changed here from the code that Visual Studio’s propa snippet spits out is that I’ve turned on property inheritance.

Here’s the markup extension that generates a unique name:

public class UniqueNameExtension : MarkupExtension
{
    private string _name;
    public UniqueNameExtension()
    {
        _name = Guid.NewGuid().ToString("N");
    }
 
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return _name;
    }
}

Finally, there’s the LocalName markup extension. Remember, this uses the inherited value of the LocalName.BaseName property, optionally appending an extra bit of text. It looks like this:

public class LocalNameExtension : MarkupExtension
{
    private string _qualifier;
 
    public LocalNameExtension()
    {
    }
    public LocalNameExtension(string qualifier)
    {
        _qualifier = qualifier;
    }
 
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        var targetProvider = (IProvideValueTarget)
            serviceProvider.GetService(typeof(IProvideValueTarget));
        var target = (FrameworkElement) targetProvider.TargetObject;
 
        string name = LocalName.GetBaseName(target);
        if (_qualifier != null) { name += _qualifier; }
        return name;
    }
}

The empty default constructor isn’t redundant. It’s there because if you provide a non-default constructor, C# stops generating the default, empty one it usually provides. (I mention this because I frequently encounter developers who don’t know this, and experience has taught me that people like to email me corrections for things in my blog that aren’t actually wrong. This feels like the sort of thing I’d get emails for, so I’m just trying to pre-empt that.)

You can download an example project from here: RadioGroupScope.zip.

There may be simpler ways to achieve this by the way—this is a solution I just made up to answer a question that a customer asked me. (You could solve this by implementing radio button exclusion in the viewmodel. That might not actually be simpler, though.) Three custom Xaml features may seem like an extravagance, but in the end, the individual parts seemed simpler this way than any of the solutions I came up with that used fewer parts. But please email me if you have suggestions for improvements.

Copyright © 2002-2013, Interact Software Ltd. Content by Ian Griffiths. Please direct all Web site inquiries to webmaster@interact-sw.co.uk