Основы 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