| Россия |
Построение кроссплатформенного Silverlight/WPF приложения
В первую очередь необходимо создать модель представления диалога, которая наследуется от класса ModalViewModelBase:
1: [Export]
2: [PartCreationPolicy(CreationPolicy.NonShared)]
3: [ExportMetadata(AopExtensions.AspectMetadata,
4: Aspects.NotifyPropertyChanged)]
5: public class TextInputModalChildViewModel :
6: ModalChildViewModelBase
7: {
8: public virtual string Text { get; set; }
9: }Далее создается представление, содержащее поле ввода, привязанное к свойству Text:
1: <UserControl
2: x:Class="CrossPlatformApplication.TextInputModalChildView"
3: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5: xmlns:i=
6: "http://schemas.microsoft.com/expression/2010/interactivity"
7: xmlns:ic=
8: "http://schemas.microsoft.com/expression/2010/interactions"
9: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
10: xmlns:mc=
11: "http://schemas.openxmlformats.org/markup-compatibility/2006"
12: mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
13:
14: <StackPanel x:Name="LayoutRoot" Background="White">
15: <TextBlock Text="Введите новый текст" />
16: <TextBox Text="{Binding Text, Mode=TwoWay}" />
17: <StackPanel Orientation="Horizontal"
18: HorizontalAlignment="Right">
19: <Button Content="OK" Command="{Binding CloseCommand}"
20: Width="80" Height="30" Margin="6">
21: <i:Interaction.Triggers>
22: <i:EventTrigger EventName="Click">
23: <ic:ChangePropertyAction
24: PropertyName="ModalResult" Value="True"
25: TargetObject="{Binding}" />
26: </i:EventTrigger>
27: </i:Interaction.Triggers>
28: </Button>
29: <Button Content="Cancel"
30: Command="{Binding CloseCommand}" Width="80"
31: Height="30" Margin="6" />
32: </StackPanel>
33: </StackPanel>
34: </UserControl>Здесь используется библиотека Expression Interactions, входящая в состав Microsoft Expression Blend 4, которая позволяет использовать кроссплатформенные Silverlight/WPF триггеры, схожие по функциональности с WPF триггерами, а также добавляют понятие поведения элемента управления, аналога которому в стандартной поставке WPF нет.
Далее, для работы с асинхронными моделями представления модальных дочерних окон удобно использовать библиотеку Reactive Extensions (Rx Framework):
1: public static class ObservableHelper
2: {
3: public static IObservable<EventPattern<EventArgs>>
4: ObserveClosed(this ICloseableViewModel childViewModel)
5: {
6: return Observable.FromEventPattern(
7: handler => childViewModel.Closed += handler,
8: handler => childViewModel.Closed -= handler);
9: }
10:
11: public static IObservable<T> SelectSender<T>
12: (this IObservable<EventPattern<EventArgs>> observable)
13: {
14: return observable.Select(ev => (T)ev.Sender);
15: }
16:
17: public static IObservable<T> WhereSucceeded<T>
18: (this IObservable<T> observable)
19: where T : IModalChildViewModel
20: {
21: return observable.Where(vm => vm.ModalResult.HasValue
22: && vm.ModalResult.Value);
23: }
24: } 1: public static class ViewModelExtension
2: {
3: /// <summary>
4: /// Resolves and shows Closeable View Model of
5: /// type <typeparamref name="T" />
6: /// </summary>
7: public static IObservable<T> ResolveAndShow<T>
8: (this IServiceLocator serviceLocator,
9: Action<T> prepareAction = null)
10: where T : ICloseableViewModel
11: {
12: var viewModel = serviceLocator.GetInstance<T>();
13:
14: if (prepareAction != null)
15: {
16: prepareAction(viewModel);
17: }
18:
19: IObservable<T> result = Observable.FromEventPattern
20: (handler => viewModel.Closed += handler,
21: handler => viewModel.Closed -= handler)
22: .SelectSender<T>();
23:
24: viewModel.Show();
25:
26: return result;
27: }
28: }Класс ObservableHelper позволяет, с одной стороны конструировать IObservable последовательность из события закрытия окна, и с другой стороны предоставляет удобные LINQ-подобные методы для взаимодействия с данной последовательностью. Так, метод WhereSucceeded налагает на последовательность ограничение, которое позволяет ей выполниться лишь в случае завершения диалога с положительным ModalResult (что, как правило, происходит при подтверждении какой-либо операции).
Класс ViewModelExtension представляет метод, который одним вызовом разрешает закрываемую модель представления из IoC контейнера, показывает её и возвращает IObservable последовательность её закрытия.
Используя введенные сущности, в модели представления главного окна добавляется логика отображения диалога. Для видимого эффекта использования диалога введенный в окне текст заменяет приветственный текст по умолчанию:
1: [Export]
2: [PartCreationPolicy(CreationPolicy.NonShared)]
3: [ExportMetadata(AopExtensions.AspectMetadata,
4: Aspects.NotifyPropertyChanged)]
5: public class MainViewModel : EntitledViewModelBase
6: {
7: // Private fields
8: private string _text = "Hello world";
9:
10: public virtual string Text
11: {
12: get { return _text; }
13: protected set { _text = value; }
14: }
15:
16: #region Commands
17:
18: private ICommand _showTextInputCommand;
19:
20: public ICommand ShowTextInputCommand
21: {
22: get
23: {
24: return _showTextInputCommand ??
25: (_showTextInputCommand =
26: new ActionCommand(ShowTextInput));
27: }
28: }
29:
30: #endregion
31:
32: #region Injected properties
33:
34: [Import]
35: public IServiceLocator ServiceLocator { private get; set; }
36:
37: #endregion
38:
39: public override string Title
40: {
41: get { return "Test Application"; }
42: }
43:
44: private void ShowTextInput()
45: {
46: ServiceLocator
47: .ResolveAndShow<TextInputModalChildViewModel>()
48: .WhereSucceeded()
49: .Subscribe(vm => Text = vm.Text);
50: }
51: }Соответственно, также изменяется разметка представления главного окна:
1: <UserControl x:Class="CrossPlatformApplication.MainView"
2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4: xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5: xmlns:mc=
6: "http://schemas.openxmlformats.org/markup-compatibility/2006"
7: mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="400">
8:
9: <Grid x:Name="LayoutRoot" Background="White"
10: MinHeight="150" MinWidth="200">
11: <TextBlock Text="{Binding Text}"
12: HorizontalAlignment="Center"
13: VerticalAlignment="Center" />
14: <Button Content="Изменить"
15: Command="{Binding ShowTextInputCommand}"
16: Width="80" Height="30"
17: HorizontalAlignment="Right"
18: VerticalAlignment="Bottom" Margin="6" />
19: </Grid>
20: </UserControl>Краткие итоги
В рамках данной лекции было написано несложное кроссплатформенное Silverlight/WPF приложение, агрегирующее все наработки предыдущих лекций. Выполненное в соответствии с шаблоном MVVM, оно стремится максимально повторно использовать как код, так и разметку, использует MEF для гибкого разрешения зависимостей между сущностями, в том числе между моделями представления и представлениями, применяет аспект INotifyPropertyChanged на модели представления, избавляя от необходимости ручного программирования логики нотификации об изменениях в каждом свойстве. При этом объем содержательного кода минимален и приложение крайне просто расширяется любым функционалом. Приведенный пример можно использовать как каркас для Silverlight/WPF приложений с богатым пользовательских интерфейсом.