środa, 28 lipca 2010

Commands WPF

Komendy to jedna z nowości w WPFie. Oferują całkiem sporo:
- oddzielenie obiektu wywołującego od logiki przetwarzającej polecenie
- pozwalają na wywoływanie tej samej logiki w wielu miejscach programu (jak i zmianę jej w zależności od wymagań)
- sygnalizowanie użytkownikowi możliwość lub jej brak wykonania konkretnej komendy

Oczywiście jak wszystko istnieją także i ciemne strony komend, które być może w przyszłości zostaną naprawione:
- brak historii wywołanych komend
- brak możliwości cofnięcia komendy
- komendy nie mogą mieć wielu stanów

Po tym krótkim wstępie wad i zalet czas na sam model:
Command - reprezentuje zadanie (np. wytnij, wklej), nie zawiera żadnego kodu odpowiadającego za wykonanie zadania
Command Bindings - łączy polecenie z logiką
Command Sources - obiekt wywołujący polecenie (np. Button, MenuItem)
Command Targets - definiuje element w którym otrzymamy efekt działania polecenia.

Definiując kontrolkę, która ma korzystać poleceń definiujemy następujące właściwości:
Command - wskazuje jakie polecenie ma zostać wywołane (np. Cut, Paste)
CommandParameter - przekazuje dowolny parametr do komendy (nieobowiązkowo)
CommandTarget - wskazuje element w którym zostanie wykonane polecenie (nieobowiązkowo)

Bindując polecenia definiujemy trzy rzeczy:
- co ma się stać gdy zostanie wywołana komenda
- jak zdecydować kiedy jest możliwość wywołania komendy
- zasięg (jeden przycisk czy np. całe okno)

Zobaczmy na przykład:

<Window x:Class="WpfApplication1.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:local="clr-namespace:WpfApplication1"
       Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="New" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed" />
    </Window.CommandBindings>
    <Grid>
        <Button Command="New" Content="New" Width="50" Height="25"/>
    </Grid>
</Window>

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = true;
        }

        private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("New document");
        }
    }
}


Prosty przykład. Po kliknięciu użytkownik zobaczy MessageBox-a. Teraz dodamy menu i tam też metodę New:
<Window x:Class="WpfApplication1.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:local="clr-namespace:WpfApplication1"
       Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="New" CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed" />
    </Window.CommandBindings>
    <Grid>
        <Menu VerticalAlignment="Top">
            <MenuItem Command="New" />
        </Menu>
        <Button Command="New" Content="New" Width="50" Height="25"/>
    </Grid>
</Window>

Dodaliśmy 3 linijki kodu. Należy zauważyć, że nie nadaliśmy żadnego Headera elementowi menu. MenuItem jest na tyle sprytny, że pobierze sobie zawartość pola Text polecenia New. Wygodne i łatwe w użyciu.


Teraz zobaczymy przykład, w którym dzięki polecenią, program sam dokonuje oceny, czy dana komenda jest możliwa do wykonania czy też nie. Na początek kod:
<Window x:Class="WpfApplication1.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:local="clr-namespace:WpfApplication1"
       Title="MainWindow" Height="350" Width="525">
    <StackPanel Orientation="Vertical">
        <Menu>
            <MenuItem Command="Copy" />
            <MenuItem Command="Cut" />
            <MenuItem Command="Paste" />
        </Menu>

        <TextBox Name="txt" />

        <StackPanel Orientation="Horizontal" Margin="0,20,0,0" >
            <Button Margin="20" Content="Copy" Command="Cut" CommandTarget="{Binding ElementName=txt}" />
            <Button Margin="20" Content="Paste" Command="Paste" CommandTarget="{Binding ElementName=txt}" />
        </StackPanel>
    </StackPanel>
</Window>

Po uruchomieniu zobaczymy:

Co należy zauważyć to to, że na starcie mamy coś w buforze schowka do wklejenia. Kolejnym aspektem jest aktywność klawisza Paste nawet po straceniu focusu z TextBoxa (w przypadku Menu klawisz Paste od razu traci aktywność). Dzieje się tak, gdyż w przypadku Buttona ustawiliśmy właściwość CommandTarget. Istotną rzeczą jest także, że możliwość Copy i Cut otrzymujemy dopiero po zaznaczeniu jakiegoś fragmentu tekstu w TextBoxie. Jak więc widać na starcie otrzymaliśmy niezły kawałek gotowej funkcjonalności.


Własne komendy
Oczywiście każdy programista chce mieć możliwość dodawania własnych poleceń. Nie jest to trudne. Zobaczmy na przykład implementacji:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;

namespace WpfApplication2
{
    public class DataCommands
    {
        private static RoutedUICommand requery;
        static DataCommands()
        {
            InputGestureCollection inputs = new InputGestureCollection();
            inputs.Add(new KeyGesture(Key.R, ModifierKeys.Control, "Ctrl+R"));
            requery = new RoutedUICommand("Requery", "Requery", typeof(DataCommands), inputs);
        }

        public static RoutedUICommand Requery
        {
            get { return requery; }
        }
    }
}


<Window x:Class="WpfApplication2.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:l="clr-namespace:WpfApplication2"
       Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="l:DataCommands.Requery" Executed="CommandBinding_Executed" />
    </Window.CommandBindings>
    <Grid>
        <Button Name="button1" Command="l:DataCommands.Requery" Content="Click me!" />
    </Grid>
</Window>


        private void CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("You create new command! Congratulations!");
        }


Jak widać nic trudnego.

piątek, 23 lipca 2010

WPF praktyczne porady

Kilka rzeczy, które ostatnim czasem wykorzystywałem (aby zapamiętać, no i może komuś też się przydadzą):

1. Dodawanie czcionki do aplikacji.
Tworząc aplikację często jest potrzeba wykorzystania niestandardowej czcionki. Aby jednak użytkownik mógł zobaczyć napisy musimy ją dodać do naszej aplikacji. Rozwiązania są dwa. Pierwsze to zainstalowanie jej w systemie wraz z aplikacją. Drugie, które tu pokaże to wkompilowanie czcionki w exe aplikacji. Na początek dodajemy czcionkę do zasobów naszej aplikacji:


Następnie w właściwości FontFamily wprowadzamy nazwę czcionki poprzedzoną znakiem #, ale nie nazwę pliku:


<TextBlock Text="Ala ma kota" FontFamily="./Fonts\#ChopinScript" 
FontSize="60" />
Efekt:

Oczywiście plik z czcionką znajdzie się w exe bez potrzeby instalowania go osobno w systemie.

2. Mnemonic Labela
Dziwna nazwa, ale całkiem fajny mechanizm. Wyobraźmy sobie formę z wieloma polami ( w przykładzie zawarłem tylko 2). Może to być jakiś formularz danych osobowych. Sporo wprowadzania danych...
Niekiedy chcemy przeskoczyć i wypełnić pola w innej kolejności niż są na formatce. Stosując klawisz Tab przeskakujemy do następnej kontrolki. Jednak label może posłużyć jako odnośnik do kontrolki której jest przypisany. Zobaczmy na scren:

Teraz chcemy aby np. po kliknięciu Alt + I TextBox odpowiedzialny za imię stał się aktywny a w wypadku kombinacji Alt + N inny itd. W kodzie będzie wyglądać to następująco:
        <StackPanel Orientation="Vertical">
            <Label Content="_Imię:" Target="{Binding ElementName=firstName}" />
            <TextBox HorizontalAlignment="Left" Name="firstName" Width="100"/>
            <Label Content="_Nazwisko:" Target="{Binding ElementName=lastName}" />
            <TextBox HorizontalAlignment="Left" Name="lastName" Width="100"/>
        </StackPanel> 

Tak więc znak _ oznacza która litera jest brana do skrótu (odpowiednik znaku & w WindowsForms) a właściwość Target wskazuje która kontrolka zostanie aktywowana. Dodatkowo po naciśnięciu Alt zostaną podświetlone odpowiednie skróty:



3. Dostęp do okna głównego aplikacji
Czynność bardzo prosta korzystając z klasy Application:
Window mainWindow = Application.Current.MainWindow;

Można także zdobyć referencję do wszystkich aktualnie wyświetlanych okien:
            WindowCollection windowsOpen = Application.Current.Windows;
            foreach (Window item in windowsOpen)
            {
                MessageBox.Show(item.Title);
            }

Oczywiście do problemu wszystkich aktualnie otwartych okien można podejść także inaczej, tworząc własną listę otwartych okien i odpowiednio nią zarządzając.

4. Dostęp do zasobów z innej biblioteki dll
W bardzo prosty sposób możemy dostać się do zasobów innej biblioteki dll. Jeżeli dla przykładu w bibliotece Zdiecia.dll znajduje się zasób w postaci folderu Zdiecie a w nim plik fotka.png. Dostać się możemy do niej w następujący sposób:

            Image image = new Image();

            image.Source = new BitmapImage(new Uri("Zdiecia;zdiecie/fotka.png", UriKind.Relative));

Ekran powitalny w aplikacji WPF

Każda aplikacja WPF w momencie uruchomienia nie ukazuje się od razu oczom użytkownika. Czas pomiędzy uruchomieniem aplikacji a rzeczywistym jej ukazaniem się wypełnia uruchomienie przez CLR środowiska .NET. 

Aby użytkownik nie czuł dyskomfortu podczas uruchamiania naszej aplikacji, możemy stworzyć ekran powitalny. W technologii WPF jest to niezwykle proste i wymaga jedynie stworzenia grafiki w jednym z popularnych formatów graficznych (jpg, bmp, png). 
Następnie stworzoną grafikę dołączamy do projektu. Zaznaczamy dodany plik i zmieniamy w właściwościach dla niego Build Action na SplashScreen:


Niestety, nie możemy tym sposobem zdefiniować sposobu wyświetlania grafiki (np. obwódka wokół zdjęcia), dlatego tworząc grafikę już na tym etapie należy wiedzieć, co chce się uzyskać.

Dependency Property Value Coercion

Wcześniej pisałem o zaletach jakie niesie z sobą korzystanie z Dependency Properties (DP). Wydaje mi się, że zbyt mało opisałem jeden z ciekawszych mechanizmów a mianowicie Value Coercion.

Mechanizm ten sprawia, że związane ze sobą DP są poprawne względem siebie. O co chodzi w stwierdzeniu "względem siebie". Otóż najprościej przedstawić całą sprawę za pomocą kontrolki Slider. Kontrolka ta posiada właściwości Min i Max określające minimalną i maksymalną wartość możliwą do ustawienia. W naszym przypadku wartość Max zawsze powinna być większa bądź równa Min. Nie może dojść do sytuacji kiedy Max jest mniejszy od Min. Właśnie tego pilnuje wspomniany w tytule mechanizm. Tak więc w momencie, kiedy próbujemy ustawić wartość Max mniejszą od Min funkcja coercion ustawia wartość Max równą Min. Za pomocą narzędzia Reflecotr możemy podejrzeć w jaki sposób zostało to osiągnięte przez Microsoft:


Natomiast wywołanie samych funkcji coercion ma się następująco:


Oczywiście sprawdzenia także wymaga sama właściwość Value i jak można łatwo się domyślić musi ona być w przedziale Min i Max.

Proste, łatwe i przyjemne - a jak może ułatwić życie :)

poniedziałek, 19 lipca 2010

Drag and Drop WPF

Jeżeli chodzi o sposób działania i obsługi to nie zmienił się zbytnio od czasów WindowsForms (opisywałem go tutaj).

Sam mechanizm jest bardzo prosty:
1. Użytkownik chwyta dane które chce przenieść
2. Razem w wciśniętym lewym przyciskiem myszy przesuwa kursor myszy w miejsce gdzie chce je umieścić. W zależności od możliwości wstawienia danych - kursor przyjmuje odpowiednią ikonkę sygnalizującą możliwość lub jej brak.
3. Użytkownik zwalnia lewy przycisk myszy a obiekt który otrzymał dane decyduje co z nimi zrobić.

Dla niektórych kontrolek wspomniana funkcjonalność jest już wbudowana (np. TextBox) dla innych sami musimy zatroszczyć się o obsługę odpowiednich zdarzeń:

Dla źródła
Wychwytujemy naciśnięcie myszki (zdarzenie MouseDown):
        private void label1_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Label l = sender as Label;
            if (l != null)
            {
                DragDrop.DoDragDrop(l, l.Content, DragDropEffects.Copy);
            }
        }

Dla kontrolki docelowej:
Ustawiamy właściwość AllowDrop na True.
Obsługujemy zdarzenie Drop:
        private void listBox1_Drop(object sender, DragEventArgs e)
        {
            ListBox lb = sender as ListBox;
            if (lb != null)
            {
                lb.Items.Add(e.Data.GetData(DataFormats.Text));
            }
        }

Można także sprawdzić jakie dane są przeciągane i w zależności od nich pozwolić lub nie na operację:
        private void listBox1_DragEnter(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.Text))
            {
                e.Effects = DragDropEffects.Copy;
            }
            else
            {
                e.Effects = DragDropEffects.None;
            }
        }
Jeżeli dane które kopiujemy nie da się skonwertować na tekst, przenoszenie zostanie zablokowane.

Tyle jeżeli chodzi o Drag & Drop w WPFie.

Dlaczego warto używać Dependency Properties oraz Routed Events podczas tworzenia aplikacji w WPF

WPF rozwinął idę właściwości i zdarzeń. Standardowe właściwości i zdarzenia zostały przepisane całkowicie od początku dzięki czemu wprowadzono nową funkcjonalność jak i zachowano bardzo dużą wydajność rozwiązań. Przyjrzyjmy się tym dwóm mechanizmom bliżej.


Dependency Propeteries

Zwykłe właściwości są znane raczej wszystkim programistą C#:
        private int _age;

        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }
Umożliwiają proste przypisanie wartości prywatnym polom klasy.
Aby stworzyć klasę, która zawiera DP (Dependency Property) musimy dziedziczyć po klasie (lub bezpośrednio) dziedziczącej z DependencyObject (większość klas WPF dziedziczy z tej klasy więc nie będzie z tym problemu).
Informacje zapisane w DP muszą być dostępne dla innych klas, z tego też powodu definiujemy właściwość jako statyczną i dodatkowo tylko do odczytu:
public static readonly DependencyProperty FirstNameProperty;
Przyjęła się konwencja, aby DP nazywać według szablonu Nazwa_właściwośćiProperty. Pozwala to na proste oddzielenie zwykłych właściwości od DP.
Jak widzimy DP oznaczone jest jako readonly (tylko do odczytu) i static, tak więc wartość może mu przypisać tylko statyczny konstruktor.
Kolejnym etapem tworzenia DP jest rejestracja. Etap ten wykonujemy w statycznym konstruktorze klasy wykorzystując do tego metodę Register(). Rejestracja przebiega dwuetapowo z tym że pierwszy etap można pominąć:
1. Tworzymy metadane czyli obiekt typu FrameworkPropertyMetadata w którym możemy ustawić takie rzeczy jak np. wartość domyślna, sposób bindowania.
2. Rejestracja DP za pomocą metody Register podając jako parametry:
- nazwę właściwośći
- typ danych właściwości
- typ będący właścicielem właściwości
- opcjonalnie: metadane
- opcjonalnie: wywołanie zwrotne (Callback) walidacji
        static MyClass()
        {
            //1 etap
            FrameworkPropertyMetadata metadata = new FrameworkPropertyMetadata("None", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
            //2 etap
            FirstNameProperty = DependencyProperty.Register("FirstName", typeof(string), typeof(MyClass), metadata, new ValidateValueCallback(MyClass.IsFirstNameValid));
        }

Aby można było łatwo korzystać z DP definiujemy wrapper w postaci zwykłej właściwości w którym wykorzystamy metody SetValue(DP, value) oraz GetValue(DP) :
        public string FirstName
        {
            set
            {
                SetValue(FirstNameProperty, value);
            }
            get
            {
                return (string)GetValue(FirstNameProperty);
            }
        }

W jaki sposób WPF wykorzystuje DP?
Większość właściwości, którym przypisujemy wartości za pomocą XAMLa czy też z kodu, jest właśnie typu DP. Podczas pobierania wartości z DP dochodzi do swoistej "reakcji łańcuchowej" w celu uzyskania jej. Hierarchię można przedstawić następująco:
1. Wartość domyślna
2. Odziedziczona wartość
3. Wartość zapisana w stylu
4. Lokalna wartość
Warto się zastanowić dlaczego wymyślono ten mechanizm. Otóż uzyskano dzięki temu zmniejszenie zasobów potrzebnych na tworzenie właściwości tam, gdzie nie są one potrzebne.

Współdzielenie DP
DP mają tą zaletę, że można je udostępnić w innych klasach bez potrzeby dziedziczenia po klasie w której są zdefiniowane. Wykorzystujemy w tym celu metodę AddOwner() przyjmującą jako parametr właściciela DP:
    public class SecondClass : DependencyObject
    {
        public static readonly DependencyProperty FirstNameProperty = MyClass.FirstNameProperty.AddOwner(typeof(SecondClass));
        public string FirstName
        {
            get
            {
                return (string)GetValue(FirstNameProperty);
            }
            set
            {
                SetValue(FirstNameProperty, value);
            }
        }
        static SecondClass()
        {
        }
    }

Attached DP
Jest to specjalny rodzaj DP, który nie odnosi się do klasy w której został zdefiniowany. Najlepszym przykładem jest Grid który posiada ADP (Attached DP) Row i Column. Kładąc kontrokę na Gridzie możemy przypisać numer wiersza i kolumny w której się zndzie:
<Button Grid.Row="2" Grid.Column="2" Width="100" Height="25" Content="Button" />
Deklaracja tego rodzaju DP odbywa się jak poprzednio z wyjątkiem dwóch ostatnich kroków. Zamiast metody Register(), wykorzystujemy metodę RegisterAttached() i nie tworzymy wrapera (ponieważ właściwości i tak nie wykorzystamy w bieżącej klasie). Zamiast wrapera tworzymy SetPropertyName oraz GetPropertyName. Przykład implementacji i ustawienia wartości ADP w XAMLu:
    class SimplePanel : StackPanel
    {
        private static readonly DependencyProperty _text;

        static SimplePanel()
        {
            _text = DependencyProperty.RegisterAttached("Text", typeof(string), typeof(SimplePanel));
        }

        public static string GetText(UIElement element)
        {
            if (element != null)
            {
                return (string)element.GetValue(SimplePanel._text);
            }
            else
            {
                return null;
            }
        }

        public static void SetText(UIElement element, string value)
        {
            if (element != null)
            {
                element.SetValue(SimplePanel._text, value);
            }
        }
    }
<Window x:Class="WpfApplication3.MainWindow"
       xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
       xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
       xmlns:c="clr-namespace:WpfApplication3"
       Title="MainWindow" Height="350" Width="525">
    <Grid>
        <c:SimplePanel>
            <Button Width="100" Height="25" c:SimplePanel.Text="Ala ma kota"></Button>
        </c:SimplePanel>
    </Grid>
</Window>


Walidacja danych w DP
Tradycyjne właściwości walidowaliśmy w momencie ustawiania nowej wartości (czyli w setterze). W DP stworzono inny mechanizm. Walidacji możemy dokonać na dwa sposoby:
1. ValidateValueCallback - może zaakceptować lub odrzucić nową wartość
2. CoerceValueCallback - posiada możliwość zmiany wartości na dopuszczalną
Samo ustawienie wartości w DP z wykorzystaniem tych mechanizmów przebiega następująco:
1. Wywoływany jest CoerceValueCallback
2. Następnie wywoływany jest ValidateValueCallback. Zwraca true w przypadku gdy wartoś jest akceptowana lub false w przeciwnym.
3. Wywołanie PropertyChangedCallback dzięki któremu możemy powiadomić inne klasy o zmianie wartości właściwości

Zacznijmy od mechanizmu ValidateValueCallback
Mechanizm ten ma chyba tylko jedną wadę: brak dostępu do pól niestatycznych. Samam metoda jest zadeklarowana jako statyczna więc automatycznie traci tę możliwość. Przykładowa implementacja:
        private static bool IsFirstNameValid(object value)
        {
            string s = (string)value;
            foreach (var item in s)
            {
                if (!char.IsLetter(item))
                {
                    return false;
                }
            }
            return true;
        }

CoerceValueCallback
        public static object CoerceAllowLetters(DependencyObject o, object value)
        {
            if (((string)value).Contains("Imie"))
            {
                return "";
            }
            return value;
        }




Routed Events

Jedno z potężniejszych narzędzi programisty WPF. Pozwalają na wywołanie nie tylko na obiekcie którego dotyczą, ale na całym drzewie (zarówno logicznym jak i wizualnym). Przykładem może być Button, którego zawartość to obrazek i tekst. Naciskając na obrazek uruchamiamy event klik dla niego, ale chcemy też aby został w naturalny sposób uruchomiony event dla całego przycisku.

Tworzenie Routed Events:
Deklaracja jest bardzo podobna do DP. Tak samo mamy do czynienia z deklaracją static readonly, oraz rejestracją w statycznym konstruktorze:
        public static readonly RoutedEvent MyEvent;

        static MyClass()
        {
            MyEvent = EventManager.RegisterRoutedEvent("My", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(MyClass));
        }
Następnie tworzymy tradycyjny wrapper na zdarzenie:
        public event RoutedEventHandler My
        {
            add
            {
                AddHandler(MyEvent, value);
            }
            remove
            {
                RemoveHandler(MyEvent, value);
            }
        }

Sharing Routed Events
Tak jak w przypadku DP tak i tu mamy możliwość współdzielenia właściwości z innymi klasami:
public static readonly RoutedEvent MyEent = MyClass.MyEvent.AddOwner(typeof(SecondClass));

Event Routing:
Dla wyobrażenia: Label a w nim StackPanel zawierający obrazek i tekst. Klikamy w obrazek.
Podczas rejestrowania zdarzenia, jako jeden z parametrów podawaliśmy RoutingStrategy. Ten wyliczeniowy typ danych zawiera trzy możliwe strategie:
1. Direct - standardowe wywołanie zdarzenia na obiekcie w którym nastąpiło ono
2. Bubbling - wywołanie zdarzenia od źródła w kierunku rodziców
3. Tunneling - zdarzenie wywołane jest najpierw na elemencie głównym, a następnie przesuwany jest w kierunku źródła
W przykładzie powyżej, przeszukiwanie będzie miało następującą kolejność:
1. Image.MouseDown
2. StackPanel.MouseDown
3. Label.MouseDown
Jeżeli nie byłoby w tym momencie zdefiniowanej obsługi zdarzenia, wywołanie przemierzałoby kolejnyh rodziców:
4. Grid
5. Window
Zdarzenie po przechwyceniu nie przechodzi już do następnych rodziców. Należy więc łapać je tam gdzie to na prawdę jest potrzebne.

RoutedEventArgs
Klasa ta zawiera szereg właściwości które pozwalają na m.in. :
Source - zidentyfikować obiekt wywołujący zdarzenie
OrginalSource - identyfikuje obiekt który jako pierwszy wywołał zdarzenie
Handled - pozwala na zatrzymanie Bubblingu lub Tunelingu

Attached Events
W przypadku, gdy którać z kontrolek nie wspiera jakiegoś zdarzenia a jest nam ono bardzo potrzebne, możemy w prosty sposób umożliwić taką obsługę. Dla przykładu Grid nie obsługuje eventu Click. W łatwy sposób można to zmienić:
    <Grid ButtonBase.Click="Grid_Click" Name="dfdf">
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="12,23,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="12,52,0,0" Name="button2" VerticalAlignment="Top" Width="75" />
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="12,90,0,0" Name="button3" VerticalAlignment="Top" Width="75" />
    </Grid>
        private void Grid_Click(object sender, RoutedEventArgs e)
        {
            button1.Content = "Ala";
            button2.Content = "Ala";
            button3.Content = "Ala";
        }

Na koniec warto odpowiedzieć na pytanie postawione w temacie:
Oba mechanizmy pozwalają m.in. na:
- powiadomienia o zmianie wartości
- wywołania zwrotne
- walidację
- dziedziczenie wartości
- współdzielenie z innymi klasami
- wywoływanie "łańcuchowe"
- udział w animacjach
- udział w stylach i szablonach
- bindowanie do danych
- nadpisywanie wartości domyślnych

Tyle na temat dwóch mechanizm, które warto poznać i rozszerzyć o dodatkowe informacje zawarte na MSDN.

sobota, 17 lipca 2010

SVN

SVN czyli kontrola wersji umożliwia uniknięcia tworzenia czegoś w stylu:
Mój_projekt_17-07-2010.08:00.zip
Mój_projekt_17-07-2010.10:00.zip
Mój_projekt_17-07-2010.16:00.zip
itd.

SVN jest o tyle przydatny, że można go wykorzystać np. w przypadku pisania prac licencjackich, magisterskich czy też najzwyklejszych dokumentów. Zawsze możemy dzięki temu wrócić do wcześniej edytowanej wersji.

Aby wygodnie korzystać z SVNu należy najpierw zainstalować serwer oraz klienta.
Server:
Ze strony http://www.visualsvn.com/visualsvn/download/ pobieramy najnowszą paczkę instalatora dla naszego systemu (w momencie pisania tego postu była to wersja 1.6.12). Aby pobrać należy przeprowadzić szybką rejestrację i już możemy instalować nasz server.

Klient:
Warto użyć czegoś "klikanego" tzn. aby nie męczyć się z poleceniami w konsoli. Bardzo fajnym narzędziem klienckim jest TortoiseSVN do pobrania ze strony http://tortoisesvn.tigris.org/ (w momencie pisania tego postu najnowszą wersją była 1.6.10). Instalujemy i resetujemy komputer.

Narzędzia mamy już zainstalowane, przechodzimy więc do tworzenia użytkownika oraz repozytorium:
Przechodzimy do VisualSVN Server Manager.
Klikamy prawym klawiszem myszy na folder Users i wybieramy opcję Create User...


Wprowadzamy login oraz hasło. Po wprowadzeniu danych powinniśmy zobaczyć stworzonego przez nas użytkownika:


Użytkownika już mamy, więc teraz tworzymy repozytorium:
Klikamy prawym klawiszem myszy na Repositories i wybieramy opcję Create New Repository...:


Wprowadzamy nazwę repozytorium i zaznaczamy opcję Create default structure:


Po zatwierdzeniu zobaczmy:


Następnie należy przypisać użytkownika do stworzonego repozytorium:
Klikamy prawym przyciskiem myszy na stworzone repozytorium i wybieramy Propeteries...


Dla wszystkich użytkowników jak powyżej zaznaczamy opcję No Access. Następnie klikamy na przycisk Add... i dodajemy stworzonego wcześniej użytkownika:


Dodanemu użytkownikowi nadajemy uprawnienia do zapisu i odczytu katalogu:


Teraz tworzymy gdzieś folder z naszym projektem, np. na pulpicie i ściągniemy pliki z repozytorium do niego:
Po stworzeniu folderu klikamy na nim prawym klawiszem myszy i wybieramy SVN Checkout...


Następnie musimy podać ścieżkę do naszego repozytorium. Łatwo można ją otrzymać korzystając z VisualSVN Server Manager. Prawym klawiszem na utworzone przez nas repozytorium i opcja Copy URL to Clipboard


Wklejamy link do adresu i zatwierdzamy:


Po ściągnięciu plików w folderze zobaczymy następujące podfoldery:


Przejdźmy teraz do przetestowania naszego repozytorium. W katalogu trunk tworzymy plik tekstowy:


Następnie klikamy prawym klawiszem myszy i wybieramy opcję TortiseSVN -> Add...


A następnie klikając na folder Test wybieramy opcję SVN Commit... W okienku które się otworzy wprowadzamy dodatkowe informacje i zatwierdzamy:



Po wykonaniu Commita plik powinien dostać zielony znaczek. 
Teraz zmodyfikujemy plik i zapiszemy go:


Pierwsze co można zaobserwować to pojawienie się czerwonego wykrzyknika zamiast zielonego znaczka akceptacji. Dzieje się tak zawsze po wprowadzeniu jakichkolwiek zmian w pliku. Aby więc wysłać kolejną wersję na serwer klikamy znów prawym przyciskiem myszy na folder Test i wykonujemy Commita.

Klikając prawym klawiszem na modyfikowany plik i wybierając opcję Diff with previous version:


Możemy zobaczyć co zmieniło się od ostatniej aktualizacji pliku:



Tyle w tym krótkim poradniku obsługi SVN. Oprócz wymienionego tutaj TortoiseSVN istnieje wtyczka, która bezpośrednio integruje się z Visualem Studio 2005/2008/2010 - AnkhSVN. Warto także i na nią zwrócić uwagę. Powodzenia w wersjonowaniu :)