【问题标题】:How can updating a Canvas attached property also update a bound view model property?更新 Canvas 附加属性如何同时更新绑定视图模型属性?
【发布时间】:2025-12-20 14:55:07
【问题描述】:

我正在使用代码隐藏中的静态 Canvas.SetTop 方法更改 WPF 画布中 UIElement 的位置(在完整的应用程序中,我使用的是复杂的 Rx 链,但对于这个示例,我已经简化了点击按钮)。

我遇到的问题是 XAML 中的附加属性 Canvas.Top 的值绑定到我的 ViewModel 中的一个属性。调用 Canvas.SetTop 绕过了我的 ViewModel 中的 set,所以我没有得到更新的值。如何更新代码隐藏中的 Canvas.Top 值以便调用 ViewModel 属性的设置器?

XAML 视图:

<Window x:Class="WpfApplication1.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="300" Width="300">
    <Grid>
        <Canvas>
            <Button Content="Move Button" Canvas.Top="{Binding ButtonTop}" Click="ButtonBase_OnClick" />
        </Canvas>
    </Grid>
</Window>

代码隐藏:

using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
    public partial class MainWindowView : Window
    {
        public MainWindowView()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }

        private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
        {
            Canvas.SetTop((UIElement) sender, Canvas.GetTop((UIElement) sender) + 5);
        }
    }
}

视图模型:

using System.Windows;

namespace WpfApplication1
{
    public class MainWindowViewModel : DependencyObject
    {
        public static readonly DependencyProperty ButtonTopProperty = DependencyProperty.
            Register("ButtonTop", typeof(int), typeof(MainWindowViewModel));

        public int ButtonTop
        {
            get { return (int) GetValue(ButtonTopProperty); }
            set { SetValue(ButtonTopProperty, value); }
        }

        public MainWindowViewModel()
        {
            ButtonTop = 15;
        }
    }
}

【问题讨论】:

  • 使ButtonTop 绑定双向,如Canvas.Top="{Binding ButtonTop, Mode=TwoWay}"?
  • 添加 Mode=TwoWay 并不能解决问题。干杯。

标签: c# wpf xaml


【解决方案1】:

首先您需要Binding Mode 设置为TwoWay

<Button Content="Move Button" Canvas.Top="{Binding ButtonTop, Mode=TwoWay}"
        Click="ButtonBase_OnClick" />

另外,如果你从后面的代码中设置它,请使用SetCurrentValue() 方法设置,否则绑定将被破坏并且 ViewModel 实例将不会被更新

UIElement uiElement = (UIElement)sender;
uiElement.SetCurrentValue(Canvas.TopProperty, Canvas.GetTop(uiElement) + 5);

就像提到的here,不要在DP的包装属性中编写代码:

WPF绑定引擎直接调用GetValueSetValue(绕过 属性设置器和获取器)。

如果您需要在属性更改时进行同步,请创建一个 PropertyChangedCallback 并在那里进行同步:

public static readonly DependencyProperty ButtonTopProperty = DependencyProperty.
        Register("ButtonTop", typeof(int), typeof(MainWindowViewModel),
                    new UIPropertyMetadata(ButtonTopPropertyChanged));

    private static void ButtonTopPropertyChanged(DependencyObject sender,
                                         DependencyPropertyChangedEventArgs args)
    {
        // Write synchronization logic here
    }

否则只有普通的 CLR 属性,你应该考虑在你的类上实现INotifyPropertyChanged

private double buttonTop;
public double ButtonTop
{
   get { return buttonTop; }
   set
   {
      if(buttonTop != value)
      {
         // Synchronize here
         buttonTop = value;
      }
   }
}

【讨论】:

  • 我在您的代码更改后设置了一个断点,并且(与我的初始代码完全相同)更新了 ViewModel 属性值,但没有调用 set 方法。我需要在 ViewModel 中调用 setter,因为它在 ViewModel 和模型之间执行额外的同步。这样的事情可能吗?
  • 它适用于普通的 CLR 属性,而不适用于 DP。当通过 XAML 包装器属性设置值时,永远不会调用。阅读有关here 的更多信息。