Szukaj na tym blogu

Pokazywanie postów oznaczonych etykietą DHD. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą DHD. Pokaż wszystkie posty

sobota, 30 października 2010

Dziennik Zdrowej Diety – checkpoint i zmiany

Zapewne zauważyliście, że sposób prowadzenia mojego bloga oraz projektu odbiega od założeń postawionych kilka miesięcy temu. Przyznam się szczerze, że pomysł przerósł moje możliwości czasowe co nie oznacza, że się poddaję.

Przez te kilka tygodni starałem się wprowadzić was w sposób tworzenia aplikacji w technologii Silverlight wraz z wykorzystaniem wzorca projektowego MVVM. Ci którzy bawią się Silverlightem na co dzień, stykają się z większością rzeczy tutaj opisanych. Zamierzam nadal dzielić się moją wiedzą, spostrzeżeniami oraz wątpliwościami, jednakże będą to raczej elementy rzemiosła niż opis krok po kroku co dzieje się w projekcie.
Projekt „Dziennik Zdrowej Diety” nabieże także innego charakteru.
Posłuży mi on raczej jako prototyp przy tworzeniu kolejnych aplikacji. Tak więc wszelkiego rodzaju nowinki, eksperymenty będą zaimplementowane dokładnie w nim.
Co chcę osiągnąć ? O tym poniżej.

Założenia:
Aplikacja ma być napisana w Silverlight 4 z wykorzystaniem wzorca MVVM oraz frameworku Prism.
Wsparcie asynchroniczne będzie zrealizowane poprzez framework Async.
We wzorcu MVVM postaram się wykorzystać elementy takich frameworków MVVM jak Caliburn, Calcium, Cinch, MVVM Light itp.
Do komunikacji z bazą danych chcę wykorzystać WCF RIA Services przykryte dodatkową warstwą abstrakcji pozwalającą mi na uniezależnienie się ViewModelu od konkretnych serwisów.

Po co mi to ?
Na początku będzie to WCF RIA Services wraz z Entity Framework 4. Z czasem chcę udostępnić możliwość korzystania z projektu na platformie mobilnej Windows Phone 7, gdzie sposób wymiany danych będzie oparty o protokół OData. Ponieważ interesuje mnie temat chmur obliczeniowych, spróbuję udostępnić możliwość trzymania aplikacji na Windows Azure.
Mam nadzieję, że dużą część aplikacji, dzięki MVVM, będzie wspólna dla każdej z platform.
Do tej pory miałem także znikomą styczność z testami jednostkowymi, tak więc postaram się zgłębić także ten temat opierając się na nUnit.

Co mam ?
Przez ostatnich kilka tygodni rozijałem już swój prototyp, w którym możecie znaleźć:
- architektura oparta o wzorzec MVVM,
- WCF RIA Services jako komunikacja z serwerem,
- Entity Framework 4 pełniący rolę dostępu do danych,
- zaimplementowany wzorzec Repository dla Entity Framework 4,
- elementy frameworka Prism.

Nad czym chcę się skupić w najbliższym czasie ?
- próba implementacji wzorca Repository dla środowiska asynchronicznego,
- wykorzystanie frameworka Async,
- stworzenie warstwy abstrakcji pomiędzy ViewModelem a serwisem,
- autoryzacja i autentykacja w WCF RIA Services

Z niewiadomych przyczyn (wrong Repository UUID) nie udało mi się dorzucić projektu prototypu do istniejącego SVN dla projektu Dziennik Zdrowej Diety dlatego też stworzyłem nowy.
Zinteresowanych zapraszam pod adres: http://devprototype.codeplex.com
Zachęcam do śledzenia moich poczynań :)

sobota, 9 października 2010

Przegląd Action (Blend SDK) w kontekście MVVM cz. 1

Dzisiaj chciałbym opisać akcje (Action) jakie znajdują się w Expression Blend w zakładce Assets -> Behaviors. Wiele z nich ułatwi wam życie pisząc aplikację Silverlight opartą o wzorzec projektowy MVVM.
Dla tych którzy nie posiadają Expression Blend, dostępne jest darmowe BlendSDK, które zawiera wszystkie funkcjonalności opisane poniżej. Link do Blend SDK -> LINK

CallMethodAction – Action, który pozwala na wywołanie dowolnej metody bezparametrowej na wskazanym obiekcie w momencie, gdy wystąpi zdarzenie na obiekcie źródłowym.
Poniżej przykład pokazujący jak wywołać metodę OnAddCommand() z ViewModel’u przy zmianie zaznaczonego elementu na kontrolce dataGrid.

MVVM: zachowanie te może posłużyć do wykonania logiki biznesowej interfejsu graficznego przy wystąpieniu odpowiedniego zdarzenia np. kontrolka DevExpress agDataGrid posiada metodę UnGroup(), która usówa wszystkie grupowania kolumn z grida. Podpinając to zachowanie do przycisku i ustawiając wyzwalacz na zdarzenie Click, możemy w całkiem zgrabny sposób wykorzystać powyższą metodę UnGroup() nie pisząc linijki kodu w CodeBehind naszego widoku.

UWAGA: Nie sugerujcie się możliwością bindowania MethodName ponieważ wskazanie metody spowoduje wystąpienie błędu. W MethodName należy wpisać nazwę metody jaką implementuje obiekt wskazany w TargetObject.


SketchFlow - nowy projekt


<sdk:DataGrid x:Name="dataGrid"
                 ItemsSource="{Binding Customers}"
                 AutoGenerateColumns="False">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectionChanged">
      <ei:CallMethodAction TargetObject="{Binding Mode=OneWay}"
                              MethodName="OnAddCommand" />
    </i:EventTrigger>
  </i:Interaction.Triggers>

ChangePropertyAction – Action, który pozwala na zmianę właściwości docelowego elementu w momencie wystąpienia zdarzenia na elemencie źródłowym.

MVVM: Na poniższym przykładzie pokazałem jak zmieniać wartości właściwości SelectedValue zawartej w ViewModel’u. Właściwość ta przechowuje aktualnie zaznaczony wiersz na kontrolce dataGrid.

UWAGA: Przy niektórych kombinacjach (np. Opacity) nie zawierzajcie w 100% Blend’owi i zerknijcie do pliku z XAML’em, ponieważ czasami źle zapisuje ustawione wartości i aplikacja się wywala (zamiast 0.55 ustawił wartość 0,55).


SketchFlow - nowy projekt


<sdk:DataGrid x:Name="dataGrid"
                 ItemsSource="{Binding Customers}"
                 AutoGenerateColumns="False">
  <i:Interaction.Triggers>
    <i:EventTrigger SourceObject="{Binding ElementName=dataGrid}"
                                EventName="SelectionChanged"
                                SourceName="dataGrid">
      <ei:ChangePropertyAction TargetObject="{Binding Mode=OneWay}"
                                  PropertyName="SelectedValue"
                                  TargetName="button"
                                  Value="{Binding SelectedItem,                        ElementName=dataGrid}">
     <ei:ChangePropertyAction.Ease>
      <BackEase EasingMode="EaseInOut"/>
     </ei:ChangePropertyAction.Ease>
      </ei:ChangePropertyAction>
    </i:EventTrigger>
  </i:Interaction.Triggers>

InvokeCommandAction – Action, który pozwala na uruchomienie komenty ICommand dla dowolnej kontrolki i na podstawie dowolnego zdarzenia.

MVVM: Moim zdaniem jest to jedna z najważniejszych Action patrząc pod kątem MVVM. Jak wiemy, Silverlight 4 wprowadził tzw. Commanding do podstaw frameworka (wcześniej można była skorzystać np. z Prism, który rozszerzał kontrolkę Button o możliwość korzystania z tej funkcjonalności). Niestety Commanding jest zaimplementowany tylko dla kontrolki Button (właściwości Commandi CommandParameter). Implementacja dla innych kontrolek oraz innych zdarzeń wymagała napisania odpowiednich Behaviors. InvokeCommandAction robi to z automatu i bez pisania nawet linijki kodu.

Poniższy przykład przedstawia wywołanie metody OnEditCommand(object obj) poprzez EditCommand typu ICommand wraz z przekazaniem do metody aktualnie zaznaczonego wiersza w dataGrid. Metoda jest wywoływana w momencie zaznaczenia wiersza w dataGrid.

Po zabawie z Reflectorem, zauważyłem, że posiadamy dwie opcje dowiązywania do ICommand:
1. Właściwość Command- związujemy się bezpośrednio z właściwością typu ICommand w naszym ViewModel'u,
2. Właściwość CommandName- związuje się do właściwości typu ICommand, wyszukując ją poprzez refleksję w skojarzonej z Action klasie.


SketchFlow - nowy projekt


<sdk:DataGrid x:Name="dataGrid"
                 ItemsSource="{Binding Customers}"
                 AutoGenerateColumns="False">
  <i:Interaction.Triggers>
    <i:EventTrigger EventName="SelectionChanged">
      <i:InvokeCommandAction Command="{Binding EditCommand, Mode=OneWay}"
                                           CommandParameter="{Binding SelectedItem, ElementName=dataGrid}" />
    </i:EventTrigger>
  </i:Interaction.Triggers>

W kolejnej części opiszę pozostałe Actions:
• ControlStoryboardAction
• GoToStateAction
• HyperlinkAction
• PlaySoundAction
• RemoveElementAction
• SetDataStoreValueAction

sobota, 2 października 2010

Dziennik Zdrowej Diety - Silverlight i zasoby na serwerze

Jak wiemy, aplikacje Silverlight działają po stronie klienta tak więc całość ściągana jest na komputer użytkownika. Z tego powodu głównym celem jaki nam przyświeca jest stworzenie w taki sposób aplikacji by plik wynikowy XAP był jak najmniejszy w celu wydajnego załadowania programu.

Dziś chciałbym przedstawić sposób dostępu aplikacji Silverlight (klient) do zasobów umieszczonych na serwerze.
Za to zadanie odpowiada klasa WebClient znajdująca się w przestrzeni nazw System.Net

Oto przykład:

private void LoadImageFromServer()
{
    this.wcImg = new WebClient();
    this.wcImg.OpenReadAsync(new Uri("../AppData/picture.jpg"),UriKind.RelativeOrAbsolute));
    this.wcImg.OpenReadCompleted += delegate(object sender, OpenReadCompletedEventArgs e)
    {
         if (e.Error == null)
         {
             using (Stream stream = e.Result)
             {
                 var image = new BitmapImage();
                 image.SetSource(stream);
                 Dispatcher.BeginInvoke(() =>
                 {
                      this.picPhoto.Source = image;
                      this.picPhoto.Width = image.PixelWidth;
                      this.picPhoto.Height = image.PixelHeight;
                 });
             }
         }
    };
    this.wcImg.DownloadProgressChanged += delegate(object sender,DownloadProgressChangedEventArgs e)
    {
         this.txtPercentageLoaded.Text = e.ProgressPercentage + "%";
    };
}

Program pobiera zdjęcie z katalogu AppData zamieszczonego w katalogu wirtualnym aplikacji.

Nic nie zostai na przeszkodzie by zamieścić wszystkie pliki graficzne bezpośrednio w projekcie Silverlight, jednakże należy pamiętać, że postępując w ten sposób, zwiększamy rozmiar naszego pliku XAP czego chcieliśmy uniknąć.

Powyższy sposób pozwala nam na utrzymanie optymalnego rozmiaru pliku XAP co jest równoważne z szybszym ściągnięciem go na maszynę kliencką oraz pozwala nam na swego rodzaju dynamikę tzn. możemy upload'ować pliki na serwer i mieć od razu do nich dostęp z poziomu aplikacji Silverlight.

środa, 22 września 2010

Sekwencja wywołań w asynchronicznym środowisku Silverlight cz. 2 - CoRutines

W dzisiejszej części przedstawię sposób wykorzystania corutines w Silverlight.
Corutines to mechanizm pozwalający na wykonywanie sekwencji kodu tzn. do puki nie skończy się działanie danego bloku kodu, program nie przejdzie dalej. W Silverlight, gdzie wszystko musi być wywoływane asynchronicznie tego typu mechanizm jest wręcz zbawieniem. Nie oznacza to, że od teraz wszelkiego rodzaju operacje będziesz wykonywał przy wykorzystaniu corutines. To by zabiło całą koncepcję technologii Silverlight, jednakże zdarzają się sytuacje gdzie do wykonania pewnego bloku kodu potrzebujesz dane będące wynikiem operacji poprzedzającego bloku kodu.
Domyślnie język C# nie wspiera corutines. Mechanizm ten można natomiast zbudować z elementów jakie dostarcza nam .NET.
Na rozwiązanie, które chcę zaproponować składa się:
- klasa wątków BackgroundWorker,
- interfejs IEnumerable,
- słówko yield.

Ponieważ większość z nas zetknęła się ze słówkiem yield na samym początku przygody z językiem C# (zapewne czytając jakieś opasłe tomisko) po czym przez resztę swojego komercyjnego / niekomercyjnego doświadczenia nie miała sposobności wykorzystać go (tak, generalizuję), postaram się na prostym przykładzie pokazać jego działanie.

class Program
{
    static void Main(string[] args)
    {
        foreach(var msg in Teksty())
        {
            Console.WriteLine(msg);
        }
        Console.ReadKey();
    }
 
    public static IEnumerable<string> Teksty()
    {
        yield return "Start";
 
        Console.WriteLine("Tutaj sobie coś wykonuję");
        Console.ReadKey();
 
        yield return "Drugi krok";
 
        Console.WriteLine("Kolejne czynności");
        Console.ReadKey();
        yield return "Zakończenie";
    }
}

Wyobraźmy sobie, że iterujemy pętlą foreach po tablicy string’ów { „A”, „B”, „C” }. Czyli każda kolejna iteracja bieże kolejny element tablicy. Dla przedstawionego poniżej przykładu tablica string’ów jest równoważna YieldTab() :

class Program
{
    static void Main(string[] args)
    {
        string[] normalTab = {"A", "B", "C"};
 
        foreach(var msg in normalTab)
        {
            Console.WriteLine(msg);
        }
        Console.ReadKey();
 
        foreach (var msg in YieldTab())
        {
            Console.WriteLine(msg);
        }
        Console.ReadKey();
    }
 
    public static IEnumerable YieldTab()
    {
        yield return "A";
        yield return "B";
        yield return "C";
    }
}


Analizując przykład pierwszy bardzo łatwo wychwycić różnicę. Przy kolejnej iteracji po IEnumerable, program wykonuje wszystko co się znajduje pomiędzy słówkami yield oraz zwraca wartość elementu.
Myślę, że koncepcja działania jest mniej więcej zrozumiała.

Zważając na powyższy przykład opiszę słowno muzycznie jak dziła mechanizm corutines.
1. Tworzymy interfejs pomocniczy IResult

public interface IResult
{
    void Execute();
    event EventHandler Completed;
}

Zadaniem metody Execute() będzie wywołanie dowolnej metody przekazanej to mechanizmu Corutines, natomiast zdarzenie Completed będzie informowało o zakończeniu działania przekazanej metody.

2. Tworzymy klasę implementującą powyższy mechanizm zintegrowany z klasą BackgroundResult

public class BackgroundResult : IResult
{
    private readonly Action action;
    public BackgroundResult(Action action)
    {
        this.action = action;
    }
 
    public void Execute()
    {
        var backgroundWorker = new BackgroundWorker();
        backgroundWorker.DoWork += (e, sender) => action();
        backgroundWorker.RunWorkerCompleted += (e, sender) => Completed(this, EventArgs.Empty);
        backgroundWorker.RunWorkerAsync();
    }
    public event EventHandler Completed;
}


Klasa ta w konstruktorze przyjmuje Action będący delegatem, który nie przyjmuje żadnych parametrów i nie zwraca, żadnej wartości. W skrócie, w konstruktorze przekazujemy referencję do metody odpowiadającej wymaganiom powyższego delegata.

Metoda Execute() uruchamia przekazaną metodę w trybie asynchronicznym i nasłuchuje moment jej zakończenia poprzez zdarzenie RunWorkerCompleted, który następnie wzbudza zdarzenie Completed.

3. Tworzymy klasę, której zadaniem będzie iteracja po IEnumerable w sposób jaki jest właściwy dla opisywanego mechanizmu Corutines.

public class ResultEnumerator
{
    private readonly IEnumerator _enumerator;
    public ResultEnumerator(IEnumerable children)
    {
        _enumerator = children.GetEnumerator();
    }
    public void Enumerate()
    {
        ChildCompleted(null, EventArgs.Empty);
    }
    private void ChildCompleted(object sender, EventArgs args)
    {
        var previous = sender as IResult;
        if (previous != null)
            previous.Completed -= ChildCompleted;
        if (!_enumerator.MoveNext())
            return;
        var next = _enumerator.Current;
        next.Completed += ChildCompleted;
        next.Execute();
    }
}


Klasa przyjmuje w konstruktorze kolekcję elementów implementujących interfejs IResult, a następnie pobiera enumerator tej kolekcji.
MoveNext() enumeratora można przyrównać do pojedyńczego odczytania z pętli np. foreach, gdzie wskazany element znajduje się we właściwości Current. Kolejne wywołanie metody MoveNext() powoduje przesunięcie obecnego elementu o jeden dalej.
Wywołując metodę Enumerate() uruchamiamy swoistą funkcję rekurencyjną ChildCompleted(object sender, EventArgs args), gdzie zakończone zadanie wywołuje ponownie tą samą funkcję.
Proces kończy się w momencie osiągnięcia kresu przekazanej kolekcji.

A tak to działa w praktyce:

public partial class MainPage : UserControl
{
    private AutoResetEvent trigger = new AutoResetEvent(false);
 
    public MainPage()
    {
        InitializeComponent();
        this.Loaded += MainPage_Loaded;
    }
 
    void MainPage_Loaded(object sender, RoutedEventArgs e)
    {
        var enumerator = new ResultEnumerator(CallWebServiceMethods());
        enumerator.Enumerate();
    }
    public IEnumerable CallWebServiceMethods()
    {
        yield return new BackgroundResult(() =>
        {
            var serwis = new LocalSerwis.Service1SoapClient();
            serwis.MetodaPierwszaCompleted += (sender, e) =>
            {
                Dispatcher.BeginInvoke(() => MessageBox.Show(e.Result));
                trigger.Set();
            };
            serwis.MetodaPierwszaAsync();
            trigger.WaitOne();
        });
        yield return new BackgroundResult(() =>
        {
            var serwis = new LocalSerwis.Service1SoapClient();
            serwis.MetodaDrugaCompleted += (sender, e) =>
            {
                Dispatcher.BeginInvoke(() => MessageBox.Show(e.Result));
                trigger.Set();
            };
            serwis.MetodaDrugaAsync("Ala ma kota");
            trigger.WaitOne();
        });
    }
}

Gdzie mój serwis ma postać:
public class Service1 : System.Web.Services.WebService
{
    [WebMethod]
    public string MetodaPierwsza()
    {
        return "Wynik Metody Pierwszej";
    }
    [WebMethod]
    public string MetodaDruga(string msg)
    {
        char[] chars = msg.ToCharArray();
        Array.Reverse(chars);
        return new string(chars);
    }
}

Ponieważ zapytania do webserwisu są również asynchroniczne, skorzystałem z klasy AutoResetEvent, która potrafi zastopować aktualny wątek (metoda WaitOne())aż do momentu uruchomienia metody Set(), która go wzbudza.
Wywołanie postaci Dispatcher.BeginInvoke(Action a) pozwala na wykonanie kodu w wątku głównym UI aplikacji Silverlight.

Źródła do przykładu znajdują się pod tym adresem: http://dhd.codeplex.com/releases/view/52747
Na zakończenie chciałbym wspomnieć, że powyższe rozwiązanie nie jest mojego autorstwa tylko Pana Roba Eisenberga, który jest twórcą frameworka MVVM Caliburn (http://caliburn.codeplex.com/).

czwartek, 16 września 2010

Sekwencja wywołań w asynchronicznym środowisku Silverlight cz. 1

Przes ostatnie kilka dni męczyłem się niemiłosiernie z asynchronicznością Silverlighta. Pomimo wielkiego entuzjazmu jaki żywię do tej technologii, pojawiły się we mnie pierwsze wątpliwości. Zacząłem się zastanawiać czy, aby na pewno Silverlight nadaje się do biznesowych rozwiązań czy też obiekt Session z XPO DevExpressa (ORM firmy DevExpress) jest w pełni dopracowany do wymagań jakie stawia Silverlight. A może to ja jestem w tym wszystkim najsłabszym ogniwem ? To ostatnie jest wielce prawdopodobne. Dosyć tego bajdużenia.

O co w tym wszystkim chodzi?

Jeśli Twój proces biznesowy wymaga byś odczytał obiekt ze źródła danych, a następnie wykorzystał informacje w nim zawarte do odczytania kolejnych danych, będziesz potrzebował sekwencyjnych wywołań asynchronicznych.
Rozwiązanie tego typu (metoda Initialize()):

public class Sample
{
    private FirstObject firstObj;
    private SecondObject secondObj;
 
    public void GetFirstObjectAsync(int id)
    {
        var service = new DummyWebService.ServiceSoapClient();
        service.GetFirstObjectCompleted += (sender, e) =>
        {
            firstObj = new FirstObject
                           {
                                Id = e.Result.Id,
                                ChildId = e.Result.ChildId
                           };
        };
        service.GetFirstObjectAsync(id);
    }
 
    public void GetSecondObjectAsync(int id)
    {
        var service = new DummyWebService.ServiceSoapClient();
        service.GetSecondObjectCompleted += (sender, e) =>
        {
            secondObj = new SecondObject
            {
                Id = e.Result.Id,
            };
        };
        service.GetSecondObjectAsync(id);
    }
 
    public void Initialize()
    {
        GetFirstObjectAsync(1);
        GetSecondObjectAsync(firstObj.Id);
    }
}


nie zda egzaminu ponieważ metody są wykonywane asynchronicznie co oznacza, że nie wiesz, która z nich wykona się pierwsza. W naszym przypadku wymogiem jest by najpierw wykonała się metoda GetFirstObjectAsync(1), a po otrzymania rezultatu, wykonała się metoda GetSecondObjectAsync(firstObj.Id).
Wywołanie tego w synchroniczny sposób spowoduje zablokowanie aplikacji.

Najprostszym rozwiązaniem jest coś takiego:


public class Sample
{
    private FirstObject firstObj;
    private SecondObject secondObj;
 
    public void GetFirstObjectAsync(int id)
    {
        var service = new DummyWebService.ServiceSoapClient();
        service.GetFirstObjectCompleted += (sender, e) =>
        {
            firstObj = new FirstObject
                           {
                                Id = e.Result.Id,
                                ChildId = e.Result.ChildId
                           };
                           GetSecondObjectAsync(firstObj.Id);
        };
        service.GetFirstObjectAsync(id);
    }
 
    public void GetSecondObjectAsync(int id)
    {
        var service = new DummyWebService.ServiceSoapClient();
        service.GetSecondObjectCompleted += (sender, e) =>
        {
            secondObj = new SecondObject
            {
                Id = e.Result.Id,
            };
        };
        service.GetSecondObjectAsync(id);
    }
 
    public void Initialize()
    {
        GetFirstObjectAsync(1);
    }
}


W ten sposób osiągniemy zamierzony efekt.
Problem pojawia się w momencie jak takich metod jest więcej. Zarządzanie takim kodem pozostawia wiele do życzenia – blah :/ ).

UWAGA:
Próba wywoływania asynchronicznie metody, wykorzystując delegata oraz jego metodę BeginInvoke() w Silverlight zakończy się wystąpieniem wyjątku NotSupportedException. Nazwa wyjątku mówi sama za siebie.

W kolejnej części postaram się pokazać jak wykonywać sekwencyjnie metody asynchroniczne przy wykorzystaniu corutines (http://pl.wikipedia.org/wiki/Wsp%C3%B3%C5%82program).

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.

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.

niedziela, 5 września 2010

Dziennik Zdrowej Diety – Wzorzec MVVM – Commanding

W poprzedni wpisie przedstawiłem podstawową koncepcję wzorca projektowego MVVM oraz pokazałem jak złączyć widok (View), będący graficzną reprezentacją naszej logiki biznesowej wraz modelem (Model) przy wykorzystaniu klasy pośredniczącej (ViewModel).
Brakującym elementem w przedstawionym przykładzie była implementacja zdarzeń jakie oferują nam kontrolki.
Z pomocą przychodzi nam wzorzec projektowy Command, który doczekał się swojej implenetacji w Silverlight 4.
Ogólna koncepcja działania polega na tym by udostępnić możliwość obsługi zdarzeń w ViewModel’u. W obecnej chwili zaimplementowane są tylko zdarzenia Click dla kontrolek będących implementacją/rozszerzeniem klasy ButtonBase oraz kontrolki HyperLink. Ograniczenia te jednak można przekroczyć poprzez własną implementację tego wzorca.
By wykorzystać tę nową funkcjonalność należy stworzyć klasę dziedziczącą po interfejsie ICommand (namespace System.Windows.Input)
namespace System.Windows.Input
{
    public interface ICommand
    {
        bool CanExecute(object parameter);
        void Execute(object parameter);
        event EventHandler CanExecuteChanged;
    }
}

CanExecute – metoda określająca status czy podpięta komenda może być wykonana,
Execute – komenda wywoływana w wyniku wywołania zdarzenia (np. wspomniany Click),
CanExecuteChanged – zdarzenie wywoływane w momencie zmiany statusu wywołania komency. Mechanizm bindujący kod XAML podpina się do tego zdarzenia tak więc możecie zauważyć dziwną zależność w postaci : nie podpinacie nic do zdarzenia, a jednak metody Execute oraz CanExecute oddziaływują na siebie.
Poniżej przedstawiam moją własną implementację wykorzystania wzorca Command.
public class MyCommand : ICommand
{
    private readonly Predicate<object> canExecute;
    private readonly Action<object> executeAction;
    
    public MyCommand(Action<object> executeAction, Predicate<object> canExecute)
    {
         this.canExecute = canExecute;
         this.executeAction = executeAction;
    }
   
    public bool CanExecute(object parameter)
    {
         if (canExecute != null)
             return canExecute(parameter);
    
         return true;    
    }
    
    public void Execute(object parameter)
    {
         if (executeAction != null)
             executeAction(parameter);
    }
    
    public event EventHandler CanExecuteChanged;
}

Klasa implemencująca interfejs ICommand korzysta z :
- delegatu Action<object> pozwalającego mi na oddelegowanie wykonania komendy do własnej implementacji metody Execute znajdującej się w ViewModel,
- delegatu Predicate<object> pozwalającego na oddelegowanie sprawdzenia testu możliwości wykonania komendy (CanExecute) do własnej implementacji metody znajdującej się w ViewModel.
Poniżej znajduje się część klasy ViewModel wykorzystującej powyższą komendę MyCommand
public class MyViewModel : INotifyPropertyChanged
{
    private readonly MyModel myModel;
    
    public MyViewModel()
    {
        myModel = new MyModel();
        ButtonCommandClick = new MyCommand(ButtonClickExecute, CanExecuteButtonClick);
    }
    
    private bool CanExecuteButtonClick(object obj)
    {
        if (!string.IsNullOrEmpty(Nickname) && Nickname == "Lukas")
            return false;
    
        return true;
    }
    
    private void ButtonClickExecute(object obj)
    {
        Description = String.Format("Witaj {0} ! To jest implementacja interfejsu ICommand", Nickname);
    }
    
    public string Nickname
    {
        get { return myModel.NickName ?? string.Empty; }
        set
        {
           if (myModel.ValidateNickName(value))
           {
               myModel.NickName = value;
               FirePropertyChanged("Nickname");
           }
        }
   }
    
   public ICommand ButtonCommandClick { get; set; }

By dopełnić dzieła wystarczy tylko zbindować właściwość Command klasy Button z właściwością ButtonCommandClick znajdującą się w naszy ViewModel’u.

<Button Content="Say Hello!" 
           Command="{Binding 
                     Source={StaticResource myViewModel},
                     Path=ButtonCommandClick}" />

Powyższy przykład jest najprostszą postacią wykorzystania możliwości jakie daje nam wzorzezc Command. Pozwala on jednak na postawienie pierwszych kroków przy zaznajamianiu się z możliwościami MVVM.
Przykładową aplikację możecie pobrać TUTAJ

wtorek, 17 sierpnia 2010

Dziennik Zdrowej Diety – Wzorzec MVVM – Koncepcja

Aplikacja Dziennik Zdrowej Diety jest udostępniona na codeplex, przez co nad rozwojem projektu może pracować wiele osób. Wśród tych osób znajdą się zapewne programiści oraz osoby odpowiedzialne za projektowanie interfejsu użytkownika. By usprawnić pracę pomiędzy te dwie profesje, zastosowałem wzorzec projektowy MVVM (Model-View-ViewModel).
Czym jest wzorzec MVVM ? To wzorzec, stworzony na potrzeby technologii WPF oraz Silverlight, którego zadaniem jest odspearowanie od siebie kolejnych warstwy aplikacji tj.



View – warstwa prezentacji, czyli plik xaml przedstawiający widok formatki z umieszczonymi na niej kontrolkami. Widokiem może być również pojedyńcza kontrolka użytkownika (UserControl).
Rzeczą charakterystyczną takiej formatki w porównaniu do zwykłaej aplikacji nie wykorzystującej wzorca MVVM (lub pokrewnych) jest fakt, że w code behind formatki nie znajduje się żaden kod związany z logiką biznesową. Dopuszczalne jest posiadanie w code behind kodu operującego na interfejsie użytkownika (nie mającego nic wspólnego z logiką biznesową).
Model – model opisuje naszą logikę biznesową. Składa się on obiektów biznesowych przybierających najczęściej postać obiektów POCO ang. Plain Old CLR Object, czyli klas zawierających dane (w postaci property) oraz logikę biznesową (walidatory danych). Model jak i View nie wiedzą o swoim istnieniu.
ViewModel – to obiekt będący łącznikiem pomiędzy modelem (Model), a widokiem (View). To właśnie ten obiekt odpowiada za pobranie właściwych informacji ze źródła danych, udostępnienia ich w odpowiedniej postaci do widoku (Converter) oraz przekazania zaktualizowanych informacji spowrotem do źródła danych. Interakcja widoku z ViewModel odbywa się za pomocą funkcjonalności Bindowania oraz wsparcia wzorca projektowego Command.


Przykład:

Na potrzeby przykładu stworzyłem prosty model zawierający 2 właściwości i jedną walidację.
namespace SimpleMVVM
{
    public class MyModel
    {
        public string NickName { get; set; }
        public string Description { get; set; }
        
        public bool ValidateNickName(string nickname)
        {
            return string.IsNullOrWhiteSpace(nickname);
        }
    }
}

Kolejnym elementem odgrywającym ważną rolę jest ViewModel, który pobiera dane z naszego modelu i udostępnia je na zewnątrz

public class MyViewModel : INotifyPropertyChanged  
    {
        private readonly MyModel myModel;

        public MyViewModel()
        {
            myModel = new MyModel();
        }

        public string Nickname
        {
            get { return myModel.NickName ?? string.Empty; }
            set 
            {
                if (myModel.ValidateNickName(value))
                {
                    myModel.NickName = value;
                    FirePropertyChanged("Nickname");
                }
            }
        }

        public string Description
        {
            get { return myModel.Description; }
            set
            {
                myModel.Description = value;
                myModel.NickName += " Hello!";
                FirePropertyChanged("Nickname");
                FirePropertyChanged("Description");
            }
        }

        protected void FirePropertyChanged(string property)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(property));
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
    }

W View deklarujemy nasz ViewModel w następujący sposób
<UserControl.Resources>
        <vm:MyViewModel x:Key="myViewModel" />
        <vm:MyConverter x:Key="myConverter" />
    </UserControl.Resources>

Dodatkowo zadeklarowaliśmy konwerter MyConverter, który ma postać

public class MyConverter : IValueConverter
    {
        public MyConverter()
        {
            
        }

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var msg = value as string;
            if (!string.IsNullOrWhiteSpace(msg) && msg.Contains("Hello!"))
                msg = msg.Replace("Hello!", "Hello World!");
            return msg ?? String.Empty;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return value;
        }
    }

Na koniec bindujemy kontrolki z View z naszym ViewModel
<TextBox Text="{Binding 
                            Source={StaticResource myViewModel}, 
                            Path=Nickname, 
                            Mode=OneWay}"
                 Grid.Column="1"
                 Grid.Row="0"
                 VerticalAlignment="Center"" />

<TextBox Text="{Binding 
                            Source={StaticResource myViewModel}, 
                            Path=Description, 
                            Mode=TwoWay, 
                            Converter={StaticResource myConverter}}"
                 Grid.Column="1"
                 Grid.Row="1"
                 VerticalAlignment="Center" />


Działanie:
Pierwszy TextBox jest zbindowany z właściwością Nickname w MyViewModel. Jest to bindowanie typu OneWay co oznacza, że widok pobiera wartości z ViewModel, a ViewModel nic nie wie o ewentualnych zmianach wartości tego TextBox’a.

Drugi TextBox jest zbindowany z właściwością Description w MyViewModel. Jest to bindowanie typu TwoWay co oznacza, że zarówno widok, jak i ViewModel są informowane o zmianie wartości zbindowanej właściwości. Dodatkowo wartość wysyłana do TextBox’a podlega konwersji w klasie MyConverter.

Efekt:
• wpisujemy w pierwszego TextBox’a tekst „Ala ma kota” – nic się nie dzieje,
• wpisujemy w drugiego TextBox’a tekst „Hello!”
• ustawia się właściwość Description instancji klasy myModel na „Hello!”
• ustawia się właściwość NickName instancji klasy myModel na „ Hello!” (zauważ, że pomimo wpisania w pierwszego TextBox’a wartości „Ala ma kota”, nie ma jej po stronie ViewModel)
• odpalane jest powiadomienie o zmianie właściwości „Description”
• pobierana jest wartość właściwości „Description” z uwzględnieniem konwertera,
• pobierana jest wartość właściwości „Nickname”

Kod źródłowy

Ktoś pewno zada pytanie „Poco to wszystko ?”. Otóż korzystając ze wzorca MVVM poza separacją widoku (projektant) od całej reszty (programista) mamy szereg dodatkowych plusów tj. możliwość stosowania testów jednostkowych, uproszczone zarządzanie kodem, schemat działania pozwalający na systematyczny rozwój aplikacji oraz łatwe wdrożenie nowych programistów do projektu.

Powyższy opis ma na celu ogólne przedstawienie koncepcji tworzenia oprogramowania przy wykorzystaniu wzorca MVVM. Ma to posłużyć większemu zrozumieniu kolejnych kroków związanych z rozwojem aplikacji „Dziennik Zdrowej Diety”.
Po więcej szczegółów odsyłam do artykułu zamieszczonego w MSDN Magazine (http://msdn.microsoft.com/en-us/magazine/dd419663.aspx).

Wartym wspomnienia jest fakt, że wzorzec MVVM zyskał na popularności dzięki czemu wielu programistów zaczęło tworzyć własne rozszerzenia tego wzorca.
I tak np. Calcium wspiera funkcjonalność Undo/Redo, ale jest opracowany tylko dla WPF podczas gdy Cinch nadaje się zarówno do WPF jak i Silverlight i dodatkowo współpracuje z MEF (Managed Extensibility Framework).
By przyjrzeć się zestawieniu frameworków MVVM odysłam pod poniższy adres:
(http://www.japf.fr/silverlight/mvvm/index.html)

wtorek, 3 sierpnia 2010

Dziennik Zdrowej Diety – Zarządzanie zasobami

Budując aplikację wielo- modułową (np. Przy wykorzystaniu Prism, MEF) musimy zadbać o zgrabne zarządzanie plikiem przechowującym nasze style, szblony.
Poniżej przedstawiam krótki przykład jak sobie z tym poradzić w Silverlight.

1) Załóżmy, że posiadamy solucję składającą się z kilku projektów.



Każdy z widoków wykorzystuje zestaw kontrolek, na których chcielibyśmy skorzystać z tego samego stylu.

2) Tworzymy nowy projekt Silverlight Class Library, a następnie dodajemy nowy element Silverlight Resource Dictionary (nasz będzie się nazywał DictStyles.xaml)













3) Wszystkie style zapisujemy w tym pliku, a jeśli chcemy z nich skorzystać to do każdego widoku dodajemy sekcję:



gdzie ResourcesStore to nazwa assembly w której znajduje się nasz plik ze stylami, component to słówko stałe, a DictStyles.xaml to nazwa naszego słownika
(po szczegóły na temat odwoływania się do zasobów odsyłam pod ten link: Channel9- Mike Taulty )

4) Teraz tylko wystarczy dodać do każdego projektu referencję do naszej dll’ki i cieszyć się z efektu.

(UWAGA: bardzo ważne by dodać referencję do dll’ki ze stylami w głównym projekci – nawet jeśli nie posiadacie tam nic poza Shell.xaml, App.xaml. Ja zmarnowałem kilka godzin zanim rozgryzłem dlaczego moje moduły w runtime nie widzą styli :/ ).

Edycja 15-08-2010.
By nie rozbijać wątku na dwa tematy dopiszę coś więcej na temat zarządzania zasobami.

Istnieje kilka sposobów dostępu do zasobów:

"myImage.jpg" - aplikacja poszukuje obrazek w assembly umieszczonym w pliku XAP. Jeśli go nie znajdzie to przeszukuje katalog ClientBin. Plik musi mieć ustawioną właściwość:
Build Action = Resource

"/myImage.jpg" - aplikacja poszukuje obrazka w pliku XAP. Jeśli go nie znajdzie to przeszukuje katalog ClientBin. Plik musi mieć ustawioną właściwość:
Build Action = Content

"/MyAssembly;component/Images/myImage.jpg" - aplikacja poszukuje obrazka w katalogu Images znajdującym się w assembly MyAssembly osadzonym w pliku XAP.

"/MyAssembly;component/Fonts/MyFont.ttf#My Font" - aplikacja poszukuje czcionkę w katalogu Fonts znajdującym się w assembly MyAssembly osadzonym w pliku XAP.

"/MyAssembly;component/Fonts/Fonty.zip#My Font" - analogicznie jak powyżej z tym, że przeszukiwane jest archiwum ZIP pod kątem wystąpienia czcionki o nazwie "My Font" (nie mylić z nazwą pliku czcionki).

poniedziałek, 2 sierpnia 2010

Dziennik Zdrowej Diety – SketchFlow

SketchFlow to dodatek do aplikacji Microsoft Expression Blend (wersja 3 i 4). Głównym zadaniem tego dodatku jest wspomożenie programisty przy tworzeniu prototypu interfejsu użytkownika.

Używając SketchFlow możesz:
• tworzyć formatki wykorzystując zestaw kontrolek WPF, Silverlight, SketchFlow,
• tworzyć animamacje i przejścia za pomocą zachowań pomiędzy stanami kontrolek,
• nawigować pomiędzy kolejnymi formatkami,
• wykorzystać przykładowe dane do zasymulowania rzeczywistej aplikacji,
• publikować stworzone rozwiązanie na serwer SharePoint.

W wielkim uproszczeniu SkechFlow mogę porównać do takiego napakowanego PowerPoint’a z większymi możliwościami integracji w tworzone rozwiązania.
Na stronie Microsoft Expression znajdziecie Expression Studio 4 Ultimate w wersji Trial pozwalającej na zabawę z całym pakietem przez 60 dni.

W czym ma nam pomoć SketchFlow ? Jest to szybki sposób na pokazanie klientowi końcowemu jak będzie wyglądał produkt końcowy.
Korzystając z uwag klienta, bardzo szybko możemy nanieść zmiany na taki prototyp dzięki czemu zaoszczędzi nam to wiele nieporozumień przy kolejnej prezentacji. (etapy tworzenia oprogramowania).

Poniżej zamieszczam krótki tutorial mający za zadanie wprowadzenie was w Świat SketchFlow.

1) Uruchamiamy Expression Blend 4. Z menu File -> New Project … wybieramy Silverlight SketchFlow Application

SketchFlow - nowy projekt



2) W wyniku kroku pierwszego otrzymujemy:



Zakładki:
Project – przedstawia nam eksplorator naszej solucji składającej się z 2 projektów. Pojedynczy Szkic (Sketch), na który będziemy tworzyć nasz interfejs, jest kontrolką UserControl w formacie XAML (Screen1.xaml).
SketchStyles.xaml pełni rolę słownika styli zdefiniowanych w aplikacji.
Sketch.Flow to najważniejszy plik naszego projektu zawierający logikę połączeń pomiędzy screen’ami, zachowania kontrolek, etc.

Resources – w zakładce tej znajdują się pliki xaml z definicjami naszych styli. Tak jak w Silverlight / WPF może to być globalny App.xaml, poszczególna formatka, czy też plik odrębny (tak jak w naszym wypadku SketchStyles.xaml)

Data – zakładka w której możemy stworzyć przykładowe dane wykorzystywane do prezentacji na szkicach. Istnieje kilka opcji kreowania takich danych:
• import z XML,
• tworzenie obiektu na podstawie klasy (w formacie xaml),
• tworzenie obiektu od podstaw.

SketchFlowMap – przedstawia mapę naszych szkiców oraz powiązania istniejące pomiędzy nimi.

Objects and Timeline – struktura kontrolek zamieszczonych na szkicu.

Properties – właściwości zaznaczonego obiektu na szkicu.

SketchFlow Animation – zakładka odpowiedzialna za tworzenie animacji na szkicu.

3) Stworzymy teraz prostą mapę naszej aplikacji. W SketchFlowMap z menu kontekstowego wybieramy Create a screen. Dodawanie kolejnych screenów tworzy nowe obiekty xaml w projekcie.


Zielony trójkącik przy screenie oznacza, który screen jest ustawiony jako startowy.

4) Stwórzmy w takim razie stronę startową naszej aplikacji.



5) Zapewne chcielibyśmy wykorzystać powyższe przyciski, logo i miejsce na konteks na pozostałych stronach. W tym celu zaznaczamy interesujące nas kontrolki i wybieramy z menu kontekstowego Make Into Component Screen (tworzy nam to szablon do wykorzystania na innych szkicach – można go porównać do Master.Page z ASP.NET).
6) W SketchFlowMap pojawił się nam nowy element oznaczony na zielono. Łączymy go z każdym szkicem, na którym chcemy z niego korzystać.



a następnie nanosimy na każdy szkic nową kontrolkę (RepeatedMenu).



Teraz wystarczy tylko poustawiać odpowiednio marginesy i kotwice kontrolki by na każdym szkicu szablon wyglądał identycznie.

7) Kolejną rzeczą jaką się zajmiemy będzie ustawienie nawigacji wykorzystując przyciski na szablonie. By tego dokonać, wchodzimy do kontrolki reprezentującej nasz szablon (RepeatedMenu), zaznaczamy przycisk i z menu kontekstowego wybieramy Navigate To, a następnie wskazujemy interesujący nas szkic.




























8) W tym momencie nasze menu powinno działać poprawnie, odwołując do żądanych przez nas stron.
9) Kolejną funkcjonalnością wartą zbadania jest możliwość tworzenia przykładowych danych pozwalających na zasymulowanie produkcyjnej wersji aplikacji.

W celu stworzenia przykładowych danych przechodzimy do zakładki Data, a następnie wybieramy, Create Sample Data -> New Sample Data. Blend zapyta się nas jak mamy nazwać przykładowe źródło danych i gdzie je przetrzymywać: Projekt, otwarty dokument. Proponują tą pierwszą opcję.






































Jak zauważyliśmy na powyższym rysunku, Blend stworzył źródło danych zawierające kolekcję składającą się z dwóch właściwości. Klikając dwukrotnie możemy zmienić nazwę właściwości. Ikonka po prawej stronie właściwości pozwala na zmianę typu danych. Dodanie MySampleDataSource spowodowało dodanie nowych obiektów do projektu (zawartość katalogu SampleData).

10) Po kilku modyfikacjach MySampleDataSource, klikamy w pierwszą ikonkę przy Collection -> Edit Sample Values. Aplikacja automatycznie generuje przykładowe dane na podstawie typu właściwości oraz jej formatowania.



11) Pozostaje nam już tylko zbindowanie stworzonego źródła danych z kontrolką (w naszym wypadku będzie do ListBox).

Bindowanie ListBox.ItemSource z kolekcją przygotowanych danych.



Bindowanie elementów ListBoxa (ItemTemplate.DataTemplate)



12) Efekt końcowy możemy zaobserwować po naciśnięciu F5



13) Warto zauważyć, że SketchFlow udostępnia nam szereg funkcjonalności pozwalających na nanoszenie notatek bezpośrednio przez klienta lub/i podczas prezentacji. Zakładka MY FEEDBACK zamieszczona na powyższym rysunku posiada narzędzia do zaznaczania oraz pozostawiania krótkich notatek wraz z możliwością eksportu do pliku by w późniejszym etapie przedstawić uwagi projektantowi.



14) W celu odtworzenia zapisanych uwag/notatek od klienta, wybieramy menu Windows -> SketchFlow FeedBack. Pojawia się nowa zakładka do której możemy wczytać uwagi (ikonka + po prawej stronie). Efektem takiego zabiegu jest szkic naszej formatki z wszystkimi uwagami naniesionymi podczas prezentacji.



15) SketchFlow posiada jeszcze jedną przydatną funkcję pozwalającą na łatwe dokumentowanie naszej pracy. Jest to eksport do Microsoft Word.
Wybieramy z menu File – Export to Microsoft Word ...



Mam nadzieję, że po krótce udało mi się przedstawić koncepcję SketchFlow, a powyższy tutorial pozwoli wam zrobić pierwsze kroki z tym interesującym narzędziem.

Dziennik Zdrowej Diety - Wprowadzenie

Rozpoczął się sierpień, a wraz z nim start projektu Dziennik Zdrowej Diety (ang. Diary of Healthy Diet – dla uproszczenia będę go nazywał DHD).
Garść wymagań funkcjonalnych (płynno- muzycznie ;-) ):
System umożliwia każdemu odwiedzającemu na przegląd aktualnych news’ów, przegląd publicznych profili użytkowników za pomocą wyszukiwarki, przegląd zamieszczonych diet oraz możliwość założenia własnego profilu i kontaktu z osobą odpowiedzialną za stronę.
W systemie będą dwa rodzaje użytkowników: administratorzy oraz zwykli użytkownicy.

Rola administratora:
- zarządzanie news’ami,
- zarządzanie zamieszczonymi dietami,
- zarządzanie profilami zwykłych użytkowników.

Rola Zwykłych użytkowników:
- możliwość założenia profilu użytkownika zawierającego kilka sekcji:
- dane kontaktowe na temat użytkownika (awatar, nickname, email, widoczność profilu etc.),
- dane na temat zdrowia (waga, wzrost, karnacja, wiek, płeć),
- dane na temat kondycji użytkownika (rodzaj pracy, aktywności fizyczne, długość snu).
- informacje o stosowanej diecie (nazwa diety, fazy diety, godziny posiłków etc.)
- informacje na temat postępów (wykres BMI, zmiany wagi),
- dziennik.
- możliwość zarządzania własnymi komentarzami,
- możliwość komentowania publicznych profili innych użytkowników,
- możliwość przeglądania zestawienia publicznych profili użytkowników jak również poszczególnych profili.

Czyli z grubsza skupię się na stworzeniu prostego CMS’a, rejestracji użytkowników i przeglądaniu efektów ich pracy nad sobą.
W kolejnej części wykorzystam SketchFlow z aplikacji Microsoft Expression Blend 4 by zaprezentować prototyp tworzonego rozwiązania.

Uwaga: Osoby, które czytają książkę Nick’a Lecrenski’ego „Silverlight 4: Problem - Design – Solution” mogą dostrzec podobieństwo dotyczące projektu jaki sobie wymyśliłem. Zapewniam, że to czysty zbieg okoliczności, choć nie ukrywam faktu, że będę posiłkował się wiedzą zaczerpniętą ze wspomnianej pozycji.

poniedziałek, 26 lipca 2010

Dziennik Zdrowej Diety - Projekt

Diary of Healthy Diet - Dziennik Zdrowej Diety

Do powstania tego projektu zainspirował mnie konkurs Macieja Aniserowicza (Konkurs), a także zmiana pracy i rozpoczęcie przygody z Silverlight na poważnie.

Założenia biznesowe jakie sobie postawiłem to stworzenie aplikacji w technologii Silverlight pozwalającej ludziom na zapoznanie się z najpopularniejszymi metodami odchudzania się (mam ślub stąd ten pomysł ;) ). Zainteresowany użytkownik będzie mógł założyć własny dziennik, w którym będzie codziennie wpisywał efekty pracy nad sobą poczynając od godzin posiłków, składu posiłków, ilości posiłków poprzez własne wymiary (biceps, klatka, pas, biodra etc.), kończąc na aktywności fizycznej (siłownia, jogging). Zainteresowane osoby będą mogły udostępnić swój dziennik publicznie dzięki czemu wraz z czasem utworzy się duża baza danych na temat efektywności danej diety w porównaniu do wieku, sylwetki, płci etc.

Technologie jakie mają mi pomóc w powyższy zadaniu to:
- .NET Framework 4.0,
- Silverlight 4,
- Prism,
- MVVM,
- Unity,
- log4net,
- Entity Framework 4,
- Expression Blend 4.
- WCF RIA Services,
- PivotViewer.

W większości są to dla mnie nowe tematy tak więc postaram się podzielić z wami własnymi doświadczeniami.

Projekt jest udostępniony pod adresem: DHD Project

Zapraszam do śledzenia moich poczynań :-)