Saturday, October 11, 2008

Binding IsChecked property of RadioButton in WPF

If you have tried to bind the RadioButton's IsChecked property in WPF to an object, you have most likely experienced the following problem: In OneWay bindings it works great. But if you have more than one RadioButtons binded TwoWay and you click on an unchecked one, you were expecting that the object to which the previously checked RadioButton was binded to receive the value of False. But you were wrong in your expectations. That's because for some reasons Microsoft does not obey bindings and does not pass the False value to the DependencyProperty and instead of that they just assign the value False directly to the property, which ruins the binding.

There are many proposed solutions to this around the internet, problem with all those is that they do not work with dynamically generated controls. So since I had to find a way to make this working with dynamic controls, decided to make a wrapper of the real RadioButton which will correctly Bind in two ways. Here is the code for the wrapper:

using System;
using System.IO;
using System.Printing;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Media;
using Microsoft.Win32;

namespace Controls
{
    public class RadioButtonExtended : RadioButton
    {
        static bool m_bIsChanging = false;

        public RadioButtonExtended()
        {
            this.Checked += new RoutedEventHandler(RadioButtonExtended_Checked);
            this.Unchecked += new RoutedEventHandler(RadioButtonExtended_Unchecked);
        }

        void RadioButtonExtended_Unchecked(object sender, RoutedEventArgs e)
        {
            if (!m_bIsChanging)
                this.IsCheckedReal = false;
        }

        void RadioButtonExtended_Checked(object sender, RoutedEventArgs e)
        {
            if (!m_bIsChanging)
                this.IsCheckedReal = true;
        }

        public bool? IsCheckedReal
        {
            get { return (bool?)GetValue(IsCheckedRealProperty); }
            set
            {
                SetValue(IsCheckedRealProperty, value);
            }
        }

        // Using a DependencyProperty as the backing store for IsCheckedReal. This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsCheckedRealProperty =
        DependencyProperty.Register("IsCheckedReal", typeof(bool?), typeof(RadioButtonExtended),
        new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Journal |
        FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        IsCheckedRealChanged));

        public static void IsCheckedRealChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            m_bIsChanging = true;
            ((RadioButtonExtended)d).IsChecked = (bool)e.NewValue;
            m_bIsChanging = false;
        }
    }
}

So now all you have to do is to use the ExtendedRadioButton instead of the built-in one and bind to the IsCheckedReal property instead of the IsChecked one.
Enjoy :)

21 comments:

Anonymous said...

It works great! Thanks, I like it because it is clean and simple (and works as expected)!

Remember, this approach will not work when used with styles and triggers, see: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/8eb8280a-19c4-4502-8260-f74633a9e2f2

Anonymous said...

Also: To get the default BindingMode (and equivalant DependencyProperty) behavior as the regular WPF RadioButton, change the DependencyProperty to:

public static readonly DependencyProperty IsCheckedRealProperty =
DependencyProperty.Register("IsCheckedReal", typeof(bool?), typeof(ToggleButton),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Journal |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
IsCheckedRealChanged));

Peter Staev said...

Thanks for the suggestion! Changed it in the code above.

As for the styles and triggers - you may be right and it may not work as expected.

rdrunner said...

Totally AWESOME! And I learned some good stuff. Thank you so much! You saved me hours!!!

Anonymous said...

Why is m_bIsChanging static??

Peter Staev said...

Well, m_bIsChanging is static because it is used in a static method. You can change to non static and create a public property that will return m_bIsChanging. After that in the static event you should use

((RadioButtonExtended)d).IsChanging = true;

and

((RadioButtonExtended)d).IsChanging = false;

Nitin said...

Although I wasted half of my day but still finally good to see a working solution.

Thanks a lot Peter :)

Anonymous said...

Well done, this works great.
Thank you.

Anonymous said...

Thank you very much! I took me hours of frustration wondering why this does not out of the box!

Anonymous said...

Wow. I spent so much time fighting with this to finally discover it was a bug, then to finally discover a workaround that actually works. Nice job, I am using your class definition to successfully work around the issue for what I was trying to do.

Ashish said...

What a resolution... Amazing.
Thanks a ton. You saved my day.
All others failed for me

Geert van Horrik said...

Maybe a little late, but why do you declare a bool?, and use a bool as property definition?

Peter Staev said...

This was a mistake. Thanks for the heads up and it is changed now in the original post.

Anonymous said...

Thanks dude! Saved a lot of time :)

mad russian said...

awesome, thanks man!

small comment about someone saying you can't apply styles, triggers..
you totally can!
just target correct namespaces for that. In the styles example:

lets say you pull
xmlns:common="clr-namespace:...RadioButtonExtended folder"

In the style TargetType="common:RadioButtonExtended"...Sample Setter Property="RadioButton.Margin" Value="8"

as long as you instanciate the RadioButtonExtended with the same namespace:

common:adioButtonExtended.. you good to go

Anyway, thanks for your post

ristogod said...

After trying many other solutions that didn't work. I tried this. Same problem every where. Binding/Convert works once on Load. After that, checking the radio buttons never seems to fire the Binding again. The ConvertBack method in my Binding is never fired.

Peter Staev said...

@ristogod
Did you set your binding to the new IsCheckedReal property or the IsChecked one? Basically it should not matter if there is a converter or not as the converting is fired automatically by the framework itself. Also take a look at the first comment where there is a link that this workaround will not work with styles and triggers.

Anonymous said...

very very nice tnx

Anonymous said...

Have later versions of .NET fixed the bug this was working around?

Anonymous said...

Great job. Thank you for your effort.

Anonymous said...

worked for me. thanks!