Search code examples
c#.netwpfcheckboxtogglebutton

Read-Only ToggleButton or CheckBox in WPF


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.

Bound model

class SomeViewModel
{
  public bool Checked => true;
}

View

<CheckBox IsChecked="{Binding Checked, Mode=OneWay}">

But unfortunately, this is not working, the user can still change the CheckBox value...


Solution

  • 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.

    ToggleButtonEx class

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

    Usage

    <CheckBox IsChecked="{Binding Checked, Mode=OneWay}" ns:ToggleButtonEx.IsReadOnly="True">