1

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;
        }
    }
}
jeebs
  • 325
  • 2
  • 9

1 Answers1

1

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;
      }
   }
}
Community
  • 1
  • 1
Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
  • I set a breakpoint after your code change and (exactly the same as my initial code) the ViewModel property value is updated, but the `set` method was not called. I need the setter to be called in the ViewModel because it performs additional synchronisation between the ViewModel and the Model. Is such a thing possible? – jeebs Jan 04 '14 at 15:29
  • It will work for normal CLR property and not for DP. When value is set via XAML wrapper properties never gets called. Read more about [here](http://stackoverflow.com/questions/4225373/setters-not-run-on-dependency-properties). – Rohit Vats Jan 04 '14 at 15:32