Опубликован: 13.12.2011 | Уровень: для всех | Доступ: платный
Лекция 12:

Особенности отображения диалоговых окон в WPF и Silverlight версиях приложения

Реализация ChildViewModelBase

После реализации всех необходимых менеджеров можно перейти непосредственно к ChildViewModelBase. Этот класс будет предоставлять своим наследникам базовый функционал, позволяющий им работать как моделям представления дочернего окна.

В класс также добавлены некоторые члены, которые пригодятся при его использовании:

  • Команда CloseCommand позволяет быстро привязывать элементы управления, закрывающие окно (такие, как кнопка "Отмена" или "Закрыть");
  • Метод Show сделан виртуальным, чтобы спецификации дочерних окон могли при необходимости переопределить его и произвести необходимую инициализацию при показе модели представления.
  • Защищенный виртуальный метод OnClosing позволяет наследнику при помощи аргумента CancelEventArgs отменить закрытие окна. Это полезно, например, при необходимости отобразить диалог подтверждения закрытия, и не закрывать модель представления, если пользователь передумал.
  • При помощи защищенного виртуального метода OnClosed классы, находящиеся ниже в иерархии наследования, могут обработать событие закрытия модели представления, например, очистив выделенные при создании или в методе Show ресурсы.
  1: /// <summary>
  2: /// Base class for View Model supporting closing
  3: /// </summary>
  4: public abstract class ChildViewModelBase : IChildViewModel
  5: {
  6:     /// <summary>
  7:     /// Shows whether a View Model has been closed
  8:     /// </summary>
  9:     public virtual bool IsClosed { get; private set; }
 10:
 11:     /// <summary>
 12:     /// View Model's title
 13:     /// </summary>
 14:     public virtual string Title
 15:     {
 16:         get { return "Безымянный"; }
 17:     }
 18:
 19:     #region Commands
 20:
 21:     private ICommand _closeCommand;
 22:
 23:     /// <summary>
 24:     /// Command closing the tab
 25:     /// </summary>
 26:     public ICommand CloseCommand
 27:     {
 28:         get
 29:         {
 30:             return _closeCommand ??
 31:                     (_closeCommand = new ActionCommand(Close));
 32:         }
 33:     }
 34:
 35:     #endregion
 36:
 37:     #region Injected properties
 38:
 39:     [Import]
 40:     public virtual ICloseableViewModelPresenter<IChildViewModel>
 41:        ViewModelPresenter { get; set; }
 42:
 43:     #endregion
 44:
 45:     /// <summary>
 46:     /// Event raised when the View Model is closed
 47:     /// </summary>
 48:     public event EventHandler Closed;
 49:
 50:     /// <summary>
 51:     /// Closes View Model
 52:     /// </summary>
 53:     public void Close()
 54:     {
 55:         if (IsClosed)
 56:         {
 57:             return;
 58:         }
 59:
 60:         var cancelEventArgs = new CancelEventArgs();
 61:         OnClosing(cancelEventArgs);
 62:
 63:         if (cancelEventArgs.Cancel)
 64:         {
 65:             return;
 66:         }
 67:
 68:         OnClosed();
 69:     }
 70:
 71:     /// <summary>
 72:     /// Registers View Model to be shown
 73:     /// </summary>
 74:     public virtual void Show()
 75:     {
 76:         ViewModelPresenter.ShowViewModel(this);
 77:     }
 78:
 79:     /// <summary>
 80:     /// Processes View Model closing attempt
 81:     /// </summary>
 82:     protected virtual void OnClosing(CancelEventArgs e)
 83:     {
 84:         //
 85:     }
 86:
 87:     /// <summary>
 88:     /// Processes View Model close
 89:     /// </summary>
 90:     protected virtual void OnClosed()
 91:     {
 92:         IsClosed = true;
 93:
 94:         if (Closed != null)
 95:         {
 96:             Closed(this, EventArgs.Empty);
 97:         }
 98:     }
 99: }

Реализация ModalChildViewModelBase

Полезным расширением реализованной модели представления ChildViewModelBase является модель представления модального диалога ModalChildViewModelBase. Модальный диалог позволяет в унифицированной форме сообщить результат выполнения вызвавшему его объекту при помощи свойства ModalResult.

Как и во всех стандартных оконных средах выполнения, установка значения ModalResult автоматически закрывает модель представления.

  1: /// <summary>
  2: /// Base class of Child View's View Model providing its modal
  3: /// result
  4: /// </summary>
  5: public abstract class ModalChildViewModelBase :
  6:    ChildViewModelBase, IModalChildViewModel
  7: {
  8:     // Private fields
  9:     private bool? _modalResult;
 10:
 11:     /// <summary>
 12:     /// Gets or sets modal result
 13:     /// </summary>
 14:     /// <remarks>
 15:     /// Setting <see cref="ModalResult" /> causes View Model
 16:     /// to close
 17:     /// </remarks>
 18:     public virtual bool? ModalResult
 19:     {
 20:         get { return _modalResult; }
 21:         protected set
 22:         {
 23:             if (value == null)
 24:             {
 25:                 throw new InvalidOperationException(
 26:                    "Unable to set ModalResult to null");
 27:             }
 28:
 29:             _modalResult = value;
 30:             Close();
 31:         }
 32:     }
 33: }

Реализация MessageViewModel

Развивая идею ModalChildViewModelBase, целесообразно реализовать одну из наиболее часто используемых моделей представления – модель представления сообщения MessageViewModel.

Данная модель представления отображает окно с сообщением, заданное набором параметров: тип сообщения (информационное, предупреждение, ошибка), доступные для нажатия кнопки (ОK, ОК/Отмена, Повторить/Отмена, или любой заданный пользователем набор), заголовок окна, и непосредственно само сообщение.

Команда ButtonCommand является здесь универсальной для любой заданной пользователем кнопки: в качестве аргумента ей передается индекс кнопки, который затем записывается в MessageResult; также при этом устанавливается свойство ModalResult, что приводит к закрытию окна.

  1: /// <summary>
  2: /// View model of modal message
  3: /// </summary> 
  4: [Export(typeof(MessageViewModel))]
  5: [PartCreationPolicy(CreationPolicy.NonShared)]
  6: [ExportMetadata(AopExtensions.AspectMetadata,
  7:  Aspects.NotifyPropertyChanged)]
  8: public class MessageViewModel : ModalChildViewModelBase
  9: {
 10:     /// <summary>
 11:     /// Initializes a new instance
 12:     /// </summary>
 13:     protected MessageViewModel()
 14:     {
 15:         //
 16:     }
 17:
 18:     // Predefined button sets
 19:     public static readonly IList<string> OkButtons
 20:        = new[] { "OК" };
 21:     public static readonly IList<string> OkCancelButtons
 22:        = new[] { "OК", "Отмена" };
 23:     public static readonly IList<string> RetryCancelButtons
 24:        = new[] { "Повторить", "Отмена" };
 25:
 26:     // Private fields
 27:     private IList<string> _buttons = OkButtons;
 28:
 29:     /// <summary>
 30:     /// List of available buttons
 31:     /// </summary>
 32:     public virtual IList<string> Buttons
 33:     {
 34:         get { return _buttons; }
 35:         set { _buttons = value; }
 36:     }
 37:
 38:     /// <summary>
 39:     /// Message execution result
 40:     /// </summary>
 41:     public virtual int MessageResult { get; protected set; }
 42:
 43:     /// <summary>
 44:     /// Body of the message
 45:     /// </summary>
 46:         
 47:     public virtual string Message { get; set; }
 48:
 49:     /// <summary>
 50:     /// Type of the message
 51:     /// </summary>
 52:     public virtual MessageType MessageType { get; set; }
 53:
 54:     /// <summary>
 55:     /// Message's caption
 56:     /// </summary>
 57:     [Localizable(true)]
 58:     public virtual string Caption { get; set; }
 59:
 60:     /// <summary>
 61:     /// View model's title
 62:     /// </summary>
 63:     public override string Title
 64:     {
 65:         get { return Caption; }
 66:     }
 67:
 68:     #region Commands
 69:
 70:     private ICommand _buttonCommand;
 71:
 72:     public ICommand ButtonCommand
 73:     {
 74:         get
 75:         {
 76:             return _buttonCommand ??
 77:                     (_buttonCommand = new ActionCommand(
 78:                   param => SetMessageResult(param.ToString())));
 79:         }
 80:     }
 81:
 82:     #endregion
 83:
 84:     /// <summary>
 85:     /// Processes View Model closing attempt
 86:     /// </summary>
 87:     protected override void OnClosing(CancelEventArgs e)
 88:     {
 89:         base.OnClosing(e);
 90:         e.Cancel = ModalResult == null;
 91:     }
 92:
 93:     /// <summary>
 94:     /// Sets specified button's index as message result
 95:     /// </summary>
 96:     private void SetMessageResult(string selectedButton)
 97:     {
 98:         if (IsClosed)
 99:         {
100:             return;
101:         }
102:
103:         Debug.Assert(Buttons.Contains(selectedButton));
104:         MessageResult = Buttons.IndexOf(selectedButton);
105:         ModalResult = true;
106:     }
107: }

Как видно из кода, особенностью представленной модели представления является то, что переопределяемый метод OnClosing не позволяет закрыть окно, не установив MessageResult – таким образом вызывающему коду гарантируется детерминированный результат отображения сообщения.

Соответствующее представление в соответствии с шаблоном MVVM не содержит сложной логики и лишь визуализирует состояние, представленное в модели представления:

  1: <UserControl x:Class="TestProject.MessageView"
  2: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
  3: xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4: xmlns:controlsHelpers
  5:    ="clr-namespace:PassportOffice.View.Controls">
  6:
  7:     <Grid>
  8:         <Grid.Resources>
  9:             <controlsHelpers:CommandReference
 10:                 x:Key="ButtonCommandReference"
 11:                 Command="{Binding ButtonCommand}" />
 12:             <controlsHelpers:MessageTypeConverter
 13:                 x:Key="MessageTypeConverter" />
 14:         </Grid.Resources>
 15:         
 16:         <Grid.ColumnDefinitions>
 17:             <ColumnDefinition Width="Auto" />
 18:             <ColumnDefinition Width="Auto" />
 19:         </Grid.ColumnDefinitions>
 20:         <Grid.RowDefinitions>
 21:             <RowDefinition Height="*" />
 22:             <RowDefinition Height="Auto" />
 23:         </Grid.RowDefinitions>
 24:
 25:         <Image x:Name="ImageMessage" Height="48"
 26:             UriSource="{Binding MessageType,
 27:                Converter={StaticResource MessageTypeConverter}}"
 28:             Grid.Column="0" Grid.Row="0" />
 29: 
 30:         <TextBlock Margin="3" MinWidth="300" MaxWidth="600"
 31:            Text="{Binding Message}" VerticalAlignment="Center"
 32:            TextWrapping="Wrap" Grid.Column="1" Grid.Row="0" />
 33:        
 34:         <ItemsControl x:Name="ButtonsControl"
 35:            HorizontalAlignment="Center"
 36:            ItemsSource="{Binding Buttons}" Margin="0,5,0,0"
 37:            Grid.Column="0" Grid.ColumnSpan="2" Grid.Row="1">
 38:             <ItemsControl.ItemsPanel>
 39:                 <ItemsPanelTemplate>
 40:                     <StackPanel Orientation="Horizontal" />
 41:                 </ItemsPanelTemplate>
 42:             </ItemsControl.ItemsPanel>
 43:             <ItemsControl.ItemTemplate>
 44:                 <DataTemplate>
 45:                     <Button Content="{Binding}"
 46:                 Command="{StaticResource ButtonCommandReference}"
 47:                 CommandParameter="{Binding}" />
 48:                 </DataTemplate>
 49:             </ItemsControl.ItemTemplate>
 50:         </ItemsControl>
 51:     </Grid>
 52: </UserControl>

Краткие итоги

В рамках данной лекции была реализована логика отображения диалоговых окон на уровне моделей представления. Для этого добавлены набор слушателей слоя представления, которые, не нарушая принципы MVVM, обеспечивают отображение регистрируемых моделей представления диалоговых окон. Также разработанные базовые классы в качестве примера были расширены наследниками, специализирующимися в отображении сообщений различного характера.

Анисимов Михаил
Анисимов Михаил
Украина
Наталия Шаститко
Наталия Шаститко
Украина, Днепропетровск, Днепропетровский Гуманитарный Университет, 2014