Szukaj na tym blogu

poniedziałek, 13 września 2010

Dziennik Zdrowej Diety – Logika UI w MVVM

Korzystając z wzorca MVVM cały czas skupiamy się na zachowaniu koncepcji czyli wszelkiego rodzaju logika biznesowa udostępniana jest widokowi poprzez ViewModel. Z drugiej strony nasz widok (View) powinien składać się tylko ze znaczników XAML. W sieci można znaleźć mnóstwo video tutoriali gdzie oglądając odnosimy wrażenie, że punktem honoru prelegenta jest pokazanie w pełni działającej aplikacji przy zachowaniu czystego CodeBehind.

Ostatnio jednak uświadomiłem sobie (a raczej wymaganie biznesowe postawiło mnie przed tym faktem), że to tylko teoria i w pewnym momencie choć byśmy bardzo chcieli musimy dodać coś do CodeBehind naszego widoku, bo przecież gdzie możemy wywołać metody udostępnione przez kontrolki (np. DevExpress AgGrid.Ungroup()) jak nie w CodeBehind widoku, na którym jest umieszczona ta kontrolka.
Tak więc schowałem swoje rządze związane z zachowaniem czystego CodeBehind do kieszenie i wymyśliłem rozwiązanie, które według mnie zachowuje założenia koncepcji MVVM.
Poniżej przykład.

ViewModel:

public class MainPageViewModel : IMainViewModel, INotifyPropertyChanged
{
    public event EventHandler SampleEvent;
    public MainPageViewModel()
    {
        if (!DesignerProperties.IsInDesignTool)
        {
            LoadData();
        }
    }

    public void LoadData()
    {
        WorkingItem = new Customer
        {
            Age = 28,
            FirstName = "Lukas"
        };
    }

    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            _firstName = value;
            RaisePropertyChanged("FirstName");
        }
    }
    private int _age;
    public int Age
    {
        get { return _age; }
        set
        {
            _age = value;
            RaisePropertyChanged("Age");
        }
    }
    public Customer WorkingItem { get; private set; }
    private ICommand _clickCommand;
    public ICommand ClickCommand
    {
        get
        {
            if (_clickCommand == null)
                _clickCommand = new MyCommand(OnClickCommand, null);
            return _clickCommand;
        }
    }
    private void OnClickCommand(object obj)
    {
        if (_age == 0)
            Age = 28;
        else
        {
            Age += 2;
            if (Age == 4 && SampleEvent != null)
                SampleEvent(this, new EventArgs());
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    private void RaisePropertyChanged(string propertyName)
    {
        if(PropertyChanged != null)
           PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Interfejs IMainViewModel:
public interface IMainViewModel
{
    event EventHandler SampleEvent;
    string FirstName { get; set; }
    int Age { get; set; }
    void LoadData();
}

CodeBehind widoku:
public partial class MainPage : UserControl
{
    public MainPage()
    {
        InitializeComponent();
        this.Loaded += (sender, ea) =>
        {
            if (this.DataContext != null && this.DataContext is IMainViewModel)
            {
                var vm = DataContext as IMainViewModel;
                vm.SampleEvent += vm_SampleEvent;
            }
        };
    }
    void vm_SampleEvent(object sender, EventArgs e)
    {
        MessageBox.Show("To jest tylko przykład");
    }
}

Nasz ViewModel implementuje zdarzenie SampleEvent, które jest subskrybowane przez widok. W celu wywołania logiki interfejsu graficznego ze strony naszego ViewModel’u, odpalamy zdarzenie i w metodzie obsługującej zdarzenie w CodeBehind widoku uruchamiamy odpowiednią logikę UI. W podanym przykładzie jest to zwykły MessageBox, ale możemy to zastąpić bardziej złożonymi metodami (np. wspomniany AgGrid.Ungroup()).

Dzięki powyższemu zabiegowi nasz widok operuje tylko na logice interfejsu graficznego oraz ViewModel nadal nie wie o istnieniu widoku przez co łatwo możemy przeportować naszą aplikację Silverligh MVVM do np. WPF MVVM.

Dla wyjaśnienia nieścisłości zaznaczam, że korzystam z MVVM dostosowanego do pracy z Expresssion Blend (ViewModel dołączony w XAML’u widoku), dlatego też podpinam się pod zdarzenia ViewModel’u w zdarzeniu Loaded widoku.

Brak komentarzy:

Prześlij komentarz