代码之家  ›  专栏  ›  技术社区  ›  Jacob

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

  •  4
  • Jacob  · 技术社区  · 14 年前

    我正在尝试在我的Silverlight3应用程序中使用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;
            }
        }
    }
    

    通用命令类是围绕委托的包装。我只是包装一个委托,因为我想知道属性的委托类型是否是最初不适合我的原因。这是那个班:

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

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

    在试图隔离问题时,我将属性类型更改为string;getter和setter中的断点也从未到达,但在它们中引发异常确实会导致应用程序终止,因此我认为这是一种框架偏心。

    为什么这些东西不起作用?我还欢迎将事件处理程序绑定到视图模型的其他简单方法。

    1 回复  |  直到 14 年前
        1
  •  9
  •   itowlson    14 年前

    这里至少有两个问题。

    首先,您依赖于正在执行的setxxx方法。当从xaml设置dp时,不会执行依赖项属性(property setter或setxxx方法)的clr包装器;相反,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为空,因此oldvalue.action会导致nullreferenceexception。您需要检查这种情况(您还应该检查newValue==null,尽管这不太可能发生)。