Поиск…


замечания

Модели и модели просмотра

Определение модели часто обсуждается горячо, и линия между моделью и моделью просмотра может быть размытой. Некоторые предпочитают не «загрязнять» свои модели с INotifyPropertyChanged интерфейсом, и вместо того, чтобы дублировать свойство модели в виде-модели, которая делает реализацию этого интерфейса. Как и многие другие аспекты разработки программного обеспечения, нет правильного или неправильного ответа. Будьте прагматичны и делайте то, что считает правильным.

Просмотр разделения

Цель MVVM состоит в том, чтобы разделить эти три различные области: модель, модель представления и вид. Хотя для представления приемлемо получить доступ к модели представления (VM) и (косвенно) модели, самым важным правилом с MVVM является то, что виртуальная машина не должна иметь доступ к представлению или его элементам управления. VM должна раскрывать все, что нужно мнению, через публичные свойства. VM не должна напрямую открывать или управлять элементами пользовательского интерфейса, такими как TextBox , Button и т. Д.

В некоторых случаях это строгое разделение может быть трудным для работы, особенно если вам нужно запустить некоторые сложные пользовательские интерфейсы. Здесь вполне приемлемо прибегать к использованию событий и обработчиков событий в файле «code-behind». Если это чисто функциональность пользовательского интерфейса, то, во всяком случае, использовать события в представлении. Также допустимо, чтобы эти обработчики событий вызывали общедоступные методы на экземпляре виртуальной машины - просто не отправляйте ссылки на элементы управления пользовательского интерфейса или что-то в этом роде.

RelayCommand

К сожалению, класс RelayCommand используемый в этом примере, не является частью инфраструктуры WPF (это должно было быть!), Но вы найдете его почти в каждом окне инструментов разработчика WPF. Быстрый поиск в Интернете выявит множество фрагментов кода, которые вы можете поднять, чтобы создать свой собственный.

Полезной альтернативой RelayCommand является ActionCommand который предоставляется как часть Microsoft.Expression.Interactivity.Core которая обеспечивает сопоставимую функциональность.

Пример базового MVVM с использованием WPF и C #

Это базовый пример использования модели MVVM в рабочем приложении Windows с использованием WPF и C #. Пример кода реализует простой диалог «информация о пользователе».

введите описание изображения здесь

Вид

XAML

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
         <RowDefinition Height="Auto"/>
     </Grid.RowDefinitions>

    <TextBlock Grid.Column="0" Grid.Row="0" Grid.ColumnSpan="2" Margin="4" Text="{Binding FullName}" HorizontalAlignment="Center" FontWeight="Bold"/>

    <Label Grid.Column="0" Grid.Row="1" Margin="4" Content="First Name:" HorizontalAlignment="Right"/>
    <!-- UpdateSourceTrigger=PropertyChanged makes sure that changes in the TextBoxes are immediately applied to the model. -->
    <TextBox Grid.Column="1" Grid.Row="1" Margin="4" Text="{Binding FirstName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Width="200"/>

    <Label Grid.Column="0" Grid.Row="2" Margin="4" Content="Last Name:" HorizontalAlignment="Right"/>
    <TextBox Grid.Column="1" Grid.Row="2" Margin="4" Text="{Binding LastName, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left"  Width="200"/>

    <Label Grid.Column="0" Grid.Row="3" Margin="4" Content="Age:" HorizontalAlignment="Right"/>
    <TextBlock Grid.Column="1" Grid.Row="3" Margin="4" Text="{Binding Age}" HorizontalAlignment="Left"/>

</Grid>

и код

public partial class MainWindow : Window
{
    private readonly MyViewModel _viewModel;

    public MainWindow() {
        InitializeComponent();
        _viewModel = new MyViewModel();
        // The DataContext serves as the starting point of Binding Paths
        DataContext = _viewModel;
    }
}

Модель просмотра

// INotifyPropertyChanged notifies the View of property changes, so that Bindings are updated.
sealed class MyViewModel : INotifyPropertyChanged
{
    private User user;
    
    public string FirstName { 
        get {return user.FirstName;} 
        set {
            if(user.FirstName != value) {
                user.FirstName = value;
                OnPropertyChange("FirstName");
                // If the first name has changed, the FullName property needs to be udpated as well.
                OnPropertyChange("FullName");
            }
        }
    }

    public string LastName {
        get { return user.LastName; }
        set {
            if (user.LastName != value) {
                user.LastName = value;
                OnPropertyChange("LastName");
                // If the first name has changed, the FullName property needs to be udpated as well.
                OnPropertyChange("FullName");
            }
        }
    }
    
    // This property is an example of how model properties can be presented differently to the View.
    // In this case, we transform the birth date to the user's age, which is read only.
    public int Age { 
        get {
            DateTime today = DateTime.Today;
            int age = today.Year - user.BirthDate.Year;
            if (user.BirthDate > today.AddYears(-age)) age--;
            return age;
        }
    }

    // This property is just for display purposes and is a composition of existing data.
    public string FullName {
        get { return FirstName + " " + LastName; }
    }

    public MyViewModel() {
        user = new User {
            FirstName = "John",
            LastName = "Doe",
            BirthDate = DateTime.Now.AddYears(-30)
        };
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChange(string propertyName) {
        if(PropertyChanged != null) {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Модель

sealed class User
{
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public DateTime BirthDate { get; set; }
} 

Модель просмотра

Модель представления - это «VM» в MV VM . Это класс, который действует как промежуточный, предоставляет модель (модели) пользователю (представлению) и обрабатывает запросы из представления, такие как команды, поднятые щелчками кнопок. Вот базовая модель представления:

public class CustomerEditViewModel
{
    /// <summary>
    /// The customer to edit.
    /// </summary>
    public Customer CustomerToEdit { get; set; }

    /// <summary>
    /// The "apply changes" command
    /// </summary>
    public ICommand ApplyChangesCommand { get; private set; }

    /// <summary>
    /// Constructor
    /// </summary>
    public CustomerEditViewModel()
    {
        CustomerToEdit = new Customer
                         {
                             Forename = "John",
                             Surname = "Smith"
                         };

        ApplyChangesCommand = new RelayCommand(
            o => ExecuteApplyChangesCommand(), 
            o => CustomerToEdit.IsValid);
    }

    /// <summary>
    /// Executes the "apply changes" command.
    /// </summary>
    private void ExecuteApplyChangesCommand()
    {
        // E.g. save your customer to database
    }
}

Конструктор создает объект модели Customer и присваивает его свойству CustomerToEdit , чтобы он был видимым для представления.

Конструктор также создает объект RelayCommand и присваивает его ApplyChangesCommand , снова делая его видимым для представления. Команды WPF используются для обработки запросов из представления, таких как кнопки или щелчки элементов меню.

RelayCommand принимает два параметра: первый - это делегат, который вызывается при выполнении команды (например, в ответ на нажатие кнопки). Второй параметр - это делегат, который возвращает логическое значение, указывающее, может ли команда выполнить; в этом примере он подключен к свойству IsValid объекта клиента. Когда это возвращает false, он отключает кнопку или элемент меню, привязанные к этой команде (другие элементы управления могут вести себя по-разному). Это простая, но эффективная функция, позволяющая избежать необходимости писать код для включения или отключения элементов управления на основе разных условий.

Если этот пример запущен и запущен, попробуйте удалить один из элементов TextBox (чтобы Customer модель Customer в недопустимое состояние). Когда вы удаляетесь из TextBox вы должны обнаружить, что кнопка «Применить» отключается.

Замечание о создании клиента

Вид-модель не реализует INotifyPropertyChanged (INPC). Это означает, что если для свойства Customer должен был быть присвоен другой объект CustomerToEdit , элементы управления представлением не изменились бы, чтобы отразить новый объект - в TextBox es все еще будут указаны имя и фамилия предыдущего клиента.

Код примера работает, потому что Customer создается в конструкторе модели представления, прежде чем он будет привязан к DataContext (в этот момент привязки подключены). В реальном приложении вы можете извлекать клиентов из базы данных в методы, отличные от конструктора. Чтобы поддержать это, виртуальная машина должна внедрить INPC, а свойство CustomerToEdit должно быть изменено для использования расширенного шаблона getter и setter, который вы видите в примере кода модели, в результате чего событие PropertyChanged в установщике.

Приложению ApplyChangesCommand представлении модели не нужно внедрять INPC, так как команда вряд ли изменится. Вы должны были бы реализовать эту модель , если вы создавали команду где - нибудь, кроме конструктора, например , какой - то Initialize() метод.

Общее правило: реализовать INPC, если свойство привязано к любым элементам управления представлением, а значение свойства может меняться где угодно, кроме как в конструкторе. Вам не нужно внедрять INPC, если значение свойства присваивается только конструктору (и вы сэкономите себя на вводе в процессе).

Модель

Модель является первой «М» в M VVM. Модель обычно представляет собой класс, содержащий данные, которые вы хотите открыть через какой-то пользовательский интерфейс.

Вот очень простой модельный класс, демонстрирующий пару свойств: -

public class Customer : INotifyPropertyChanged
{
    private string _forename;
    private string _surname;
    private bool _isValid;

    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Customer forename.
    /// </summary>
    public string Forename
    {
        get
        {
            return _forename;
        }
        set
        {
            if (_forename != value)
            {
                _forename = value;
                OnPropertyChanged();
                SetIsValid();
            }
        }
    }

    /// <summary>
    /// Customer surname.
    /// </summary>
    public string Surname
    {
        get
        {
            return _surname;
        }
        set
        {
            if (_surname != value)
            {
                _surname = value;
                OnPropertyChanged();
                SetIsValid();
            }
        }
    }

    /// <summary>
    /// Indicates whether the model is in a valid state or not.
    /// </summary>
    public bool IsValid
    {
        get
        {
            return _isValid;
        }
        set
        {
            if (_isValid != value)
            {
                _isValid = value;
                OnPropertyChanged();
            }
        }
    }

    /// <summary>
    /// Sets the value of the IsValid property.
    /// </summary>
    private void SetIsValid()
    {
        IsValid = !string.IsNullOrEmpty(Forename) && !string.IsNullOrEmpty(Surname);
    }

    /// <summary>
    /// Raises the PropertyChanged event.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    private void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Этот класс реализует интерфейс INotifyPropertyChanged который предоставляет событие PropertyChanged . Это событие должно возникать при изменении одного из значений свойства - вы можете увидеть это в действии в приведенном выше коде. Событие PropertyChanged является ключевым элементом в механизмах привязки данных WPF, так как без него пользовательский интерфейс не сможет отразить изменения, внесенные в значение свойства.

Модель также содержит очень простую процедуру проверки, которая вызывается из настроек свойств. Он устанавливает публичное свойство, указывающее, находится ли модель в допустимом состоянии. Я включил эту функцию, чтобы продемонстрировать «специальную» функцию команд WPF, которую вы вскоре увидите. Структура WPF предоставляет ряд более сложных подходов к проверке, но они не входят в рамки этой статьи .

Вид

Вид - это «V» в M V VM. Это ваш пользовательский интерфейс. Вы можете использовать конструктор drag-and-drop Visual Studio, но большинство разработчиков в конечном итоге кодируют исходный XAML - опыт, похожий на запись HTML.

Вот XAML простого представления, позволяющего редактировать модель Customer . Вместо того, чтобы создавать новое представление, это можно просто вставить в файл MainWindow.xaml проекта WPF, расположенный между тегами <Window ...> и </Window> : -

<StackPanel Orientation="Vertical"
            VerticalAlignment="Top"
            Margin="20">
    <Label Content="Forename"/>
    <TextBox Text="{Binding CustomerToEdit.Forename}"/>

    <Label Content="Surname"/>
    <TextBox Text="{Binding CustomerToEdit.Surname}"/>

    <Button Content="Apply Changes"
            Command="{Binding ApplyChangesCommand}" />
</StackPanel>

Этот код создает простую форму ввода данных, состоящую из двух элементов TextBox один для имени клиента и имя для фамилии. Существует Label над каждым TextBox , и «Применить» Button в нижней части формы.

Найдите первый TextBox и посмотрите на его свойство Text :

Text="{Binding CustomerToEdit.Forename}"

Вместо того, чтобы устанавливать текст TextBox в фиксированное значение, этот специальный фигурный синтаксис привязки вместо этого привязывает текст к «пути» CustomerToEdit.Forename . Каков этот путь относительно? Это «контекст данных» представления - в данном случае наша модель представления. Путь привязки, как вы можете понять, - это свойство CustomerToEdit модели view-model, которое имеет тип Customer который, в свою очередь, предоставляет свойство, называемое Forename следовательно, «пунктирное» обозначение пути.

Аналогично, если вы посмотрите на XAML Button , у него есть Command , привязанная к свойству ApplyChangesCommand модели представления. Это все, что необходимо для подключения кнопки к команде VM.

DataContext

Итак, как вы устанавливаете модель представления в контексте данных представления? Один из способов заключается в том, чтобы установить его в «код-за». Нажмите F7, чтобы увидеть этот файл кода, и добавьте строку в существующий конструктор, чтобы создать экземпляр модели представления и назначить его свойству DataContext окна. Это должно выглядеть так:

    public MainWindow()
    {
        InitializeComponent();

        // Our new line:-
        DataContext = new CustomerEditViewModel();
    }

В системах реального мира для создания модели представления часто используются другие подходы, такие как вложение зависимостей или рамки MVVM.

Командующий в MVVM

Команды используются для обработки Events в WPF при соблюдении MVVM-шаблона.

Обычный EventHandler будет выглядеть так (находится в Code-Behind ):

public MainWindow()
{
    _dataGrid.CollectionChanged += DataGrid_CollectionChanged;
}

private void DataGrid_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
        //Do what ever
}

Нет, чтобы сделать то же самое в MVVM, мы используем Commands :

 <Button Command="{Binding Path=CmdStartExecution}" Content="Start" />

Я рекомендую использовать какой-то префикс ( Cmd ) для ваших свойств команды, потому что вам в основном нужны они в xaml - таким образом их легче распознать.

Поскольку MVVM вы хотите обработать эту команду (для Button «eq» Button_Click ) в вашей Button_Click ViewModel .

Для этого нам в основном нужны две вещи:

  1. System.Windows.Input.ICommand
  2. RelayCommand (например, взято отсюда .

Простой пример может выглядеть так:

private RelayCommand _commandStart;
public ICommand CmdStartExecution
{
   get
   {
      if(_commandStart == null)
      {
          _commandStart = new RelayCommand(param => Start(), param => CanStart());
      }
      return _commandStart;
   }
}

public void Start()
{
   //Do what ever
}

public bool CanStart()
{
    return (DateTime.Now.DayOfWeek == DayOfWeek.Monday); //Can only click that button on mondays.
}

Итак, что это делает в деталях:

ICommand - это то, к чему привязывается элемент Control в xaml. RelayCommand направит вашу команду на Action (т. RelayCommand Method ). Null-Check просто гарантирует, что каждая Command будет инициализироваться только один раз (из-за проблем с производительностью). Если вы прочитали ссылку для RelayCommand выше, вы, возможно, заметили, что RelayCommand имеет две перегрузки для своего конструктора. (Action<object> execute) и (Action<object> execute, Predicate<object> canExecute) .

Это означает, что вы можете (дополнительно) добавить второй Method возвращающий bool чтобы сообщить, что « Control » может срабатывать «Событие» или нет.

Хорошо, что Button например, будет Enabled="false" если Method вернет false

CommandParameters

<DataGrid x:Name="TicketsDataGrid">
    <DataGrid.InputBindings>
        <MouseBinding Gesture="LeftDoubleClick" 
                      Command="{Binding CmdTicketClick}" 
                      CommandParameter="{Binding ElementName=TicketsDataGrid, 
                                                 Path=SelectedItem}" />
    </DataGrid.InputBindings>
<DataGrid />

В этом примере я хочу передать DataGrid.SelectedItem в Click_Command в моей ViewModel.

Ваш метод должен выглядеть так, в то время как сама реализация ICommand остается прежней.

private RelayCommand _commandTicketClick;

public ICommand CmdTicketClick
{
   get
   {
       if(_commandTicketClick == null)
       {
           _commandTicketClick = new RelayCommand(param => HandleUserClick(param));
       }
       return _commandTicketClick;
   }
}

private void HandleUserClick(object item)
{
    MyModelClass selectedItem = item as MyModelClass;
    if (selectedItem != null)
    {
        //Do sth. with that item
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow