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

4

我正在尝试在我的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中放置断点时,它们从未到达。

为了分离问题,我将属性类型更改为字符串;获取器和设置器中的断点也从未到达,但在其中抛出异常确实导致应用程序终止,因此我认为这是框架的特异性。

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


有趣。你尝试过使用非泛型版本吗? - smaclell
好的想法。然而,我刚刚尝试创建了一个RoutedEventCommand类,并将Command<RoutedEventHandler>的实例替换为它,但行为是相同的。 - Jacob
1个回答

9
你至少有两个问题。
首先,你依赖于SetXxx方法被执行。当从XAML设置DP时,CLR包装器(属性setter或SetXxx方法)不会被执行;相反,WPF直接设置内部管理的DP“slot”的值。(这也解释了为什么你的断点从未被触发。)因此,处理更改的逻辑必须始终出现在OnXxxChanged回调中,而不是setter中;无论更改来自何处,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的情况不太可能发生,但您也应该检查)。


非常感谢。更改处理程序解决了问题。 - Jacob
@itowlson,这是一个很好的解释。让我理解了一些曾经让我头疼的事情。谢谢。 - infografnet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接