2010-09-27 4 views
127

У меня возникла проблема создания диалоговых окон добавления и редактирования для моего приложения wpf.Хорошая или плохая практика для диалогов в wpf с MVVM?

Все, что я хочу сделать в своем коде, было чем-то вроде этого. (Я в основном использую ViewModel первого подход с MVVM)

ViewModel, которая вызывает диалоговое окно:

var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM); 
// Do anything with the dialog result 

Как это работает?

Во-первых, я создал диалоговое службу:

public interface IUIWindowDialogService 
{ 
    bool? ShowDialog(string title, object datacontext); 
} 

public class WpfUIWindowDialogService : IUIWindowDialogService 
{ 
    public bool? ShowDialog(string title, object datacontext) 
    { 
     var win = new WindowDialog(); 
     win.Title = title; 
     win.DataContext = datacontext; 

     return win.ShowDialog(); 
    } 
} 

WindowDialog является особенным, но просто окно. Мне нужно, чтобы держать мое содержание:

<Window x:Class="WindowDialog" 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    Title="WindowDialog" 
    WindowStyle="SingleBorderWindow" 
    WindowStartupLocation="CenterOwner" SizeToContent="WidthAndHeight"> 
    <ContentPresenter x:Name="DialogPresenter" Content="{Binding .}"> 

    </ContentPresenter> 
</Window> 

Проблема с диалогами в МОФ является dialogresult = true может быть достигнуто только в коде. Вот почему я создал интерфейс для своего dialogviewmodel для его реализации.

public class RequestCloseDialogEventArgs : EventArgs 
{ 
    public bool DialogResult { get; set; } 
    public RequestCloseDialogEventArgs(bool dialogresult) 
    { 
     this.DialogResult = dialogresult; 
    } 
} 

public interface IDialogResultVMHelper 
{ 
    event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog; 
} 

Всякий раз, когда мой ViewModel считает, что пришло время для dialogresult = true, затем поднять это событие.

public partial class DialogWindow : Window 
{ 
    // Note: If the window is closed, it has no DialogResult 
    private bool _isClosed = false; 

    public DialogWindow() 
    { 
     InitializeComponent(); 
     this.DialogPresenter.DataContextChanged += DialogPresenterDataContextChanged; 
     this.Closed += DialogWindowClosed; 
    } 

    void DialogWindowClosed(object sender, EventArgs e) 
    { 
     this._isClosed = true; 
    } 

    private void DialogPresenterDataContextChanged(object sender, 
           DependencyPropertyChangedEventArgs e) 
    { 
     var d = e.NewValue as IDialogResultVMHelper; 

     if (d == null) 
      return; 

     d.RequestCloseDialog += new EventHandler<RequestCloseDialogEventArgs> 
            (DialogResultTrueEvent).MakeWeak(
             eh => d.RequestCloseDialog -= eh;); 
    } 

    private void DialogResultTrueEvent(object sender, 
           RequestCloseDialogEventArgs eventargs) 
    { 
     // Important: Do not set DialogResult for a closed window 
     // GC clears windows anyways and with MakeWeak it 
     // closes out with IDialogResultVMHelper 
     if(_isClosed) return; 

     this.DialogResult = eventargs.DialogResult; 
    } 
} 

Теперь по крайней мере, я должен создать в моем файле ресурсов на DataTemplate (app.xaml или что-то):

<DataTemplate DataType="{x:Type DialogViewModel:EditOrNewAuswahlItemVM}" > 
     <DialogView:EditOrNewAuswahlItem/> 
</DataTemplate> 

Ну вот и все, теперь я могу назвать диалоги из моих ViewModels:

var result = this.uiDialogService.ShowDialog("Dialogwindow Title", dialogwindowVM); 

Теперь, мой вопрос, вы видите какие-либо проблемы с этим решением?

Редактировать: для полноты. ViewModel должны осуществлять IDialogResultVMHelper, а затем он может поднять его в OkCommand или что-то вроде этого:

public class MyViewmodel : IDialogResultVMHelper 
{ 
    private readonly Lazy<DelegateCommand> _okCommand; 

    public MyViewmodel() 
    { 
     this._okCommand = new Lazy<DelegateCommand>(() => 
      new DelegateCommand(() => 
       InvokeRequestCloseDialog(
        new RequestCloseDialogEventArgs(true)),() => 
         YourConditionsGoesHere = true)); 
    } 

    public ICommand OkCommand 
    { 
     get { return this._okCommand.Value; } 
    } 

    public event EventHandler<RequestCloseDialogEventArgs> RequestCloseDialog; 
    private void InvokeRequestCloseDialog(RequestCloseDialogEventArgs e) 
    { 
     var handler = RequestCloseDialog; 
     if (handler != null) 
      handler(this, e); 
    } 
} 

EDIT 2: я использовал код из здесь, чтобы сделать мой EventHandler зарегистрировать слабый:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(Веб-сайт не больше существует, WebArchive Mirror)

public delegate void UnregisterCallback<TE>(EventHandler<TE> eventHandler) 
    where TE : EventArgs; 

public interface IWeakEventHandler<TE> 
    where TE : EventArgs 
{ 
    EventHandler<TE> Handler { get; } 
} 

public class WeakEventHandler<T, TE> : IWeakEventHandler<TE> 
    where T : class 
    where TE : EventArgs 
{ 
    private delegate void OpenEventHandler(T @this, object sender, TE e); 

    private readonly WeakReference mTargetRef; 
    private readonly OpenEventHandler mOpenHandler; 
    private readonly EventHandler<TE> mHandler; 
    private UnregisterCallback<TE> mUnregister; 

    public WeakEventHandler(EventHandler<TE> eventHandler, 
           UnregisterCallback<TE> unregister) 
    { 
     mTargetRef = new WeakReference(eventHandler.Target); 

     mOpenHandler = (OpenEventHandler)Delegate.CreateDelegate(
          typeof(OpenEventHandler),null, eventHandler.Method); 

     mHandler = Invoke; 
     mUnregister = unregister; 
    } 

    public void Invoke(object sender, TE e) 
    { 
     T target = (T)mTargetRef.Target; 

     if (target != null) 
      mOpenHandler.Invoke(target, sender, e); 
     else if (mUnregister != null) 
     { 
      mUnregister(mHandler); 
      mUnregister = null; 
     } 
    } 

    public EventHandler<TE> Handler 
    { 
     get { return mHandler; } 
    } 

    public static implicit operator EventHandler<TE>(WeakEventHandler<T, TE> weh) 
    { 
     return weh.mHandler; 
    } 
} 

public static class EventHandlerUtils 
{ 
    public static EventHandler<TE> MakeWeak<TE>(this EventHandler<TE> eventHandler, 
                UnregisterCallback<TE> unregister) 
     where TE : EventArgs 
    { 
     if (eventHandler == null) 
      throw new ArgumentNullException("eventHandler"); 

     if (eventHandler.Method.IsStatic || eventHandler.Target == null) 
      throw new ArgumentException("Only instance methods are supported.", 
              "eventHandler"); 

     var wehType = typeof(WeakEventHandler<,>).MakeGenericType(
          eventHandler.Method.DeclaringType, typeof(TE)); 

     var wehConstructor = wehType.GetConstructor(new Type[] 
          { 
           typeof(EventHandler<TE>), typeof(UnregisterCallback<TE>) 
          }); 

     IWeakEventHandler<TE> weh = (IWeakEventHandler<TE>)wehConstructor.Invoke(
             new object[] { eventHandler, unregister }); 

     return weh.Handler; 
    } 
} 
+1

Возможно, вам не хватает xmlns: x = "http://schemas.microsoft.com/winfx/2006/xaml" refernece в вашем WindowDialog XAML. –

+0

Фактически пространство имен xmlns: x = "[http: //] schemas.microsoft.com/winfx/2006/xaml" без скобок – reggaeguitar

+0

см. Http://stackoverflow.com/questions/16993433/mvvm-light-wpf -binding-multiple-instance-of-a-window-to-a-viewmodel/16994523 # 16994523 – reggaeguitar

ответ

43

Это хороший подход, и я себе подобных в прошлом. Действуй!

Одна из незначительных вещей, которые я определенно сделал бы, - это сделать событие получения логического значения, когда вам нужно установить «false» в DialogResult.

event EventHandler<RequestCloseEventArgs> RequestCloseDialog; 

и класс EventArgs:

public class RequestCloseEventArgs : EventArgs 
{ 
    public RequestCloseEventArgs(bool dialogResult) 
    { 
     this.DialogResult = dialogResult; 
    } 

    public bool DialogResult { get; private set; } 
} 
+0

thx, я изменю свое событие :) – blindmeis

+7

Думаю, вместо 'bool' должен существовать собственный EventArgs, полученный из базового класса EventArgs, который содержит свойство' bool'. Делегат 'EventHandler' имеет ограничение класса на общий параметр, который требует, чтобы тип был получен из' EventArgs'. С 'bool' как общим параметром это не скомпилируется (по крайней мере, не в VS2010, я не знаю, изменилось ли это с более ранних версий). – Slauma

+0

Вы правы, я исправил образец кода. Спасибо –

15

Я использую почти идентичный подход в течение нескольких месяцев, и я очень счастлива с ним (то есть я еще не чувствовал настоятельно рекомендуем переписать его полностью ...)

В моей реализации я использую IDialogViewModel, который раскрывает такие вещи, как заголовок, кнопки standad для отображения (чтобы иметь согласованное видимость во всех диалоговых окнах), событие RequestClose и еще несколько вещей, способный управлять размером и поведением окна

+0

thx, название должно действительно зайти в мой IDialogViewModel. другие свойства, такие как размер, стандартная кнопка, я уйду, потому что все это происходит, по крайней мере, из набора данных. – blindmeis

+1

Это то, что я и сделал сначала, просто используйте SizeToContent для управления размером окна. Но в одном случае мне нужно было сделать окно изменчивым, поэтому мне пришлось немного его подстроить ... –

+0

mhh thx для этой информации :) – blindmeis

2

Если вы говорите о диалоговых окнах, а не только о всплывающих сообщениях, рассмотрите мой подход ниже. Ключевые моменты:

  1. я передать ссылку на Module Controller в конструктор каждого ViewModel (вы можете использовать инъекции).
  2. Это Module Controller имеет общедоступные/внутренние методы для создания диалоговых окон (просто создавая, не возвращая результата). Поэтому, чтобы открыть диалоговое окно в ViewModel я пишу: controller.OpenDialogEntity(bla, bla...) окно
  3. Каждый диалог уведомляет о его результате (как OK, Сохранить, Отмена и т.д.) через Weak Events. Если вы используете PRISM, то легче публиковать уведомления, используя this EventAggregator.
  4. Для обработки результатов диалога я использую подписку на уведомления (опять Weak Events и EventAggregator в случае PRISM). Чтобы уменьшить зависимость от таких уведомлений, используйте независимые классы со стандартными уведомлениями.

Плюсы:

  • Меньше кода. Я не против использования интерфейсов, но я видел слишком много проектов, где избыточность использования интерфейсов и уровней абстракции вызывает больше проблем, чем помощь.
  • Открытые диалоговые окна через Module Controller - это простой способ избежать сильных ссылок и по-прежнему позволяет использовать макеты для тестирования.
  • Уведомление через слабые события уменьшает количество потенциальных утечек памяти.

Минусы:

  • Не легко отличить требуемое уведомление от других в обработчике. Два решения:
    • отправить уникальный маркер на открытие окна диалога и проверить, что маркер в подписке
    • использовать общие классы уведомлений <T> где T является перечислением лиц (или для простоты она может быть типом ViewModel).
  • Для проекта должно быть соглашение об использовании классов уведомлений для предотвращения их дублирования.
  • Для чрезвычайно больших проектов Module Controller может быть перегружен методами создания окон. В этом случае лучше разделить его на несколько модулей.

P.S. Я довольно давно использую этот подход и готов отстаивать свое право на комментарии и приводить некоторые примеры, если это необходимо.