niedziela, 14 listopada 2010

State Pattern

State Pattern - zgodnie z definicją: pozwala na zmianę zachowania obiektu przez zmianę jego stanu wewnętrznego.

Spójrzmy na diagram:

Context - klasa, która może posiadać różne stany
State - definiuje wspólny interfejs dla wszystkich stanów, dzięki temu są wymienne.
ConcreteState - definiuje konkretną definicję stanu.

Działanie wzorca jest bardzo proste. Wywołując metodę Handle(), wykorzystywana jest odpowiednia implementacja stanu.

Patrząc na powyższy diagram, można odnieść wrażenie, że definicja wzorca jest praktycznie taka sama jak w przypadku wzorca Strategy. Jest jednak mała różnica między tymi dwoma wzorcami:
State Pattern:
- zbiór zachować opakowanych klasami
- w danym momencie klasa odnosi się do jednego z tych stanów
- klient nie zna aktualnego (lub bardzo mało wie) stanu obiektu - nie definiuje go jawnie
Strategy Pattern:
- klient sam definiuje obiekt strategi który jest wykorzystywany w operacji

Możemy także podejść do tego od innej strony:
Mówiąc Strategy Pattern myślmy o zastępstwie dla dziedziczenia.
Mówiąc State Pattern myślmy o instrukcji if/switch w naszej klasie i wielu sprawdzanych warunkach.

Na koniec oczywiście przykład wykorzystania wzorca:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    public interface CocaColaMachineState
    {
        void GetCookCan();
    }

    public class ReadyState : CocaColaMachineState
    {
        public CocaColaMachine cocaColaMachine;
        public ReadyState(CocaColaMachine cocaColaMachine)
        {
            this.cocaColaMachine = cocaColaMachine;
        }

        public void GetCookCan()
        {
            Console.WriteLine("Have a good day!");
            cocaColaMachine.canCount -= 1;
            if (cocaColaMachine.canCount <= 0)
            {
                cocaColaMachine.state = cocaColaMachine.emptyState;
            }
        }
    }

    public class EmptyState : CocaColaMachineState
    {
        public CocaColaMachine cocaColaMachine;
        public EmptyState(CocaColaMachine cocaColaMachine)
        {
            this.cocaColaMachine = cocaColaMachine;
        }

        public void GetCookCan()
        {
            Console.WriteLine("The Machine is Empty. Sory :(");
        }
    }

    public class CocaColaMachine
    {
        public ReadyState readyState { get; private set; }
        public EmptyState emptyState { get; private set; }
        public CocaColaMachineState state { get; set; }
        public int canCount { get; set; }

        public CocaColaMachine(int canCount)
        {
            readyState = new ReadyState(this);
            emptyState = new EmptyState(this);

            this.canCount = canCount;
            if (canCount > 0)
            {
                state = readyState;
            }
            else
            {
                state = emptyState;
            }
        }

        public void GetCookCan()
        {
            state.GetCookCan();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            CocaColaMachine cocaColaMachine = new CocaColaMachine(4);
            cocaColaMachine.GetCookCan();
            cocaColaMachine.GetCookCan();
            cocaColaMachine.GetCookCan();
            cocaColaMachine.GetCookCan();
            cocaColaMachine.GetCookCan();
        }
    }
}

Kształty, pędzle i transformacje

Kształty – inaczej prymitywy graficzne pozwalają na tworzenie prostych obiektów graficznych takich jak: linie, elipsy, prostokąty, wielokąty itd. Co odróżnia ich od zwykłych elementów graficznych znanych np. z OpenGL? Każdy kształt to klasa która:
Sama się maluje i tworzy – nie musimy obsługiwać zdarzeń znanych np. z WindowsForms (Paint)
Ich układ i właściwości są takie jak innych elementów WPF
Wspierają te same zdarzenia co inne elementy – nie musimy nakładać pracy aby korzystać z zdarzeń kliknięcia czy też naciśnięcia przycisku

Właściwości klasy Shape:
Fill – ustawia pędzel odpowiedzialny za wypełnienie kształtu
Stroke – ustawia pędzel odpowiedzialny za krawędź kształtu
StrokeThickness – ustawia grubość obramowania
StrokeStartLineCap oraz StrokeEndLineCap – determinuje początek i koniec malowanego kształtu (tylko dla linii – inne kształty są zamknięte)
Strech – decyduje w jaki sposób kształt wypełnia dostępne miejsce
DefiningGeometry – pozwala na ustawienie rozmiarów kształtu
GeometryTransform – pozwala na przypisanie transformacji n. obrót
RenderedGeometry – udostępnia finalnie renderowany obiekt

Prymitywy graficzne:
Line – linia
Rectangle – prostokąt
Ellipse – elipsa
Polyline – tworzy układ linii na podstawie wartości x,y
Polygon - wielokąt

Skalowanie kształtów
Aby w łatwy sposób skalować kształt można użyć o tego celu kontrolki Viewbox. Prosty przykład:
<Window x:Class="WpfApplication5.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="MainWindow" Height="250" Width="250">
    <Grid>
        <Viewbox HorizontalAlignment="Left">
            <Canvas Width="200" Height="200">
                <Ellipse Canvas.Left="20" Canvas.Top="20" Fill="Red" Width="20" Height="20" />
            </Canvas>
        </Viewbox>
    </Grid>
</Window>


Powiększając okno powiększa się wraz z nim namalowane koło.


Pędzle
Wypełniają wnętrze kształtów. Kilka faktów nt. pędzli:
Wspierają powiadamianie o zmianie stanu – pozwala to na automatyczne przemalowywanie elementów używających dany pędzel
Umożliwiają tworzenie częściowej przeźroczystości (wystarczy ustawić właściwość Opacity)
Klasa SystemBrushes daje nam dostęp do aktualnie używanych przez system Windows pędzli

Do dyspozycji mamy kilka rodzajów pędzli:
SolidColorBrush – maluje jednolity kolor
LinearFradientBrush – pozwala na tworzenie gradientu
RdialGradientBrush – podobny do poprzedniego z tym że tworzy gradient w formie koła
ImageBrush – maluje za pomocą obrazka
DrawingBrush – maluje dany obszar za pomocą kształtów lub zdefiniowanej bitmapy
VisualBrush – pozwala wykorzystać wygląd innych kontrolek podczas malowania (np. efekt odbicia w przypadku przycisku)
BitmapCacheBrush – podobny w działaniu do VisualBrush jednak łatwiejszy w użyciu w przypadku potrzeby użycia takiego pędzla w kilku miejscach programu

Przykład użycia niektórych pędzli:
<Window x:Class="WpfApplication5.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="MainWindow" Height="250" Width="250">
    <Grid>
        <Canvas>
            <Rectangle Canvas.Left="10" Canvas.Top="10" Width="50" Height="50" >
                <Rectangle.Fill>
                    <SolidColorBrush Color="Blue" />
                </Rectangle.Fill>
            </Rectangle>
            <Rectangle Canvas.Left="70" Canvas.Top="10" Width="50" Height="50" >
                <Rectangle.Fill>
                    <LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
                        <GradientStop Color="Yellow" Offset="0" />
                        <GradientStop Color="Red" Offset="0.3" />
                        <GradientStop Color="Blue" Offset="0.6" />
                        <GradientStop Color="Coral" Offset="1" />
                    </LinearGradientBrush>
                </Rectangle.Fill>
            </Rectangle>
            <Rectangle Canvas.Left="130" Canvas.Top="10" Width="50" Height="50" >
                <Rectangle.Fill>
                    <RadialGradientBrush GradientOrigin="1">
                        <GradientStop Color="Yellow" Offset="0" />
                        <GradientStop Color="Red" Offset="0.3" />
                        <GradientStop Color="Blue" Offset="0.6" />
                        <GradientStop Color="Coral" Offset="1" />
                    </RadialGradientBrush>
                </Rectangle.Fill>
            </Rectangle>
        </Canvas>
    </Grid>
</Window>


Transformacje
Pozwalają na malowanie elementów graficznych i zmianę sposobu ich ułożenia np. obrócenie.
Transformacje z których możemy korzystać w WPFie:
TranslateTransform – wykorzystujemy jeżeli chcemy dany obiekt namalować w innym miejscu (parametry X,Y)
RotateTransform – pozwala obrócić dany obiekt (Angle, CenterX, CenterY)
ScaleTransform – skaluje obiekt (ScaleX, ScaleY, CenterX, CenterY)
SkewTransform – pozwala pochylić obiekt
MatrixTransform – pozwala modyfikować współrzędne za pomocą dostarczonej macierzy.
TransformGroup – pozwala na ustawienie wszystkich transformacji w jednym czasie. Kolejność transformacji wpływa na wynik ostatecznego wyglądu figury.
Przykład odwrócenia:
<Window x:Class="WpfApplication5.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="MainWindow" Height="250" Width="250">
    <Grid>
        <Canvas>
            <Rectangle Canvas.Left="10" Canvas.Top="10" Width="50" Height="50" >
                <Rectangle.Fill>
                    <SolidColorBrush Color="Blue" />
                </Rectangle.Fill>
                <Rectangle.RenderTransform>
                    <RotateTransform Angle="30" />
                </Rectangle.RenderTransform>
            </Rectangle>
        </Canvas>
        <Button Width="100" Height="30">Button
            <Button.RenderTransform>
                <RotateTransform Angle="50" />
            </Button.RenderTransform>
        </Button>
    </Grid>
</Window>


Tworzenie grafiki można ułatwić sobie za pomocą narzędzia Expression Designer. Pozwala ono na eksport tworzonej grafiki do XAML-a.

sobota, 13 listopada 2010

Style

Style można porównać do CSS stosowanego podczas tworzenia stron internetowych. Dzięki temu w jednym miejscu webmaster ustala w jaki sposób poszczególne części witryny będą wyświetlane na stronie. W WPF style umożliwiają ustawienie każdej właściwości typu dependency.
Prosty przykład przypisania stylu do przycisku:
<Window x:Class="WpfApplication4.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style x:Key="MyButton">
            <Setter Property="Control.FontSize" Value="20" />
            <Setter Property="Control.FontStyle" Value="Italic" />
            <Setter Property="Control.Width" Value="100" />
            <Setter Property="Control.Height" Value="50" />
        </Style>
    </Window.Resources>
    <Grid>
        <Button Style="{StaticResource ResourceKey=MyButton}">Button</Button>
    </Grid>
</Window>

Jak można zauważyć istnieje kilka właściwości stylu które można ustawić:
Setters - pozwalają na przypisanie danych właściwości kontrolce
Triggers - pozwalają na automatyczną zmianę ustawień stylu (np. kiedy zmienić się właściwość można zmienić wygląd itp)
Resources - pozwala na wykorzystanie zasobów (np. jeżeli mamy zamiar zmienić dużą ilość właściwości)
BasedOn - pozwala dziedziczyć inne style
TargetType - pozwala na przypisanie stylu do elementów danego typu w sposób całkowicie automatyczy

Oprócz przypisywania stylu Właściwością, możliwe jest także przypisanie zdarzeniom. Nie jest to tak często stosowana technika, jednak aby kiedyś spotykając taką metodę nie być zdziwionym, krótki przykład:
<Window x:Class="WpfApplication4.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style x:Key="MyButton">
            <Setter Property="Control.FontSize" Value="20" />
            <Setter Property="Control.FontStyle" Value="Italic" />
            <Setter Property="Control.Width" Value="100" />
            <Setter Property="Control.Height" Value="50" />
            <EventSetter Event="Button.Click" Handler="Click" />
        </Style>
    </Window.Resources>
    <Grid>
        <Button Style="{StaticResource ResourceKey=MyButton}">Button</Button>
    </Grid>
</Window>

Sam kod obsługi zdarzenia:
        public void Click(object sender, EventArgs e)
        {
            MessageBox.Show("You click me!");
        }

Po kliknięciu na przycisk otrzymamy następujący efekt:

Style mogą po sobie dziedziczyć. Jeżeli chcemy wykorzystać jakiś styl do budowy np. przycisku, a następnie reużyć go do zbudowania przycisku ale z czerwoną czcionką można to zrobić w następujący sposób:
<Window x:Class="WpfApplication4.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style x:Key="MyButton">
            <Setter Property="Control.FontSize" Value="20" />
            <Setter Property="Control.FontStyle" Value="Italic" />
            <Setter Property="Control.Width" Value="100" />
            <Setter Property="Control.Height" Value="50" />
            <EventSetter Event="Button.Click" Handler="Click" />
        </Style>
        <Style x:Key="MyRedButton" BasedOn="{StaticResource ResourceKey=MyButton}">
            <Setter Property="Control.Foreground" Value="Red" />
        </Style>
    </Window.Resources>
    <Grid>
        <Button Style="{StaticResource ResourceKey=MyButton}">Button</Button>
        <Button Style="{StaticResource ResourceKey=MyRedButton}" Margin="202,215,200,46">RedButton</Button>
    </Grid>
</Window>

Wynik:


Triggers
Umożliwiają one automatyczną zmianę stylu w przypadku zmiany np. jakiejś właściwości.
Do dyspozycji mamy kilka rodzajów trigerów:
Trigger - najprostszy wariant, wywołuje się w momencie zmiany określonej właściwości
MultiTrigger - jak wyżej z tą różnicą, że można określić kilka monitorowanych właściwości
DataTrigger - używamy go w przypadku bindowania danych
MultiDataTrigger - podobnie jak MultiTrigger
EventTrigger - pozwala na uzyskanie animacji w przypadku zajścia zdarzenia

Zobaczmy na przykład wykorzystania Triggerów:
W tym przykładzie po najechaniu na przycisk, jego kolor zmieni się na żółty:
<Window x:Class="WpfApplication4.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <Style x:Key="MyButton">
            <Style.Setters>
                <Setter Property="Control.FontSize" Value="20" />
                <Setter Property="Control.FontStyle" Value="Italic" />
                <Setter Property="Control.Width" Value="100" />
                <Setter Property="Control.Height" Value="50" />
                <EventSetter Event="Button.Click" Handler="Click" />
            </Style.Setters>
            <Style.Triggers>
                <Trigger Property="Control.IsMouseOver" Value="True" >
                    <Setter Property="Control.Background" Value="Yellow" />
                </Trigger>
            </Style.Triggers>
        </Style>
        <Style x:Key="MyRedButton" BasedOn="{StaticResource ResourceKey=MyButton}">
            <Setter Property="Control.Foreground" Value="Red" />
        </Style>
    </Window.Resources>
    <Grid>
        <Button Style="{StaticResource ResourceKey=MyButton}">Button</Button>
        <Button Style="{StaticResource ResourceKey=MyRedButton}" Margin="202,215,200,46">RedButton</Button>
    </Grid>
</Window>


Korzystanie ze styli jest proste i intuicyjne. Przy użyciu narzędzia Blend można ułatwić sobie ich tworzenie.

poniedziałek, 8 listopada 2010

Zasobby (Resources)

Zasoby umożliwiają zapisanie informacji o użytych pędzlach, stylach i późniejsze wykorzystanie ich.
Zalety zasobów to min.:
- zwiększenie wydajności poprzez zapamiętanie często używanych styli, kolorów itp.
- reużywalność - nadając styl np. przyciskom zmiana w jednym miejscu powoduje zmianę we wszystkich korzystających z danego zasobu
- łatwa wymiana na nowe (np. wykorzystanie w systemie skinów)

Wszystko, co jest związane z zasobami przechowywane jest w kolekcji Resources (instancja klasy ResourceDictionary). Elementy tej kolekcji indexowane są poprzez ciągi znaków (string).
Zasoby przechowywane są najczęściej na poziomie okna lub też całej aplikacji. Dlaczego na poziomie okna? Gdyż każdy element ma dostęp do własnych zasobów jak i rodzica.
<Window x:Class="WpfApplication3.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <LinearGradientBrush x:Key="MyColor">
            <GradientStopCollection>
                <GradientStop Color="Blue" Offset="0.5" />
                <GradientStop Color="Orange" Offset="1.0" />
            </GradientStopCollection>
        </LinearGradientBrush>
    </Window.Resources>
    <Grid>
        <Rectangle Width="100" Height="100" Fill="{StaticResource ResourceKey=MyColor}" />
    </Grid>
</Window>


Pozwala to na uzyskanie następującego kwadratu:

W tym przypadku stosujemy statyczne zasoby - czyli takie, które raz przypiszemy i nie będziemy ich zmieniać podczas działania naszego programu. Oczywiście istnieją także dynamiczne zasoby, które można zmieniać podczas działania programu - ale o nich trochę później.

Hierarchia zasobów
Jak już wiemy, zasoby można umieszczać zarówno na poziomie aplikacji, okien jak i pojedyńczych elementów (np. przycisku). Ważne jest zdanie sobie sprawy z tego, że dany zasób szukany jest najpierw lokalnie a następnie u coraz to wyższego rodzica. Tak więc możliwe jest zdefiniowanie dwóch zasobów o takiej samej nazwie w zasięgu przycisku jak i okna. Ważne z drugiej strony jest to, że nie można nadać tej samej nazwy dwóm zasobom na tym samym poziomie.

Statyczne i dynamiczne zasoby
Jak już wiemy z wcześniejszego wywodu, statyczne zasoby przypisujemy raz podczas startu aplikacji a dynamiczne kiedy chcemy. Oczywiście może na pierwszy rzut oka wydać się, że stosowanie dynamicznych zasobów daje o wiele większe możliwości. Jest to prawda jednak stosowanie ich pociąga za sobą spadek wydajności naszej aplikacji. Dlatego też stosowanie dynamicznych zasobów powinno być w pełni odpowiedzialną decyzją

Słowniki zasobów
Słowniki pozwalają na łatwie zapisywanie często używanych zasobów i korzystanie z nich w aktualnej jak i przyszłych aplikacjach. Tworzenie ich jest niezwykłe proste:
Klikamy prawym przyciskiem myszy i wybieramy Add->Resource Dictionary...
Po dodaniu nowego słownika warto sprawdzić czy dodany plik XAML kompilowany jest w trybie Page:

Jest to ważne ze względów wydajnościowych.
Aby korzystać z tak stworzonych słowników, należy je połączyć z słownikami na poziomie aplikacji czy też okna:
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Dictionary1.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

Jak widać korzystanie z zasobów jak i słowników zasobów może bardzo ułatwić nam życie. W przedstawionym, bardzo prostym przykładzie użycie słownika wydaje się zbędne. Jednak warto o nim pomyśleć przy tworzeniu bardziej zaawansowanych styli jak i szablonów kontrolek.

niedziela, 7 listopada 2010

Skiny w aplikacji WPF

Zmiana wyglądu naszej aplikacji (tzw. skiny) to jeden z częściej wykorzystywanych mechanizmów współczesnych aplikacji. Szare okienka ustępują coraz to bardziej rozbudowanym graficznie aplikacją.

Korzystanie ze skinów w WPF jest niezwykle proste.
Przejdę bezpośrednio do przykładu. Stworzymy nową aplikację WPF wykorzystując Visual Studio 2010.

Na formatkę położymy dwa przyciski:

Następnie tworzymy dwa słowniki:

Te dwa słowniki to RedTemplate oraz BlueTemplate. Same definicje słowników są w tym pokazie bardzo proste:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style TargetType="{x:Type Button}">
        <Setter Property="Background" Value="Blue" />
    </Style>
</ResourceDictionary>





Ustawiamy tylko jedną właściwość odpowiedzialną za kolor tła przycisku. Teraz należy jeszcze tylko dodać odpowiednie zdarzenia, które uaktywnią odpowiedni kolor po kliknięciu w przycisk:

        private ResourceDictionary currentColors;
        private Collection<ResourceDictionary> appResources;

        public MainWindow()
        {
            InitializeComponent();

            //zapisujemy aktualnie używane słowniki
            appResources = App.Current.Resources.MergedDictionaries;
        }

        private void btnRed_Click(object sender, RoutedEventArgs e)
        {
            if (currentColors != null)
            {
                appResources.Remove(currentColors);
            }

            currentColors = (ResourceDictionary)App.LoadComponent(new Uri(@"RedTemplate.xaml", UriKind.Relative));

            if ((ResourceDictionary)App.LoadComponent(new Uri(@"RedTemplate.xaml", UriKind.Relative)) != null)
            {
                appResources.Add((ResourceDictionary)App.LoadComponent(new Uri(@"RedTemplate.xaml", UriKind.Relative)));
            }
        }

        private void btnBlue_Click(object sender, RoutedEventArgs e)
        {
            if (currentColors != null)
            {
                appResources.Remove(currentColors);
            }

            currentColors = (ResourceDictionary)App.LoadComponent(new Uri(@"BlueTemplate.xaml", UriKind.Relative));

            if ((ResourceDictionary)App.LoadComponent(new Uri(@"BlueTemplate.xaml", UriKind.Relative)) != null)
            {
                appResources.Add((ResourceDictionary)App.LoadComponent(new Uri(@"BlueTemplate.xaml", UriKind.Relative)));
            }
        }

Jak widać nic trudnego, a można dokonać wiele więcej zmian (np. wyglądu przycisku itp.).
Bardzo ciekawy przykład można zobaczyć w filmiku Jared Bienz-a dostępnego pod adresem:
klik