Как добиться чистых файлов с выделенным кодом? - PullRequest
2 голосов
/ 22 февраля 2010

Работая с WPF, рекомендуется хранить небольшой и чистый код xaml.cs за файлами. Шаблон MVVM помогает достичь этой цели с помощью привязок данных и привязок команд, где любая бизнес-логика обрабатывается в классах ViewModel.

Я использую принципы шаблона MVVM, и мои файлы с выделенным кодом довольно хороши и чисты. Любые события нажатия кнопки обрабатываются с помощью привязки команд, и есть еще несколько элементов управления, которые также поддерживают привязку команд. Однако в элементах управления есть несколько событий, которые не имеют свойств Command и CommandParameter, и, следовательно, я не вижу прямого способа использования привязок. Как лучше всего избавиться от логики в файлах кода для таких событий? Например. обработка событий мыши внутри элемента управления.

Ответы [ 3 ]

4 голосов
/ 22 февраля 2010

Хороший подход для этого - использовать прикрепленное поведение . Само поведение может перехватить нужное вам событие и запустить соответствующую команду с параметрами, которые вы хотите. По сути, это тот же код, но он просто удаляет его из кода и позволяет вам выражать свои намерения исключительно в XAML.

Есть несколько примеров поведения на CodePlex , или вот базовый пример, который выполняет команду:

using System.Windows;
using System.Windows.Input;
using System.Windows.Interactivity;

/// <summary>
/// Trigger action to execute an ICommand command
/// </summary>
public class ExecuteCommand : TriggerAction<FrameworkElement>
{
    #region Dependency Properties

    /// <summary>
    /// Command parameter
    /// </summary>
    public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(ExecuteCommand), new UIPropertyMetadata(null, OnCommandParameterChanged));

    /// <summary>
    /// Command to be executed
    /// </summary>
    public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(ExecuteCommand), new UIPropertyMetadata(null, OnCommandChanged));

    #region Public Properties

    /// <summary>
    /// Gets or sets the command
    /// </summary>
    public ICommand Command
    {
        get
        {
            return (ICommand)this.GetValue(CommandProperty);
        }

        set
        {
            this.SetValue(CommandProperty, value);
        }
    }

    /// <summary>
    /// Gets or sets the command parameter
    /// </summary>
    public object CommandParameter
    {
        get
        {
            return (object)this.GetValue(CommandParameterProperty);
        }

        set
        {
            this.SetValue(CommandParameterProperty, value);
        }
    }
    #endregion

    /// <summary>
    /// Executes the command if it is not null and is able to execute
    /// </summary>
    /// <param name="parameter">This argument not used</param>
    protected override void Invoke(object parameter)
    {
        if (this.Command != null && this.Command.CanExecute(this.CommandParameter))
        {
            this.Command.Execute(this.CommandParameter);
        }
    }

    /// <summary>
    /// Called on command change
    /// </summary>
    /// <param name="oldValue">old ICommand instance</param>
    /// <param name="newValue">new ICommand instance</param>
    protected virtual void OnCommandChanged(ICommand oldValue, ICommand newValue)
    {
    }

    /// <summary>
    /// Called on command parameter change
    /// </summary>
    /// <param name="oldValue">old ICommand instance</param>
    /// <param name="newValue">new ICommand instance</param>
    protected virtual void OnCommandParameterChanged(object oldValue, object newValue)
    {
    }

    /// <summary>
    /// Called on command parameter change
    /// </summary>
    /// <param name="o">Dependency object</param>
    /// <param name="e">Dependency property</param>
    private static void OnCommandParameterChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        ExecuteCommand invokeCommand = o as ExecuteCommand;
        if (invokeCommand != null)
        {
            invokeCommand.OnCommandParameterChanged((object)e.OldValue, (object)e.NewValue);
        }
    }

    /// <summary>
    /// Called on command change
    /// </summary>
    /// <param name="o">Dependency object</param>
    /// <param name="e">Dependency property</param>
    private static void OnCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        ExecuteCommand invokeCommand = o as ExecuteCommand;
        if (invokeCommand != null)
        {
            invokeCommand.OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
        }
    }        
    #endregion
}

}

Затем можно использовать пространство имен System.Windows.Interactivity (сборка включена в Blend 3), чтобы перехватить событие и запустить команду следующим образом:

<i:Interaction.Triggers>
    <i:EventTrigger EventName="MouseLeftButtonUp">
        <triggers:ExecuteCommand Command="{Binding MyCommand}" CommandParameter="MyParameter" />
    </i:EventTrigger>
</i:Interaction.Triggers>

Для более сложных событий с несколькими параметрами вам может потребоваться создать определенное поведение, а не использовать общее поведение, как в примере выше. Обычно я предпочитаю создавать свой собственный тип для хранения параметров и сопоставления с ним, а не с определенным требованием EventArgs в моей ViewModel.

* Одна вещь, которую я должен добавить, это то, что я определенно не парень типа «0 кода в коде позади», и я думаю, что принудительное принудительное соблюдение этого принципа несколько упускает смысл MVVM. Пока код содержит без логики и, следовательно, ничего, что действительно не нужно тестировать, я могу жить с некоторыми небольшими фрагментами кода для «преодоления разрыва» между View и ViewModel. Это также иногда необходимо, если у вас есть «умное представление», как я его обычно называю, такое как элемент управления браузером или что-то, с чем вам нужно общаться из вашей ViewModel. Некоторые люди будут получать вилы, чтобы даже предложить такую ​​вещь, поэтому я оставил этот бит до последнего и ответил на ваш вопрос первым: -) *

1 голос
/ 22 февраля 2010

Мой совет был бы спорным. Не делай этого . То есть сделай это, но будь очень осторожен. Одной из основных целей MVVM является упрощение разработки. Если это заставляет вас писать код, который трудно понять и поддержать - вы выбрали неправильный путь.

Пожалуйста, не поймите меня неправильно. Я не говорю " написать все в коде ". Я говорю - вполне нормально иметь некоторый код в коде, если этот код упрощает понимание. И не думай, что ты нарушаешь шаблон. Шаблоны просто рекомендации ...

0 голосов
/ 22 февраля 2010

Я решил это с помощью следующей стратегии, но я не знаю, является ли это идеальным решением.

Для событий, которые не поддерживают привязку команд, я обрабатываю само событие в коде файла, но на самом деле я не делаю там никакой бизнес-логики. Затем связанный класс ViewModel имеет функцию для обработки события, поэтому обработчик события code-behind вызывает соответствующую функцию своего класса ViewModel.

например. У меня есть несколько событий мыши. Давайте посмотрим на событие MouseDown на холсте. Это вызовет обработчик события в коде позади, и обработчик события просто передаст вызов ViewModel. В этом случае все, что мне нужно знать, это то, какая кнопка мыши нажата, и какова текущая возможность, поэтому я не передаю MouseEventArgs:

private void CanvasMouseDown(object sender, MouseButtonEventArgs e)
{
    var mousePositionX = e.GetPosition(_myCanvas).X;
    var mousePositionY = e.GetPosition(_myCanvas).Y;
    _vm.MouseDown(e.ChangedButton, mousePositionX, mousePositionY);
}

Затем у ViewModel есть функция MouseDown, в которой я на самом деле обрабатываю событие:

public void MouseDown(MouseButton button, double mousePositionX, mousePositionY)
{
    switch (button)
    {
        case MouseButton.Left:
            // Do something 
            break;
        case MouseButton.Right:
            // Do something 
            break;
        case MouseButton.Middle:
            // Do something 
            break;
    }

    _mousePositionX = mousePositionX;
    _mousePositionY = mousePositionY;
}

Похоже ли это на разумный способ получить код из вашего кода позади? Есть лучшие решения? Лучшая практика?

...