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 :)

30 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!

Anonymous said...

very great

Anonymous said...

If I set the IsCheckedReal via databinding during runtime, its unchecks the other radiobuttons correct, but doesn't set the radiobutton as checked. On initialze it works.
Any suggestions? Thanks in advance.
Peter

Peter Staev said...

I'm not sure understand your explanation, but make sure you have bound all the radio buttons to the IsCheckedReal property and not the original RadioButton.IsChecked property.

Anonymous said...

Hello Peter
Thanks for the reply. All of my radio buttons are only bound to IsCheckedReal.
To clarify: If I set the property on the ViewModel (which is bound with IsCheckedReal) during runtime this button is not visually set (but the internal state is correct)
I seems to be that the GUI is not updated. Or in other words: if I set the button in the viewmodel during runtime, all buttons are unchecked and none of them is checked.
In operation with the mouse it works correct.
Thanks again,
Peter

Peter Staev said...

In this case make sure your ViewModel implements correctly the INotifyPropertyChanged (http://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged(v=vs.110).aspx) interface. It is required when you want your model changes to be reflected on the UI (not specifically for the Radiobuttons but for everything that you have bound). Hope this helps.

Anonymous said...

Hello Peter
Thanks for the answer. INotifyPropertyChanged works correct. The changes are comming to the radiobutton. But the radiobutton GUI works not correct if the button is set programmatically. (other buttons will be cleard but none is set)
Anyway, Thanks for your help.
Peter

Peter Staev said...

I have no idea why it does not for you. I have created a simple project locally and setting the boolean values in the model correctly updates the UI.

Anonymous said...

Hello Peter
Thanks a lot for your help and patience. Now I have made also a small project and there the radiobutton works perfekt. There must be something in my project that disturbs it.
Peter

Anonymous said...

Hello Peter
Now I found the reason for the strange behaviour. I have used GroupName in the radiobuttons. Without it works. My solution is now to overwrite OnChecked and OnUnchecked (and do nothing) in the rb and do the logic in the viewmodel.
Thanks again, Peter