Основы MVVM
Шаблон Приложение Windows Phone с привязкой к данным
Приложение, построенное с использованием данного шаблона, реализует основные принципы, характерные для шаблона MVVM. Нельзя сказать, что такое приложение в полной мере соответствует принципам MVVM. Например, модель и модель представления разделены недостаточно чётко (хотя, в данном случае можно говорить о том, что файл ItemViewModel, представляющий собой класс, определяющий структуру данных, которая используется в приложении, ближе к понятию "Model", чем к понятию "ViewModel", которое присутствует в его названии). В файлах программного кода страниц, которые формируют пользовательский интерфейс, производятся некоторые действия. Однако, есть в этом шаблоне и реализация основополагающих идей MVVM. В частности, он предусматривает использование данных периода проектирования, связь между исходными данными и представлением реализована с помощью системы привязки данных. В интерфейсе используются шаблоны, что закладывает основу для работы со списками данных переменной длины.
Рассмотрим приложение, созданное по этому шаблону, подробнее. На рис. 21.1. приведено окно Visual Studio, в котором развёрнута структура проекта, в визуальном конструкторе открыта страница MainPage.xaml.
увеличить изображение
Рис. 21.1. Приложение, созданное по шаблону Приложение Windows Phone с привязкой к данным
Начнём рассмотрение проекта с папки SampleData. Здесь находится XAML-файл (Листинг 21.1), MainViewModelSampleData, который содержит данные периода проектирования. Структура данных, которые описаны в файле определяется классом ItemViewModel. В заголовке XAML-файла описано пространство имен vm, соответствующее пространству имен класса ItemViewModel. В файле содержится, кроме того, описание одиночного свойства из класса MainViewModel.
<vm:MainViewModel
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:L5_1.ViewModels"
SampleProperty="Пример значения свойства текста">
<vm:MainViewModel.Items>
<vm:ItemViewModel ID="0" LineOne="первая схема"
LineTwo="Maecenas praesent accumsan bibendum"
LineThree="Maecenas praesent accumsan bibendum
dictumst eleifend facilisi faucibus
habitant inceptos interdum lobortis
nascetur"/>
<vm:ItemViewModel ID="1" LineOne="вторая схема"
LineTwo="Dictumst eleifend facilisi faucibus"
LineThree="Pharetra placerat pulvinar sagittis
senectus sociosqu suscipit torquent
ultrices vehicula volutpat maecenas
praesent"/>
</vm:MainViewModel.Items>
</vm:MainViewModel>
Листинг
21.1.
Фрагмент файла MainViewModelSampleData.xaml
Этот файл и представляет собой тот контекст данных, который назначен страницам приложения. При этом в режиме проектирования контекст данных для страницы MainPage (Листинг 21.2) соответствует общему списку данных, на этой странице выводится список элементов. А на странице DetailsPage (Листинг 21.3) выводится подробная информации об элементе с главной страницы, которого, при работе приложения, коснулся пользователь. В режиме проектирования контекст данных страницы так же установлен на вышеупомянутый файл, но контекст данных сетки LayoutRoot уточнён до элемента с индексом [0].
<phone:PhoneApplicationPage
x:Class="L5_1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--Для контекста данных установлен верхний пример данных, и LayoutRoot содержит корневую сетку, где размещается все содержимое страницы-->
<Grid x:Name="LayoutRoot" Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel содержит имя приложения и заголовок страницы-->
<StackPanel Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="МОЕ ПРИЛОЖЕНИЕ" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="имя страницы" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel содержит LongListSelector и LongListSelector ItemTemplate. Поместите здесь дополнительное содержимое-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<phone:LongListSelector x:Name="MainLongListSelector" Margin="0,0,-12,0" ItemsSource="{Binding Items}"
SelectionChanged="MainLongListSelector_SelectionChanged">
<phone:LongListSelector.ItemTemplate>
<DataTemplate>
<StackPanel Margin="0,0,0,17">
<TextBlock Text="{Binding LineOne}" TextWrapping="Wrap" Style="{StaticResource PhoneTextExtraLargeStyle}"/>
<TextBlock Text="{Binding LineTwo}" TextWrapping="Wrap" Margin="12,-6,12,0"
Style="{StaticResource PhoneTextSubtleStyle}"/>
</StackPanel>
</DataTemplate>
</phone:LongListSelector.ItemTemplate>
</phone:LongListSelector>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
Листинг
21.2.
Фрагмент файла MainPage.xaml
Класс ItemViewModel, по сути, ближе всего к понятию "модели", принятом в MVVM. Он описывает базовую структуру данных, применяемых в программе. Эти данные применяются, во-первых, при заполнении страницы MainPage – на ней выводится список элементов, во-вторых – при заполнении страницы DetailsPage, которая содержит подробное описание одного элемента. Как уже было сказано, в режиме проектирования данные, выводимые на страницах, определены в файле MainViewModelSampleData.xaml. При запуске приложения контекст данных страницы MainPage (Листинг 21.4) устанавливается на объект типа MainViewModel, созданный в классе App (Листинг 21.5). Похожая процедура выполняется и при вызове страницы DetailsPage (Листинг 21.6). В XAML контекст данных установлен на один из элементов, описанных в MainViewModelSampleData.xaml, в режиме исполнения он программно устанавливается на один из элементов, присутствующих в объекте типа MainViewModel, который создаётся в классе App.
<phone:PhoneApplicationPage
x:Class="L5_1.DetailsPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
d:DataContext="{d:DesignData SampleData/MainViewModelSampleData.xaml}"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}"
SupportedOrientations="Portrait" Orientation="Portrait"
shell:SystemTray.IsVisible="True">
<!--Для контекста данных установлен верхний пример данных и первый элемент из коллекции примеров данных ниже,
и LayoutRoot содержит корневую сетку, где размещается все содержимое страницы-->
<Grid x:Name="LayoutRoot" Background="Transparent" d:DataContext="{Binding Items[0]}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!--TitlePanel содержит имя приложения и заголовок страницы-->
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
<TextBlock Text="МОЕ ПРИЛОЖЕНИЕ" Style="{StaticResource PhoneTextNormalStyle}"/>
<TextBlock Text="{Binding LineOne}" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
</StackPanel>
<!--ContentPanel содержит текст сведений. Поместите здесь дополнительное содержимое-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Text="{Binding LineThree}" TextWrapping="Wrap" Style="{StaticResource PhoneTextNormalStyle}"/>
</Grid>
</Grid>
</phone:PhoneApplicationPage>
Листинг
21.3.
Фрагмент файла DetailPage.xaml
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using L5_1.Resources;
using L5_1.ViewModels;
namespace L5_1
{
public partial class MainPage : PhoneApplicationPage
{
// Конструктор
public MainPage()
{
InitializeComponent();
// Задайте для контекста данных элемента управления LongListSelector пример данных
DataContext = App.ViewModel;
}
// Загрузка данных для элементов ViewModel
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (!App.ViewModel.IsDataLoaded)
{
App.ViewModel.LoadData();
}
}
// Обработка выбранных элементов, измененных в LongListSelector
private void MainLongListSelector_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Если выбранный элемент равен NULL (ничего не выбрано), никакие действия не требуются
if (MainLongListSelector.SelectedItem == null)
return;
// Переход на новую страницу
NavigationService.Navigate(new Uri("/DetailsPage.xaml?selectedItem=" +
(MainLongListSelector.SelectedItem as ItemViewModel).ID, UriKind.Relative));
// Сброс выбранного элемента в NULL (ничего не выбрано)
MainLongListSelector.SelectedItem = null;
}
}
}
Листинг
21.4.
Фрагмент файла MainPage.xaml.cs
