Опубликован: 13.12.2011 | Доступ: свободный | Студентов: 1021 / 34 | Оценка: 4.29 / 4.57 | Длительность: 13:56:00
Самостоятельная работа 6:

Построение кроссплатформенного Silverlight/WPF приложения

< Лекция 12 || Самостоятельная работа 6: 123

В первую очередь необходимо создать модель представления диалога, которая наследуется от класса 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 приложений с богатым пользовательских интерфейсом.

< Лекция 12 || Самостоятельная работа 6: 123