Szukaj na tym blogu

czwartek, 9 września 2010

Dziennik Zdrowej Diety – Blendability MVVM

Bawiąc się Silverlightem bardzo polubiłem narzędzie Expression Blend. Pomimo braków w talencie graficznym dzięki temu programowi potrafię coś zrobić z UI co wywołuje we mnie emocje typu WOW! Choć daleko mi do perfekcji to nie ukrywam, że Blend bardzo usprawnia pracę podczas tworzenia aplikacji Silverlight.

Ponieważ wzorzec MVVM w aplikacjach Silverlight jest na topie, warto byłoby zapewnić mu współpracę z Blend’em. Oczywiście miejscem najbardziej do tego nadającym się jest ViewModel. To właśnie z jego właściwościami chcemy zbindować kontrolki z naszego widoku, wykorzystując do tego wspomniany powyżej program.

Istnieje kilka strategii łączenia ViewModel z View, których efektem końcowym jest przypisanie ViewModel do właściwości DataContext naszego widoku.
Możemy np.:
- wstrzykiwać ViewModel do View np. w konstruktorze przy wykorzystaniu narzędzia DI,
- wstrzykiwać View do ViewModel,
- stworzyć instancję ViewModel w statycznych zasobach widoku,
- stworzyć instancję ViewModel bezpośrednio w DataContext naszego widoku.

Ważnym ograniczeniem jakie narzuca nam budowa i mechanizm działania XAML oraz sam Blend jest fakt, że instancja ViewModel umieszczona w postaci StaticResource bądź UserControl.DataContext musi posiadać konstruktor bezparametrowy co może bardzo utrudnić życie korzystając z wszelkiego rodzaju kontenerów DI (przygoda z tą funkcjonalnością w połączeniu z Prism zajęła mi ostatnio trochę czasu).

W sieci można znaleźć przykłady pokazując jak stworzyć tzw. ViewModelLocator pozwalający na zbudowanie fasady wokół naszego docelowego ViewModel i udostępnieniu go w postaci właściwości.
Niestety jeśli nasz ViewModel posiada konstruktor parametrowy, który jest wymagany do uruchomienia byspełnić swoje zadanie, Blend może odmówić posłuszeństwa (funkcjonalnością, która by mnie uradowała było by wsparcie dla kontenerów DI wtrybie projektowania, ale to chyba tylko moje mżonki).

Poniżej zamieszczam moją implementację rozwiązania.
public class MainViewModel : ViewModelBase
{
    private IEventAggregator _eventAggregator;
    private IUnityContainer _container;

    //Wcześniej korzystałem z kontenera DI i Unity Constructor Injection:
    //public MainViewModel(IUnityContainer ua, IEventAggregator ea)
    //{
    //    LoadData();
    //}
    //co się nie sprawdziło przy korzystaniu z Blend'a

    public MainViewModel()
    {
        if(!DesignerProperties.IsInDesignTool)
        {
            _container = ServiceUnityContainer.Container;
            _eventAggregator = _container.Resolve<IEventAggregator>();
            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 RelayCommand<object>(OnClickCommand);
            return _clickCommand;
        }
    }

    private void OnClickCommand(object obj)
    {
        if(_age != 0)
            Age = 28;
        else
        {
            Age = _age + 2;
        }
    }
}


Właściwość InDesignMode przechowuje informacje na temat obecnego trybu (swoją drogą nigdy za bardzo nie rozumiałem jak VisualStudio czy też Blend potrafi sobie skompilować (w trybie projektowania aplikacji) klasę i zainicjować ją wartościami).
Ponieważ wykorzystuję Unity Container, którego instancja dla całej aplikacji jest ustawiana w bootstraperze mojego programu, stworzyłem klasę ServiceUnityContainer, której głównym zadaniem jest przechowywanie referencji do wspomnianego kontenera.
Wykorzystując powyższą implementację otrzymujemy wsparcie MVVM dla Expression Blend, jak również nie tracimy nic z funkcjonalności naszego ViewModel podczas działania aplikacji.
Poniżej efekt w Blend.


SketchFlow - nowy projekt


Spostrzeżenie: W sieci można znaleźć bindowanie ViewModel w taki oto sposób:

<UserControl.Resources>
    <ViewModels:MyViewModel x:Key="vM"  />
</UserControl.Resources>

<UserControl.DataContext>
    <Binding Source="{StaticResource vM}"/>
</UserControl.DataContext>
Dla Silverlight w wersji 4, powyższe rozwiązanie powodowało u mnie wystąpienie wyjątku JavaScript.

Brak komentarzy:

Prześlij komentarz