为什么这个Silverlight附加属性不起作用?

时间:2010-02-20 19:09:39

标签: c# data-binding silverlight-3.0 mvvm delegates

我正在尝试在Silverlight 3应用程序中使用MVVM模式,并且在绑定到视图模型工作的命令属性时遇到问题。首先,我正在尝试添加一个名为ClickCommand的附加属性,如下所示:

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

通用Command类是委托的包装器。我只是包装一个委托,因为我想知道是否有一个属性的委托类型是事情本来不适合我的原因。这是班级:

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

以下是我使用附加属性的方法:

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

语法似乎被接受了,我认为当我使用字符串属性类型测试所有这些时,它运行正常。这是绑定到的视图模型类:

public class MyViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public Command<RoutedEventHandler> DoThatThing
    {
        get
        {
            return new Command<RoutedEventHandler>(
                (s, e) => Debug.WriteLine("Never output!"));
        }
    }
}

永远不会调用Command属性中包含的委托。此外,当我将断点放在附加属性的getter和setter中时,它们永远不会到达。

在尝试隔离问题时,我将属性类型更改为字符串; getter和setter中的断点也从未到达,但在它们中抛出异常导致应用程序终止,所以我认为这是一个框架怪癖。

为什么这些东西不起作用?我也欢迎将事件处理程序绑定到视图模型的备用,希望更简单的方法。

1 个答案:

答案 0 :(得分:9)

这里至少有两个问题。

首先,您依赖于正在执行的SetXxx方法。当从XAML设置DP时,不执行依赖项属性的CLR包装器(属性setter或SetXxx方法);相反,WPF直接设置内部管理的DP“槽”的值。 (这也解释了为什么你的断点永远不会被击中。)因此,处理更改的逻辑必须始终出现在setX中的OnXxxChanged回调中, not ;当属性发生变化时,WPF将为您调用该回调,无论该更改来自何处。因此(例子来自略微不同的命令实现,但应该给你一个想法):

// 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's 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
  }
}

其次,即使进行了此更改,在尚未设置值的控件上设置ClickCommand也会导致异常,因为oldValue为null,因此oldValue.Action会导致NullReferenceException。你需要检查这种情况(你也应该检查newValue == null虽然这不太可能发生)。