WPF: MVVM Pattern

Od jakiegoś czasu staram się posiąść tajemną sztukę tworzenia aplikacji przy użyciu WPF+MVVM+PRISM. Pewnie niektórzy z Was się spytają: „Czemu tak późno?”. Z przykrością muszę powiedzieć, że nie miałem po prostu czasu. No ale jak to się mówi, na naukę nigdy nie jest za późno, więc powiedziałem sobie „Do dzieła!!!”.

Wychowany na tworzeniu aplikacji przy użyciu CAB-a (Composite UI Application Block)  staram się wydzielić jakąś część wspólną(bazową) dla poszczególnych elementów i stworzyć coś na kształt CORE-a. Wiadomo, że na początku nidy nie przystąpimy do pisania aplikacji zaczynając od tworzenia klas abstrakcyjnych, z których później będą dziedziczyć docelowe klasy. Jednak po pierwszym czy drugim refactoringu ta sztuka może się nam udać.

Poniżej chciałem się z Wami podzielić jak w prosty sposób przygotować sobie klasy bazowe dla wzorca MVVM.

  1. Model jako podstawa
  2. Początek tworzenia bazy wzorca MVVM powinniśmy zacząć od stworzenia Modelu, który będzie tzw. fundamentem dla pozostałych elementów wzorca.

    public abstract class Model : IModel, INotifyPropertyChanged, IDisposable
        {
            protected Model()
            {
            }
    
            #region INotifyPropertyChanged
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected virtual void OnPropertyChanged(string propertyName)
            {
                this.VerifyPropertyName(propertyName);
                if (PropertyChanged != null)
                {
                    PropertyChanged(this,new PropertyChangedEventArgs(propertyName));
                }
            }
    
            protected void RaisePropertyChanged<T>(Expression<Func<T>> property)
            {
                if (PropertyChanged != null)
                    PropertyChanged(this, property.CreateChangeEventArgs());
            }
    
            #endregion
    
            #region Debugging
    
            [Conditional("DEBUG")]
            [DebuggerStepThrough]
            public void VerifyPropertyName(string propertyName)
            {
                if (TypeDescriptor.GetProperties(this)[propertyName] == null)
                {
                    string msg = "Invalid property name: " + propertyName;
    
                    if (this.ThrowOnInvalidPropertyName)
                        throw new Exception(msg);
                    else
                        Debug.Fail(msg);
                }
            }
    
            protected virtual bool ThrowOnInvalidPropertyName { get; private set; }
    
    #if DEBUG
            ~Model()
            {
                string msg = string.Format("Model {0} ({1}) Finalized", this.GetType().Name, this.GetHashCode());
                System.Diagnostics.Debug.WriteLine(msg);
            }
    #endif
    
            #endregion
    
            #region IDisposable
    
            public void Dispose()
            {
                this.OnDispose();
            }
    
            protected virtual void OnDispose()
            {
            }
    
            #endregion
        }
    

    Jak można wyżej zauważyć, klasa Model jest abstrakcyjna oraz implementuje 2 ważne interfejsy: INotifyPropertyChanged (do powiadamiania o zmianie wartości jakiejś Property) oraz IDisposable (przydatny do zabijania obiektów, np. podczas użycia using). W sumie jest to nasza bazowa klasa, więc zawiera logikę, która przyda się we wszystkich klasach z niej dziedziczących. Interfejs IModel jest pusty i przyda się w generycznych odwołaniach do obiektów(o ile jakieś tak nisko cast-owane będą :))

  3. Widok
  4. Widok we wzorcu MVVM to nic innego jak element interfejsu użytkownika(UserControl, Window), który będzie zarządzany przez ViewModel. Najważniejszym elementem widoku w WPF jest properta DataContext, za pomocą której będzie wstrzykiwana zależność pomiędzy View i go kontrolującym ViewModel-em. Dlatego też na potrzeby wzorca wystarczy, że każdy widok będzie implementował stworzony przeze mnie interfejs IView:

    public interface IView
        {
            object DataContext { get; set; }
        }
    

    Prawda, że proste 🙂

  5. ViewModel
  6. Na koniec pozostaje nam ostatni element wzorca do przygotowania, czyli ViewModel. Inaczej niż wcześniej zastanówmy się na początku, co jest nam potrzebne w obiekcie ViewModel. Na pewno musi być powiązany z widokiem, musi dziedziczyć z klasy Model oraz fajnie jakby był generyczny w zależności od widoku. Wydaje się proste i jest proste:

    public abstract class ViewModel : Model, IViewModel
        {
            protected ViewModel(IView view)
                :this(view,false)
            {
            }
    
            protected ViewModel(IView view, bool isChild)
            {
                if (view == null)
                    throw new ArgumentException("view");
    
                this.View = view;
    
                if (!isChild)
                {
                    Dispatcher.CurrentDispatcher.BeginInvoke(new Action<IView, ViewModel>(SetDataContext), View, this);
                }
            }
    
            private static void SetDataContext(IView view, IViewModel viewModel)
            {
                view.DataContext = viewModel;
            }
    
            public IView View
            {
                get;
                private set;
            }
        }
    
        public abstract class ViewModel<TView> : ViewModel, IViewModel<TView>
            where TView : IView
    
        {
            protected ViewModel(TView view, bool isChild)
                :base(view,isChild)
            {
            }
    
            public new TView View
            {
                get
                {
                    return (TView)base.View;
                }
            }
        }
    

    Dodatkowo dodałem jeszcze lazy dispatch dla wstrzykiwania ViewModel-u do widoku.

W taki oto łatwy i przyjemny sposób można stworzyć podstawę dla wzorca MVVM. W kolejnych moich wpisach dotyczących PRISM oraz MVVM dla WPF pojawią się takie elementy jak użycie Commands, okienka modalne, Bootstraper, UnityContainer, Master-Detail i pewnie wiele innych, nad którymi aktualnie pracuję i dopieszczam.
Zapraszam 🙂
Advertisements

~ - autor: kaprys12 w dniu Luty 21, 2010.

Odpowiedzi: 5 to “WPF: MVVM Pattern”

  1. nie wiem jak zacząć..
    kliknałem na Start i co dalej?

  2. Kompilator miałczy mi, że nie ma interfejsów: IModel ora IViewModel i nie ma żadnego Resolve-a.
    Chciałem użyć tego przykładu do projektu WindowsFormsApplication. Robię coś głupiego, czegoś brakuje czy to tylko dla WPF-a?

    • Sam musisz utworzyć interfejsy IView i IViewModel. Przykład podany przeze mnie jest tylko ogólną charakterystyką na czym polega wzorzec MVVM. Napisz z czym masz problem i co chcesz uzyskać to może będę w stanie Ci pomóc.

  3. Witam,

    poczytałem ostatnio o MVVM zwłaszcza w WPFie i prawdę mówiąc delikatnie niepokoi mnie linijka w klasie VM gdzie ViewModel ma możliwość trzymania referencji do widoku. Z tego co pamiętam to puryści MVVM zalecają żeby VM nie trzymał referencji do wjusa, a nawet nie trzymał referencji do innego VM. Poza tym zalecane jest żeby Code-Behind dla widoku zawierał minimalną ilość kodu, a najlepiej domyślną (sam konstruktor). Myślę, że podejście z tworzeniem własnego frameworka MVVM jest jak najbardziej właściwe – w życiodajnym necie jest duuużo tego ustrojstwa (MVVM Light Toolkit, Cinch, Prism, Caliburn, itp). Oczywiście może to być trochę pracochłonne, ale w zamian dostajemy pełną kontrolę nad tym jak on pod spodem działa. Ja osobiście zsyntetyzowałem parę najpotrzebniejszych funkcjonalności z dostępnych fw jak np. messaging, klasy bazowe dla vm, commanding i działa to dość przywoicie.


    Pozdrawiam,
    Maćko

    • Masz rację, VM nie powinien nic wiedzieć o widoku. Post mój miał raczej pokazywać samą ideę wzorca MVVM, niż jego praktyczne zastosowanie. Chociaż przed napisaniem się tego posta spotkałem się wpisami, w których VM miał referencję do widoku, aby móc obsłużyć takie rzeczy jak Storyboarding czy jakieś nietypowe eventy.
      Obecnie, jeśli chcę coś zrobić na szybko, to raczej korzystam z gotowych frameworków, tj. Caliburn Micro, nRoute czy Ninject. Jednak kiedy robię coś na poważnie, to raczej piszę swój własny framework dostosowany do potrzeb aplikacji. A wszystko po to, aby było to jak najlżejsze i nie zawierało nadmiarowych elementów.

Skomentuj

Wprowadź swoje dane lub kliknij jedną z tych ikon, aby się zalogować:

Logo WordPress.com

Komentujesz korzystając z konta WordPress.com. Log Out / Zmień )

Zdjęcie z Twittera

Komentujesz korzystając z konta Twitter. Log Out / Zmień )

Facebook photo

Komentujesz korzystając z konta Facebook. Log Out / Zmień )

Google+ photo

Komentujesz korzystając z konta Google+. Log Out / Zmień )

Connecting to %s

 
%d bloggers like this: