OneWay binding for ToggleButton's IsChecked property in WPF
Asked Answered
E

5

10

I have a ToggleButton with its IsChecked property bound to a property using a OneWay binding.

<ToggleButton
    Command="{Binding Path=SomeCommand}"
    IsChecked="{Binding Path=SomeProperty, Mode=OneWay}" />

The SomeCommand toggles the boolean SomeProperty value, and a PropertyChanged event is raised for SomeProperty.

If I change SomeProperty in my viewmodel the ToggleButton depresses correctly. However if I click the ToggleButton the binding seems to get lost and the button no longer gets checked according to the value of SomeProperty. Any ideas on how to fix this problem?

Eternity answered 22/9, 2010 at 16:25 Comment(2)
What does your SomeCommand do? I have a number of ToggleButtons that do the same thing, with the same kind of binding, and all SomeCommand does is negate the current value of SomeProperty.Galle
SomeProperty was actually a different type and I was using a converter to change it to a boolean. I left that out to make the question simpler.Eternity
C
7

This is by design when using oneway data binding. Add the attached property PresentationTraceSources.TraceLevel=High to your binding and you will see when it gets disconnected. This link describes the problem as well (without offering any solution): Debugging Data Binding in WPF

The way I normally solve it is to use a command for user interaction and code behind to change the control appearance because of some changed properties.

Castrate answered 22/9, 2010 at 18:12 Comment(1)
Why this answer got accepted? It doesn't solve the problem (which exists and can be easily reproduced).Allocation
C
13

There is a simple and elegant way to solve the original poster's problem - replacing IsChecked property of the ToggleButton with an attachable property that will set IsChecked of the button in its change handler:

namespace TBFix
{
  public class TBExtender
  {
    public static readonly DependencyProperty IsCheckedProperty =
      DependencyProperty.RegisterAttached("IsChecked", 
                                          typeof(bool),
                                          typeof(TBExtender),
                                          new PropertyMetadata(OnChanged));

    public static bool GetIsChecked(DependencyObject obj)
    {
      return (bool)obj.GetValue(IsCheckedProperty);
    }
    public static void SetIsChecked(DependencyObject obj, bool value)
    {
      obj.SetValue(IsCheckedProperty, value);
    }

    private static void OnChanged(DependencyObject o,
                                  DependencyPropertyChangedEventArgs args)
    {
      ToggleButton tb = o as ToggleButton;
      if (null != tb)
        tb.IsChecked = (bool)args.NewValue;
    }
  }
}

XAML then will look like this:

<ToggleButton Command="{Binding Path=SomeCommand}"
              TBFix:TBExtender.IsChecked="{Binding Path=SomeProperty,
                                                   Mode=OneWay}" />

EDIT: The OP solution does not work, because when the button is pressed the IsChecked property is set in the code (this is the way MS implemented ToggleButton control) - setting the property removes the binding from it and so it stops working.

By using attached property we can overcome this problem because it is never assigned a value in code and so the bindings stays intact.

Custard answered 28/4, 2011 at 17:21 Comment(3)
Hi Alex, Thanks for providing the elegant solution, it works for me. but I don't understand how it works. Can you explain a little bit more?Gaslit
I've added few words on the idea behind my solution. You can read more about attached properties on MSDN (msdn.microsoft.com/en-us/library/vstudio/…)Custard
This is indeed elegant!Unitive
C
7

This is by design when using oneway data binding. Add the attached property PresentationTraceSources.TraceLevel=High to your binding and you will see when it gets disconnected. This link describes the problem as well (without offering any solution): Debugging Data Binding in WPF

The way I normally solve it is to use a command for user interaction and code behind to change the control appearance because of some changed properties.

Castrate answered 22/9, 2010 at 18:12 Comment(1)
Why this answer got accepted? It doesn't solve the problem (which exists and can be easily reproduced).Allocation
B
3

To my knowledge Sinatr is correct regarding the 'desync' that occurs, at least in newer frameworks.

Another simple way around the issue is to remove the mode=oneway and implement an empty setter. Ex:

    bool _MyIsEnabled;
    public bool MyIsEnabled
    {
        get { return _MyIsEnabled; }
        set { }
    }

With this binding setup you can you change the value of the backing variable from your command binding function, or from whether you need to. Just remember to call RaisePropertyChanged.

Bakery answered 26/1, 2018 at 15:55 Comment(1)
Love this easy workaround - a little hacky, but way simpler than creating custom controlsAsiaasian
A
1

I have similar problem.

It is not "the binding seems to get lost" (unless it's earlier frameworks problems). Binding continues working and can be easily proved by changing property outside of that command (e.g. in handler of another button click event/command).

The problem is what IsChecked can be changed in two ways: 1) binding (when value of SomeProperty is changed button will be updated 2) user (if user press button he will change IsChecked, but binding is OneWay so SomeProperty will not be updated).

So you may have desync occurs when SomeProperty == false but button IsChecked == true or vice-versa.

To optimize performance binding mechanism is checking if new value is different from current. So if desync occurs and you try to update SomeProperty with the value which it already have, then nothing will happens.

Workaround is simple: update property in 2 steps

SomeProperty = !set;
SomeProperty = set;

where set is the value you need (e.g. opposite to current SomeProperty).

Allocation answered 5/2, 2016 at 11:2 Comment(0)
E
0

I think what the problem boils down to is a conflict between the command and the IsChecked binding. My solution was to change my viewmodel to expose a bool? and bind that to the IsChecked property. I did not bind a command to the ToggleButton. Elsewhere in my code where I want to toggle the property I use SomeCommand.

Eternity answered 22/9, 2010 at 18:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.