WPF中ToggleButton的IsChecked属性的单向绑定

10

我有一个ToggleButton,它的IsChecked属性通过单向绑定绑定到一个属性上。

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

SomeCommand 切换布尔类型的 SomeProperty 值,并发出 PropertyChanged 事件。

如果我在我的视图模型中更改了 SomeProperty,则 ToggleButton 会正确地弹起/按下。然而,如果我点击 ToggleButton,则数据绑定似乎会丢失,按钮将不再根据 SomeProperty 的值进行选中状态的更改。有没有什么方法可以解决这个问题?


你的 SomeCommand 是做什么的?我有许多执行相同操作、具有相同绑定类型的 ToggleButton,而 SomeCommand 所做的就是取反 SomeProperty 的当前值。 - Wonko the Sane
SomeProperty 实际上是另一种类型,我使用了一个转换器将其转换为布尔值。我忽略了这个细节以使问题更简单。 - sourcenouveau
5个回答

13

有一种简单而优雅的方法来解决原帖作者的问题 - 使用可附加属性替换ToggleButton的IsChecked属性,在其更改处理程序中设置该按钮的IsChecked属性:

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 的代码将会是这个样子:

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

编辑:原帖的解决方案不起作用,因为当按下按钮时,代码会设置IsChecked属性(这是MS实现ToggleButton控件的方式)- 设置该属性将从中删除绑定,因此它将停止工作。

通过使用附加属性,我们可以克服此问题,因为在代码中从未分配值,因此绑定保持完整。


嗨,Alex,感谢您提供优雅的解决方案,它对我很有效。但是我不明白它是如何工作的。您能再解释一下吗? - Peter Lee
我在我的解决方案背后的思路上添加了一些文字。您可以在MSDN上阅读有关附加属性的更多信息(http://msdn.microsoft.com/en-us/library/vstudio/ms749011(v=vs.100).aspx)。 - Alex_P
这确实很优雅! - akshay2000

7
这是使用单向数据绑定时的设计。将附加属性PresentationTraceSources.TraceLevel=High添加到您的绑定中,您将看到它何时断开连接。此链接也描述了该问题(但没有提供任何解决方案):在WPF中调试数据绑定
我通常解决它的方法是使用用户交互的命令和代码后台更改控件外观,因为某些属性已更改。

为什么这个答案被接受了?它并没有解决问题(问题确实存在且可以轻易地再现)。 - Sinatr

3
据我所知,Sinatra在新框架中涉及的“desync”是正确的。针对这个问题的另一个简单方法是删除mode = oneway并实现一个空setter。例如:
    bool _MyIsEnabled;
    public bool MyIsEnabled
    {
        get { return _MyIsEnabled; }
        set { }
    }

通过这个绑定设置,您可以从命令绑定函数或需要时更改后备变量的值。只需记得调用RaisePropertyChanged。


1
喜欢这个简单的解决办法 - 有点技巧性,但比创建自定义控件要简单得多。 - Brent George

1

我有类似的问题。

它不是“绑定似乎丢失”(除非是早期框架的问题)。绑定仍然有效,并且可以通过在其他按钮点击事件/命令的处理程序中更改属性来轻松证明。

问题在于IsChecked可以通过两种方式更改:1)绑定(当SomeProperty的值更改时,按钮将更新)2)用户(如果用户按下按钮,则会更改IsChecked,但绑定是OneWay,因此SomeProperty不会更新)。

因此,当SomeProperty == false但按钮IsChecked == true或反之亦然时,可能会发生不同步

为了优化性能,绑定机制会检查新值是否与当前值不同。因此,如果发生不同步,并且您尝试使用已经拥有的值更新SomeProperty,则不会发生任何事情。

解决方法很简单:分两步更新属性。

SomeProperty = !set;
SomeProperty = set;

其中set是您需要的值(例如与当前SomeProperty相反)。


0
我认为问题的关键在于命令和IsChecked绑定之间的冲突。我的解决方案是更改我的视图模型以公开一个bool?并将其绑定到IsChecked属性。我没有将命令绑定到ToggleButton。在我的代码的其他地方,我想要切换属性时,我使用SomeCommand

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接