2010-09-27 9 views
127

Ostatnio miałem problem z tworzeniem okien dialogowych dodawania i edytowania mojej aplikacji wpf.Dobra lub zła praktyka dla okien dialogowych w WPF z MVVM?

Wszystko, co chcę zrobić w moim kodzie, to coś takiego. (Najczęściej ViewModel pierwsze podejście MVVM)

ViewModel który wywołuje okno dialogowe:

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

Jak to działa?

pierwsze, stworzyliśmy usługę dialogowe:

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 jest specjalnego, ale proste okno. Muszę go trzymać moje treści:

<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> 

Problem z okien dialogowych w WPF jest dialogresult = true można osiągnąć tylko w kodzie. Dlatego stworzyłem interfejs dla mojego dialogviewmodel, aby go zaimplementować.

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

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

Ilekroć mój ViewModel uważa, że ​​nadszedł czas na dialogresult = true, następnie podnieść tego zdarzenia.

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; 
    } 
} 

Teraz przynajmniej mam stworzyć DataTemplate w moim pliku zasobów (app.xaml lub coś):

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

Dobrze to wszystko, mogę teraz nazwać dialogi z moich ViewModels:

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

Teraz moje pytanie, czy widzisz jakieś problemy z tym rozwiązaniem?

Edycja: dla kompletności. ViewModel powinny wdrożyć IDialogResultVMHelper i to może podnieść go w OkCommand lub coś takiego:

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: Kiedyś kod z tutaj, aby mój Podprogram zarejestrować słaby:
http://diditwith.net/2007/03/23/SolvingTheProblemWithEventsWeakEventHandlers.aspx
(Strona internetowa nie już nie istnieje, 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

Prawdopodobnie brakuje w naszym WindowDialog XAML xmlns: x = "http://schemas.microsoft.com/winfx/2006/xaml" refernece. –

+0

W rzeczywistości przestrzenią nazw jest xmlns: x = "[http: //] schemas.microsoft.com/winfx/2006/xaml" bez nawiasów – reggaeguitar

+0

patrz http://stackoverflow.com/questions/16993433/mvvm-light-wpf -binding-multiple-instances-of-a-window-to-a-viewmodel/16994523 # 16994523 – reggaeguitar

Odpowiedz

43

jest to dobre podejście i ja równiez w przeszłości. Idź po to!

Jedną z drobnych rzeczy, które zdecydowanie zrobiłbym, jest sprawienie, że wydarzenie otrzyma wartość logiczną, gdy trzeba ustawić "false" w oknie dialogowym DialogResult.

event EventHandler<RequestCloseEventArgs> RequestCloseDialog; 

i klasy EventArgs:

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

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

thx, zmienię moje wydarzenie :) – blindmeis

+7

Myślę, że zamiast 'bool' musi istnieć niestandardowa EventArgs wyprowadzona z podstawowej klasy' EventArgs', która zawiera własność 'bool'. Delegat 'EventHandler' ma ograniczenie klasy dla parametru ogólnego, który wymaga, aby typ pochodził od' EventArgs'. Z 'bool' jako parametrem ogólnym to się nie kompiluje (przynajmniej nie w VS2010, nie wiem, czy to prawdopodobnie zmieniło się z wcześniejszych wersji). – Slauma

+0

Masz rację, poprawiłem przykładowy kod. Dzięki –

15

Używam niemal identyczny podejście do kilku miesięcy i jestem bardzo z niego zadowolony (czyli jeszcze nie czuł chęć przepisania go całkowicie ...)

W mojej implementacji używam numeru , który eksponuje takie rzeczy, jak tytuł, przyciski trybu gotowości do pokazania (w celu uzyskania spójnego wyglądu we wszystkich oknach dialogowych), zdarzenie RequestClose i kilka innych rzeczy, które należy w stanie kontrolować rozmiar i zachowanie okna

+0

thx, tytuł powinien naprawdę iść w moim IDialogViewModel. inne właściwości, takie jak rozmiar, standardowy przycisk opuszczę, ponieważ to wszystko pochodzi z co najmniej płyty. – blindmeis

+1

To też zrobiłem na początku, po prostu użyj SizeToContent, aby kontrolować rozmiar okna. Ale w jednym przypadku musiałem zmienić rozmiar okna, więc musiałem go trochę poprawić ... –

+0

mhh thx za te informacje :) – blindmeis

2

Jeśli mówisz o oknach dialogowych, a nie tylko o wyskakujących okienkach wiadomości, rozważ moje podejście poniżej. Kluczowe punkty to:

  1. mijam odniesienie do Module Controller do konstruktora każdego ViewModel (można użyć zastrzyk).
  2. To Module Controller ma publiczne/wewnętrzne metody tworzenia okien dialogowych (tylko tworzenie, bez zwracania wyniku). Stąd, aby otworzyć okno dialogowe w to ViewModel piszę: controller.OpenDialogEntity(bla, bla...) okno
  3. Każdy dialog informuje o jej wyniku (jak OK, Save, Anuluj, etc.) poprzez Weak Events. Jeśli używasz PRISM, łatwiej jest publikować powiadomienia za pomocą this EventAggregator.
  4. Do obsługi wyników dialogów używam subskrypcji powiadomień (ponownie Weak Events i EventAggregator w przypadku PRISM). Aby zmniejszyć zależność od takich powiadomień, użyj niezależnych klas ze standardowymi powiadomieniami.

Plusy:

  • mniej kodu. Nie mam nic przeciwko używaniu interfejsów, ale widziałem zbyt wiele projektów, w których nadmierne korzystanie z interfejsów i warstw abstrakcji sprawia więcej problemów niż pomocy.
  • Otwarte okna dialogowe przez Module Controller to prosty sposób na uniknięcie silnych odniesień i wciąż pozwala na wykorzystanie makiet do testowania.
  • Powiadomienie przez słabe zdarzenia zmniejsza liczbę potencjalnych wycieków pamięci.

Wady:

  • Nie łatwo odróżnić od innych wymaganych powiadomienie w module obsługi. Dwa rozwiązania:
    • wysłać unikalny token po otwarciu okna dialogowego i jest sprawdzić, czy znak w subskrypcji
    • użyć klas generycznych powiadomień <T> gdzie T jest wyliczenie podmiotów (lub dla uproszczenia może to być rodzaj ViewModel).
  • Dla projektu należy zawrzeć umowę o korzystaniu z klas powiadomień, aby zapobiec ich duplikowaniu.
  • W przypadku ogromnie dużych projektów, Module Controller może zostać przytłoczony metodami tworzenia okien. W takim przypadku lepiej podzielić go na kilka modułów.

P.S. Używam tego podejścia już od dłuższego czasu i jestem gotów bronić jego uprawnień w komentarzach i podać kilka przykładów, jeśli jest to wymagane.

Powiązane problemy