Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

WCF RIA Services. Внедряем паттерн Model-View-ViewModel (MVVM). Часть 4

Anna | 17.06.2014 | нет комментариев

Введение

Паттерн Model-View-ViewModel (MVVM) применяется для создания слабо связанных приложений на Silverlight и WPF. В данном курсе мы не будем рассматривать основы данного подхода, а легко узнаем как внедрить его в наш план, тот, что мы создавали на протяжении 3 уроков. Если не внедряться в заросли, то MVVM – это одна из альтернатив, которая является логическим становлением таких паттернов программирования как MVC и MVP и всецело поддерживает привязку данных, команды, и все вероятности, которые предоставляют Silverlight и WPF. Модель вида (ViewModel) отвечает за предоставление виду (View) всех нужных источников. То есть предоставляются все нужные свойства для того, что б вид мог легко осуществлять привязку данных, создание команд, а в модели вида тем временем присутствует и работает каждая нужная приложению логика. Структурно, устанавливаемый «DataContext» равен экземпляру модели вида, с которым происходит связывание данных (binding).

Основным превосходством такого подхода является фактически полная автономность вида от модели вида, что дерзко дозволено выразить, как само­стоятельную разработку всякой части программистом и дизайнером. Так же, славным следствием такого распределения является довольная легкость создания модульных текстов (unit test), так как логика безусловно не связана с UI (интерфейсом пользователя).

И по обычии, вступительной точкой в наш урок является план, сделанный на предыдущем этапе нашего обучения.

Шаг 1: Создание модели вида


С самого предисловие мы организовывали всю логику в сопутствующем файле для страницы (имя_страницы.xaml.cs). Тем самым мы породили мощную связанность логики и представления. А это в большинстве случаев дюже дрянный тон. Пришло время разграничить эти представления. Перенесем каждый код в модель вида. Создаем новейший класс в клиентском плане с именем «TasksViewModel». Дальше, было бы не нехорошо проанализировать представление и определить, какие свойства необходимо будет сделать в модели вида. На картинке ниже мы видим форму, которую необходимо будет сделать. Для начала добавим к двум верхним TextBox кнопку выбора даты, применяя DatePickers, удалим из DataGrid неиспользуемый столбцы и слегка подправим оставшиеся для приобретения больше симпатично и читабельного вида.

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

public class TasksViewModel
 {
     public DateTime LowerSearchDate { get; set; }
     public DateTime UpperSearchDate { get; set; }
     public ICommand SearchByDateCommand { get; set; }
     public ICommand AddTaskCommand { get; set; }
     public ICommand SaveChangesCommand { get; set; }
     public IEnumerable<Task> Tasks { get; set; }
 }

Также нужно реализовать интерфейс INotifyPropertyChanged у всякого свойства, которое должно отслеживать и изменяться в UI при изменениях. В коде выглядеть все это будет так:

public class TasksViewModel : INotifyPropertyChanged
 {
     public event PropertyChangedEventHandler PropertyChanged = delegate { };
     DateTime _LowerSearchDate;
     public DateTime LowerSearchDate
     {
         get
         {
             return _LowerSearchDate;
         }
         set
         {
             if (value != _LowerSearchDate)
             {
                 _LowerSearchDate = value;
                 PropertyChanged(this, new PropertyChangedEventArgs("LowerSearchDate"));
             }
         }
     }

     DateTime _UpperSearchDate;
     public DateTime UpperSearchDate
     {
         get
         {
             return _UpperSearchDate;
         }
         set
         {
             if (value != _UpperSearchDate)
             {
                 _UpperSearchDate = value;
                 PropertyChanged(this, new PropertyChangedEventArgs("UpperSearchDate "));
             }
         }
     }

Для вероятности применять свойства ICommand необходимо добавить соответствующую реализацию. Будем применять простейшую реализацию, еще называемую RelayCommand. Но в реальных планах советую применять DelegateCommand, тот, что предлагает Prism.

Для создания RelayCommand нужно добавить новейший класс в клиентский план с именем «RelayCommand»:

namespace TaskManager
{
    public class RelayCommand<T> : ICommand
    {
        Action<T> _TargetExecuteMethod;
        Func<T, bool> _TargetCanExecuteMethod;

        public RelayCommand(Action<T> executeMethod)
        {
            _TargetExecuteMethod = executeMethod;
        }

        public RelayCommand(Action<T> executeMethod, Func<T,bool> canExecuteMethod)
        {
            _TargetExecuteMethod = executeMethod;
            _TargetCanExecuteMethod = canExecuteMethod;
        }

        public void RaiseCanExecuteChanged() 
        {
            CanExecuteChanged(this, EventArgs.Empty); 
        }
        #region ICommand Members

        bool ICommand.CanExecute(object parameter)
        {
            if (_TargetCanExecuteMethod != null)
            {
                T tparm = (T)parameter;
                return _TargetCanExecuteMethod(tparm);
            }
            if (_TargetExecuteMethod != null)
            {
                return true;
            }
            return false;
        }

        public event EventHandler CanExecuteChanged = delegate { };

        void ICommand.Execute(object parameter)
        {
            if (_TargetExecuteMethod != null)
            {
                _TargetExecuteMethod((T)parameter);
            }
        }
        #endregion
    }
}

Так как модель вида предоставляет все нужные данные для вида, а DomainContext – отвечает за своевременное обновление данных при их изменении, то навязывается явственное решение, что при применении паттерна MVVM в RIA Services, необходимо легко применять DomainContext внутри модели вида.

TasksDomainContext _Context = new TasksDomainContext();

 public TasksViewModel()
 {
     SearchByDateCommand = new RelayCommand<object>(OnSearchByDate);
     AddTaskCommand = new RelayCommand<object>(OnAddTask);
     SaveChangesCommand = new RelayCommand<object>(OnSaveChanges);
     Tasks = _Context.Tasks;
     if (!DesignerProperties.IsInDesignTool)
     {
         _Context.Load(_Context.GetTasksQuery());
     }
 }

В коде выше, мы легко создаем TasksDomainContext внутри модели вида, а в конструкторе инициализируем команды, что б связать их с надобными способами. Качество Tasks модели вида содержит в себе ссылку на коллекцию сущностей Tasks, которая в свою очередь предоставляется контекстом домена, и которое вызывает события INotifyCollectionChanged для обеспечения востребованной информации в представлении в случае метаморфозы коллекции, которое появляется при вызове способа «load» либо «SubmitChanges» и обновляет сущности в фоновом режиме. Обратите внимание на качество «DesignerProperties.IsInDesignTool» в конструкторе, на которое идет проверка в блоке «If», которое предотвращает вызов способа «Load» из дизайнера, так как это повлечет ошибку.

Дальнейшим шагом будет перенос способов из MainPage.xaml.cs в модель вида. То есть перенос логики. MainPage.xaml.cs очищаем всецело, внутри остается только это:

namespace TasksManager
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
        }

Обратите внимание, что способ поиска теперь использует отложенное выполнение, про которое мы говорили в третьем уроке. Способ GetTasksByStartDate больше не потребен, так как заказчик может указать службе домена что именно искать и как формировать запрос. Так же создание новго задания вынесено в отдельное всплывающее окно, в котором дозволено редактировать добавляемые данные. Значимо подметить, что пример вызова всплывающего окна непринужденно из модели вида – не вовсе успешный пример. Впрочем это сделано для облегчения кода, так как рассматривать полновесный MVVM и все его превосходства не является основной целью этих уроков. Больше верное определение и применение MVVM предоставляет Prism.

private void OnSearchByDate(object param)
 {
     _Context.Tasks.Clear();
     EntityQuery<Task> query = _Context.GetTasksQuery();
     LoadOperation<Task> loadOp = _Context.Load(query.Where(t => t.StartDate >= LowerSearchDate && t.StartDate <= UpperSearchDate));
 }

 private void OnAddTask(object param)
 {
     // Данный подход не является правильным
     // Больше детально про конструкцию MVVM дозволено узнать, ознакомившись с Prism 4
     AddTaskView popup = new AddTaskView();
     popup.DataContext = new Task();
     popup.Closed  = delegate
     {
         if (popup.DialogResult == true)
         {
             Task newTask = popup.DataContext as Task;
             if (newTask != null) _Context.Tasks.Add(newTask);
         }
     };
     popup.Show();
 }

 private void OnSaveChanges(object param)
 {
     _Context.SubmitChanges();
 }

Сейчас добавим всплывающее окно. На клиентском плане «Добавить» — «Сделать элемент» — «Страница Silverlight» с именем «AddTaskView» и дальнейшим содержимым:

<controls:ChildWindow x:Class="TaskManager.AddTaskView"
           xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
           xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
           Width="361" Height="287" 
           Title="Add Task" mc:Ignorable="d" xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.DomainServices" xmlns:my="clr-namespace:TaskManager.Web" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
    <Grid x:Name="LayoutRoot" Margin="2">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <Button x:Name="CancelButton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" />
        <Button x:Name="SaveButton" Content="Save" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1"
                Click="OKButton_Click" />
        <Grid DataContext="{Binding}" HorizontalAlignment="Left" Margin="12,12,0,0" Name="grid1" VerticalAlignment="Top" Width="315">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="237" />
                <ColumnDefinition Width="4*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="Auto" />
                <RowDefinition Height="100" />
            </Grid.RowDefinitions>
            <sdk:Label Content="Description:" Grid.Column="0" Grid.Row="3" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="3" Height="91" HorizontalAlignment="Left" Margin="3,3,0,6" Name="descriptionTextBox" Text="{Binding Path=Description, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}" VerticalAlignment="Center" Width="221" />
            <sdk:Label Content="End Date:" Grid.Column="0" Grid.Row="2" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <controls:DatePicker Grid.Column="1" Grid.Row="2" Height="23" HorizontalAlignment="Left" Margin="3,3,0,3" Name="endDateDatePicker" SelectedDate="{Binding Path=EndDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}" VerticalAlignment="Center" Width="120" />
            <sdk:Label Content="Start Date:" Grid.Column="0" Grid.Row="1" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <controls:DatePicker Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="3,3,0,3" Name="startDateDatePicker" SelectedDate="{Binding Path=StartDate, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}" VerticalAlignment="Center" Width="120" />
            <sdk:Label Content="Task Name:" Grid.Column="0" Grid.Row="0" HorizontalAlignment="Left" Margin="3" VerticalAlignment="Center" />
            <TextBox Grid.Column="1" Grid.Row="0" Height="23" HorizontalAlignment="Left" Margin="3,3,0,3" Name="taskNameTextBox" Text="{Binding Path=TaskName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true, TargetNullValue=''}" VerticalAlignment="Center" Width="221" />
        </Grid>
    </Grid>
</controls:ChildWindow>

Все. Модель вида на данном этапе всецело готова и функциональна. Перейдем к доработке нашего UI.

Шаг 2: Объединяем Вид и Модель вида

Начиная с первой части мы используем DomainDataSource, тот, что был механически сгенерирован при «drag and drop». При применении MVVM нужно будет избавиться от DomainDataSource, так как его применение в XAML нарушает доктрину распределения MVVM.

Добавляем DataContext. Указываем имя модели вида. И Добавляем связывание на основе сделанных свойств:

<UserControl x:Class="TaskManager.MainPage" ...>
     <UserControl.DataContext>
         <local:TasksViewModel/>
     </UserControl.DataContext>
         <Grid x:Name="LayoutRoot" Background="White">
         <sdk:DataGrid ItemsSource="{Binding Tasks}" .../>
         <Button Command="{Binding SearchByDateCommand}" .../>
         <Button Command="{Binding AddTaskCommand}" ... />
         <Button Command="{Binding SaveChangesCommand}" ... />
         <sdk:DatePicker SelectedDate="{Binding LowerSearchDate}" ... />
         <sdk:DatePicker SelectedDate="{Binding UpperSearchDate}" ... />
     </Grid>
 </UserControl>

Видео для этого урока

Исходники

На Github

Источник: programmingmaster.ru
Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB