Почему это свойство Silverlight не работает? - PullRequest
4 голосов
/ 20 февраля 2010

Я пытаюсь использовать шаблон MVVM в своем приложении Silverlight 3, и у меня возникают проблемы с привязкой к свойству команды рабочей модели представления. Во-первых, я пытаюсь добавить прикрепленное свойство под названием 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, никогда не вызывается. Кроме того, когда я размещаю точки останова в получателе и установщике присоединенного свойства, они никогда не достигаются.

Пытаясь изолировать проблему, я изменил тип свойства на строку; точка останова в геттере и сеттере также никогда не достигалась, но бросив в них исключение, приложение завершилось, поэтому я думаю, что это эксцентричность фреймворка.

Почему этот материал не работает? Я также приветствую альтернативные, надеюсь, более простые способы привязки обработчиков событий для просмотра моделей.

1 Ответ

9 голосов
/ 20 февраля 2010

У вас есть как минимум две проблемы здесь.

Во-первых, вы полагаетесь на выполняемый метод SetXxx. Оболочки CLR для свойств зависимостей (метод установки свойств или метод SetXxx) не выполняются, когда DP устанавливается из XAML; скорее, WPF напрямую устанавливает значение внутреннего слота DP. (Это также объясняет, почему ваши контрольные точки никогда не были достигнуты.) Следовательно, ваша логика для обработки изменений всегда должна появляться в обратном вызове OnXxxChanged, не в установщике; 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, хотя это вряд ли когда-либо произойдет).

...