Szukaj na tym blogu

sobota, 27 listopada 2010

Przegląd PRISM 4 – część 4

W tej części będzie trochę nietypowo.

Dla przypomnienia, zaznaczam, że cały przegląd PRISM 4 oparty jest o ebook’a pt. "Developers Guide to Microsoft Prism. Building MVVM and Modular Applications with WPF and Silverlight"
Część 4 dotyczy najbardziej oczekiwanych przeze mnie rozdziałów „Implementing the MVVM pattern” oraz „Advanced MVVM Scenarios”. Ponieważ w tej części książki, autor opisał założenia wzorca MVVM, które są opisane w moich wcześniejszych postach, posłużę się odwołaniem do nich przy opisie poszczególnych działów będących częścią wymienionych powyżej rozdziałów.

Implementing the MVVM pattern
Class Responsibilities and Characteristics


W tej części znajdziemy opis wzorca projektowego MVVM. Zainteresowanych odsyłam do posta „Dziennik Zdrowej Diety – Wzorzec MVVM – Koncepcja”

Class interactions
Część ta opisuje mechanizm Bindowania oraz interakcję pomiędzy View, a ViewModel’em za pomocą interfejsów INotifyPropertyChanged, INotifyCollectionChanged, ICollectionView, Commanding, IDataError, INotifyDataErrorInfo.

Sposób wykorzystania pierwszych dwóch interfejsów znajdziecie również w poście „Dziennik Zdrowej Diety – Wzorzec MVVM – Koncepcja” . Mechanizm Commandingu jest przedstawiony w poście „Dziennik Zdrowej Diety – Wzorzec MVVM – Commanding”

Poniżej przedstawię możliwości jakie daje nam interfejs ICollectionView oraz DelegateCommand będący PRISM’ową implementacją interfejsu ICommand.

ICollectionView to interfejs, który umożliwia nam sortowanie, filtrowanie, grupowanie oraz śledzenie aktualnie zaznaczonego elementu z kolekcji. Jest to bardzo pomocne z punktu widzenia MVVM ponieważ otrzymujemy dosyć bogatą funkcjonalność operacji na kolekcjach po stronie ViewModel.
PagedCollectionView jest klasą, która w Silverlight (w WPF jest to klasa ListCollectionView) implementuje omawiany interfejs.
Poniżej przedstawiam krótki przykład jak tego używać (implementacja w DevPrototype)

public Dictionary SelectedDictionary { get; set; }
public ObservableCollection DictionariesCollection
{
    get { return _dictionariesCollection; }
    set
    {
        _dictionariesCollection = value;
        Dictionaries = new PagedCollectionView(_dictionariesCollection);
        Dictionaries.CurrentChanged += SelectedItemChanged;
        NotifyOfPropertyChange(() => DictionariesCollection);
    }
}
public ICollectionView Dictionaries
{
    get { return _dictionaries; }
    set
    {
        _dictionaries = value;
        NotifyOfPropertyChange(() => Dictionaries);
    }
}

private void SelectedItemChanged( object sender, EventArgs e )
{
     SelectedDictionary = Dictionaries.CurrentItem as Dictionary;
}
private void OnGroupCommand(object obj)
{
    Dictionaries.GroupDescriptions.Add(new PropertyGroupDescription("Description"));
}
private void OnUngroupCommand(object obj)
{
    Dictionaries.GroupDescriptions.Clear();
}
private void OnSortCommand(object obj)
{
   Dictionaries.SortDescriptions.Add(new SortDescription("Description",ListSortDirection.Ascending));
}

DelegateCommand implementuje interfejs ICommand (polecam post „Dziennik Zdrowej Diety – Wzorzec MVVM – Commanding” oraz dokłada dwie rzeczy. Pierwsza to możliwość wymuszenie ponownego sprawdzenia metody CanExecute przez metodę RaiseCanExecuteChanged(); Drugą rzeczą jest powiązanie wartości właściwości IsEnabled z wynikiem metody CanExecute. Te dwie rzeczy świetnie się uzupełniają w scenariuszach, gdzie np. mamy walidowane pola oraz przycisk „Zapisz”. Warto pamiętać, że podpinając DelegateCommand pod Button, nie mamy możliwości własnoręcznego zbindowania propercji IsEnabled. Klasa ta była także budowana dla kontrolek dziedziczących z ButtonBase. Ludzie od Prisma sami sugerują w książce, że dla innych kontrolek oraz innych zdarzeń warto przyjrzeć się Behavior’om z SDK Blenda. Głównie mają na myśli InvokeCommandAction oraz CallMethodAction. Po szczegóły na temat tych dwóch Behavior’ów odsyłam do posta „Przegląd Action (Blend SDK) w kontekście MVVM cz. 1„

Construction and Wire-Up
Dział ten opisuje starategie zarządzania View oraz ViewModel. Mamy tutaj do wybory:
- ViewModel First – najpierw inicjalizowany jest ViewModel, a następnie wewnątrz ViewModelu instancjonowana jest formatka View. Należy pamiętać by trzymać w takim ViewModelu bezparametrowy konstruktor w celu prawidłowego wyświetlania naszego View w designerze VS bądź Blend.
- View First – najpierw inicjalizowany jest View. Tworzenie instancji ViewModelu może się odbywać zarówno w kodzie XAML jak i w zdarzeniu Loaded. Po szczegóły odnośnie tej strategii odsyłam do posta „Dziennik Zdrowej Diety – Blendability MVVM”

MessageBox i PopUp
To w sumie jedyna oryginalna rzecz jaką stworzył team PRISM na potrzeby wzorca MVVM. Zestaw klas i interfejsów IInteractionRequest, InteractionRequestTrigger, Confirmation i Notification ma na celu wspomóc programistę przy tworzeniu wyskakujących okienek, okienek typu MessageBox w taki sposób by w ViewModelu nie było bezpośredniego odwołania do np. specyficznych rzeczy dostępnych tylko w Silverlight. Ułatwia to także testowanie, gdzie wywołanie tego typu komponentów odbywa się na zasadzie zdarzeń.
Prosty przykłąd

var result = interactionService.ShowMessageBox(
           "Are you sure you want to cancel this operation?",
           "Confirm",
           MessageBoxButton.OK );

if (result == MessageBoxResult.Yes)
{
CancelRequest();
}

Odnośnie interakcji naszego ViewModel’u z logiką UI odsyłam do posta „Dziennik Zdrowej Diety – Logika UI w MVVM”, gdzie przedstawiłem własną koncepcję jak nasz ViewModel może operować z logiką dostępną po stronie widoku nie będąc bezpośrednio powiązanym z tym widokiem.

czwartek, 25 listopada 2010

Przegląd PRISM 4 – część 3

Agenda
• Czy jest aplikacja modułowa ?
• Inicjalizacja modułów

Czym jest moduł ?
Pozwoliłem sobie na przetłumaczenie pierwszego akapitu z 4 rozdziału ebooka o Prism 4. Oto on.

Aplikacja modułowa to aplikacja podzielona na zestaw funkcjonalnych jednostek (zwanych modułami), które mogę być zintegrowane w jedną większą aplikację.
Moduł jest swego rodzaju kontenerem części funkcjonalnościcałej aplikacji i zazwyczaj stanowi zbiór powiązanych ze sobą zagadnień. Moduł taki może zawierać zbirór elementów opisujących część logiki biznesowej, część infrastruktury aplikacji takiej jak serwisy odpowiedzialne za logowanie lub autentykację użytkownika. Moduły są niezależne względem siebie, ale potrafią komunikować się ze sobą opierając się na mechanizmie rozgłoszeniowym.
Aplikacja modułowa może ułatwić Ci jej rozwój, testowanie, wdrażanie czy też rozszerzanie.


To w jaki sposób podzielisz aplikację zależy tylko i wyłącznie od Ciebie. Może to być podział ze względu na odpowiednie tematy biznesowe np. moduł zamówień, moduł faktur. Może to być również podział ze względu na architekturę aplikacji np. moduł dostępu do danych, moduł UI, moduł narzędziowy. Warto skorzystać z obu rozwiązań i posiadać w aplikacji podział na moduły techniczne i moduły biznesowe. Z własnych doświadczeń nie polecam tworzenia osobnych modułów dla każdego widoku lub pary widoków (np. widok listy użytkowników wraz z widokiem szczegółów użytkownika). Wraz z rozwojem aplikacji, zwiększy się ilość takich modułów, a wasza praca z Visual Studio przy np. 50 modułach (projektach) stanie się koszmarem.

Inicjalizacja modułów

SketchFlow - nowy projekt


Cały proces ładowania modułu zaczyna się od wywołania funkcji:
_moduleManager.LoadModule("CalendarModule");

Powyższy rysunek przedstawia praktycznie wszystko co powinieneś wiedzieć czyli:

Zarejestruj moduł w katalogu modułów
Dla Silverlight’a Prism dostarcza dwa sposoby rejestracji modułów (moduły rejestrujemy w bootstraperze)
Pierwszy to inicjalizacja modułu bezpośrednio w kodzie
protected override IModuleCatalog GetModuleCatalog()
{
    var moduleCatalog = new ModuleCatalog();
            
    var calendarModuleInfo = new ModuleInfo
    {
        ModuleName = "CalendarModule",
        ModuleType = "MVVMBasic.CalendarModule.Module, MVVMBasic.CalendarModule, Version=1.0.0.0",
        Ref = "MVVMBasic.CalendarModule.xap",
        InitializationMode = InitializationMode.WhenAvailable
    };
    moduleCatalog.AddModule(waitWindowModuleInfo);
    return moduleCatalog;
}  

Drugi sposób to załadowanie modułów do katalogu na podstawie pliku XAML

private const string ModuleCatalogUri = "/MVVMBasic;component/ModulesCatalog.xaml";
protected override IModuleCatalog CreateModuleCatalog()
{
    return
        Microsoft.Practices.Prism.Modularity.ModuleCatalog.CreateFromXaml(new Uri(ModuleCatalogUri,
                                                                                          UriKind.Relative));
}

Gdzie plik XAML ma postać:

<Modularity:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
                          xmlns:sys="clr-namespace:System;assembly=mscorlib" 
                          xmlns:Modularity="clr-namespace:Microsoft.Practices.Prism.Modularity;assembly=Microsoft.Practices.Prism">
    <Modularity:ModuleInfoGroup Ref="MVVMBasic.CalendarModule.xap" InitializationMode="WhenAvailable">
        <Modularity:ModuleInfo ModuleName="MVVMBasic.CalendarModule" ModuleType = "MVVMBasic.CalendarModule.Module, MVVMBasic.CalendarModule, Version=1.0.0.0",/>
    </Modularity:ModuleInfoGroup>
</Modularity:ModuleCatalog>

Odnajdywanie modułów
Krok ten dotyczy aplikacji WPF. Programista musi wskazać lokalizację katalogu w którym znajdują się moduły.
Pobież interesujący Cię modułu z odpowiedniej lokalizacji
Dla aplikacji Silverlight, w kroku tym pobierany jest moduł z odpowiedniej lokalizacji sieciowej (katalog /ClientBin w katalogu wirtualnym naszej hostowanej aplikacji) i przenoszony i załadowany do pamięci po stronie klienckiej.
Ponieważ pobranie modułu to ściągnięcie z serwera kolejnych paczek danych (które mogą być duże), mamy możliwość określenia w jaki sposób ma być pobierany moduł. Mówi nam o tym właściwość InitializationMode, która przyjmuje wartości:

• InitializationMode.OnDemand – pobiera moduł na żądanie,
• InitializationMode.WhenAvailable – pobiera moduł w tle w osobnym wątku.

Zainicjalizuj moduł
Po pobraniu modułu, następuje jego inicjalizacja, której zadaniem jest zintegrowanie tego modułu z naszą aplikacją. Inicjalizacja następuje poprzez wyszukanie w ściągniętym module typu określonego w katalogu modułów jako ModuleType.
Jest to klasa implementująca interfejs IModule.
namespace MVVMBasic.CalendarModule
{
    public class Module : IModule
    {
        #region init
        private readonly IRegionManager _regionManager;
        readonly IUnityContainer _container;
        readonly IEventAggregator _eventAggregator;
        
        public Module(IRegionManager regionManager, IUnityContainer container,
            IEventAggregator eventAggregator)
        {
            _regionManager = regionManager;
            _container = container;
            _eventAggregator = eventAggregator;
        }
        #endregion

        #region IModule Members
        public void Initialize()
        {    
            // zarejestruje nowe typy w kontenerze  
            _container.RegisterType<ICalendarView, CalendarView>();
            _container.RegisterType<ICalendarViewModel, CalendarViewModel>();

     // Powiadom menadzer regionow, w ktorym regionie wyswietli sie dany widok
            _regionManager.RegisterViewWithRegion("CalendarView", () => new CalendarView());

     // Powiadom wszystkich nasluchujacych o zakonczeniu inicjalizacji modulu
            _eventAggregator.GetEvent<ViewInitializedEvent>().Publish("CalendarModule");
        }
        #endregion
    }
}

Zupełnym minimum by zainicjalizować moduł jest implementacja metody Initialize. Jednakże nasz moduł składa się z zestawu widoków, które należy odpowiednio zarejestrować w odpowiednich kontenerach, menadżerach.

środa, 24 listopada 2010

Przegląd PRISM 4 – część 2

Agenda
• Kontener DI
• Rola kontenera DI w Prism
• Wykorzystanie kontenera DI
• ServiceLocator

Kontener DI
Kontener DI (ang. Dependency Injection) jest to wzorzec projektowy mający na celu usunąć z systemu zależności względem klas i wstrzykiwać obiekty implementujące dany interfejs. Słowno muzycznie: kontener stanowi swego rodzaju mapę, do której rejestrujemy powiązania pomiędzy interfejsem i klasą na zasadzie klucz, wartość. Jeśli potrzebujemy obiekt implementujący dany interfejs to odpytujemy się kontenera wskazując mu odpowiedni klucz (interfej), przez co w zamian otrzymujemy wartość (klasa).
Podejście to uniezależnia naszą aplikację od konkretnej implementacji przez co bardzo łatwo będzie można podmienić mechanizm funkcjonowania np.

public interface ICalendarDataService
{
    void GetAllCalendars(Action<Calendar> result, object state);
}

Interfejs ten posiada deklarację metody zwracającej wszystkie klendarze.

public class AzureCalendarDataService : ICalendarDataService
{
    public void GetAllCalendars(Action<Calendar> result, object state)
    {
        // pobierz kalendarze z SQL Azure
    }
}

public class XMLCalendarDataService : ICalendarDataService
{
    public void GetAllCalendars(Action<Calendar> result, object state)
    {
        // pobierz kalendarze z pliku XML
    }
}

public class EFCalendarDataService : ICalendarDataService
{
    public void GetAllCalendars(Action<Calendar> result, object state)
    {
        // pobierz kalendarze z DB za pomocą Entity Framework
    }
}

Powyższe klasy implementują nasz interfejs pobierając kalendarze z różnych źródeł danych.
Ponieważ aplikacja bazuje na interfejsach, nie musi wiedzieć skąd są pobierane dane.
Podmiana źródła danych polega na rejestracji w kontenerze danych odpowiedniej klasy dla naszego interfejsu ICalendarDataService.
Benefity są interesujące (testowalność, prosta konfiguracja, niezależność etc.)

Rola kontenera DI w Prism
Koncepcją Prisma jest możliwość stworzenia aplikacji składającej się z modułów. Tak na prawdę nie interesuje nas jak dana funkcjonalność będzie zaimplementowana. Ważne by moduł implementował wskazany interfejs.

Prism dostarcza implementację dwóch kontenerów:
- MEF,
- Unity.

By z nich skorzystać, wystarczy by nasz Boostraper podziedziczył po odpowiedniej klasie. I tak dla MEF będzie to MefBootstraper, a dla Unity będzie to UnityBootstraper. Oczywiście jeśli ktoś lubi StructureMap, AutoFac albo inny kontener DI, nie stoi nic na przeszkodzie by napisać własną implementację Boostrapera rejestrującego wasz ulubiony konter.

Wykorzystanie kontenera DI
Najczęściej wykorzystywanymi operacjami przy pracy z kontenerem DI są:
- rejestracja typu,
- pobieranie instancji typu z kontenera,
- rejestracja zadanej instancji (Singleton),
- wstrzykiwanie obiektów do konstruktora

Rejestracja typu w kontenerze Unity
this.Container.RegisterType<ICalendarDataService, XMLCalendarDataService>();

Rejestracja typu w kontenerze MEF
[Export(typeof(ICalendarDataService))]
public class XMLCalendarDataService: ICalendarDataService
{
}

Pobieranie instancji typu z kontenera Unity
var cal = this.Container.Resolve<ICalendarDataService>();

Pobieranie instancji typu z kontenera MEF
var cal = this.Container.GetExportedValue<ICalendarDataService>();

Rejestracja zadanej instancji (Singletone) w kontenerze Unity
container.RegisterInstance<ICalendarDataService>(new AzureCalendarDataService);

Rejestracja zadanej instancji (Singletone) w kontenerze MEF
this.Container.ComposeExportedValue<ICalendarDataService>(this.serwisKalendarza);

Wstrzykiwanie obiektów do konstruktora.
W Unity operacja ta dzieje się automatycznie czyli np.
public class CalendarViewModel
{
    private IEventAggregator _eventAggregator;

    public CalendarViewModel(IEventAggregator eventAggregator)
    {
       _eventAggregator = eventAggregator;
    }
}

Zakończy się zainicjalizowaniem _eventAggregator odpowiednim obiektem zarejestrowanym w kontenerze Unity.

W MEF działa to analogicznie z tym, że nad klasą musimy umieścić atrybut

[ImportingConstructor]
public class CalendarViewModel

Przedstawione przykłady to tylko szybka ściąga. Po szczegóły odsyłam pod adresy:
http://unity.codeplex.com
http://mef.codeplex.com

ServiceLocator
ServiceLocator to poprostu nakładka na nasze kontenery mająca na celu ułatwienie przy próbie zmiany kontenera podczas procesu tworzenia aplikacji.
Jedyną opcją jaką oferuje ServiceLocator jest możliwość pobrania typu obiektu i to w dodatku opakowanego w object przez co musimy to jeszcze rzutować na odpowiedni interfejs.
ServiceLocator.Current.GetInstance<IRegionNavigationService>();

Fajną rzeczą z ServiceLocatorem jest fakt, że jest on klasą statyczną dostępną w całej aplikacji tak więc przydaje się w sytuacjach gdzie potrzebujemy coś pobrać z kontenera, a nie mamy do niego dostępu np. tworzymy nasze ViewModel’e w stylu Blendability, gdzie instancja jest tworzona przez View (View First strategy).

wtorek, 23 listopada 2010

Przegląd PRISM 4 – część 1

Tematem części 1 będzie, krótki opis na temat tego czym jest Prism, oraz opis Boostrapera.

Co to jest Prism ?
Prism to framework zawierająy szereg narzędzi ułatwiających budowę aplikacji składających się z niezależnych modułów. Wbrew pozorom, tak jak wiele osób uważa, Prism nie jest kolejnym frameworkiem MVVM. Wiele z ficzerów (Unity, EventAggregator) wchodzących w skład Prisma jest wykorzystywanych we własnych implementacjach MVVM typu Cinch, Caliburn etc.

Dlaczego warto korzystać z Prism’a ?
Głównie po to by móc tworzyć niezależne od siebie moduły tzn. zespoły programistów skupiają się na tworzeniu tylko danego kawałka funkcjonalności całego projektu (może to być część biznesowa jak również techniczna- infrastrukturalna). Zabieg ten wprowadza łatwiejsze testowanie poszczególnych modułów, zarządzanie całym projektem czy też aktualizacje danych funkcjonalności. W środowisku Silverlight modułowość pozwala także na przesłanie do klienta tylko tych modułów, których aktualnie potrzebuje przez co transfer danych pomiędzy serwerem, a klientem jest mniejszy (= działa szybciej).

Co nowego w wersji 4 ?
- wsparcie dla MEF (Managed Extensibility Framework) – MEF pełni rolę kontenera DI oraz zarządzania modułami,
- wsparcie dla MVVM - o tym szerzej w kolejnych częściach,
- wsparcie dla nawigacji .
Poza dodatkowymi funkcjonalnościami w wersji czwartej, zmianom uległa licencja, namespace etc.

Założenia
Z racji osobistych powódek, założyłem, że wykorzystam:
- Unity jako kontener UI,
- modułowość,
- nawigację opartą o przełączanie widoków (view- switching navigation),
- Prism’owe rozszerzenia dla MVVM (o ile będą lepsze od tego co sobie wypracowałem do tej pory).
Będę także przekazywał wystarczające minimum informacji by zacząć pracę z Prism. Po resztę szczegółów dotyczących wykorzystywanych funkcjonalności odsyłam do dokumentacji.

Bootstraper - koncepcja

SketchFlow - nowy projekt


Kluczowym elementem dla modułowej aplikacji wykorzystująca Prism jest Bootstraper.
Zadaniem jego jest inicjalizacja narzędzi podstawowych tj.:
- kontener DI – Prism oferuje nam Unity oraz MEF (jeśli chcemy, możemy z łatwością zaimplementować inny kontener),
- agregatora zdarzeń – odpowiedzialny za komunikację pomiędzy „luźno powiązanymi” modułami,
- menadżerów regionów, logowania, wyjątków,
Dodatkowo rejestruje w kontenerze DI wszelkiego rodzaju interfejsy implementowane przez powyższe narzędzia.
Po wykonaniu powyższych zadań, Bootstraper inicjalizuje Shell, który jest niczym innym jak głównym widokiem aplikacji zawierającym regiony do których będą wczytywane widoki, style i zasoby wykorzystywane przez całą aplikację. Poprzez analogię, Shell można porównać do ASP.NET-owego MasterPage.

Kod warty więcej niż 1000 słów
Inicjalizacja Bootstrapera odbywa się w App.xaml.cs

private void Application_Startup(object sender, StartupEventArgs e)
{
    var bootstrapper = new Bootstrapper();
    bootstrapper.Run();
}

A tak wygląda kod Bootstrapera

public class Bootstrapper : UnityBootstrapper
{
    private readonly CallbackLogger callbackLogger = new CallbackLogger();
 
    /// 
    /// Utworzenie widoku Shell
    /// ServiceLocator przejmuje funkcję Container.Resolve<IType>()
    /// opakowując kontener DI
    /// 
    protected override DependencyObject CreateShell()
    {
        return ServiceLocator.Current.GetInstance<MainPage>();
    }
 
    /// 
    /// Zainicjalizowanie głównego widoku
    /// 
    protected override void InitializeShell()
    {
        base.InitializeShell();
        Application.Current.RootVisual = (UIElement)this.Shell;
    }
 
    /// 
    /// Utworzenie własnego loggera 
    /// 
    protected override ILoggerFacade CreateLogger()
    {
        return this.callbackLogger;
    }
 
    /// 
    /// Konfiguracja kontenera DI: inicjalizacja Unity oraz mapowanie interfejs- typ np. IEventAggregator
    /// 
    protected override void ConfigureContainer()
    {
        base.ConfigureContainer();
        this.Container.RegisterInstance(this.callbackLogger);
        this.Container.RegisterType<ICustomerDetailsView, CustomerDetailsView>();
        this.Container.RegisterType<ICustomerDetailsViewViewModel, CustomerDetailsViewViewModel>();
        this.Container.RegisterType<IErrorWindow, ErrorWindow>();
        this.Container.RegisterType<IUnitOfWorkFactory, BasicUnitOfWorkFactory>();
        this.Container.RegisterType(typeof(IRepository<>), typeof(BasicRepository<>));
    }
 
    /// 
    /// Utworzenie katalogu modułów na podstawie pliku XAML
    /// 
    protected override IModuleCatalog CreateModuleCatalog()
    {
        return ModuleCatalog.CreateFromXaml(new 
         Uri("/MVVMBasic;component/ModulesCatalog.xaml", UriKind.Relative));
    }
}


Tak jak zapowiadałem, DevPrototype zawiera wszystkie powyższe zmiany prezentowane w tym poście (http://devprototype.codeplex.com)

poniedziałek, 22 listopada 2010

Listopadowe wyzwanie

Już od kilku lat zauważyłem, że listopad jest dla mnie jałowym miesiącem. Nic mi się nie chce robić, najlepiej to poszedłbym spać (chociaż noc przespana), marudzę, jęczę, człapię, kszątam się z kąta w kąt .... mógłbym tak wymieniać.

Niestety moje zachowanie było widoczne również na blogu- albo jak kto woli brak zachowania, rekacji, "Halo !!! Jest tam kto ?"

Ileż tak można ?

Na szczęście są osoby, które w tym czasie pracują pełną parą.
Całkiem niedawno (12.11.2010) pojawiła się 4 część frameworku Prism.

Chłopaki się postarali i oprócz ciekawych funkcjonalności, stworzyli obszerną dokumentację w postaci ebooka pt. "Developers Guide to Microsoft Prism. Building MVVM and Modular Applications with WPF and Silverlight"
(chętnych odsyłam pod adres: http://compositewpf.codeplex.com/

Był konkurs "DAJ SIĘ POZNAĆ", teraz będzie konkurs "CZY ŁUKASZ DA RADĘ ?" aka "PRZEGLĄD PRIS 4"

Na czym on polega ?
Ebook składa się z 11 rozdziałów oraz kilku dodatków.
Daję sobie czas do końca miesiąca by:
- przerobić całego ebooka,
- streścić wam rozdziały w postaci postów na blogu
- zaimplementować kilka ciekawych ficzerów w moim projekcie DevPrototype (http://devprototype.codeplex.com/).

Myślę, że to całkiem fajne wyzwanie mające na celu wyciągnięcie mnie z listopadowego dreszczowca.
Nagroda ? Kupię sobie piwo :D albo i dwa :D (a co tam :P)

Zainteresowanych zapraszam do śledzenia mojego bloga.