By default, WPF ToggleButton or CheckBox (which inherits ToggleButton) doesn't have some kind of ReadOnly property to prevent user from changing the value.
One technic consists in setting IsHitTestVisible="False"
and Focusable="False"
.
I thought that I could also achieve this binding the IsChecked
property to a get
only accessor.
class SomeViewModel
{
public bool Checked => true;
}
<CheckBox IsChecked="{Binding Checked, Mode=OneWay}">
But unfortunately, this is not working, the user can still change the CheckBox value...
In this case unfortunately, the binding is getting out of synchronization when user changes the value.
Below a solution which consists in always refreshing the binding when the user changes the value.
public static class ToggleButtonEx
{
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.RegisterAttached(
"IsReadOnly", typeof(bool), typeof(ToggleButtonEx), new PropertyMetadata(default(bool), OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is not ToggleButton toggleButton)
throw new InvalidOperationException($"This property may only be set on {nameof(ToggleButton)}.");
if ((bool)e.NewValue)
{
toggleButton.Checked += OnCheckChanged;
toggleButton.Unchecked += OnCheckChanged;
}
else
{
toggleButton.Checked -= OnCheckChanged;
toggleButton.Unchecked -= OnCheckChanged;
}
}
private static void OnCheckChanged(object sender, RoutedEventArgs e)
{
var binding = ((ToggleButton)sender).GetBindingExpression(ToggleButton.IsCheckedProperty);
binding?.UpdateTarget();
}
public static void SetIsReadOnly(DependencyObject element, bool value)
{
element.SetValue(IsReadOnlyProperty, value);
}
public static bool GetIsReadOnly(DependencyObject element)
{
return (bool)element.GetValue(IsReadOnlyProperty);
}
}
<CheckBox IsChecked="{Binding Checked, Mode=OneWay}" ns:ToggleButtonEx.IsReadOnly="True">