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
}
}
- 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;
- 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ę. 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.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?
new MyChildView()
, ale to mały pomysł, który miałem. Pomyślałem, że się tym podzielę. =)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.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.
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
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();
...
}
}
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
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