Страницы

Поиск по вопросам

четверг, 4 октября 2018 г.

Сервис создания модальных и немодальных окон в контексте паттерна MVVM

Как в контексте паттерна MVVM правильно и красиво реализовать сервис создания модальных и немодальных окон. Хотелось бы что-то вроде:
myWindowSrv.ShowWindow(myChildViewModel);
Т.е. мы вызываем сервис создания окон, передаем ему нужную ViewModel и сервис на основе типа этой ViewModel отображает нужную View


Ответ

В качестве базы вы можете сделать так:
class DisplayRootRegistry { Dictionary vmToWindowMapping = new Dictionary();
public void RegisterWindowType() where Win: Window, new() where VM : class { var vmType = typeof(VM); if (vmType.IsInterface) throw new ArgumentException("Cannot register interfaces"); if (vmToWindowMapping.ContainsKey(vmType)) throw new InvalidOperationException( $"Type {vmType.FullName} is already registered"); vmToWindowMapping[vmType] = typeof(Win); }
public void UnregisterWindowType() { var vmType = typeof(VM); if (vmType.IsInterface) throw new ArgumentException("Cannot register interfaces"); if (!vmToWindowMapping.ContainsKey(vmType)) throw new InvalidOperationException( $"Type {vmType.FullName} is not registered"); vmToWindowMapping.Remove(vmType); }
public Window CreateWindowInstanceWithVM(object vm) { if (vm == null) throw new ArgumentNullException("vm"); Type windowType = null;
var vmType = vm.GetType(); while (vmType != null && !vmToWindowMapping.TryGetValue(vmType, out windowType)) vmType = vmType.BaseType;
if (windowType == null) throw new ArgumentException( $"No registered window type for argument type {vm.GetType().FullName}");
var window = (Window)Activator.CreateInstance(windowType); window.DataContext = vm; return window; } }
Имея это, можно накручивать сверху разную логику открытия/закрытия. Например, вы можете писать команды открытия/закрытия вручную:
class DisplayRootRegistry { // начало см. выше
Dictionary openWindows = new Dictionary(); public void ShowPresentation(object vm) { if (vm == null) throw new ArgumentNullException("vm"); if (openWindows.ContainsKey(vm)) throw new InvalidOperationException("UI for this VM is already displayed"); var window = CreateWindowInstanceWithVM(vm); window.Show(); openWindows[vm] = window; }
public void HidePresentation(object vm) { Window window; if (!openWindows.TryGetValue(vm, out window)) throw new InvalidOperationException("UI for this VM is not displayed"); window.Close(); openWindows.Remove(vm); }
public async Task ShowModalPresentation(object vm) { var window = CreateWindowInstanceWithVM(vm); window.WindowStartupLocation = WindowStartupLocation.CenterScreen; await window.Dispatcher.InvokeAsync(() => window.ShowDialog()); } }
Пример использования:


public partial class App : Application { DisplayRootRegistry displayRootRegistry = new DisplayRootRegistry(); MainVM mainVM;
public App() { displayRootRegistry.RegisterWindowType(); displayRootRegistry.RegisterWindowType(); }
protected override async void OnStartup(StartupEventArgs e) { base.OnStartup(e); await RunProgramLogic(); Shutdown(); }
async Task RunProgramLogic() { while (true) { mainVM = new MainVM();
displayRootRegistry.ShowPresentation(mainVM);
while (true) { var askCloseVM = new AskVM("Do you want to close the application?"); await Task.Delay(TimeSpan.FromSeconds(2)); await displayRootRegistry.ShowModalPresentation(askCloseVM); if (askCloseVM.Answer == true) break; } displayRootRegistry.HidePresentation(mainVM); await Task.Delay(TimeSpan.FromSeconds(2));
var askReopenVM = new AskVM("Maybe reopen again?"); await displayRootRegistry.ShowModalPresentation(askReopenVM); if (askReopenVM.Answer != true) break; } } }

public class MainVM { public string Message => "hello world!"; }



public class AskVM : INotifyPropertyChanged { public AskVM(string question) { Question = question; }
public string Question { get; private set; }
bool? answer = null; public bool? Answer { get { return answer; } set { if (answer != value) { answer = value; NotifyPropertyChanged(); } } }
void NotifyPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
public event PropertyChangedEventHandler PropertyChanged; }



public partial class AskDialog : Window { public AskDialog() { InitializeComponent(); Closed += (o, args) => BindableDialogResult = DialogResult; SetBinding(BindableDialogResultProperty, new Binding("Answer")); }
void OnYes(object sender, RoutedEventArgs e) { DialogResult = true; }
#region dp bool? BindableDialogResult public bool? BindableDialogResult { get { return (bool?)GetValue(BindableDialogResultProperty); } set { SetValue(BindableDialogResultProperty, value); } }
public static readonly DependencyProperty BindableDialogResultProperty = DependencyProperty.Register("BindableDialogResult", typeof(bool?), typeof(AskDialog), new FrameworkPropertyMetadata( null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault)); #endregion }
Или вы можете «доверить» закрытие/открытие окна самому окну, связываясь с ним через Binding — здесь делайте как вам кажется лучше.
По поводу того, как организовать модальные окна, ещё можно заглянуть в этот вопрос: Метод, ожидающий действие пользователя

Комментариев нет:

Отправить комментарий