📜 ⬆️ ⬇️

WCF RIA Services. Implement the Model-View-ViewModel (MVVM) pattern. Part 4

WCF RIA Services. Start. Part 1
WCF RIA Services. Receiving data. Part 2
WCF RIA Services. Update data. Part 3
WCF RIA Services. Implement the Model-View-ViewModel (MVVM) pattern. Part 4

Introduction


The Model-View-ViewModel (MVVM) pattern is used to create loosely coupled Silverlight and WPF applications. In this course, we will not consider the basics of this approach, but simply learn how to implement it in our project, which we created in the course of 3 lessons. If not embedded in the wilds, MVVM is one of the alternatives that is a logical development of programming patterns such as MVC and MVP and fully supports data binding, commands, and all the features provided by Silverlight and WPF. The view model (ViewModel) is responsible for providing the view (View) all the necessary resources. That is, all the necessary properties are provided so that the view can easily perform data binding, create commands, and in the view model, in the meantime, all the logic necessary for the application is present and working. Structurally, the installed DataContext is equal to the instance of the view model with which the data is bound (binding).

The main advantage of this approach is the almost complete independence of the species from the species model, which can roughly be expressed as the independent development of each part by the programmer and designer. Also, a pleasant consequence of this separation is the sufficient ease of creating modular texts (unit test), since the logic is absolutely not connected with the UI (user interface).
')
And by tradition, the entry point to our lesson is the project created at the previous stage of our training.

Step 1: Creating a View Model



From the very beginning we organized all the logic in the accompanying file for the page (page_name.xaml.cs). Thus, we have generated a strong connection between logic and representation. And this is in most cases a very bad tone. The time has come to distinguish these concepts. We take out all the code in the view model. Create a new class in the client project with the name "TasksViewModel". Further, it would not be bad to analyze the view and determine which properties need to be created in the view model. In the picture below we see the form that will need to be created. To begin with, we will add a date selection button to the top two TextBox using DatePickers, remove unused columns from the DataGrid and slightly tweak the remaining ones to get a more attractive and readable look.

Having fluently studied the UI presented above, you can easily identify six properties that you will need to add to the view model: two DateTime properties for the start and end dates, respectively, three commands for buttons, and a Tasks collection. It will look like this:

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; } } 

It is also necessary to implement the INotifyPropertyChanged interface for each property that should be monitored and changed in the UI with changes. In the code, it will all look like this:

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


To be able to use ICommand properties, you need to add an appropriate implementation. We will use the simplest implementation, also called RelayCommand. But in real projects I advise you to use DelegateCommand, which Prism offers.

To create a RelayCommand, you need to add a new class to the client project named “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 } } 

Since the view model provides all the necessary data for the view, and DomainContext is responsible for timely data updates when they change, the obvious solution is that when using the MVVM pattern in RIA Services, you just need to use DomainContext inside the view model.

 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()); } } 

In the code above, we simply create a TasksDomainContext inside the view model, and in the constructor we initialize the commands to associate them with the necessary methods. The Tasks property of a view model contains a reference to a collection of Tasks entities, which in turn is provided by the domain context, and which triggers INotifyCollectionChanged events to provide up-to-date information in the view if the collection changes when the “load” or “SubmitChanges” and updates entities in the background. Pay attention to the “DesignerProperties.IsInDesignTool” property in the designer, which is checked in the “If” block, which prevents calling the “Load” method from the designer, as this will cause an error.

The next step is to transfer the methods from MainPage.xaml.cs to the view model. That is the transfer of logic. MainPage.xaml.cs cleared completely, only this remains inside:

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


Notice that the search method now uses deferred execution, which we talked about in the third lesson. The GetTasksByStartDate method is no longer needed, since the client can tell the domain service what to look for and how to form a request. Also, the creation of a new task is placed in a separate pop-up window in which you can edit the added data. It is important to note that the example of calling a pop-up window directly from the view model is not an entirely successful example. However, this is done to simplify the code, since it is not the main purpose of these lessons to consider a full MVVM and all its advantages. More correct definition and use of MVVM provides 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(); } 


Now add a popup window. On the client project “Add” - “Create item” - “Silverlight Page” with the name “AddTaskView” and the following contents:

 <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> 

Everything. Model type at this stage is fully prepared and functional. We turn to the refinement of our UI.

Step 2: Link the View and View Model


Starting from the first part, we are using the DomainDataSource, which was automatically generated by drag and drop. When using MVVM, you will need to get rid of the DomainDataSource, since its use in XAML violates the concept of MVVM separation.

Add a DataContext. Specify the name of the view model. And add a binding based on the properties created:

 <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> 

Video for this lesson




Sources


On github

Source: https://habr.com/ru/post/215321/


All Articles