• This is a usual time of the year for summaries so let’s keep the tradition alive and write one. Here’s my 2017 achievements split between months. January Blogging for 18 days straight – nothing near gutek’s achievement (whole year!) but still a nice streak Microsoft MVP title (thx Konrad Kokosa for pointing this one ;)) February […]

  • “Advent of Code is a series of small programming puzzles for a variety of skill levels.” Each day was a fun an interesting coding challenge. I’ve decided to practice and code this in python to learn the skill. Some of them might not be the best python scripting as I was short on time in […]

  • Some time ago I’ve attended a .net developer days 2017 conference. I was quite busy since (traveling, teaching .net, working) that only now I got some time to share some thoughts about it. As a bonus I’m including a short interview I did during the event. I need to state here, just to be clear, […]

  • We developers don’t like documentation. We don’t like to read it, and we even more we don’t like to write it. But sometimes it is worth to read it. Like when you find out that by using datetime in SQL DB you got a milliseconds precision but only if it ends on 0,3,7 (link). That […]

  • In the previous post we’ve removed some of the technical debt that could be found in our NetDeveloperPoland Website application. In this one we will remove it even more. We can even maybe reach a B? Let’s see where we’ll end up at the end of this part.

Dziś dokończenie poprzedniej części traktującej o Page based navigation. Dziś temat dotyczący przekazywania danych pomiędzy stronami.

Oj dana, dana, dana…

Informację do strony możemy przekazać na trzy sposoby. Pierwszy z nich to wykorzystując przeładowaną metodę Navigate, która jako drugi argument przyjmuje argument typu object.

NavigationService.Navigate(“Page1.xaml”, 12);

natomiast odczytać możemy go za pomocą eventu LoadCompleted.

NavigationService.LoadCompleted += NavigationService_LoadCompleted;

void NavigationService_LoadCompleted(object sender, NavigationEventArgs e)

{

    if (e.ExtraData == null) return;

    //zrób coś z wartością

}

Wiadomo wadą takiej metody jest brak silnego typowania. W bardzo prosty sposób, możemy jednak to silne typowanie uzyskać:

var page = new Page1(12);

NavigationService.Navigate(page);

Prawda, że proste? Jest jednak jeszcze 3 nowe rozwiązanie.

Page functions

Jest to nowość, która została dodana do WPF’a a głównym jej zadaniem jest lepsze wsparcie sytuacji strony ustawień, która działa na zasadzie pokazanie strony ustawień, zmiana ich i powrót do poprzedniej strony. Pogrubiłem fragment dotyczący powrotu, gdyż to jest najistotniejszy właściwość PageFunctions. Mianowicie, potrafią one zwracać wartość.

PageFunction tak naprawdę nie różni się znacząco od Page (dziedziczy po niej) i jedyną widoczną w XAML’u różnicą (poza oczywiście tagiem głównym) jest definicja zwracanego typu.

<PageFunction

  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”

  x:Class=”WpfExamDemo.PageFunction1″

  x:TypeArguments=”sys:String

  Title=”PageFunction1″>

Jak łatwo zauważyć w powyższym przykładzie nasza funkcja będzie zwracać łańcuch znaków. Aby uzyskać dostęp do zwracanej przez tę funkcję wartości musimy podpiąć się pod zdarzenie Return:

var pageFunction = new PageFunction1();

pageFunction.Return += pageFunction_Return;

i odczytać wartość za pomocą właściwości Result. Dane możemy zwrócić poprzez wywołanie OnReturn z obiektem typu ReturnEventArgs.

OnReturn(new ReturnEventArgs<string>(“string”));

Tyle na dziś. W kolejnym odcinku – Configure Application Settings.

Dziś w kolejnym odcinku mojego przygotowania do WPF’a przyjrzymy się mechanizmowi, który nosi wdzięczną nazwę Configure Page Based navigation.

Zacznę od opisania Page Base navigation, gdyż nie bardzo wiem co może kryć się pod tą konfiguracją – zobaczymy co z tego wyniknie

Jak zacząć

Jeśli chcemy użyć Page Base navigation, powinniśmy zacząć od zdefiniowania naszego okna jako NavigationWindow (lub Frame o różnicach później) oraz zdefiniować Source:

<NavigationWindow x:Class=”WpfExamDemo.Window1″

  xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”

  xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”

  Height=”300″ Width=”300″ Source=”Page1.xaml”>   

NavigationWindow>

Dzięki temu w prosty sposób uzyskujemy następujący efekt:


oczywiście Page1.xaml musi mieć jako obiekt główny Page.

NavigationWindow czy Frame

W zasadzie oba mogą być użyte jako element główny w przypadku takiego typu aplikacji. Frame od NavigationWindow różni się tym, że Frame zajmować może tylko fragment ekranu oraz mogą być zagnieżdżone. Frame ma domyślnie ukryte przyciski nawigacyjne – jeśli chcemy je pokazać, musimy ustawić NavigationUIVisibility na true. Jeśli chcemy uzyskać ten sam efekt dla NavigationWindow musimy użyć właściwości ShowNavigationUI. Frame nie posiada także właściwości Title.

NavigationService

To klasa, która umożliwia interakcję pomiędzy Frame’em a zawierającym go oknem. Aby uzyskać do niej dostęp wystarczy zawołać property o nazwie NavigationService. Klasa ta udostępnia sporo metod związanych z nawigacją po stronie takich jak: Navigate, GoBack, GoForward, Refresh oraz kilka eventów dzięki, którym możemy dowiedzieć się, kiedy jakie zdarzenie wstępuje. Pełna lista na MSDN

Nawigacja

Jak poruszać się pomiędzy stronami? Możemy to uczynić na 3 sposoby:

  • metoda Navigate
  • użyć HyperLink’ów
  • użyć dziennika (Journal)

Navigate

Aby użyć metody navigate możemy po prostu zawołać ją podająć Uri strony, którą chcemy załadować, bądź już stworzony obiekt strony.

NavigationService.Navigate(new Uri(“Page1.xaml”));

var page = new Page1();

NavigationService.Navigate(page);

W przypadku Uri może to być zarówno plik XAML jak i strona HTML. Jako ciekawostkę – ten sam efekt można uzyskać za pomocą właściwości Content lub Source na obikcie NavigationService.

HyperLink’i

Za dużo filozofii w tym przypadku nie ma. Wystarczy napisać:

<Hyperlink NavigateUri=”Page1.xaml”>LinkHyperlink>

Hyperlinki mogą linkować oczywiście do stron HTML. Niestety w drugą stronę nie jest to możliwe. Dodatkowo Hyperlinki wspierają zaawansowanie linkowanie tak jak tag A, czyli możemy linkować bezpośrednio do konkretnego Frame’a (ustawiając TargetName na linku) bądź dodajać # i linkując bezpośrednio do konkretnego elementu w Frame o takiej właśnie nazwie.

Dziennik (Journal)

Dziennik służy do pamiętania historii odwiedzonych stron. Dostęp do niego mają oba kontenery (NavigationWindow oraz Frame), jednak Frame nie musi go mieć zawsze i uzależnione jest to od stanu JournalOwnership. Możliwe wartości to:

  • OwnsJournal – Frame ma swój dziennik
  • UsesParentJournal – historia zapisywana jest w rodzicu lub nie jeśli rodzic nie posiada dziennika
  • Automatic – domyślnie. Jeśli jest to NavigationWindow to używa trybu UwnsJournal, jeśli Frame to UsesParentJournal

Dodatkowo należy pamiętać, że nawigacja pomiędzy stronami za pomocą Uri zawsze tworzy nową instancję strony, nawet jeśli już ją daną stronę odwiedzaliśmy, dlatego pamiętanie stanu strony leży w naszej odpowiedzialności. W przypadku używania dziennika można wymusić użycie starej instancji poprzez ustawienie JournalEntry.KeepAlive na true.

<Page x:Class=”WpfExamDemo.Page1″

  xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”

  xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”

  Title=”Page1″ JournalEntry.KeepAlive=”True”>

Navigation events

Niezależnie od sposobu przejścia pomiędzy stronami (oczywiście wykluczająć przechodzenia pomiędzy linkami w stronach HTML) odbywa się to asynchronicznie i parę eventów. Dokładnie rozrysowane jest to na schemacie. Brakuje tylko na nim jakie

Tym razem temat rozbiłem na dwie części. Następnym razem o przenoszeniu danych pomiędzy stronami i PageFunctions – odcinek 4.5.

Po krótkiej przerwie powracamy do tematu przygotowań do egzaminu z WPF. Dzisiejszy temat to komendy (commands).
Komendy to nowość, która znalazła się w WPFie (ale sama koncepcja nie jest nowa), a która dostarcza luźno powiązany połączenie pomiędzy akcją a jej uruchomieniem. Dla przykładu: Standardowa aplikacja ma wiele ‘miejsc’, które umożliwiają wklejenie tekstu (Paste). Menu główne, menu kontekstowe, toolbar itp. Zapewne, każdy z nas zaimplementowałby to ładnie jako jedna metoda, która jest odpowiednio wołana w tych miejscach.

Jednak za pomocą komend, możemy to uzyskać w jeszcze prostszy sposób. Komendy implementują prosty interface (ICommand z trzemam metodami: Execute, CanExecute oraz CanExecuteChange – więcej poniżej) a cała ich siła w tym, że multum z nich już jest dostępna w WPFie. Dodatkowo część standardowych kontrolek ma wsparcie dla komend, co czynie jest jeszcze bardziej użytecznymi

Komendy wbudowane

Jest ich sporo…naprawdę sporo a wszystko podzielone na 5 kategorii.

ApplicationCommands

Lista dostępnych komend.

ComponentCommands

Lista dostępnych komend.

MediaCommands

Lista dostępnych komend.

NavigationCommands

Lista dostępnych komend.

EditingCommands

Lista dostępnych komend.

Jak tego używać?

Jeśli już przebrnęliśmy przez długą listę komend, warto zobaczyć jak tego użyć w praktyce.

Z kodu:

help.Command = ApplicationCommands.Help;

oraz z XAML’a:

<Button Content=”Pomoc” x:Name=”help” Command=”ApplicationCommands.Help” />

Jeśli jednak uruchomimy taki przykładowy kod, przycisk będzie permanentnie zablokowany. Jeśli się nad tym zastanowić to jest to oczywiste. Akurat ta komenda nie wie, kiedy Pomoc może być aktywna a kiedy nie. Aby to określić musimy zdefiniować CommandBinding.

CommandBindings.Add(new CommandBinding(ApplicationCommands.Help, HelpExecuted, CanExecute));

void CanExecute(object sender, CanExecuteRoutedEventArgs e)

{

  // e.CanExecute ustawiamy na true jeśli komenda ma być aktywna

}

 

void HelpExecuted(object sender, ExecutedRoutedEventArgs e)

{

    // uruchamiamy co mamy do zrobienia

}

Zanim przejdziemy do dalszej części, warto wspomnieć, że każda komenda posiada zalokalizowaną pod dany język nazwę, którą można użyć zamiast ustawionego na sztywno tekstu. Dzięki temu przynajmniej część naszej aplikacji będzie od razu przetłumaczona. Robi się to banalnie prosto:

help.Content = ApplicationCommands.Help.Text;

Nasze własne komendy

Tak jak już pisałem wcześniej komendy implementują prosty interface ICommand. Aby stworzyć własną komendę wystarczy go zaimplementować:

  • Execute – wołana, gdy akcja komendy ma być uruchomiona
  • CanExecute – wołana, gdy następuje sprawdzenie czy dana komenda może być uruchomiona czy nie.
  • CanExecuteChanged – powinniśmy wołać ten event, gdy stan komendy zmienia się

Kontrolki wbudowane

Prawdziwą siłę komend, możemy podziwiać w połączeniu ze standardowymi kontrolkami np. TextBox’em. Wystarczy bowiem wpisać poniższy kod:

<StackPanel Orientation=”Horizontal” Height=”30″>

    <Button Command=”Cut” CommandTarget=”{Binding ElementName=textBox}” Content=”Cut” />

    <Button Command=”Paste” CommandTarget=”{Binding ElementName=textBox}” Content=”Paste”/>

    <TextBox x:Name=”textBox” Width=”220″ />

StackPanel>

A przyciski Cut i Paste będą zachowywać się zgodnie ze stanem kontrolki TextBox.

Skróty klawiaturowe

Na koniec jeszcze odnośnie skrótów klawiaturowych. Aby podpiąć daną kombinację klawiszy pod daną komendę wystarczy jedna prosta linijka:

InputBindings.Add(new InputBinding(ApplicationCommands.Help, new KeyGesture(Key.H)));

To chyba wszystko jeśli chodzi o komendy. Następnym razem będzie o: Configure page-based navigation – cz.1 i cz. 2. Do zobaczenia.

Bibliografia: WPF Unleashed, MSDN

Witajcie dziś kolejna porcja przygotowania do egzaminu WPF (70-502). Zgodnie z obietnicą dziś będzie trochę o eventach.

Tak więc w WPF eventy możemy deklarować:
w XAMLu:

<Grid Loaded=”Grid_Loaded” x:Name=”grid”>

oraz ‘standardowo’ w kodzie:

grid.Loaded += grid_Loaded;

I jeśli chodzi o konfigurację eventów to byłoby chyba na tyle, gdyby nie fakt, że WPF troszkę w tym fragmencie burzy ‘nasz porządek wszechświata’.

Routed Events

Czym są ‘rutowalne’ eventy? (pisałem już zresztą o nich kiedyś) Są to eventy, które potrafią trawersować logiczne i wizualne drzewo elementów (czym są tę drzewa pisałem już dawno tu). Dla przykładu klikając na przycisk, który jako “treść” ma jakiś wzór. Pomimo tego, że tak naprawdę klikamy na wzór to przycisk widzi kliknięcie, ponieważ event przechodzi po drzewie (czy to logicznym czy wizualnym). Jak dodać taki ‘rutowalny’ event. Nie jest to bardzo skomplikowane.

  • Zaczynamy od definicji eventu:

    public static readonly RoutedEvent NewRoutedEvent;

    Z ciekawostek warto zauważyć, że jest to statyczna deklaracja o typie RoutedEvent.

  • Kolejnym krokiem jest inicjalizacja tegoż:

    Window1.NewRoutedEvent =

                EventManager.RegisterRoutedEvent(“New”, RoutingStrategy.Bubble,

                                        typeof(RoutedEventHandler),

                                        typeof(Window1));

    i dokonujemy tego w statycznym konstruktorze. Aby zarejestrować nowy event musimy nadać mu unikalną nazwę (w obrębie danego typu), wybrać strategię rutowania (więcej za chwilę), oraz określić jakiego typu jest to event, oraz kto jest ‘odpowiedzialny’ za niego.

  • A zaraz po tym dodajemy (opcjonalne) przypinanie i odpinanie handlerów (kodu obsługującego??):

    public event  RoutedEventHandler NewRouted

    {

        add { AddHandler(Window1.NewRoutedEvent, value); }

        remove { AddHandler(Window1.NewRoutedEvent, value); }

    }

  • Wywołanie to proste:

    RaiseEvent(new RoutedEventArgs(NewRoutedEvent, this));

Strategie rutowania

Jednym z parametrów przy rejestracji rodzaj strategii, która będzie użyta do propagacji eventu. Możliwe opcje to:

public enum RoutingStrategy

{

    Bubble,

    Direct,

    Tunnel

}

  • Bubble – event jest w pierwszej kolejności wywoływany na źródłowym elemencie i porusza się ‘w górę’ drzewa, aż do elementu głównego (lub do czasu jego obsłużenia)
  • Direct – event wywoływany jest tylko i wyłącznie źródłowym eventu. Zachowanie identyczne do standardowych .NET’owych eventów
  • Tunnel – event na samym początku wywoływany jest na elemencie głównym i porusza się ‘w dół’, aż do elementu źródłowego.

Idea za tym, aby mieć event ‘podróżujący’ w dół oraz w górę jest taka, że eventy podróżujące w dół (zgodnie z konwencją ze słówkiem Preview) są wywoływane jako pierwsze i dzięki temu mamy możliwość zablokowania (poprzez ustawienie Handled na true) takiego eventa przed dotarciem do elementu źródłowego i przed propagacją w górę.

Przez to, że eventy te są trochę bardziej skomplikowane od zwykłych klasa RoutedEventArgs została rozbudowana o kilka pól:

  • Source – element w drzewie logicznym, który zapoczątkował event
  • OrginalSource – element w drzewie wizualnym, który zapoczątkował event
  • Handled – jeśli ustawione na true zapobiega dalszej propagacji eventu
  • RoutedEvent – zawiera konkretną instancję rutowalnego eventu

Attached events

Można się zastanawiać jak taki event może sobie podróżować po drzewie kontrolek w przypadku, gdy dana kontrolka wspiera dany typ eventu. Ale co w przypadku, gdy tego nie robi?

Z pomocą przychodzą nam Attached events. Przypinanie do takiego eventu działa na podobnej zasadzie jak Attached properties. Jedyne co musimy zrobić to poprzedzić nazwę eventu, nazwą kontrolki, która go udostępnia np.:

<Grid ButtonBase.Click=”grid_Click”>

i to wszystko. Od tej chwili grid będzie dostawał powiadomienia, gdy przycisk zostanie naciśnięty.

Na dziś tyle o eventach – następnym razem: Commands.

Trwa (17.03-19.03)MIX’09 a na nim jedną z nowości ma być Silverlight 3. Jeśli nie dane nam było odwiedzić LosAngeles w tym roku to nie musimy rozpaczać :)(przynajmniej nie z powodu MIX’a), gdyż już teraz możemy obejrzeć jedną z nowo obecnych funkcjonalności Silverlighta 3. Jest nią OOB czyli działanie Out of Body..ups Browser.

Po krótce jedną z nowości w Silverlight 3 będzie możliwośc zainstalowania aplikacji na komputerze lokalnie. Dostępnymi miejscami jest Pulpit lub Menu start.

Wraz z funkcjonalnością dostępne jest proste API, które umożliwia ustawienie aplikacji w tryb offline jak również wykrycie zmiany trybu uruchomienia. Oczywiście możemy także wykryć jak nasza aplikacja została uruchomiona – czy z przeglądarki czy w trybie Offline. W trybie tym nadal możemy wykryć zmiany wersji na nowszą użyć jej.

Z prezentowanych nowości jest również możliwośc wykrycia połączenia z siecią (statyczna klasa NetworkInterface).

Film jest dostępny pod tym adresem http://silverlight.net/learn/learnvideo.aspx?video=187318

Jak tylko znajdę chwilę to sam to chętnie potestuję – dajcie mi tylko SL 3 🙂

Jedno z moich noworocznych postanowień to zdanie egzaminu (choć powinienem napisać, że już samo przygotowanie i podejście do egzaminu będzie wystarczające :-)) 70-502 czyli WPF’a a że czasu w 2009 roku coraz mniej 😉 postanowiłem zabrać się za przygotowania. Jako, że nikt chętny do nauki na żywo nie był postanowiłem, że pouczę się z czytelnikami :).

Materiału jest sporo (32 tematy), tak więc chyba nie ma co owijać w bawełnę – zaczynamy. Liczę, że takie przejście po tematach będzie dla kogoś przydatne.

1/32 – Creating a WPF application (13 percent) – Select an application type

Visual Studio 2008 pozwala nam stworzyć 4 rodzaje aplikacji z wykorzystaniem technologii WPF:

  • WPF Application
  • WPF Browser Application
  • WPF Custom Control Library
  • WPF User Control Library

Pierwszego typu nie trzeba chyba tłumaczyć – zwykła aplikacja typu desktop.

WPF Browser Application to tzw. XBAP. Aplikacja w technologii WPF w oknie przeglądarki. Ten typ aplikacji umożliwia stworzenie aplikacji, która wyświetlana będzie w oknie przeglądarki. Główna różnica jest taka, że XBAP uruchamiany jest w tzw. “sandbox’ie” i nie ma dostępu wszystkich zasobów (np. dostęp do tylko do IsolatedStorage, brak WCF’a) i działa tylko w IE a po zainstalowaniu SP1 do .NET Frameworka 3.5 także w Firefox’ie. Pełna lista różnic jest tu.

Ostatnie dwa typy to biblioteki kontrolek. User Controls to zbiór kontrolek zawierających zarówno logikę jak i wygląd. Custom Controls to kontrolki tzn. “lookless”, nieposiadające zdefiniowanego wyglądu a jedynie zachowanie w plikach .cs

Następnym razem trochę o eventach czyli temat nr 2- Configure event handling.

Wow…jak ten czas zleciał. Jeszcze dobrze pamiętam jak ruszałem ze stroną dla C2C a konferencja już jutro.
Pomoc przy takiej konferencji to sporo pracy (choć moja część ograniczała się i tak tylko do małego fragmentu) i nerwów. Tak, tak…niestety nerwów też. Jednak w rozliczeniu uważam, że pomoc przy takiej konferencji może wyjść nam tylko na plus.

Moje zyski z C2C’09:

  • kontakty, kontakty i jeszcze raz kontakty
  • nowe doświadczenia
  • ciekawe sesje (mam nadzieję, że uda mi się choć jedną odwiedzić)

Na prawdę ilość nowych osób, które poznałem i z którymi dane mi było pracować jest znacząca i cenię niezwykle to sobie.

Do zobaczenia na C2C’09.

W poprzedniej części było trochę o teorii VisualStateManager’a. Dziś zajmiemy się praktyką. Zobaczymy jak zamienić nieruchomy przycisk w coś bardziej ruchomego.

Zacznijmy od podstaw:

<ControlTemplate>

    <Canvas>

        <Canvas.RenderTransform>

            <ScaleTransform CenterX=”15″ CenterY=”15″ ScaleX=”1″ ScaleY=”1″

                          x:Name=”scaleTransform”/>

        Canvas.RenderTransform>

        <Ellipse Stroke=”Black” StrokeThickness=”4″

            Fill=”Orange” Width=”30″ Height=”30″ />

        <Line Stroke=”Black” StrokeThickness=”4″

                  X1=”9″ Y1=”9″ X2=”21″ Y2=”21″ />

        <Line Stroke=”Black” StrokeThickness=”4″

                  X1=”9″ Y1=”21″ X2=”21″ Y2=”9″ />

 

        <VisualStateManager.VisualStateGroups>

            <VisualStateGroup x:Name=”CommonStates”>

 

            VisualStateGroup>

        VisualStateManager.VisualStateGroups>

    Canvas>

ControlTemplate>

Na początek dodaliśmy transformację ScaleTransform i nadaliśmy jej nazwę. Przyda się, gdy będziemy chcieli zmienić nasz przycisk.
Kolejnym krokiem będzie dodanie jak ma wyglądać nasz przycisk w poszczególnych stanach. Na pierwszy “ogień” MouseOver

<VisualState x:Name=”MouseOver”>

    <Storyboard>

        <DoubleAnimation Storyboard.TargetName=”scaleTransform” Duration=”0:0:0.125″

                        To=”1.2″ Storyboard.TargetProperty=”ScaleX”/>

        <DoubleAnimation Storyboard.TargetName=”scaleTransform” Duration=”0:0:0.125″

                        To=”1.2″ Storyboard.TargetProperty=”ScaleY”/>

    Storyboard>

VisualState>

Prosta animacja zwiększająca przycisk do 120%. Jednak, jeśli uruchomimy taki kod to po najechaniu kursorem kontrolka pozostaje w stanie w jakim była po najechaniu kursorem. Tego co nam brakuje to zdefiniowany stan “Normal”. Uzupełnimy go zatem.

Nasz kod wygląda zatem tak:

<VisualState x:Name=”MouseOver”>

    <Storyboard>

        <DoubleAnimation Storyboard.TargetName=”scaleTransform” Duration=”0:0:0.125″

                        To=”1.2″ Storyboard.TargetProperty=”ScaleX”/>

        <DoubleAnimation Storyboard.TargetName=”scaleTransform” Duration=”0:0:0.125″

                        To=”1.2″ Storyboard.TargetProperty=”ScaleY”/>

    Storyboard>

VisualState>

<VisualState x:Name=”Normal”>

    <Storyboard>

        <DoubleAnimation Storyboard.TargetName=”scaleTransform” Duration=”0:0:0.125″

                        To=”1″ Storyboard.TargetProperty=”ScaleX”/>

        <DoubleAnimation Storyboard.TargetName=”scaleTransform” Duration=”0:0:0.125″

                        To=”1″ Storyboard.TargetProperty=”ScaleY”/>

    Storyboard>

VisualState>

Przycisk na chwilę obecną prezentuje się następująco:

Dodajmy zatem kolejne stany. Chcemy, aby przycisk “wyszarzał” gdy jest w trybie disabled.
Po pierwsze musimy dodać nazwę dla elipsy:

<Ellipse Stroke=”Black” StrokeThickness=”4″

            Fill=”Orange” Width=”30″ Height=”30″ x:Name=”ellipse” />

A następnie definiujemy zachowanie dla trybu “disabled”. Znów prosta animacja zmieniająca kolor z pomarańczowego na szary.

<VisualState x:Name=”Disabled”>

    <Storyboard>

        <ColorAnimation To=”Gray” Duration=”0:0:0.3″

                      Storyboard.TargetName=”ellipse”

                      Storyboard.TargetProperty=”(SolidColorBrush.Fill).Color”/>

    Storyboard>

VisualState>

Jednak przy takiej definicji w przypadku zmiany ze stanu Disabled do Normal, kontrolka powróci do stanu Normal, bez żadnego przejścia.

Możemy to zmienić definiując przejścia. Zdefiniujmy zatem jedno:

<VisualStateGroup.Transitions>

    <VisualTransition From=”Disabled” To=”Normal”>

        <Storyboard>

            <ColorAnimation To=”Orange” Duration=”0:0:0.3″

                      Storyboard.TargetName=”ellipse”

                      Storyboard.TargetProperty=”(SolidColorBrush.Fill).Color”/>

        Storyboard>

    VisualTransition>

VisualStateGroup.Transitions>

Dzięki temu nasza kontrolka zachowuje się następująco:

Teraz, skoro nasz przycisk poprawnie zachowuje się w stanie “normalnym” oraz “disabled” spróbujmy dodać mu trochę ruchu, gdy użytkownik klika na niego. Oczywiście przydatny będzie tu stan Pressed.

Mając za sobą to co już napisaliśmy, dodanie animacji dla trybu Pressed, będzie niezwykle proste. Zakręćmy trochę naszym przyciskiem.

Dodajemy nową transformację

<Canvas.RenderTransform>

    <TransformGroup>

        <ScaleTransform CenterX=”15″ CenterY=”15″ ScaleX=”1″ ScaleY=”1″

                      x:Name=”scaleTransform”/>

        <RotateTransform CenterX=”15″ CenterY=”15″ Angle=”0″

                        x:Name=”rotateTransform”/>

    TransformGroup>

Canvas.RenderTransform>

i używamy jej do animacji przycisku podczas naciskania przy okazji troszkę go zmniejszając:

<VisualState x:Name=”Pressed”>

    <Storyboard>

        <DoubleAnimation Storyboard.TargetName=”scaleTransform” Duration=”0:0:0.125″

              To=”.9″ Storyboard.TargetProperty=”ScaleX”/>

        <DoubleAnimation Storyboard.TargetName=”scaleTransform” Duration=”0:0:0.125″

              To=”.9″ Storyboard.TargetProperty=”ScaleY”/>

        <DoubleAnimation Storyboard.TargetName=”rotateTransform” Duration=”0:0:0.25″

              To=”180″ Storyboard.TargetProperty=”Angle” />

    Storyboard>

VisualState>

Przycisk prezentuje się teraz następująco:

Na koniec możemy jeszcze przycisk “odkręcić”

<VisualTransition From=”Pressed” To=”MouseOver”>

    <Storyboard>

        <DoubleAnimation Storyboard.TargetName=”rotateTransform” Duration=”0:0:0.25″

          To=”0″ Storyboard.TargetProperty=”Angle” />

    Storyboard>

VisualTransition>

<VisualTransition From=”Pressed” To=”Normal”>

    <Storyboard>

        <DoubleAnimation Storyboard.TargetName=”rotateTransform” Duration=”0:0:0.25″

          To=”0″ Storyboard.TargetProperty=”Angle” />

    Storyboard>

VisualTransition>

Aby mieć całkowicie animowany przycisk pozostaje nam jeszcze do zaimplementowania kilka stanów i przejść, ale to już pozostawiam dla każdego jako ‘zadanie domowe’. Myślę, że po tym co tu zostało pokazane nie będzie to trudne.

Pełny kod przycisku – CloseButton.xaml.

Jako, że ostatnio sporo czytam o technologii Silverlight. Dziś będzie trochę o VisualStateManager.

Gdy kompletnie zmieniamy wygląd kontrolki, wraz z możliwością zaprojektowania jej na nowo tracimy niestety wszelkie efekty związane ze stanami w jakich ona się znajduje. Tak więc przycisk nie podświetli się ładnie, gdy kursor znajdzie się nad nim ani gdy przycisk zostanie naciśnięty. Jednak jest na to rozwiązanie. Wspomniany na początku VisualStateManager. Możemy go zdefiniować jako część ControlTemplate i za jego pomocą dodać interakcję do naszej nowo udekorowanej kontrolki.

Stany i przejścia

Stany, które możemy definiować, w których może się znajdować nasza kontrolka to:

Stan Grupa
Disabled CommonStates
MouseOver CommonStates
Normal CommonStates
Pressed CommonStates
Focused FocusStates
Unfocused FocusStates

Dodatkowo prócz tego jak kontrolka ma wyglądać kontrolka w poszczególnych stanach, mamy możliwość zdefiniowana jak będzie się zachowywać pomiędzy przejściami. Przejścia są możliwe tylko pomiędzy stanami znajdującymi się w tej samej grupie.

Przykład

W ramach przykładu w następnym wpisie spróbujemy zdefiniować style i przejścia dla takiego przycisku.

XAML:

<UserControl x:Class=”VisualStateManagerDemo.CloseButton”

  xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”

  xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml” >

    <Button x:Name=”closeBtn”>               

        <Button.Template>

            <ControlTemplate>               

                <Canvas>

                    <Ellipse Stroke=”Black” StrokeThickness=”4″

                            Fill=”Orange” Width=”30″ Height=”30″ />

                    <Line Stroke=”Black” StrokeThickness=”4″

                            X1=”9″ Y1=”9″ X2=”21″ Y2=”21″ />

                    <Line Stroke=”Black” StrokeThickness=”4″

                        X1=”9″ Y1=”21″ X2=”21″ Y2=”9″ />

                Canvas>

            ControlTemplate>

        Button.Template>

    Button>

UserControl>

I na koniec dwa ciekawe linki do użytecznych narzędzi:

  • Silverlight Spy – pozwala na podgląd co się dzieje w naszej Silverlight-owej aplikacji, zresztą tak samo jak każda inna aplikacja typu Spy
  • XapOptimizer – to dzieło ComponentOne umożliwiające wgranie naszego pliku .xap i poddanie go optymalizacji pod względem wielkości. Usuwa odwołania do niepotrzebnych bibliotek, usuwa znaki końca linii oraz nadmiarowe spacje. Dodatkowo można obfuskować nasz kod.

Narzędzia, które powinny znaleźć się w użyciu każdego developer SL. Miłej zabawy z narzędziami i do następnego odcinka.

Spokojnie, w tym poście nie będzie o kulturze osobistej programistów :). Zamiast tego będzie trochę o kulturze aplikacji i problemach/wyzwaniach z tym związanych. Jednak na początek napiszę dlaczego będzie o tym. Wydawać by się mogło, że wszyscy są świadomi kwestii narodowościowych i tego, że jeśli planujemy, naszą aplikację wypuścić w wielu regionach musimy to uwzględnić.

Wydawać by się mogło, że jest to oczywiste a jednak nadal są błędy z tym związane. Ostatnio w Unity (oficjalnie jeszcze nie potwierdzony; czekam na odpowiedź) – komponencie Dependency Injection z teamu Pattern & Practices. Jak widać nawet w tak dużym (i na pewno świadomym tych problemów) zespole taki problem się prześlizgnął. Parametry zdefiniowane w config’u aplikacji parsują używając aktualnej kultury aplikacji zamiast robić to w trybie Invariant. Przez to jeśli chcemy aplikację uruchamiać w różnych lokalizacjach (jeśli tylko ustawienia regionalne są inne np. US i Polska) – dla każdej musimy przygotować oddzielny plik konfiguracyjny.

Parsowanie z odpowiednią kulturą liczb to poważny problem. Wystarczy zerknąć na taką niewielką instrukcję.

var wartosc = double.Parse(“0.12121212”);

Jaki będzie wynik takiego parsowania? Zależy od ustawień. Na komputerze z ustawioną kulturą polską – dostaniemy wyjątek. Na komputerze w Stanach Zjednoczonych dostaniemy poprawną wartość liczbową. Specjalnie użyłem słowa poprawną, gdy wykonanie takiej instrukcji na komputerze w Danii spowoduje, że zmienna wartosc będzie zawierać…liczbę 12121212.0. Tak! Ten ułamek zamieni się w taką właśnie liczbę. Zdziwiony? Nie jesteś sam.
Musimy niestety uważać przy parsowaniu, bo nawet samo wyłapywanie wyjątków nic nie da – tu po prostu wyjątek nie zostanie rzucony.

Tak więc na zakończenie prośba. Podchodźmy ostrożnie do każdego parsowania liczb. Jeśli coś przychodzi od użytkownika, parsujmy z użyciem jego ustawień regionalnych. Natomiast wszystkie wartości nie uzyskane z UI powinniśmy przechowywać i parsować z ustawieniami InvariantCulture.

Na koniec link. Zapewne znany, ale moim zdaniem zawsze dobrze go sobie przypomnieć