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

33 comments:

  1. Anonymous9:02 PM

    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

    ReplyDelete
  2. Anonymous9:27 PM

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

    ReplyDelete
  3. 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.

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

    ReplyDelete
  5. Anonymous9:01 AM

    Why is m_bIsChanging static??

    ReplyDelete
  6. 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;

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

    Thanks a lot Peter :)

    ReplyDelete
  8. Anonymous5:42 PM

    Well done, this works great.
    Thank you.

    ReplyDelete
  9. Anonymous9:41 AM

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

    ReplyDelete
  10. Anonymous3:54 AM

    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.

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

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

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

    ReplyDelete
  14. Anonymous2:55 PM

    Thanks dude! Saved a lot of time :)

    ReplyDelete
  15. 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

    ReplyDelete
  16. 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.

    ReplyDelete
  17. @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.

    ReplyDelete
  18. Anonymous3:10 PM

    very very nice tnx

    ReplyDelete
  19. Anonymous5:42 PM

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

    ReplyDelete
  20. Anonymous10:28 PM

    Great job. Thank you for your effort.

    ReplyDelete
  21. Anonymous12:19 PM

    worked for me. thanks!

    ReplyDelete
  22. Anonymous9:35 AM

    very great

    ReplyDelete
  23. Anonymous12:30 PM

    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

    ReplyDelete
  24. 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.

    ReplyDelete
  25. Anonymous3:35 PM

    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

    ReplyDelete
  26. 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.

    ReplyDelete
  27. Anonymous5:53 PM

    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

    ReplyDelete
  28. 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.

    ReplyDelete
  29. Anonymous11:07 AM

    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

    ReplyDelete
  30. Anonymous3:11 PM

    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

    ReplyDelete
  31. Anonymous1:00 PM

    Working well, thank you very much

    ReplyDelete
  32. Hi Peter,

    Thanks for your solution. I implemented same in my code and it worked fine for the scenario where normal radio button was causing trouble.
    However on testing further I found few issue in the implementation.

    If we will set value from ViewModel then it sets value perfectly fine but doesn't revert the value of previously selected radio from binded variable. It only reflects on UI.

    so I have come up with some minor changes which takes care of all the problem.

    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)
    {
    this.IsChecked = false;
    }

    void RadioButtonExtended_Checked(object sender, RoutedEventArgs e)
    {
    this.IsChecked = true;
    }

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

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

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

    ReplyDelete
  33. Thank you. It helps me much. Awesome... :D

    ReplyDelete