Próbuję nauczyć się WPF i problemu MVVM, ale wpadłem w kłopoty. To pytanie jest podobne, ale nie takie samo jak to (obsługa-dialogów-w-wpf-z-mvvm) ...
Mam formularz „Login” napisany przy użyciu wzorca MVVM.
Ten formularz ma ViewModel, który przechowuje nazwę użytkownika i hasło, które są powiązane z widokiem w XAML przy użyciu normalnych powiązań danych. Posiada również polecenie „Login”, które jest powiązane z przyciskiem „Zaloguj się” w formularzu, a także przy użyciu normalnego wiązania danych.
Po uruchomieniu polecenia „Login” wywołuje funkcję w ViewModel, która wyłącza się i wysyła dane przez sieć, aby się zalogować. Po zakończeniu tej funkcji dostępne są 2 działania:
Login był nieprawidłowy - po prostu wyświetlamy MessageBox i wszystko jest w porządku
Login był prawidłowy, musimy zamknąć formularz logowania i zwrócić go, aby był prawdziwy, ponieważ
DialogResult
...
Problem polega na tym, że ViewModel nie wie nic o rzeczywistym widoku, więc w jaki sposób można go zamknąć i poprosić o zwrócenie określonego DialogResult? Mógłbym przykleić trochę kodu do CodeBehind i / lub przekazać View do ViewModel, ale wygląda na to, że całkowicie pokonałby cały punkt MVVM ...
Aktualizacja
W końcu właśnie naruszyłem „czystość” wzoru MVVM i kazałem Viewowi opublikować Closed
zdarzenie i ujawnić Close
metodę. ViewModel po prostu zadzwoniłby view.Close
. Widok jest znany tylko przez interfejs i podłączony do kontenera IOC, więc nie można utracić możliwości testowania ani konserwacji.
Wydaje się głupie, że przyjęta odpowiedź to -5 głosów! Chociaż jestem w pełni świadomy dobrych uczuć, które można uzyskać, rozwiązując problem, będąc „czystym”, z pewnością nie jestem jedynym, który uważa, że 200 linii zdarzeń, poleceń i zachowań, aby uniknąć metody jednowierszowej w nazwy „wzorów” i „czystości” są nieco śmieszne ...
Close
metoda jest nadal najlepszym rozwiązaniem. Cała reszta w innych, bardziej skomplikowanych oknach dialogowych to MVVM i baza danych, ale wydawało się głupotą wdrażanie tutaj wielkich „rozwiązań” zamiast prostej metody…Odpowiedzi:
Zainspirowała mnie odpowiedź Thejuana, by napisać prostszą załączoną własność. Bez stylów, bez wyzwalaczy; zamiast tego możesz po prostu to zrobić:
Jest to prawie tak czyste, jakby zespół WPF dobrze to zrobił i uczynił DialogResult przede wszystkim właściwością zależności. Po prostu umieść
bool? DialogResult
właściwość na swoim ViewModel i zaimplementuj INotifyPropertyChanged, i voila, twój ViewModel może zamknąć okno (i ustawić jego DialogResult) po prostu przez ustawienie właściwości. MVVM tak jak powinno być.Oto kod DialogCloser:
Opublikowałem to również na moim blogu .
źródło
Z mojego punktu widzenia pytanie jest całkiem dobre, ponieważ to samo podejście byłoby stosowane nie tylko w przypadku okna „Logowanie”, ale w przypadku każdego rodzaju okna. Przejrzałem wiele sugestii i żadne z nich nie jest dla mnie OK. Zapoznaj się z moją sugestią zaczerpniętą z artykułu o wzorze projektu MVVM .
Każda klasa ViewModel powinna dziedziczyć
WorkspaceViewModel
poRequestClose
zdarzeniu iCloseCommand
właściwościICommand
typu. Domyślna implementacjaCloseCommand
właściwości podniesieRequestClose
zdarzenie.Aby zamknąć
OnLoaded
okno, należy zastąpić metodę okna:lub
OnStartup
metoda Twojej aplikacji:Wydaje mi się, że implementacja
RequestClose
zdarzeń iCloseCommand
właściwości wWorkspaceViewModel
są dość jasne, ale pokażę, że są spójne:I kod źródłowy
RelayCommand
:PS Nie traktuj mnie źle dla tych źródeł! Gdybym je miał wczoraj, zaoszczędziłoby mi to kilka godzin ...
PPS Wszelkie uwagi i sugestie są mile widziane.
źródło
customer.RequestClose
w kodzie za plikiem XAML, nie narusza wzorca MVVM? Równie dobrze możesz powiązaćClick
moduł obsługi zdarzeń za pomocą przycisku zamykania, ponieważ i tak dotknąłeś kodu i zrobiłeś tothis.Close()
! Dobrze?Użyłem dołączonych zachowań, aby zamknąć okno. Powiąż właściwość „signal” w ViewModel z dołączonym zachowaniem (faktycznie używam wyzwalacza) Gdy jest ustawiona na true, zachowanie zamyka okno.
http://adammills.wordpress.com/2009/07/01/window-close-from-xaml/
źródło
Istnieje wiele komentarzy na temat zalet i wad MVVM tutaj. Dla mnie zgadzam się z Nirem; to kwestia odpowiedniego użycia wzorca, a MVVM nie zawsze pasuje. Wydaje się, że ludzie byli gotowi poświęcić wszystkie najważniejsze zasady projektowania oprogramowania, aby dopasować go do MVVM.
To powiedziawszy ... myślę, że twoja sprawa może być dobrze dopasowana z odrobiną refaktoryzacji.
W większości przypadków, z którymi się spotkałem, WPF umożliwia obejście BEZ wielu
Window
s. Może mógłbyś spróbować użyćFrame
s iPage
s zamiast Windowsa zDialogResult
s.W twoim przypadku moja sugestia byłaby
LoginFormViewModel
obsługiwana,LoginCommand
a jeśli login jest nieprawidłowy, ustaw właściwość naLoginFormViewModel
odpowiednią wartość (false
lub pewną wartość wyliczeniową jakUserAuthenticationStates.FailedAuthentication
). Zrobiłbyś to samo dla udanego logowania (true
lub innej wartości wyliczeniowej). Następnie użyjesz takiego,DataTrigger
który reaguje na różne stany uwierzytelnienia użytkownika i możesz użyć prostejSetter
zmianySource
właściwościFrame
.Po zwróceniu okna logowania
DialogResult
myślę, że to miejsce, w którym się mylisz; żeDialogResult
jest naprawdę własnością Twojej ViewModel. W moim, co prawda ograniczonym doświadczeniu z WPF, gdy coś nie jest w porządku, zwykle dlatego, że myślę o tym, jak zrobiłbym to samo w WinForm.Mam nadzieję, że to pomaga.
źródło
Zakładając, że okno dialogowe logowania jest pierwszym oknem, które zostanie utworzone, wypróbuj to w klasie LoginViewModel:
źródło
Jest to proste i czyste rozwiązanie - dodajesz zdarzenie do ViewModel i instruujesz okno, aby zamknęło się po uruchomieniu zdarzenia.
Aby uzyskać więcej informacji, zobacz mój post na blogu, Zamknij okno z ViewModel .
XAML:
ViewModel:
Uwaga: w przykładzie użyto Pryzmatu
DelegateCommand
(patrz Pryzmat: Dowodzenie ), ale w tym przypadkuICommand
można zastosować dowolną implementację.Możesz używać zachowań z tego oficjalnego pakietu.
źródło
Sposób, w jaki sobie z tym poradzę, to dodanie modułu obsługi zdarzeń w moim ViewModel. Po pomyślnym zalogowaniu użytkownika uruchomiłbym zdarzenie. Moim zdaniem dołączam się do tego wydarzenia, a kiedy się uruchomi, zamknę okno.
źródło
Oto, co początkowo zrobiłem, co działa, ale wydaje się dość rozwlekłe i brzydkie (globalne statyczne cokolwiek nigdy nie jest dobre)
1: App.xaml.cs
2: LoginForm.xaml
3: LoginForm.xaml.cs
4: LoginFormViewModel.cs
Później usunąłem cały ten kod i właśnie
LoginFormViewModel
wywołałem metodę Close w jego widoku. Okazało się, że jest o wiele ładniejszy i łatwiejszy do naśladowania. IMHO punkt wzorów jest dać ludziom łatwiejszy sposób, aby zrozumieć, co aplikacja robi, i w tym przypadku, MVVM robił to znacznie trudniejsze do zrozumienia niż gdybym nie go stosować, i był obecnie anty -pattern.źródło
Do twojej wiadomości wpadłem na ten sam problem i myślę, że wymyśliłem rozwiązanie, które nie wymaga globalizacji ani statyki, chociaż może nie być najlepszą odpowiedzią. Pozwalam wam to sami zdecydować.
W moim przypadku ViewModel, który tworzy okno, które ma zostać wyświetlone (pozwala nazwać to ViewModelMain), również wie o LoginFormViewModel (wykorzystując powyższą sytuację jako przykład).
Więc stworzyłem właściwość na LoginFormViewModel, która była typu ICommand (nazwijmy ją CloseWindowCommand). Następnie, zanim wywołam funkcję .ShowDialog () w oknie, ustawiam właściwość CloseWindowCommand w LoginFormViewModel na metodę window.Close () instancji okna, którą utworzyłem. Następnie wewnątrz LoginFormViewModel wszystko, co muszę zrobić, to wywołać CloseWindowCommand.Execute (), aby zamknąć okno.
Przypuszczam, że jest to trochę obejście / hack, ale działa dobrze, nie naruszając wzorca MVVM.
Zapraszam do krytykowania tego procesu tak bardzo, jak chcesz, mogę to wziąć! :)
źródło
Jest to prawdopodobnie bardzo późno, ale natknąłem się na ten sam problem i znalazłem rozwiązanie, które działa dla mnie.
Nie mogę wymyślić, jak stworzyć aplikację bez okien dialogowych (może to tylko blokada myśli). Byłem w impasie z MVVM i pokazałem dialog. Więc natknąłem się na ten artykuł CodeProject:
http://www.codeproject.com/KB/WPF/XAMLDialog.aspx
Który jest UserControl, który zasadniczo pozwala, aby okno znajdowało się w drzewie wizualnym innego okna (niedozwolone w Xaml). Ujawnia również wartość logiczną DependencyProperty o nazwie IsShowing.
Możesz ustawić styl, taki jak zwykle w słowniku zasobów, który zasadniczo wyświetla okno dialogowe za każdym razem, gdy właściwość Content formantu! = Null za pośrednictwem wyzwalaczy:
W widoku, w którym chcesz wyświetlić okno dialogowe, po prostu to:
A w ViewModel wszystko, co musisz zrobić, to ustawić właściwość na wartość (Uwaga: klasa ViewModel musi obsługiwać INotifyPropertyChanged, aby widok wiedział, że coś się stało).
tak:
Aby dopasować ViewModel do widoku, powinieneś mieć coś takiego w słowniku zasobów:
Dzięki temu otrzymujesz jednoliniowy kod do wyświetlenia okna dialogowego. Problem pojawia się w tym, że nie można tak naprawdę zamknąć okna dialogowego za pomocą powyższego kodu. Dlatego właśnie musisz umieścić zdarzenie w klasie bazowej ViewModel, którą DisplayViewModel dziedziczy i zamiast powyższego kodu, napisz to
Następnie możesz obsłużyć wynik okna dialogowego poprzez oddzwonienie.
Może się to wydawać trochę skomplikowane, ale po ułożeniu podstaw jest to całkiem proste. Znowu to moja implementacja, jestem pewien, że są jeszcze inne :)
Mam nadzieję, że to pomaga, uratowało mnie.
źródło
Ok, więc to pytanie ma już prawie 6 lat i nadal nie mogę znaleźć tutaj tego, co uważam za właściwą odpowiedź, więc pozwólcie mi podzielić się moimi „2 centami” ...
Właściwie mam 2 sposoby na zrobienie tego, pierwszy jest prosty ... drugi na prawym, więc jeśli szukasz właściwego, po prostu pomiń # 1 i przejdź do # 2 :
1. Szybki i łatwy (ale niekompletny)
Jeśli mam tylko mały projekt, czasami po prostu tworzę CloseWindowAction w ViewModel:
I ktokolwiek tworzy skrzynkę Widoku lub w kodzie Widoku za, właśnie ustawiam Metodę, którą Wywoła Działanie:
(pamiętaj, że MVVM dotyczy separacji View i ViewModel ... kod View zachowuje się nadal w View i tak długo, jak istnieje właściwa separacja, nie naruszasz wzorca)
Jeśli jakiś ViewModel utworzy nowe okno:
Lub jeśli chcesz go w głównym oknie, po prostu umieść go pod konstruktorem widoku:
kiedy chcesz zamknąć okno, po prostu wywołaj akcję na swoim ViewModel.
2. Właściwa droga
Teraz właściwym sposobem na to jest użycie Pryzmatu (IMHO), a wszystko na ten temat można znaleźć tutaj .
Możesz złożyć wniosek o interakcję , zapełnić go dowolnymi danymi, które będą potrzebne w nowym oknie, zjeść je, zamknąć, a nawet otrzymać dane z powrotem . Wszystko to jest zamknięte i zatwierdzone przez MVVM. Otrzymujesz nawet status zamknięcia okna , na przykład jeśli użytkownik
Canceled
lubAccepted
(przycisk OK) okno i dane z powrotem, jeśli są potrzebne . Jest to nieco bardziej skomplikowane i Odpowiedź nr 1, ale o wiele bardziej kompletna i zalecana przez Microsoft.Link, który podałem, zawiera wszystkie fragmenty kodu i przykłady, więc nie zawracam sobie głowy umieszczeniem tutaj kodu, po prostu przeczytaj artykuł o pobieraniu Prism Quick Start i uruchom go, naprawdę bardzo łatwo jest nie docenić sprawiają, że działa, ale korzyści są większe niż tylko zamknięcie okna.
źródło
+=
dodać delegata i wywołać akcję, spowoduje to zwolnienie wszystkich z nich .... Albo będzie muszę stworzyć specjalną logikę na maszynie wirtualnej, aby wiedziała, które okno zamknąć (być może mieć kolekcję akcji zamykających) .... Ale myślę, że posiadanie wielu widoków powiązanych z jedną maszyną wirtualną nie jest najlepszą praktyką, jest to lepiej zarządzać posiadaniem jednego wystąpienia i jednego wystąpienia maszyny wirtualnej, które są ze sobą powiązane, i być może nadrzędna maszyna wirtualna zarządzająca wszystkimi podrzędnymi maszynami wirtualnymi powiązanymi ze wszystkimi widokami.źródło
Możliwe, że ViewModel ujawnia zdarzenie, w którym rejestruje się widok. Następnie, gdy ViewModel decyduje o czasie zamknięcia widoku, uruchamia to zdarzenie, które powoduje zamknięcie widoku. Jeśli chcesz, aby określona wartość wyniku była zwracana, to miałbyś do tego właściwość w ViewModel.
źródło
Aby dodać do ogromnej liczby odpowiedzi, chcę dodać następujące. Zakładając, że masz ICommand na ViewModel i chcesz, aby to polecenie zamknęło jego okno (lub dowolne inne działanie w tym zakresie), możesz użyć czegoś takiego jak poniżej.
Nie jest idealny i może być trudny do przetestowania (ponieważ trudno jest wyśmiewać / wycierać ładunek elektrostatyczny), ale jest czystszy (IMHO) niż inne rozwiązania.
Erick
źródło
Wdrożyłem rozwiązanie Joe White'a, ale wystąpiły problemy z okazjonalnymi błędami „ DialogResult można ustawić tylko po utworzeniu okna i wyświetleniu go jako okna dialogowego ”.
Trzymałem ViewModel wokół po tym, jak widok został zamknięty, a od czasu do czasu otworzyłem nowy widok przy użyciu tej samej maszyny wirtualnej. Wygląda na to, że zamknięcie nowego widoku przed zrzuceniem starego widoku spowodowało, że DialogResultChanged próbował ustawić właściwość DialogResult w zamkniętym oknie, wywołując w ten sposób błąd.
Moim rozwiązaniem była zmiana DialogResultChanged, aby sprawdzić właściwość IsLoaded okna :
Po wprowadzeniu tej zmiany wszelkie załączniki do zamkniętych okien dialogowych są ignorowane.
źródło
Skończyłem mieszanie odpowiedzi Joe White'a i kodu z odpowiedzi Adama Millsa , ponieważ musiałem pokazać kontrolę użytkownika w programowo utworzonym oknie. Tak więc DialogCloser nie musi znajdować się w oknie, może znajdować się w samej kontroli użytkownika
DialogCloser znajdzie okno kontrolki użytkownika, jeśli nie zostało ono dołączone do samego okna.
źródło
Zachowanie jest tutaj najwygodniejszym sposobem.
Z jednej strony można go przypisać do danego modelu widoku (który może zasygnalizować „zamknij formularz!”)
Z drugiej strony ma dostęp do samego formularza, dzięki czemu może subskrybować niezbędne zdarzenia specyficzne dla formularza, wyświetlać okno dialogowe potwierdzenia lub cokolwiek innego.
Za pierwszym razem pisanie niezbędnych zachowań jest nudne. Jednak od teraz możesz go ponownie używać w każdym formularzu, którego potrzebujesz, dzięki jednopowłokowemu fragmentowi kodu XAML. W razie potrzeby możesz wyodrębnić go jako osobny zespół, aby można go było włączyć do każdego następnego projektu.
źródło
Dlaczego nie przekazać okna jako parametru polecenia?
DO#:
XAML:
źródło
Window
typu, który nie jest „czystym” MVVM. Zobacz tę odpowiedź, w której maszyna wirtualna nie jest ograniczona doWindow
obiektu.Innym rozwiązaniem jest utworzenie właściwości z INotifyPropertyChanged w widoku modelu, takim jak DialogResult, a następnie w Code Behind, napisz to:
Najważniejszym fragmentem jest
_someViewModel_PropertyChanged
.DialogResultPropertyName
może być publicznym ciągiem constSomeViewModel
.Używam tego rodzaju sztuczki, aby wprowadzić pewne zmiany w kontrolkach widoku w przypadku, gdy jest to trudne w ViewModel. OnPropertyChanged w ViewModel możesz robić cokolwiek chcesz w View. ViewModel jest nadal „testowalny jednostkowo”, a niektóre małe wiersze kodu w kodzie za nim nie mają znaczenia.
źródło
Wybrałbym tę drogę:
źródło
Przeczytałem wszystkie odpowiedzi, ale muszę powiedzieć, że większość z nich nie jest wystarczająco dobra lub nawet gorsza.
Możesz sobie z tym poradzić z klasą DialogService, której zadaniem jest pokazanie okna dialogowego i zwrócenie jego wyniku. Mam przykładowy projekt demonstrujący jego wdrożenie i wykorzystanie.
Oto najważniejsze części:
Czy to nie jest po prostu prostsze? bardziej przejrzysty, bardziej czytelny i ostatni, ale nie mniej ważny, do debugowania niż EventAggregator lub inne podobne rozwiązania?
jak widać, w moich modelach widoków zastosowałem pierwsze podejście ViewModel opisane w moim poście tutaj: Najlepsze praktyki dotyczące wywoływania View from ViewModel w WPF
Oczywiście w prawdziwym świecie
DialogService.ShowDialog
trzeba mieć więcej opcji do skonfigurowania okna dialogowego, np. Przyciski i polecenia, które powinny wykonać. Można to zrobić na inny sposób, ale jest poza zakresem :)źródło
Chociaż nie odpowiada to na pytanie, jak to zrobić za pomocą viewmodel, pokazuje to, jak to zrobić, używając tylko XAML + SDK mieszanki.
Zdecydowałem się pobrać i używać dwóch plików z Blend SDK, z których oba można pobrać jako pakiet od Microsoft za pośrednictwem NuGet. Pliki to:
System.Windows.Interactivity.dll i Microsoft.Expression.Interactions.dll
Microsoft.Expression.Interactions.dll daje ładne możliwości, takie jak możliwość ustawiania właściwości lub wywoływania metody na viewmodel lub innym celu, a także zawiera inne widżety.
Niektóre XAML:
Zauważ, że jeśli wybierasz się na proste zachowanie OK / Cancel, możesz uciec w / używając właściwości IsDefault i IsCancel, o ile okno jest pokazane w / Window.ShowDialog ().
Osobiście miałem problemy z przyciskiem, dla którego właściwość IsDefault była ustawiona na true, ale była ukryta podczas ładowania strony. Wydawało się, że nie chce ładnie grać po tym, jak zostało pokazane, więc właśnie ustawiam właściwość Window.DialogResult, jak pokazano powyżej, i działa dla mnie.
źródło
Oto proste rozwiązanie wolne od błędów (z kodem źródłowym), Działa dla mnie.
Wyprowadź swój ViewModel z
INotifyPropertyChanged
Utwórz obserwowalną właściwość CloseDialog w ViewModel
}
Dołącz uchwyt w widoku, aby zmienić tę właściwość
Teraz jesteś prawie gotowy. W module obsługi zdarzenia make
DialogResult = true
źródło
Utwórz
Dependency Property
w swoimView
/ dowolnymUserControl
(lubWindow
chcesz zamknąć). Jak poniżej:I powiąż go z właściwości ViewModel :
Nieruchomość w
VeiwModel
:Teraz uruchom operację zamykania, zmieniając
CloseWindow
wartość w ViewModel. :)źródło
Tam, gdzie musisz zamknąć okno, po prostu umieść to w viewmodel:
ta-da
źródło
Wystarczy!
źródło