{"id":61,"date":"2011-11-30T17:10:00","date_gmt":"2011-11-30T17:10:00","guid":{"rendered":"http:\/\/tpodolak.com.hostingasp.pl\/blog\/2011\/11\/30\/mvvm-i-okna-modalne\/"},"modified":"2016-01-31T00:30:58","modified_gmt":"2016-01-31T00:30:58","slug":"mvvm-i-okna-modalne","status":"publish","type":"post","link":"https:\/\/tpodolak.com\/blog\/2011\/11\/30\/mvvm-i-okna-modalne\/","title":{"rendered":"MVVM i okna modalne"},"content":{"rendered":"<p>Cz\u0119sto podczas u\u017cytkowania program\u00f3w zachodzi konieczno\u015b\u0107 pokazania dodatkowego okna, s\u0142u\u017c\u0105cego do interakcji z u\u017cytkownikiem. Najcz\u0119\u015bciej okno takie nale\u017cy pokaza\u0107 w okre\u015blonym przypadku. Jak ju\u017c wcze\u015bniej zosta\u0142o wspomniane ca\u0142a logika powinna by\u0107 umieszczona w <span style=\"font-style: italic;\">ViewMoedlu<\/span>, zatem nasuwa si\u0119 pytanie, w jaki spos\u00f3b z <span style=\"font-style: italic;\">ViewModelu <\/span>pokaza\u0107 okno modalne &#8211; przecie\u017c <span style=\"font-style: italic;\">ViewModel <\/span>nie powinien mie\u0107 \u017cadnych informacji o widoku. Rozwi\u0105zanie tego problemu polega na oddelegowaniu pokazywania widok\u00f3w lub <span style=\"font-style: italic;\">MessageBox-\u00f3w<\/span> do wyspecjalizowanych klas tzw. serwis\u00f3w. Ponadto w przypadku gdy chcemy pokazywa\u0107 proste komunikaty mo\u017cemy r\u00f3wnie\u017c skorzysta\u0107 z <span style=\"font-style: italic;\">Interaction\/Interactivity<\/span> oraz klasy Prisma <span style=\"font-style: italic;\">InteractionRequest<T><\/span>.<\/p>\n<h3>1. <span> InteractionRequest<T><\/h3>\n<p>Jak ju\u017c wcze\u015bniej wspomniano klasa InteractionRequest<T> odpowiedzialna jest za pokazywanie komunikat\u00f3w u\u017cytkownikowi &#8211; koordynuje ona dzia\u0142anie pomi\u0119dzy <span style=\"font-style: italic;\">ViewModelem <\/span>a widokiem. W celu skorzystania z klasy <span style=\"font-style: italic;\">InteractionRequest <\/span>w ViewModelu tworzymy publiczn\u0105 w\u0142a\u015bciwo\u015b\u0107 udostp\u0119niaj\u0105c\u0105 obiekt typu <span style=\"font-style: italic;\">InteractionRequest <\/span>na zewn\u0105trz<\/p>\n<pre lang=\"csharp\">\r\npublic InteractionRequest<Confirmation> Request { get; set; }\r\nRequest = new InteractionRequest<Notification>();\r\n<\/pre>\n<p>Jak wida\u0107 za generyczny typ <span style=\"font-style: italic;\">T<\/span> zosta\u0142a podstawiona klasa <span style=\"font-style: italic;\">Notification<\/span>. Parametr <span style=\"font-style: italic;\">T<\/span> mo\u017ce przyjmowa\u0107 klasy dziedzicz\u0105ce po klasie <span style=\"font-style: italic;\">Notification<\/span>. Domy\u015blnie w Prismie s\u0105 dwie takie klasy:<\/p>\n<ul>\n<li><span style=\"font-style: italic;\">Notification <\/span>&#8211; wspomniana ju\u017c wcze\u015bniej, s\u0142u\u017cy do powiadomienia u\u017cytkownika o jakim\u015b zdarzeniu<\/li>\n<li><span style=\"font-style: italic;\">Confirmation <\/span>&#8211; r\u00f3wnie\u017c s\u0142u\u017cy powiadominiu u\u017cytkownika o jaki\u015b zdarzeniu, ale zwraca ona reakcj\u0119 u\u017cytkownika (czy potwierdzi\u0142 dan\u0105 akcj\u0119 czy nie &#8211; co\u015b jak MessageBox YesNo)<\/li>\n<\/ul>\n<p>W celu wywo\u0142ania okienka nale\u017cy odpali\u0107 funkcj\u0119 <span style=\"font-style: italic;\">Raise <\/span> na przyk\u0142ad w taki spos\u00f3b:<\/p>\n<pre lang=\"csharp\">\r\nRequest.Raise(new Notification { Content = \"Info dla u\u017cytkownika\", Title = \"sth\" }, confirmation => AfterMessageAction());\r\n<\/pre>\n<p>Funkcja <span style=\"font-style: italic;\">Raise <\/span>przyjmuje jeden lub dwa parametry. Przedstawy bardziej zaawansowan\u0105 wersj\u0119 tej funkcji &#8211; t\u0105 z dwoma parametrami. W pierwszym parametrze podajemy obiekt typu <span style=\"font-style: italic;\">Notification<\/span>, w kt\u00f3rym przekazujemy informacje do przekazania u\u017cytkownikowi (<span style=\"font-style: italic;\">Content<\/span>) oraz tytu\u0142 okna (<span style=\"font-style: italic;\">Title<\/span>). Drugim parametrem jest delegat Action<Notification> , kt\u00f3ry okre\u015bla co mamy zrobi\u0107 po zamkni\u0119ciu okna przez u\u017cytkownika.<br \/>\nOstatni\u0105 rzecz\u0105 jak\u0105 musimy zrobi\u0107, \u017ceby wykorzysta\u0107 <span style=\"font-style: italic;\">NotificationRequest <\/span>jest zdefiniowanie triggera w widoku, kt\u00f3ry b\u0119dzie reagowa\u0142 na odpalenie funkcji <span style=\"font-style: italic;\">Raise<\/span>. Robimy to w nast\u0119puj\u0105cy spos\u00f3b:<\/p>\n<ul>\n<li>definiujemy w XAML-u alias do namespaca interactivity\n<pre lang=\"xml\">\r\nxmlns:i=\"http:\/\/schemas.microsoft.com\/expression\/2010\/interactivity\"\r\n<\/pre>\n<\/li>\n<li>tworzymy <span style=\"font-style: italic;\">InteractionRequestTrigger<\/span>\n<pre lang=\"xml\">\r\n<i:Interaction.Triggers>\r\n      <prism:InteractionRequestTrigger SourceObject=\"{Binding Request}\">\r\n          <prism:PopupChildWindowAction><\/prism:PopupChildWindowAction>\r\n      <\/prism:InteractionRequestTrigger>\r\n<\/i:Interaction.Triggers>\r\n<\/pre>\n<\/li>\n<\/ul>\n<h3>2. <span>MessageBoxService<\/h3>\n<p>Alternatyw\u0105 dla InteractionRequest<T> jest stworzenie w\u0142asnej klasy, do kt\u00f3rej zosta\u0142yby oddelegowane wszystkie pro\u015bby o pokazania <span style=\"font-style: italic;\">MessageBoxa<\/span>. Przyk\u0142adowa implementacja takiego serwisu mo\u017ce wygl\u0105da\u0107 w nast\u0119puj\u0105cy spos\u00f3b. Po pierwsze stw\u00f3rzmy interfejs z funkcjami potrzebnymi do pokazania <span style=\"font-style: italic;\">MessageBoxa<\/span><\/p>\n<pre lang=\"csharp\">\r\npublic interface IMessageBoxService\r\n{      \r\n    MessageBoxResult ShowMessageBox (string info, string caption,MessageBoxButton buttons);      \r\n}\r\n<\/pre>\n<p>Nast\u0119pnie zaimplementujmy ten interfejs w klasie <span style=\"font-style: italic;\">MessageBoxService <\/span>&#8211; w\u0142a\u015bnie do tej klasy b\u0119d\u0105 oddelegowywane wszystkie pro\u015bby o pokazanie <span style=\"font-style: italic;\">MessageBoxa <\/span><\/p>\n<pre lang=\"csharp\">\r\npublic class MessageBoxService : IMessageBoxService\r\n{\r\n    public MessageBoxResult ShowMessageBox(string info, string caption, MessageBoxButton buttons)\r\n    {\r\n        return MessageBox.Show(info, caption, buttons);\r\n    }\r\n}\r\n<\/pre>\n<p>Taki serwis musimy nast\u0119pnie przekaza\u0107 do ka\u017cdego <span style=\"font-style: italic;\">ViewModelu <\/span>, w kt\u00f3rym b\u0119dzie potrzeba pokazania <span style=\"font-style: italic;\">MessageBoxa<\/span>. Jako, \u017ce r\u0119czne przekazywanie tego obiektu mo\u017ce by\u0107 nu\u017c\u0105ce, mo\u017cemy skorzysta\u0107 z dobrodziejstw Inversion of Control. Ja skorzysta\u0142em z kontenera z Unity. Po pierwsze musimy zarejestrowa\u0107 nasz typ w kontenerze<\/p>\n<pre lang=\"csharp\">\r\n_container.RegisterType<IMessageBoxService, MessageBoxService>();\r\n<\/pre>\n<p>nast\u0119pnie <span style=\"font-style: italic;\">MessageBoxService <\/span>mo\u017ce zosta\u0107 wstrzykni\u0119ty do ka\u017cdego ViewModelu przy np. przy pomocy wstrzykiwania w\u0142a\u015bciwo\u015bci.<\/p>\n<pre lang=\"csharp\">\r\n[Dependency]\r\npublic IMessageBoxService MessageBoxService\r\n{\r\n  get { return  messageBoxService; }\r\n  set { messageBoxService = value; }\r\n}\r\n<\/pre>\n<p>Oczywi\u015bcie \u017ceby nasza w\u0142a\u015bciwo\u015b\u0107 zosta\u0142a wstrzykni\u0119ta, nasz <span style=\"font-style: italic;\">ViewModel <\/span>musi zosta\u0107 stworzony przy u\u017cycia kontenera <span style=\"font-style: italic;\">IO<\/span><br \/>\nZauwa\u017cmy, \u017ce w\u0142a\u015bciwo\u015b\u0107 MessageBoxService nie jest konkretnym typem, lecz interfejsem. Dzi\u0119ki takiemu podej\u015bciu nasza aplikacja wci\u0105\u017c jest w \u0142atwy spos\u00f3b testowalna oraz ViewModel nie ma nic wsp\u00f3lnego z klasami widoku.<\/p>\n<h3>3. <span> ModalWindowService<\/h3>\n<p>Do pokazywania widok\u00f3w(jako okien) z poziomu ViewModelu pos\u0142u\u017cmy si\u0119 podobnym mechanizmem jak w przypadku pokazywania MessageBox-\u00f3w. Zasada dzia\u0142ania klasy, kt\u00f3ra stworzymy b\u0119dzie taka sama, jednak\u017ce b\u0119dziemy musieli odrobin\u0119 poszerzy\u0107 jej funkcjonalno\u015b\u0107. Po pierwsze tak jak poprzednio stw\u00f3rzmy odpowiedni interfejs &#8211; <span style=\"font-style: italic;\">IModalWindowService<\/span>.<br \/>\n<span style=\"font-style: italic;\">UWAGA<br \/>\nPrzyk\u0142ad <span style=\"font-style: italic;\">ModalWindowServicu <\/span>zostanie zaprezentowany dla Silverlight-a, gdy\u017c okna modalne w silerlighcie to tak naprawd\u0119 okna semi-modalne. G\u0142\u00f3wny w\u0105tek si\u0119 nie zatrzymuje, jednak\u017ce u\u017cytkownik nie ma mo\u017cliwo\u015bci interakcji z oknami b\u0119d\u0105cymi pod aktualnie widocznym oknem.<\/span><\/p>\n<pre lang=\"csharp\">\r\npublic interface IModalDialogService\r\n{\r\n    void ShowDialog<TViewModel>(IModalView<TViewModel> view,Action<TViewModel> CloseAction) where TViewModel : ViewModelBase;       \r\n}\r\n<\/pre>\n<p>Zauwa\u017cmy, \u017ce funkcja <span style=\"font-style: italic;\">ShowDialog <\/span>w parametrze nie przyjmuje konkretnego widoku, lecz interfejs, kt\u00f3ry ka\u017cde okno modalne powinno implementowa\u0107 &#8211; <span style=\"font-style: italic;\">IModalView<\/span>. Interfejs ten mo\u017ce wygl\u0105da\u0107 w nast\u0119puj\u0105cy spos\u00f3b:<\/p>\n<pre lang=\"csharp\">\r\npublic interface IModalView<TViewModel> where TViewModel : ViewModelBase\r\n{\r\n    \/\/\/ \r\n    \/\/\/ ViewModel odpowiedzialny za dane okno\r\n    \/\/\/ \r\n    TViewModel ViewModel { get; set; }\r\n    \/\/\/ \r\n    \/\/\/ DialogResult po zamknieciu okna\r\n    \/\/\/ \r\n    bool? DialogResult { get; set; }\r\n    \/\/\/ \r\n    \/\/\/ zamkniecie okna\r\n    \/\/\/ \r\n    void Close();\r\n    \/\/\/ \r\n    \/\/\/ Pokazanie okna\r\n    \/\/\/ \r\n    void Show();\r\n    \/\/\/ \r\n    \/\/\/ Zdarzenie wywo\u0142ywane w momencie zamkniecia okna modalnego\r\n    \/\/\/ \r\n    event EventHandler Closed;\r\n    \/\/\/ \r\n    \/\/\/ Zdarzenie wywo\u0142ywane w chwli zamykania widoku \r\n    \/\/\/ \r\n    event EventHandler<CancelEventArgs> Closing;\r\n}\r\n<\/pre>\n<p>Jako, \u017ce okna modalne w Silverlighcie s\u0105 tak naprawd\u0119 oknami semi-modalnymi (patrz wyt\u0142umaczenie w sekcji UWAGA) musimy wiedzie\u0107 kiedy dane okno zostanie zamkni\u0119te. Dlatego, te\u017c interfejs ten zawiera zdarzenie<\/p>\n<pre lang=\"csharp\">\r\nevent EventHandler Closed;\r\n<\/pre>\n<p>ponadto wypada\u0142oby sprawdzi\u0107 czy dane wpisane w oknie s\u0105 poprawne, dlatego te\u017c dodano zdarzenie<\/p>\n<pre lang=\"csharp\">\r\nevent EventHandler<CancelEventArgs> Closing;\r\n<\/pre>\n<p>w przypadku gdy dane wpisane w oknie nie b\u0119d\u0105 poprawne, nasz <span style=\"font-style: italic;\">ModalDialogService <\/span>zapobiegnie zamkni\u0119ciu okna. Ostatecznie nasza klasa ModalDialogService mo\u017ce wygl\u0105da\u0107 w nast\u0119puj\u0105cy spos\u00f3b:<\/p>\n<pre lang=\"csharp\">\r\npublic class ModalDialogService : IModalDialogService\r\n{\r\n    private EventHandler<CancelEventArgs> ClosingEventHandler;\r\n    private EventHandler CloseEventHandler;\r\n    public void ShowDialog<TViewModel>(IModalView<TViewModel> view, Action<TViewModel> CloseAction) where TViewModel : ViewModelBase\r\n    {\r\n        view.Closing += ClosingEventHandler = (sender, args) =>\r\n        {\r\n            if (view.DialogResult.HasValue && view.DialogResult.Value)\r\n            {\r\n                if (!(args.Cancel = !view.ViewModel.Validate())) \/\/ jezeli mozna zamknac okno to odczepiamy handler\r\n                    view.Closing -= ClosingEventHandler;\r\n            }\r\n        };\r\n        if (CloseAction != null)\r\n        {\r\n            view.Closed += CloseEventHandler = (sender, args) =>\r\n                {\r\n                    CloseAction(view.ViewModel);\r\n                    view.Closed -= CloseEventHandler;\r\n                };\r\n        }\r\n        view.Show();\r\n    }\r\n}\r\n<\/pre>\n<p>Nasz serwis podobnie jak w przypadku <span style=\"font-style: italic;\">MessageBoxServicu <\/span>rejestrujemy w kontenerze, a nast\u0119pnie wstrzykujemy go do <span style=\"font-style: italic;\">ViewModelu<\/span><\/p>\n<pre lang=\"csharp\">\r\n_container.RegisterType<IModalDialogService, ModalDialogService>();\r\n[Dependency]\r\npublic IMessageBoxService MessageBoxService\r\n{\r\n get { return  messageBoxService; }\r\n set { messageBoxService = value; }\r\n}\r\n<\/pre>\n<p>Pozostaje nam jedynie kwestia wyja\u015bnienia w jaki spos\u00f3b wywo\u0142a\u0107 nasze okno. Nie powinni\u015bmy tworzy\u0107 okna w <span style=\"font-style: italic;\">ViewModelu<\/span>, gdy\u017c \u0142amie to zasad\u0119 <span style=\"font-style: italic;\">MVVM-a<\/span>. Dlatego te\u017c po raz kolejny skorzystamy z kontenera <span style=\"font-style: italic;\">IoC<\/span>. W kontenerze IoC rejestrujemy sobie nasz widok<\/p>\n<pre lang=\"csharp\">\r\n_container.RegisterType<IModalView<AddPersonViewModel>,AddPersonView>()\r\n<\/pre>\n<p>jako, \u017ce <span style=\"font-style: italic;\">ViewModel<\/span> jest wstrzykiwany do View poprzez Dependency Injection nale\u017cy r\u00f3wnie\u017c w kontenerze zarejestrowa\u0107 odpowiedni typ ViewModelu &#8211; czyli w tym przypadku <span style=\"font-style: italic;\">AddPersonViewModel<\/span><\/p>\n<pre lang=\"csharp\">\r\n_container.RegisterType<AddPersonView>()\r\n<\/pre>\n<p>Maj\u0105c ju\u017c zarejestrowane w kontenerze wszystkie niezb\u0119dne typu mo\u017cemy wywo\u0142a\u0107 okno z ViewModelu w nast\u0119puj\u0105cy spos\u00f3b:<\/p>\n<pre lang=\"csharp\">\r\nModalDialogService.ShowDialog<EditAllPlayersViewModel>(_container.Resolve<IModalView<AddPersonViewModel>>(), model => {\/\/tutaj jakas logika });\r\n<\/pre>\n<p>Obiekt widoku otrzymujemy z kontenera IoC wykorzystuj\u0105c funkcj\u0119 Resolve, w miejsce LambdaExpression wstawiamy nasze wyra\u017cenie w kt\u00f3rym okre\u015blamy co zrobimy po poprawnym zamkni\u0119ciu okna. Zauwa\u017cmy, \u017ce <span style=\"font-style: italic;\">ViewModel<\/span> nie ma \u017cadnych informacji o widoku &#8211; kontener nie zwraca nam konkretnego typu lecz interfejs. Dzi\u0119ki takiemu podej\u015bciu nasza aplikacja mo\u017ce by\u0107 w \u0142atwy spos\u00f3b testowalna.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Cz\u0119sto podczas u\u017cytkowania program\u00f3w zachodzi konieczno\u015b\u0107 pokazania dodatkowego okna, s\u0142u\u017c\u0105cego do interakcji z u\u017cytkownikiem. Najcz\u0119\u015bciej okno takie nale\u017cy pokaza\u0107 w okre\u015blonym przypadku. Jak ju\u017c wcze\u015bniej zosta\u0142o wspomniane ca\u0142a logika powinna by\u0107 umieszczona w ViewMoedlu, zatem nasuwa si\u0119 pytanie, w jaki spos\u00f3b z ViewModelu pokaza\u0107 okno modalne &#8211; przecie\u017c ViewModel nie powinien mie\u0107 \u017cadnych informacji o [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[116,43],"tags":[248,189],"class_list":["post-61","post","type-post","status-publish","format-standard","hentry","category-modalwindow","category-mvvm","tag-modalwindow","tag-mvvm"],"_links":{"self":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/61","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/comments?post=61"}],"version-history":[{"count":6,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/61\/revisions"}],"predecessor-version":[{"id":563,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/posts\/61\/revisions\/563"}],"wp:attachment":[{"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/media?parent=61"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/categories?post=61"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tpodolak.com\/blog\/wp-json\/wp\/v2\/tags?post=61"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}