Why does this Silverlight property not work?

I am trying to use the MVVM template in my Silverlight 3 application, and I am having problems binding a working view model to the command property. First, I'm trying to add an attached ClickCommand property, for example:

public static class Command { public static readonly DependencyProperty ClickCommandProperty = DependencyProperty.RegisterAttached( "ClickCommand", typeof(Command<RoutedEventHandler>), typeof(Command), null); public static Command<RoutedEventHandler> GetClickCommand( DependencyObject target) { return target.GetValue(ClickCommandProperty) as Command<RoutedEventHandler>; } public static void SetClickCommand( DependencyObject target, Command<RoutedEventHandler> value) { // Breakpoints here are never reached var btn = target as ButtonBase; if (btn != null) { var oldValue = GetClickCommand(target); btn.Click -= oldValue.Action; target.SetValue(ClickCommandProperty, value); btn.Click += value.Action; } } } 

The generic Command class is the wrapper around the delegate. I only wrap the delegate because I was wondering if I had a delegate type for the property, because initially I did not work for me. Here is this class:

 public class Command<T> /* I'm not allowed to constrain T to a delegate type */ { public Command(T action) { this.Action = action; } public T Action { get; set; } } 

This is how I use the attached property:

 <Button u:Command.ClickCommand="{Binding DoThatThing}" Content="New"/> 

The syntax seems acceptable, and I think that when I tested all of this with a string type of properties, it worked fine. Here's a view model class that is associated with:

 public class MyViewModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged = delegate { }; public Command<RoutedEventHandler> DoThatThing { get { return new Command<RoutedEventHandler>( (s, e) => Debug.WriteLine("Never output!")); } } } 

The delegate contained in the Command property is never called. Also, when I put breakpoints in the getter and setter of the attached property, they are never reached.

When I try to isolate the problem, I change the type of the property to a string; a breakpoint in the getter and setter also was not reached, but throwing an exception in them led to the termination of the application, so I consider it an eccentricity of the structure.

Why is this stuff not working? I also welcome alternative, hopefully simpler ways to bind event handlers to view models.

+4
source share
1 answer

You have at least two problems.

First, you rely on the SetXxx method to be executed. CLR wrappers for dependency properties (property setting tool or SetXxx method) are not executed if DP is installed from XAML; rather, WPF directly sets the “DP” value of the slot controlled by internal management. (This also explains why your breakpoints never hit.) Therefore, your change processing logic should always occur in the OnXxxChanged callback, and not in the customizer; WPF will call this callback for you when the property changes no matter where the change comes from. This way (the example is taken from a slightly different implementation of the commands, but should give you an idea):

 // Note callback in PropertyMetadata public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(Click), new PropertyMetadata(OnCommandChanged)); // GetXxx and SetXxx wrappers contain boilerplate only public static ICommand GetCommand(DependencyObject obj) { return (ICommand)obj.GetValue(CommandProperty); } public static void SetCommand(DependencyObject obj, ICommand value) { obj.SetValue(CommandProperty, value); } // WPF will call the following when the property is set, even when it set in XAML private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { ButtonBase button = d as ButtonBase; if (button != null) { // do something with button.Click here } } 

Secondly, even with this change, installing ClickCommand on a control that does not yet have a value set will throw an exception, because oldValue is null, and therefore oldValue.Action raises a NullReferenceException. You need to check this case (you should also check for newValue == null, although this is unlikely to ever happen).

+9
source

Source: https://habr.com/ru/post/1301922/


All Articles