Передача данных между страницами в WinRT
129WinRT --- Передача данных между страницами
Очень часто страницам приходится обмениваться данными. Например, страницы часто совместно используют модель представления. Хорошим местом для хранения данных, передаваемых между страницами, является класс App. Не бойтесь добавлять методы и свойства в этот класс. Например, вы можете добавить открытое свойство с именем ViewModel, которое имеет открытый get-метод доступа с закрытым set-методом, чтобы это свойство могло инициализироваться в конструкторе App.
С другой стороны, если исходить из философии, что данные должны быть видны только тем классам, которым необходимо знать об их существовании, размещать все в классе App нежелательно. Существуют хорошо структурированные механизмы передачи и возвращения данных между страницами в ходе навигации.
Проект DataPassingAndReturning содержит две простые страницы, демонстрирующие эти способы. Первая страница, как обычно, называется MainPage, а вторая называется DialogPage, потому что по своей функциональности она близка к диалоговому окну. Страница MainPage может переходить только к DialogPage, a DialogPage может только возвращаться к MainPage.
Из-за ограниченности навигации страницам не нужно сохранять свое состояние. Чтобы программа стала еще проще, она не сохраняет состояние навигации или состояние страниц в случае приостановки и не реализует команды перехода с клавиатуры или мыши. Несмотря на свою простоту, программа хорошо демонстрирует базовые приемы передачи данных. Файл XAML страницы DialogPage содержит три элемента управления RadioButton и кнопку Button с текстом «Закончить»:
<Page ...>
<Grid Background="#FF1D1D1D">
<StackPanel>
<TextBlock Text="Выберите цвет"
FontSize="60"
Margin="40"
HorizontalAlignment="Center" />
<StackPanel Name="radios"
HorizontalAlignment="Center"
Margin="40">
<RadioButton Content="Красный" Margin="16">
<RadioButton.Tag>
<Color>Red</Color>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Зеленый" Margin="16">
<RadioButton.Tag>
<Color>Green</Color>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Синий" Margin="16">
<RadioButton.Tag>
<Color>Blue</Color>
</RadioButton.Tag>
</RadioButton>
</StackPanel>
<Button Content="Закончить"
HorizontalAlignment="Center"
Margin="40"
Click="Return_BtnClick" />
</StackPanel>
</Grid>
</Page>
Обратите внимание: у каждого элемента RadioButton свойству Tag задано значение Color, соответствующее кнопке.
Файл фонового кода DialogPage отвечает за получение цвета, выбранного при помощи переключателей, и его возвращение MainPage. Интересно, что файл MainPage.xaml очень похож на DialogPage.xaml - только у панели Grid есть имя, средний элемент управления RadioButton установлен, а кнопка Button содержит текст «Загрузить цвет»:
<Page ...>
<Grid Background="#FF1D1D1D" x:Name="layoutGrid">
<StackPanel>
<TextBlock Text="Выберите цвет"
FontSize="60"
Margin="40"
HorizontalAlignment="Center" />
<StackPanel Name="radios"
HorizontalAlignment="Center"
Margin="40">
<RadioButton Content="Красный" Margin="16">
<RadioButton.Tag>
<Color>Red</Color>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Зеленый" Margin="16" IsChecked="True">
<RadioButton.Tag>
<Color>Green</Color>
</RadioButton.Tag>
</RadioButton>
<RadioButton Content="Синий" Margin="16">
<RadioButton.Tag>
<Color>Blue</Color>
</RadioButton.Tag>
</RadioButton>
</StackPanel>
<Button Content="Загрузить цвет"
HorizontalAlignment="Center"
Margin="40"
Click="Goto_BtnClick" />
</StackPanel>
</Grid>
</Page>
Элементы управления RadioButton из MainPage используются для выбора исходного состояния RadioButton в DialogPage; это означает, что страница MainPage должна передавать данные DialogPage.
Данные, передаваемые между MainPage и DialogPage, состоят из единственного значения Color, но в реальных приложениях их может быть намного больше. Чтобы отразить эту возможность в своем приложении, мы определим классы, предназначенные специально для передачи данных между страницами. Класс для передачи данных от MainPage к DialogPage выглядит так:
using Windows.UI;
namespace WinRTTestApp
{
public class PassData
{
public Color InitializeColor { set; get; }
}
}
В этом простом примере данные, возвращаемые от DialogPage к MainPage, выглядят практически так же:
using Windows.UI;
namespace WinRTTestApp
{
public class ReturnData
{
public Color ReturnColor { set; get; }
}
}
Конечно, в данной ситуации можно использовать тот же самый класс, но в общем случае для этих двух задач будут использоваться разные классы.
Будьте внимательны! Далее я буду часто переключаться между файлами фонового кода MainPage и DialogPage в соответствии с направлением логических переходов и передачи данных.
Передача данных от MainPage к DialogPage - простой случай. Когда пользователь щелкает на кнопке «Загрузить цвет» в MainPage, файл фонового кода создает объект типа PassData и просматривает коллекцию элементов управления RadioButton, чтобы определить, какой из них установлен. Полученное значение Color задается свойству InitializeColor объекта PassData, который становится вторым аргументом Navigate:
private void Goto_BtnClick(object sender, RoutedEventArgs e)
{
// Создать объект PassData
PassData passData = new PassData();
// Задание свойства InitializeColor по состоянию элементов RadioButton
foreach (UIElement child in radios.Children)
if ((child as RadioButton).IsChecked.Value)
passData.InitializeColor = (Color)(child as RadioButton).Tag;
// Передача объекта Navigate
this.Frame.Navigate(typeof(DialogPage), passData);
}
При вызове метода OnNavigatedTo класса DialogPage свойство Parameter аргументов события содержит объект, переданный во втором аргументе Navigate. Класс DialogPage использует его для инициализации своего набора элементов управления RadioButton:
protected override void OnNavigatedTo(NavigationEventArgs args)
{
// Получение объекта, переданного во втором аргументе Navigate
PassData passData = args.Parameter as PassData;
// Использование его для инициализации элементы RadioButton
foreach (UIElement child in radios.Children)
if ((Color)(child as RadioButton).Tag == passData.InitializeColor)
(child as RadioButton).IsChecked = true;
base.OnNavigatedTo(args);
}
Было бы удобно, если бы у метода GoBack был дополнительный параметр, в котором можно было бы вернуть данные целевой странице. Но такого параметра нет. Механизма не существует, поэтому приходится применять другие средства.
Одна из возможностей: после того как DialogPage вызовет GoBack, вызывается переопределение OnNavigatedFrom класса DialogPage. Свойство Content аргумента событий содержит экземпляр MainPage, к которому будет осуществлен переход. Это означает, что MainPage может определить открытое свойство или метод, предназначенный специально для получения информации от DialogPage, и DialogPage сможет задать это свойство или вызвать этот метод в своем переопределении OnNavigatedFrom.
private void Return_BtnClick(object sender, RoutedEventArgs e)
{
this.Frame.GoBack();
}
С архитектурной точки зрения такой прием выглядит сомнительно, потому что класс DialogPage должен располагать информацией о типах страниц, с которых осуществляется переход. В общем случае данное решение нельзя признать хорошим. Гораздо лучше определить в DialogPage событие Completed с типом данных, которые класс должен вернуть:
public sealed partial class DialogPage : Page
{
public event EventHandler<ReturnData> Completed;
// ...
}
Класс MainPage должен назначить обработчик для этого события. Сделать это можно только в методе OnNavigatedFrom, потому что аргументы события включают свойство Content с экземпляром DialogPage, к которому осуществляется переход:
protected override void OnNavigatedFrom(NavigationEventArgs args)
{
if (args.SourcePageType.Equals(typeof(DialogPage)))
(args.Content as DialogPage).Completed += OnDialogPageCompleted;
base.OnNavigatedFrom(args);
}
MainPage знает о DialogPage, потому что переход осуществляется к DialogPage. Но приложение также может переходить и к другим страницам, поэтому оно проверяет свойство SourcePageType аргументов события, чтобы быть уверенным в том, что тип страницы для данного конкретного события OnNavigatedFrom был определен правильно.
В этой схеме классу DialogPage не нужно знать о MainPage; собственно, так и должно быть. Изоляция потребителя информации от поставщика информации - одна из главных целей применения событий в контексте объектно-ориентированного программирования.
DialogPage может инициировать событие Completed в обработчике Click кнопки Button, но я решил реализовать эту логику в OnNavigatedFrom:
protected override void OnNavigatedFrom(NavigationEventArgs args)
{
if (Completed != null)
{
// Создание объекта ReturnData
ReturnData returnData = new ReturnData();
// Задание свойства ReturnColor по состоянию элементов управления RadioButton
foreach (UIElement child in radios.Children)
if ((child as RadioButton).IsChecked.Value)
returnData.ReturnColor = (Color)(child as RadioButton).Tag;
// Инициирование события Completed
Completed(this, returnData);
}
base.OnNavigatedFrom(args);
}
Если для события Completed имеется обработчик, DialogPage создает экземпляр ReturnData и задает свойство ReturnColor по состоянию коллекции элементов управления RadioButton. В обработчике Completed класс MainPage использует данные, полученные от DialogPage, для задания свойства Background своего объекта Grid и проверки RadioButton:
private void OnDialogPageCompleted(object sender, ReturnData args)
{
// Назначение фона по возвращенному цвету
layoutGrid.Background = new SolidColorBrush(args.ReturnColor);
// Задание состояния RadioButton для возврата цвета
foreach (UIElement child in radios.Children)
if ((Color)(child as RadioButton).Tag == args.ReturnColor)
(child as RadioButton).IsChecked = true;
(sender as DialogPage).Completed -= OnDialogPageCompleted;
}
В конце выполнения обработчик отсоединяет себя от отправителя. Но в представленном коде имеется недостаток: по умолчанию экземпляр MainPage, который назначает обработчик события Completed в DialogPage, не является экземпляром MainPage, к которому возвращается DialogPage! Для решения этой проблемы необходимо задать NavigationCacheMode другое значение вместо Disabled.
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.NavigationCacheMode = NavigationCacheMode.Enabled;
}
// ...
}
Это необходимо сделать только в MainPage. Обеспечение единственности экземпляра абсолютно логично для страницы, которая с точки зрения архитектуры является центром приложения. Экземпляр MainPage, который передает данные странице DialogPage, должен быть тем же экземпляром, который получает от нее результаты.