简体   繁体   中英

How can updating a Canvas attached property also update a bound view model property?

I'm changing the position of a UIElement within a WPF Canvas by using the static Canvas.SetTop method in the code-behind (in the full application I'm using a complex Rx chain but for this example I've simplified it to a button click).

The problem I have is that the value of the attached property, Canvas.Top in the XAML, is bound to a property in my ViewModel. Calling Canvas.SetTop bypasses the set in my ViewModel so I don't get the updated value. How can I update the Canvas.Top value in the code-behind so that the ViewModel properties' setter is called?

XAML View:

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

Code-behind:

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

ViewModel:

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

First of all you need to set Binding Mode to TwoWay :

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

Also, if you are setting it from code behind, set using SetCurrentValue() method otherwise binding will be broken and ViewModel instance won't be updated :

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

Like mentioned here , do not write code in wrapper properties of DP's:

The WPF binding engine calls GetValue and SetValue directly (bypassing the property setters and getters).

If you need to synchronize on property change, create a PropertyChangedCallback and do synchronization over there:

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
    }

Otherwise simply have normal CLR property and you should consider implementing INotifyPropertyChanged on your class:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM