Najlepsze podejście do tworzenia nowego okna w WPF przy użyciu MVVM

W poście: Jak ViewModel powinien zamknąć formularz? Opublikowałem moją wizję, jak zamknąć okna za pomocą MVVM. A teraz mam pytanie: jak je otworzyć.

Mam główne okno (widok główny). Jeśli użytkownik kliknie na przycisk "Pokaż", powinno zostać wyświetlone okno "Demo" (okno modalne). Jaki jest preferowany sposób tworzenia i otwierania okien przy użyciu wzorca MVVM? Widzę dwa podejścia ogólne:

Pierwszy (chyba najprostszy). Event handler "ShowButton_Click" powinien być zaimplementowany w kodzie za głównym oknem w następujący sposób:

        private void ModifyButton_Click(object sender, RoutedEventArgs e)
        {
            ShowWindow wnd = new ShowWindow(anyKindOfData);
            bool? res = wnd.ShowDialog();
            if (res != null && res.Value)
            {
                //  ... store changes if neecssary
            }
        }
  1. jeśli "pokażemy" stan przycisku powinien być zmieniony (włączony/wyłączony) będziemy musieli dodać logikę, która będzie zarządzać stanem przycisku;
  2. Kod źródłowy jest bardzo podobny do" starych " WinForms i źródeł MFC - Nie wiem, czy to dobre czy złe, proszę o radę.
  3. Coś jeszcze mi umknęło?

Inne podejście:

W MainWindowViewModel zaimplementujemy właściwość "ShowCommand", która zwróci interfejs ICommand polecenia. Comman z kolei:

  • podniesie "ShowDialogEvent";
  • zarządza stanem przycisku.

To podejście będzie bardziej odpowiednie dla MVVM, ale będzie wymagało dodatkowego kodowania: Klasa ViewModel nie może "pokazać okna", więc MainWindowViewModel podniesie tylko "ShowDialogEvent", MainWindowView będziemy musieli dodać obsługę zdarzeń w jego metodzie MainWindow_Loaded, coś takiego:

((MainWindowViewModel)DataContext).ShowDialogEvent += ShowDialog;

(ShowDialog-podobny do metody 'ModifyButton_Click'.)

Więc moje pytania są: 1. Widzisz jakieś inne podejście? 2. Myślisz, że jeden z wymienionych jest dobry czy zły? (dlaczego?) Wszelkie inne myśli są mile widziane. Dzięki.
Author: Community, 2010-01-21

6 answers

Ostatnio też o tym myślałem. Oto mój pomysł, jeśli użyjesz Unity w swoim projekcie jako' kontener ' lub cokolwiek innego do iniekcji zależności. Domyślam się, że normalnie można by nadpisać App.OnStartup() i utworzyć swój model, zobaczyć model i zobaczyć tam, i dać każdemu odpowiednie odniesienia. Używając Unity, dajesz kontenerowi odniesienie do modelu, a następnie używasz kontenera do "rozwiązywania" widoku. Kontener Unity wstrzykuje twój model widoku, więc nigdy go bezpośrednio nie tworzysz. Gdy twój widok zostanie rozwiązany, wywołujesz Show() na nim.

W przykładzie wideo, które obejrzałem, kontener Unity został utworzony jako zmienna lokalna w OnStartup. Co zrobić, jeśli utworzyłeś ją jako publiczną statyczną właściwość readonly w klasie aplikacji? Następnie możesz użyć go w głównym modelu widoku, aby utworzyć nowe okna, automatycznie wstrzykiwając zasoby, których Nowy widok potrzebuje. Coś w stylu App.Container.Resolve<MyChildView>().ShowDialog();.

Przypuszczam, że mógłbyś jakoś wyśmiewać wynik tego wezwania do kontenera jedności w Twoim testy. Alternatywnie, być może mógłbyś napisać metody takie jak ShowMyChildView() w klasie aplikacji, która w zasadzie robi to, co opisałem powyżej. Może być łatwo wyśmiać wezwanie do App.ShowMyChildView(), ponieważ zwróci ono bool?, co?

Cóż, to może nie być wcale lepsze niż użycie new MyChildView(), ale to mały pomysł, który miałem. Pomyślałem, że się tym podzielę. =)
 16
Author: Benny Jobigan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-18 10:04:28

Niektóre frameworki MVVM (np. MVVM Light ) wykorzystują wzorzec mediatora . Aby otworzyć nowe okno (lub utworzyć dowolny Widok), jakiś kod specyficzny dla widoku zasubskrybuje wiadomości od mediatora, a model ViewModel wyśle te wiadomości.

Tak:

Subopis

Messenger.Default.Register<DialogMessage>(this, ProcessDialogMessage);
...
private void ProcessDialogMessage(DialogMessage message)
{
     // Instantiate new view depending on the message details
}

In ViewModel

Messenger.Default.Send(new DialogMessage(...));

Wolę robić subskrypcję w klasie singleton, która "żyje"tak długo, jak część interfejsu użytkownika aplikacji. Podsumowując: ViewModel przechodzi wiadomości takie jak "muszę utworzyć widok", A interfejs słucha tych wiadomości i działa na nich.

Nie ma jednak "idealnego" podejścia.
 17
Author: arconaut,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-02-27 22:34:08

Jestem trochę spóźniony, ale istniejące odpowiedzi są niewystarczające. Wyjaśnię dlaczego. Ogólnie:

    To jest w porządku, aby uzyskać dostęp do ViewModels z widoku,
  • błędem jest uzyskiwanie dostępu do widoków z ViewModels , ponieważ wprowadza kołową zależność i sprawia, że ViewModels jest trudny do przetestowania.

Benny Jobigan ' s anwer:

App.Container.Resolve<MyChildView>().ShowDialog();
To niczego nie rozwiązuje. Uzyskujesz dostęp do widoku z ViewModel w sposób tigtly coupled fashion. Jedyna różnica od {[5] } jest to, że przeszedłeś przez warstwę indrection. Nie widzę żadnej przewagi nad bezpośrednim wywołaniem ctor MyChildView.

Byłoby czystsze, gdybyś użył interfejsu do widoku:

App.Container.Resolve<IMyChildView>().ShowDialog();`

Teraz model widoku nie jest wyraźnie połączony z widokiem. Jednak uważam, że tworzenie interfejsu dla każdego widoku jest dość niepraktyczne.

Arconaut ' s anwer:

Messenger.Default.Send(new DialogMessage(...));
Tak jest lepiej. Wydaje się, że Messenger lub EventAggregator lub inny pub / sub wzorce są uniwersalnym rozwiązaniem dla everyhing w MVVM :) wadą jest to, że trudniej jest debugować lub nawigować do DialogMessageHandler. To zbyt pośrednie imho. Na przykład, jak odczytać wyjście z okna dialogowego? modyfikując DialogMessage?

Moje Rozwiązanie:

Możesz otworzyć okno z MainWindowViewModel w następujący sposób:

var childWindowViewModel = new MyChildWindowViewModel(); //you can set parameters here if necessary
var dialogResult = DialogService.ShowModal(childWindowViewModel);
if (dialogResult == true) {
   //you can read user input from childWindowViewModel
}

DialogService pobiera tylko model widoku okna dialogowego, więc twoje modele są całkowicie niezależne od widoków. W czasie wykonywania DialogService może znaleźć odpowiedni Widok (używając konwencji nazewnictwa dla przykład) i pokazuje go, lub może być łatwo wyśmiewany w testach jednostkowych.

W moim przypadku używam tego interfejsu:

interface IDialogService
{
   void Show(IDialogViewModel dialog);
   void Close(IDialogViewModel dialog); 
   bool? ShowModal(IDialogViewModel dialog);
   MessageBoxResult ShowMessageBox(string message, string caption = null, MessageBoxImage icon = MessageBoxImage.No...);
}

interface IDialogViewModel 
{
    string Caption {get;}
    IEnumerable<DialogButton> Buttons {get;}
}

Gdzie DialogButton określa DialogResult lub ICommand lub oba.

 3
Author: Liero,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-10 18:30:41

Spójrz na moje obecne rozwiązanie MVVM do wyświetlania dialogów modalnych w Silverlight. Rozwiązuje większość problemów, o których wspomniałeś, ale jest całkowicie wyodrębniony z konkretnych rzeczy platformy i może być ponownie użyty. Nie używałem też żadnego kodu - tylko wiązania z poleceniami Delegatecomands, które implementują ICommand. Okno dialogowe jest w zasadzie widokiem-osobną kontrolką, która ma swój własny ViewModel i jest wyświetlana z ViewModel głównego ekranu, ale wyzwalana z interfejsu użytkownika za pomocą powiązania DelagateCommand.

Zobacz pełne rozwiązanie Silverlight 4 tutaj modalne okna dialogowe z MVVM i Silverlight 4

 2
Author: Roboblob,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 21:24:05

Używam kontrolera, który obsługuje wszystkie informacje przechodzące między widokami. Wszystkie viewmodele używają metod w kontrolerze, aby zażądać więcej informacji, które mogą być zaimplementowane jako okna dialogowe, inne widoki itp.

Wygląda to mniej więcej tak:

class MainViewModel {
    public MainViewModel(IView view, IModel model, IController controller) {
       mModel = model;
       mController = controller;
       mView = view;
       view.DataContext = this;
    }

    public ICommand ShowCommand = new DelegateCommand(o=> {
                  mResult = controller.GetSomeData(mSomeData);
                                                      });
}

class Controller : IController {
    public void OpenMainView() {
        IView view = new MainView();
        new MainViewModel(view, somemodel, this);
    }

    public int GetSomeData(object anyKindOfData) {
      ShowWindow wnd = new ShowWindow(anyKindOfData);
      bool? res = wnd.ShowDialog();
      ...
    }
}
 1
Author: adrianm,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-21 16:55:02

Moje podejście jest podobne do adrianma. jednak w moim przypadku kontroler nigdy nie działa z konkretnymi typami widoków. Kontroler jest całkowicie odsprzęgnięty od widoku - w taki sam sposób, jak model ViewModel.

Jak to działa można zobaczyć na przykładzie ViewModelWPF Application Framework (WAF).

.

Pozdrawiam,

Jbe

 0
Author: jbe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-25 21:18:03