Creo que una de las mejores cosas que tiene wpf es el sistema de binding acompañado del MVVM, pero de vez en cuando uno se encuentra con un dolor de cabeza como el binding de los radioButtons.
Lo más prolijo parece ser bindear las opciones de los radioButtons contra una property de algún enum en el viewmodel. Esto funciona bien, utilizando un EnumBooleanConverter, siempre que no se nos ocurra cambiar el valor de la property por código en lugar de hacer click sobre los botones.
Si intentamos cambiar la property por código nos vamos a encontrar con que se rompe el binding entre los radioButtons y la property que era un enum.
Hay un par de soluciones dando vuelta por internet, algunos hasta proponen reemplazar los radiobuttons por una lista; en el viewmodel ponen una lista con las opciones y otra property para el selectedValue. Luego un datatemplate que renderize un radioButton por cada item de la lista. Parece demasiado complejo y complica bastante el diseño del viewmodel.
Para mi la mejor opción es el workaround de Peter’s Tips & Tricks. Lo malo de esto es que hay que heredar del control para implementarlo.
Mi solución es exactamente la misma que la de Peter’s, hace un attach a los eventos Checked y Unchecked para manipular el valor de la property IsCheckedReal, pero en lugar de heredar uso attached behaviors.
El behavior expone dos properties, “IsCheckedReal” y “AttachBehavior”. La primera es para hacer el binding contra el enum del viewmodel, la segunda es un bool para hacer un on/off del behavior.
Ejemplo de uso:
<RadioButton behavior:RadioButtonBehavior.AttachBehavior="True"
behavior:RadioButtonBehavior.IsCheckedReal="{Binding Path=Scope, Converter={StaticResource EnumBooleanConverter}, ConverterParameter=Local}" Content="Local" />
RadioButtonBehavior:
using System.Windows;
using System.Windows.Controls;
namespace Aquadize.Foundation.Behavior
{
/// <summary>
/// Behavior para hacer twoway binding de la property IsChecked en un RadioButton.
/// La solución está basada en: http://pstaev.blogspot.com/2008/10/binding-ischecked-property-of.html
/// Otras soluciones: http://www.phdesign.com.au/programming/wpf-radiobutton-binding-to-ischecked-property/
/// </summary>
public static class RadioButtonBehavior
{
private static bool _isChanging;
#region IsCheckedReal Property
public static bool? GetIsCheckedReal(DependencyObject obj)
{
return (bool?)obj.GetValue(IsCheckedRealProperty);
}
public static void SetIsCheckedReal(DependencyObject obj, bool? value)
{
obj.SetValue(IsCheckedRealProperty, value);
}
public static readonly DependencyProperty IsCheckedRealProperty =
DependencyProperty.RegisterAttached("IsCheckedReal", typeof(bool?), typeof(RadioButtonBehavior),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.Journal | FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
OnIsCheckedRealChanged));
#endregion
#region AttachBehavior Property
public static bool GetAttachBehavior(DependencyObject obj)
{
return (bool)obj.GetValue(AttachBehaviorProperty);
}
public static void SetAttachBehavior(DependencyObject obj, bool value)
{
obj.SetValue(AttachBehaviorProperty, value);
}
public static readonly DependencyProperty AttachBehaviorProperty =
DependencyProperty.RegisterAttached("AttachBehavior", typeof(bool), typeof(RadioButtonBehavior), new UIPropertyMetadata(false, OnAttachBehaviorChanged));
#endregion
private static void OnAttachBehaviorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var radioButton = d as RadioButton;
// solo se puede attachar el behavior en un RadioButton
if (radioButton == null)
return;
var attach = (bool)e.NewValue;
if (attach)
{
radioButton.Checked += new RoutedEventHandler(radioButton_Checked);
radioButton.Unchecked += new RoutedEventHandler(radioButton_Unchecked);
}
else
{
radioButton.Checked -= new RoutedEventHandler(radioButton_Checked);
radioButton.Unchecked -= new RoutedEventHandler(radioButton_Unchecked);
}
}
private static void OnIsCheckedRealChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var radioButton = d as RadioButton;
if (radioButton == null)
return;
_isChanging = true;
radioButton.IsChecked = (bool)e.NewValue;
_isChanging = false;
}
private static void radioButton_Unchecked(object sender, RoutedEventArgs e)
{
var radioButton = sender as RadioButton;
if (!_isChanging)
SetIsCheckedReal(radioButton, false);
}
private static void radioButton_Checked(object sender, RoutedEventArgs e)
{
var radioButton = sender as RadioButton;
if (!_isChanging)
SetIsCheckedReal(radioButton, true);
}
}
}
