Read-Only ToggleButton or CheckBox in WPF

195 views Asked by At

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

2

There are 2 answers

8
Kino101 On BEST ANSWER

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">
0
Emperor Eto On

This should accomplish what I believe you want:

Model

class SomeViewModel
{
  public bool Checked 
  {
      get => true;
      set {}
  }
}

XAML

<CheckBox IsHitTestVisible="False"
          IsChecked="{Binding Checked}">

Leaving the binding as two-way causes WPF to fetch the updated value from the getter - which in this case is always true - after the button is pressed and the source property is updated, overriding the default behavior which would be just to toggle it.

I agree with some of the comments that some kind of visual indication is preferable - or perhaps more accurately, the absence of visual feedback (e.g. the button shouldn't light up on hover). IsHitTestVisible=false would help with this, but of course it's up to you.