wtorek, 8 maja 2012

70-511 Rozdział 2 - Zdarzenia i komendy - komendy

Komendy to nowy sposób obsługi często powtarzanych czynności. Przykładami komend w WPF są np. Copy, Paste, Cut.
Wbudowane komendy znajdziemy w jednej z 5 statycznych klas:
  • ApplicationCommands
  • ComponentCommands
  • EditingCommands
  • MediaCommands
  • NavigationCommands
Procedura implementacji komendy:
  1. Decyzja czy będzie używana jedna z dostępnych (zaimplementowanych już) komend czy będzie to stworzona nowa implementacja
  2. Skojarzenie komendy z kontrolkami
  3. Stworzenie metody obsługującej komendę
  4. Stworzenie kontrolki CommandBinding bindującej komendę z uchwytem
  5. Dodanie bindingu komendy do kolekcji Commands okna, tam gdzie wywoływana jest komenda

Skojarzenie komendy z kontrolką
Wiele kontrolek implementuje interfejs ICommandSource dzięki czemu udostępniają właściwość Command, np. Button. Komenda jest wywoływana w tym przypadku po wciśnięciu przycisku:


Code:
<Button Command="ApplicationCommands.Find">Button</Button>


Wywołanie komendy za pomocą gestów
Komendę można także skojarzyć z gestami myszy czy zdarzeniami klawiatury:


Code:
ApplicationCommands.Find.InputGestures.Add(new
MouseGesture(MouseAction.LeftClick, ModifierKeys.Control));
ApplicationCommands.Find.InputGestures.Add(new
KeyGesture(Key.Q, ModifierKeys.Control));



Wywołanie komendy z kodu:
Jeżeli kontrolka nie udostępnia właściwości Command, można zawartość procedury komendy wywołać bezpośrednio z kodu:


Code:
ApplicationCommands.Find.Execute(aParameter, TargetControl);

aParameter - parametr komendy (jeżeli nie jest wymagany można przesłać null)
TargetControl - tutaj zostanie rozpoczęte poszukiwanie metodą bubble w poszukiwaniu CommandBinding.


Uchwyty komend
Sygnatura uchwytu do komendy:


Code:
private void myCommandHandler(object sender, ExecutedRoutedEventArgs e)
{
// obsługa
}




Command Bindings
Obiekt tego typu skleja całą architekturę komend - łączy komendę z jej uchwytem. Dodając obiekt typu CommandBinding do kolekcji CommandBindings okna lub innej kontrolki umożliwia wywołanie kodu w momencie zadziałania komendy. Poniżej kod umożliwiający stworzenie i rejestrację CommandBinding:


Code:
CommandBinding abinding = new CommandBinding();
abinding.Command = ApplicationCommands.Find;
abinding.Executed += new ExecutedRoutedEventHandler(myCommandHandler);
this.CommandBindings.Add(abinding);


Command Bubbling
Każda kontrolka posiada kolekcję CommandBindings. Jest to związane z tym iż podobnie jak w Routed Events tu także występuje mechanizm bubblingu. Przerwanie tego mechanizmu przebiega tak samo jak dla zdarzeń.


Wyłączanie komend
Jeżeli komenda nie uczestniczy w bindingu automatycznie jest wyłączona. Innym sposobem jest użycie metody CanExecute która zwraca wartość typu bool mówiącą czy dana komenda jest możliwa do wykonania czy też nie.
Aby przechwycić zdarzenie CanExecute:
1. Tworzymy metodę która determinuje czy można wykonać komendę:


Code:
bool canExecute;
void abinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute = canExecute;
}

2. Dla bindingu dodajemy obsługę zdarzenia CanExecute:


Code:
abinding.CanExecute += new CanExecuteRoutedEventHandler(abinding_CanExecute);

Można także to zrobić w kodzie XAML:


Code:
<Window.CommandBindings>
<CommandBinding Command="ApplicationCommands.Find"
Executed="CommandBinding_Executed"
CanExecute="abinding_CanExecute" />
</Window.CommandBindings>



Tworzenie własnych komend
W tym przypadku Microsoft zaleca stworzenie statycznej klasy która eksponuje komendę do użycia:


Code:
    public static class MyCommands
    {
        private static RoutedUICommand launch_command;

        static MyCommands()
        {
            InputGestureCollection myInputGestures = new
                InputGestureCollection();
            myInputGestures.Add(new KeyGesture(Key.L, ModifierKeys.Control));
            launch_command = new RoutedUICommand("Launch", "Launch",
                                                 typeof (MyCommands), myInputGestures);
        }

        public RoutedUICommand Launch
        {
            get { return launch_command; }
        }
    }

70-511 Rozdział 2 - Zdarzenia i komendy - zdarzenia

WPF oferuje znacznie bardziej rozbudowany system obsługi zdarzeń niż ten znany z WindowsForms. WPF udostępnia tzw. routet events, mechanizm umożliwiający przechwytywanie zdarzenia na wielu poziomach hierarchii drzewa wizualnego.

3 typy routed events:
  • Direct Events - zdarzenia, które są najbliższe tym znanym z WindowsForms. Zdarzenie to jest wywoływane i obsługiwane tylko u źródła, przykład: MouseLeave'
  • Bubbling events - wywołane najpierw u źródła, a następnie przechodzi w dół drzewa wizualnego. Przykładem może być okno w którym na panelu leży label. Zdarzenie MouseDown wywołane na labelu, przejdzie na panel a następnie na okno. Na każdym z tych poziomów możemy je obsłużyć.
  • Tuneling Events - jest to zdarzenie działające odwrotnie do Bubbling event, swoją drogę rozpoczyna w najstarszym przodku, aż do kontrolki która faktycznie je wywołała
Zdarzenia typu Tunelling są w WPF oznaczane słówkiem Preview np PreviewKeyDown.
Zdatzenie typu Tunelling są zawsze wykonywane przed zdarzeniami typu Bubbling.


Routet events args
Klasa ta zawiera bogate informacje o zdarzeniu oraz jego źródła. Właściwości które można w niej znaleźć:
  • Handled - determinuje czy zdarzenie zostało obsłużone. Można dzięki temu zatrzymać dalszy postęp bubblingu lub tunelingu
  • OriginalSource - zwraca informację o źródle skąd pochodzi zdarzenie. Przeważnie zwraca to samo co właściwość Source, dla kontrolek typu composite może być to jednak inna wartość
  • RoutedEvent - w przypadku łapania więcej niż jednego zdarzenia za pomocą tego samego uchwytu, można dzięki tej właściwości zidentyfikować zdarzenie
  • Source - zwraca obiekt który wywołał zdarzenie


Dodawanie uchwytu
Uchwyt do zdarzenia najlepiej dodać w XAML-u:


Code:
<Button VerticalAlignment="Top" Click="button1_Click">Button</Button>

Istnieje możliwość przypięcia zdarzenia do kontrolki, która w normalnych warunkach go nie rzuca. Są to tzw. Attached Events. Dla przykładu można przypiąć zdarzenie kliknięcia Gridowi, który posiada button:


Code:
    <Grid Button.Click="Grid_Click">
        <Button Name="button1" VerticalAlignment="Top" >Button</Button>
    </Grid>



EventManager
Jest to klasa, która zajmuje się rejestracją wszystkich zdarzeń w aplikacji WPF. Właściwości:
  • GetRoutedEvents - zwraca tablicę wszystkich zaarejestrowanych Routed Events
  • GetRoutedEventsForOwner - zwraca tablicę wszystkich zarejestrowanych zdarzeń dla podanego obiektu
  • RegisterClassHandler - rejestruje uchwyt na poziomie klasy
  • RegisterRoutedEvent - rejestruje uchwyt na poziomie instancji klasy


Tworzenie nowego Routed Event
1. Tworzymy publiczne, tylko do odczytu zdarzenie:


Code:
public static readonly RoutedEvent MyEvent;

2. Tworzymy wrapper dla Routed Event:


Code:
        public event RoutedEventHandler MyEventWrapper
        {
            add
            {
                AddHandler(MyEvent, value);
            }
            remove
            {
                RemoveHandler(MyEvent, value);
            }
        }

3. Używając EventManager rejestrujemy zdarzenie w konstruktorze klasy, która będzie go używać:


Code:
EventManager.RegisterRoutedEvent("MyEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Button));

Podajemy kolejno: nazwę zdarzenia, rodzaj, typ delegaty, klasę która jest jego właścicielem.
Wywoływanie zdarzenia w kodzie polega na stworzeniu obiektu klasy RoutedEventArgs, a następnie wywołanie zdarzenia za pomocą metody RaiseEvent:


Code:
            RoutedEventArgs myEventArgs = new RoutedEventArgs(myControl.myNewEvent);
            RaiseEvent(myEventArgs);



Tworzenie uchwytu na poziomie klasy
Uchwyt taki wywoływany jest dla każdej klasy żądanego typu i jest wywoływany przed uchwytem na poziomie instancji klasy. Tworzenie takiego uchwytu:

1. Tworzymy statyczną metodę o takiej samej sygnaturze jak zdarzenie:


Code:
private static void SuperClickHandlerMethod(object sender, RoutedEventArgs e)
{
//obsługa
}

2. W statycznym konstruktorze, który będzie korzystał z tego zdarzenia tworzymy delegate do metody:


Code:
RoutedEventHandler MyHandler = new RoutedEventHandler(SuperClickHandlerMethod);

3. Także w statycznym konstruktorze rejestrujemy uchwyt:


Code:
EventManager.RegisterClassHandler(typeof(Button),
SuperClickEvent,MyHandler);



Zdarzenia na poziomie aplikacji
Klasa Application zawiera standardowe zdarzenia, które mogą zajść podczas korzystania z aplikacji. Wybrane z nich:
  • Activated - wywoływane podczas pierwszego uruchomienia aplikacji jak i przy przejściu z innej do naszej
  • Deactivated - wywołuje się w momencie przejścia do innej aplikacji
  • DispatcherUnhandledException - Wywoływany w momencie gdy wyjątek nie zostanie nigdzie obsłużony
  • Exit - wywołuje się niezależnie od powodu zamknięcia aplikacji
  • SessionEnding - wywoływany w momencie gdy użytkownik wylogowuje się lub zamyka komputer
  • Startup - wywoływany w momencie uruchamiania aplikacji
 Instrukcja tworzenia uchwytu na poziomie aplikacji:
 1. W pliku App.xaml dodajmy zdarzenie:


Code:
<Application x:Class="Application"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="Window1.xaml" Startup="App_Startup">

2. W kodzie C# obsługujemy je:


Code:
void App_Startup(object sender, StartupEventArgs e)
{
// obsługa
}

poniedziałek, 7 maja 2012

70-511 Rozdział 1 - Tworzenie interfejsu użytwkownika - Style i triggery

W tej części zostaną omówione style oraz triggery.

Style
Style w WPF są podobne do stylów w HTML - CSS. Definiując style, łatwiej wpływać na wygląd całej aplikacji. Klasa Style posiada następujące właściwości:
  • BasedOn - pozwala na "dziedziczenie" z innego stylu
  • Resources - kolekcja lokalnych zasobów dla stylu
  • Setters - kolekcja Setterów dla właściwości bądź zdarzeń
  • TargetType - typ elementu który ma przyjąć styl
  • Triggers - kolekcja triggerów, pozwalająca na zaprojektowanie zmian w interfejsie użytkownika w przypadku zmiany wartości którejś z właściwości

Settery
Można je podzielić na dwie kategorie:
  • settery dla właściwości - ustawiają wartości właściwości
  • settery dla zdarzeń - ustawiają uchwyty dla zdarzeń
Dwie ważne właściwości:
  • Property - określa która właściwość ma być ustawiona
  • Value - wartość, którą ma przyjąć właściwość
Setter ma następującą składnię:


Code:
<Setter Property="Element.PropertyName" Value="Red" />

Przykład:


Code:
<Style>
<Setter Property="Control.Background" Value="Red" />
</Style>



EventSetter
Podobnie jak setter dla właściwości, posiada dwie ważne właściwości:
  • Event - typ zdarzenia któemu chcemy nadać wartość
  • Handler - uchwyt
Właściwość Event ma następującą składnię: Element.EventName


Code:
<EventSetter Event="Button.MouseEnter" Handler="Button_MouseEnter" />


Style można przypisywać bezpośrednio do kontrolki:


Code:
<Button Height="25" Name="Button1" Width="100">
<Button.Style>
<Style>
<Setter Property="Button.Content" Value="Style set directly" />
<Setter Property="Button.Background" Value="Red" />
</Style>
</Button.Style>
</Button>

bądź pisać je jako zasoby:


Code:
<Window.Resources>
<Style x:Key="StyleOne">
<Setter Property="Button.Content" Value="Style defined in resources" />
<Setter Property="Button.Background" Value="Red" />
</Style>
</Window.Resources>

Przypisanie stylu do przycisku:


Code:
<Button Name="Button1" Style="{StaticResource StyleOne}" Height="30" Width="200" />

Styl można przypisać do wszystkich kontrolek danego typu ustawiając właściwość TargetType:


Code:
<Window.Resources>
<Style TargetType="Button">
<Setter Property=" Content" Value="Style set for all buttons" />
<Setter Property="Background" Value="Red" />
</Style>
</Window.Resources>

Taki zabieg dodatkowo spowoduje że nie trzeba używać pełnego kwalifikatora dla właściwości której przypisujemy wartość


Ustawianie stylów z poziomu kodu:
Style dużo łatwiej definiuje się w XAML, jednak czasami zachodzi konieczność dynamicznego ich tworzenia np. w wypadku aplikacji w której użytkownik chce sam ustalić wygląd aplikacji. Przykładowy kod:


Code:
Style aStyle = new Style();
Setter aSetter = new Setter();
aSetter.Property = Button.BackgroundProperty;
aSetter.Value = Brushes.Red;
aStyle.Setters.Add(aSetter);
Setter bSetter = new Setter();
bSetter.Property = Button.ContentProperty;
bSetter.Value = "Style set programmatically";
aStyle.Setters.Add(bSetter);
Button1.Style = aStyle;

Można także zdefiniować styl w XAML i dynamicznie go przypisać do kontrolki:


Code:
<Window.Resources>
<Style x:Key="StyleOne">
<Setter Property="Button.Content" Value="Style applied in code" />
<Setter Property="Button.Background" Value="Red" />
</Style>
</Window.Resources>


Style aStyle;
aStyle = (Style)this.Resources["StyleOne"];
Button1.Style = aStyle;


Dziedziczenie stylów:
Jeżeli mamy zdefiniowany jeden styl dla przycisków, a chcemy aby na formatce jeden z nich się odróżniał, możemy dziedziczyć styl i np. zmienić tylko dla niego kolor np.




Triggery
Triggery pozwalają na zmianę wartości kontrolek w reakcji na zmianę innej wartości. Wyróżniamy 5 rodzai triggerów:
  • Property trigger (Trigger) - w momencie gdy wartość właściwości przyjmuje wartość Value zostaje aktywowany
  • Multi-trigger (MultiTrigger) - sprawdza na raz kilka wartości; zostaje aktywowany w momencie kiedy wszystkie są zgodne z wartością Value
  • Data trigger (DataTrigger) - sprawdza zbindowane pola, aktywuje się gdy wartość pola ma wartość równą polu Value
  • Multi-data trigger (MultDataTrigger) - to samo co wyżej tylko że dla kilku pól
  • Event trigger (EventTrigger) - wykonuje akcje w momencie gdy dane zdarzenie ma miejsce
Trigery inne niż EventTrigger mogą być definiowane tylko w kodzie stylu, w innym przypadku dostaniemy błąd w czasie wykonania.


Property Triggers
Najczęściej wykorzystywany rodzaj triggerów. Ważne właściwości tego rodzaju triggerów:
  • EnterActions - kolekcja obiektów typu Akcja. Akcje są wykonane w momencie, gdy trigger zostaje aktywowany
  • ExitActions - kolekcja akcji, które są wykonywane w momencie gdy trigger przestaje być aktywny
  • Property - właściwość, która ma być monitorowana
  • Setters - kolekcja setterów, które są aplikowane w momencie gdy trigger się aktywuje
  • Value - wartość do porównania z właściwością Property
Prosty przykład zmiany czcionki, gdy myszką najedziemy na przycisk:


Code:
<Style.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter Property="Button.FontWeight" Value="Bold" />
</Trigger>
</Style.Triggers>


Multi-triggers
Sprawdzanych jest kilka właściwości jednocześnie. W momencie gdy wszystkie warunki zajdą, nadawana jest odpowiednia wartość wyspecyfikowanym właściwością. Przykład:


Code:
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Button.IsMouseOver" Value="True" />
<Condition Property="Button.IsFocused" Value="True" />
</MultiTrigger.Conditions>
<MultiTrigger.Setters>
<Setter Property="Button.FontWeight" Value="Bold" />
</MultiTrigger.Setters>
</MultiTrigger>
</Style.Triggers>

W tym przypadku kolor czcionki przycisku zostanie pogrubiony kiedy myszka znajduje się nad przyciskiem i jest ustawiony na nim focus.


Data Triggers i Multi-data Triggers
Są bardzo podobne do Triggerów, z tym że sprawdzają zmiany w polach które są bindowane. Przykład:


Code:
<Style.Triggers>
<DataTrigger Binding="{Binding Path=CustomerName}" Value="Fabrikam">
<Setter Property="Label.Background" Value="Red" />
</DataTrigger>
</Style.Triggers>

Multi-data triggers są podobne do Multi-triggers, tak samo posiadają kolekcję warunków oraz setterów, jednak nasłuchują pól zbindowanych:


Code:
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=CustomerName}" Value="Fabrikam" />
<Condition Binding="{Binding Path=OrderSize}" Value="500" />
</MultiDataTrigger.Conditions>
<MultiDataTrigger.Setters>
<Setter Property="Label.Background" Value="Red" />
</MultiDataTrigger.Setters>
</MultiDataTrigger>
</Style.Triggers>



Event Triggers
Są wywoływanie w momencie, gdy wyspecyfikowane zdarzenie ma miejsce. Wykorzystywane są przede wszystkim w przypadku animacji. Dwa proste przykłady:

Code:
<EventTrigger RoutedEvent="Button.Click">
<SoundPlayerAction Source="C:\myFile.wav" />
</EventTrigger>

W momencie naciśnięcia przycisku zostanie odtworzony utwór. Drugi przykład:


Code:
<EventTrigger RoutedEvent="Button.Click">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Duration="0:0:5"
Storyboard.TargetProperty="Height" To="200" />
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>

W momencie gdy przycisk zostanie kliknięty, jego wysokość zostanie zwiększona do 200 jednostek.


Kolejność w jakiej jest ustalana wartość właściwości. Najniższy numer oznacza najwyższy priorytet:
  1. Ustawienie przez system coerci.
  2. Ustawione przez animacje
  3. W kodzie C#, XAML, bądź bindowanie
  4. Szablon
  5. Niejawny styl
  6. Triggery Stylu
  7. Triggery Szablonu
  8. Domyślny styl
  9. Dziedziczenie
  10. Metadata

sobota, 5 maja 2012

70-511 Rozdział 1 - Tworzenie interfejsu użytwkownika - Zasoby

W tym poście zostaną omówione zasoby, czyli obiekty i pliki które nie są tworzone w czasie działania programu, a dostarczone przed kompilacją.


Używanie binarnych zasobów (plików)
Aby dodać zasób do naszej aplikacji, postępujemy według poniższego schematu:
  1. Z menu projektu wybieramy Add Existing Item
  2. Po otwarciu okna dialogowego wyboru plików, wskazujemy plik który chcemy dodać
  3. W oknie właściwości zmieniamy dla naszego pliku właściwość Build Action na Resource.
Ważne! Dla WPF nie ustawiamy nigdy Build Action na Embedded Resource
Uaktualnienie pliku zasobów odbywa się poprzez ponowne wykonanie powyższych kroków.


Ładowanie zasobów
Ładowanie zasobów np. do kontrolki Image jest bardzo proste. Wystarczy do właściwości Source podstawić ścieżkę do pliku graficznego. W zależności czy plik znajduje się na tej samej wysokości w drzewie folderów, czy na innym ścieżka przyjmie odpowiednią postać:


Code:
        <Image Source="folder/pic.png" Stretch="Fill"/>
        <Image Source="pic.png" Stretch="Fill"/>


Pack URIs
Pokazana powyżej składnia jest skróconą wersją tzw. Pack URI. Pełna składnia ma następującą postać:
pack://<Authority>/<Folder>/<FileName>
  • Authority przyjmuje jedną z dwóch postaci application:,,, - jeżeli zasób pochodzi z aktualnie uruchomionej aplikacji lub siteOfOrigin:,,, - aplikacja ma szukać zasobu w jej źródłach.
  • Folder - folder w którym znajduje się zasób
  • FileName - nazwa zasobu 
Przydaje się to zwłaszcza w przypadku podawania ścieżki do zasobu w kodzie:


Code:
image.Source = new BitmapImage(new Uri("pack://application:,,,/folder/pic.png"));


Używanie zasobów z innych bibliotek Dll
Jest możliwe użycie zasobu znajdującego się w innej bibliotece dll niż aplikacja. W tym celu należy skorzystać z następującego Pack URI:
pack://application:,,,/<AssemblyName>;component/<Folder>/<FileName>


Pliki nie wkompilowywane
W dużej ilości przypadków będzie potrzeba wymiany któregoś z plików, np. będzie to szablon na podstawie którego będą generowane zaproszenia. Użycie w takim przypadku wkompilowywanych zasobów, zmuszałoby nas do kompilacji aplikacji za każdym razem kiedy szablon się zmienił. Dodatkowo pliki dźwiękowe czy też filmowe, które są odtwarzane za pomocą kontrolki MediaPlayer nie mogą korzystać z składni Pack URI.
Aby dodać plik jako zasób nie kompilowany, postępujemy według poniższych kroków:
  1. Z menu projektu wybieramy opcję Add Existing Item.
  2. Wybieramy plik który chcemy dodać
  3. Ustawiamy we właściwościach Build Action na Content oraz Copy To Output Directory na Copy Always
Po dodaniu pliku używamy relatywnej ścieżki przy odwołaniu się do niego:


Code:
<MediaElement Source="music.mp3"/>



Użycie siteOfOrigin Pack URI dla plików często zmienianych
Zdarza się, że mamy zamiar użyć pliku który jeszcze nie znajduje się w solucji bądź też będzie często zmieniany. W przypadku XAML-a plik musi się znajdować już w czasie kompilacji w odpowiednim katalogu aby można go było użyć w aplikacji. Rozwiązaniem tego problemu jest wcześniej wspomniany siteOfOrigi, który nie wkompilowuje zasobu do Dll. Dzięki temu plik może być podmieniony bez potrzeby rekompilacji całej aplikacji. Przykład:


Code:
<Image Source="pack://siteOfOrigin:,,,/image.jpg"/>




Odwołanie do zasobów z kodu
Do zasobów zawartych w aplikacji można odwołać się z kodu C#:


Code:
            StreamResourceInfo myInfo = Application.GetResourceStream(new Uri("text.txt", UriKind.Relative));
            var myReader = new StreamReader(myInfo.Stream);
            var textContent = myReader.ReadToEnd();

obiekt klasy StreamResourceInfo posiada dwie właściwości:
  • Stream - strumień do zawartości pliku
  • ContentType - rodzaj zawartości


Tworzenie Dll zawierających tylko zasoby
Rozwiązanie to jest przydatne w przypadku gdy chcemy często zmieniać pliki zasobów. Dzięki stworzeniu takiej Dll, nie ma potrzeby kompilowania i podmiany następnie całej aplikacji, a wystarczy podmienić jedną bibliotekę na nową. Kroki które należy wykonać aby stworzyć taką bibliotekę:
  1. W VS tworzymy projekt typu Empty Project.
  2. W Solution Explorer klikamy prawym przyciskiem myszy na nazwie projektu i wybieramy opcję Properties. Na zakładce Application, ustawiamy Application Type na Class Library
  3. W menu projektu wybieramy Add Existing Item aby dodać plik zasobów.
  4. Dodanemu pliku zmieniamy Build Action na Embedded Resource
  5. Budujemy Dll
Dostęp z kodu do zasobu:
  1. Pobieramy nazwę Dll: 
    Code:
    System.Reflection.AssemblyName.GetAssemblyName("C:\\myAssembly.dll"));
  2. Ładujemy Dll-kę do pamięci używając pobranej nazwy:
    Code:
    var asm = System.Reflection.Assembly.Load(aName);
  3. Następnie za pomocą metod GetManifestResourceNames - pobieramy nazwy zasobów w Dll a za pomocą metody GetManifestResourceStream - pobieramy strumień do nich. Poniżej przykład pobrania obrazka i przypisania go do kontrolki Image:
    Code:
    var res = asm.GetManifestResourceNames();
    pictureBox1.Image = new
    System.DrawingBitmap(asm.GetManifestResourceStream(res[0]));


Używanie logicznych zasobów
Zasoby logiczne to zasoby które nie są widoczne w wizualnym drzewie naszej aplikacji. Przykładem takiego zasobu jest np. Brush. Dzięki temu raz stworzony zasób pędzla możemy użyć w wielu miejscach.
Resource Logiczne możemy tworzyć na różnych poziomach: od aplikacji po zasoby kontrolki, w zależności gdzie chcemy reużywać raz stworzony zasób. Przykład:


Code:
<Window.Resources>
<RadialGradientBrush x:Key="myBrush">
<GradientStop Color="CornflowerBlue" Offset="0" />
<GradientStop Color="Crimson" Offset="1" />
</RadialGradientBrush>
</Window.Resources>

Każdy zasób musi mieć właściwość x:Key która jest unikalna w danej kolekcji zasobów.
Dostęp do tak zdefiniowanych zasobów odbywa się poprzez przypisanie zasobu do właściwości która jest z nim kompatybilna np.:


Code:
<Grid Background="{StaticResource myBrush}">
</Grid>



Statyczne i dynamiczne zasoby
Oprócz statycznych zasobów istnieją także dynamiczne zasoby. Główne różnice to:
  • Dynamiczne zasoby są tworzone za każdym odwołaniem się do nich
  • Ponieważ są tworzone za każdym razem, zmniejszają wydajność ogólną aplikacji
Jak jest więc sens ich stosowania? Jeżeli w kodzie chcielibyśmy zmienić np. kolor stosowany przez pędzel, to w przypadku StaticResource zmiana nie odniesie rezultatu, w przypadku DynamicResources zmiana zostanie odnotowana przez użytkownika.



Słowniki zasobów (Resource Dictionary)
Słowniki zasobów tworzą osobny plik w którym są przechowywane zasoby. Dzięki temu można je reużywać w wielu aplikacjach w prosty sposób. Aby dodać nowy słownik postępujemy według poniższych kroków:
  1. Dodajemy nowy element do solucji typu Resource Dictionary
  2. W pliku dodajemy kolejne zasoby
Aby można było teraz użyć zasobów ze słownika, należy go połączyć z zasobami aplikacji (bądź okna lub innej kontrolki gdzie chcemy z nich skorzystać). Przykład mergowania:


Code:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml" />
<ResourceDictionary Source="Dictionary2.xaml" />
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="BlueBrush" Color="Blue" />
</ResourceDictionary>
</Window.Resources>



Dostęp do zasobów w kodzie
Zasoby w kodzie można zarówno odczytywać jak i nadpisywać. Dostęp jest możliwy na dwa sposoby:
  • Za pomocą metody (SolidColorBrush)Button1.FindResource("myBrush");
  • Odwołując się konkretnie do zasobów kontrolki (SolidColorBrush)this.Resources["myBrush"];

piątek, 4 maja 2012

70-511 Rozdział 1 - Tworzenie interfejsu użytwkownika - Panele i układ

W tej części zajmiemy się powtórką związaną z kontenerami wykorzystywanymi podczas ustawiania kontrolek na formatce.

Podstawowe właściwości wszystkich kontrolek związanych z układem:
  • FlowDirection - kierunek w którym tekst jak i zawartość innych kontrolek będzie wyświetlany
  • Height - wysokość; domyślnie Auto
  • HorizontalAlignment - wyrównanie w poziomie (chodzi o rodzica kontrolki - parent)
  • HorizonalContentAlignment - wyrównanie zawartości kontrolki
  • Margin - margines
  • MaxHeight - maksymalna wysokość
  • MaxWidth - maksymalna szerokość
  • MinHeight - minimalna wysokość
  • MinWidth - minimalna szerokość
  • Padding - ilość spacji pomiędzy kontrolką i kontrolkami przechowywanymi w niej
  • VerticalAlignment - położenie pionowe
  • VerticalContentAlignment - położenie pionowe elementów wewnątrz kontrolki
  • Width - szerokość

Grid
Najczęściej używana kontrolka. Pozwala na definiowanie wierszy i kolumn.
Kontrolki wewnątrz Grid-a otrzymują dodatkowe właściwości które mówią w których kolumnach i wierszach mają się znaleźć. Właściwości te to:
  • Grid.Row - wiersz
  • Grid.Column - kolumna
  • Grid.RowSpan - ile wierszy ma zająć kontroka
  • Grid.ColumnSpan - ile kolumn ma zająć
GridSplitter - kontrolka umożliwiająca zmienianie wielkości wierszy i kolumn podczas działania programu. Najważniejsze właściwości GridSplitter:
  • Grid.Column - określa w której kolumnie znajdzie się GridSplitter
  • Grid.ColumnSpan - określa przez ile kolumn ma przebiegać GridSplitter - dla poziomego GridSplittera jego wartość powinna być równa ilości zdefiniowanych kolumn w Gridzie
  • Grid.Row - określa w którym wierszu ma się znaleźć GridSplitter
  • Grid.RowSpan - określa przez ile wierszy ma przebiegać GridSplitter - dla pionowego GridSplittera jego wartość powinna być równa ilości zdefiniowanych wierszy w Gridzie
  • Height - określa wysokość GridSplittera - dla pionowego powinna ona wynosić Auto
  • HorizontalAlignment - określa poziome położenie GridSplittera. Dla poziome powinna mieć wartość Stretch, dla pionowego - Top lub Bottom
  • Margin - Określa margines pomiędzy GridSplitterem a kontrolkami - typowo 0
  • ResizeBehavior - określa które kolumny lub wiersze są przeskalowane w stosunku do kolumny lub wiersza, dla których GridSplitter jest zdefiniowany. Domyślnie BasedOnAlignment, który określa zmianę rozmiaru na podstawie wierszy i kolumn do których przylega GridSplitter
  • ResizeDirection - określa czy zwiększane są kolumny/wiersze. Domyślnie ustawione jest Auto co jest równoważne zmianie wielkości na podstawie pozycji GridSplittera
  • ShowsPreview - decyduje czy GridSplitter ma aktualizować wielkość kolumny.wiersza w czasie interakcji z użytkownikiem
  • VerticalAlignment - Ustala poziome ułożenie GridSplittera. Dla pionowego ustawienie powinno mieć wartość Stretch, dla poziomego Left or Right
  • Width - określa szerokość GridSplittera. Dla poziomego ustawienie powinno mieć wartość Auto  

Lista kroków które należy wykonać aby poprawnie wstawić GridSplitter:
  1. Przeciągamy z przybornika kontrolkę GridSplittera do komórki w której chcemy żeby się znalazł
  2. Ustawiamy jego margines na 0
  3. Dla pionowego GridSplittera ustawiamy właściwość VerticalAlignment na Stretch. Dla poziomego ustawiamy HorizontalAlignment na Stretch. Drugą właściwość ustawiamy tak jak chcemy aby zachowywał się nasz GridSplitter
  4. Dla poziomego GridSplittera ustawiamy właściwość Width na Auto a Height na 2 lub więcej. Dla pionowego ustawiamy odpowiednio właściwość Height.
  5. Dla pionowego ustawiamy właściwość Grid.RowSpan na ilość wierszy w Gridzie którym się znajduje dla poziomego Grid.ColumnSpan na ilość kolumn


UniformGrid Control
Tworzy jednolity Grid, którego komórki mają jednakową wielkość. Możemy ustawić ilość wierszy i kolumn w nim. Dodając kolejne kontrolki do niego są wstawiane do kolejnych komórek.


StackPanel
Ustawia kontrolki jak na stosie - jedna po drugiej. Możliwe jest dodawanie ich w orientacji pionowej jak i poziomej poprzez właściwość Orientation


WrapPanel
Dodaje kolejne kontrolki w poziomych "paskach". Kiedy w jednym wierszy już się nie mieszczą są dodawane do kolejnego. Za pomocą właściwości FlowDirection można zmienić kierunek dodawania kontrolek.


DockPanel
Pozwala na dokowanie kontrolek, przypisując je do odpowiednich brzegów. Jest to rozwiązanie dobrze znane z WindowsForms. Posiada dodatkowo właściwość LastChildFill, która powoduje że ostatnia kontrolka zajmuje całą dostępną powierzchnię.


Canvas
Pozwala na umieszczanie kontrolek poprzez ustawienie koordynat. Podczas zwiększania/zmniejszania rozmiaru Canvasu kontrolki pozostają na swoich miejscach.


Dostęp do kontrolek zawartych w danym kontenerze odbywa się poprzez właściwość Children i indeks kontrolki której referencję chcemy otrzymać.

70-511 Rozdział 1 - Tworzenie interfejsu użytwkownika - kontrolki zaawansowane

W tej części zajmiemy się kontrolkami, mogącymi przechowywać wiele elementów.

ListBox
Kontrolka zaprojektowana aby wyświetlać listy, której elementy są typu ListBoxItem. Kontrolka automatycznie dodaje potrzebne paski przewijania jeżeli są potrzebne. Domyślnie można zaznaczyć tylko jeden element na liście, ustawiając właściwość SelectionMode można uzyskać dodatkowe możliwości:
  • Single - domyślna wartość - zaznaczanie pojedynczego elementu
  • Multiple - możliwość zaznaczenia wielu elementów
  • Extended - użytkownik może zaznaczyć kilka sąsiadujących elementów za pomocą klawisza Shift, lub kilka niesąsiadujących za pomocą klawisza CTRL.

ComboBox
Kontrolka różni się od poprzedniej tylko sposobem prezentacji elementów - jest to rozwijana lista z której wybieramy elementy. Listę możemy otworzyć z poziomu kodu ustawiając właściwość IsDropDownOpen na True.


TreeView i TreeViewItem
Pozwala na wyświetlanie list w postaci drzewiastej. Kontrolka TreeViewItem służy do tworzenia struktury drzewa i posiada takie właściwości jak:
Referencję do aktualnie zaznaczonego elementu w drzewie możemy pobrać za pomocą właściwości SelectedItem.


Menu, MenuItem i Separator
Wyświetla kontrolki typu MenuItem. Menu posiada właściwość IsMainMenu która po ustawieniu na True powoduje że naciśnięcie Alt + F10 ustawi aktywność na nim. Podstawowe właściwości MenuItem:
  • Command - komenda przypisana do danej pozycji menu
  • Header - Wyświetlany text w menu
  • Icon - ikona pozycji w menu
  • IsChecked - pokazuje zaznaczanie/odznaczenie po lewej stronie menu
  • Items - lista kontrolek MenuItem przypisanych do danej pozycji Menu
Aby dodać separator wystarczy użyć kontrolki Separator:


Code:
        <Menu>
            <MenuItem Header="_Plik">
                <MenuItem Header="Dodaj"/>
                <Separator/>
                <MenuItem Header="Zamknij"/>
            </MenuItem>
        </Menu>



ContextMenu
Menu kontekstowe skojarzone jest z konkretną kontrolką. Pojawia się po wciśnięciu prawego przycisku myszy bądź po naciśnięciu Shift+F10. Przykład definicji:


Code:
        <ListBox>
            <ListBox.ContextMenu>
                <ContextMenu>
                    <MenuItem Header="Wytnij" />
                    <MenuItem Header="Kopiuj" />
                    <MenuItem Header="Wklej" />
                </ContextMenu>
            </ListBox.ContextMenu>
        </ListBox>


ToolBar
Kontrolka świetnie nadająca się do umieszczania popularnych komend w postaci przycisków, check-boxów oraz labeli. Ważną właściwością jest ToolBar.OverflowMode, która decyduje w jaki sposób mają być wyświetlane kontrolki które się na nim nie mieszczą:
  • OverflowMode.Always - wyświetla z boku indykator, że są jeszcze inne polecenia do wyboru
  • OverflowMode.AsNeeded - wyświetla indykator tylko wtedy kiedy kontrolki faktycznie się nie mieszczą
  • OverflowMode.Never - dodatkowe komendy (które nie mieszczą się) nie będą dostępne
ToolBarTray - kontrolka ta umożliwia przemieszczanie toolbarów które się w niej znajdują.



StatusBar
Kontrolka używana w celu wyświetlania informacji o aktualnym stanie aplikacji (np. za pomocą Labela, ProgressBar itp)

70-511 Rozdział 1 - Tworzenie interfejsu użytwkownika - podstawowe kontrolki

Pierwszy rozdział treningu, dotyczy budowania interfejsu użytkownika. Platforma .NET umożliwia budowanie interfejsu użytkownika za pomocą WindowsForms lub WPF.
Budowa interfejsu w każdym z tych frameworków rządzi się swoimi prawami. My zajmiemy się WPFem.
Kontrolki w WPF można podzielić na 3 rodzaje:


  • Kontrolki indywidualne - spełniają najczęściej jedno zadanie: pozwalają na wprowadzenie tekstu (TextBox), wyświetlanie tekstu (Label, TextBlock), klikanie (Button) itp
  • Kontrolki przechowujące kolekcje obiektów - przechowują grupy powiązanych ze sobą obiektów - np. ListBox, Menu, TreeView
  • Kontrolki odpowiedzialne za rozmieszczenie innych kontrolek na formatce - np. Grid, StackPanel, Canvas

Content Controls
Kontrolka Content Control dziedziczy po klasie ContentControl i pozwala na zagnieżdżenie w sobie pojedynczej kontrolki. Zagnieżdżona kontrolka może być dowolnego typu, a odwołanie się do niej czy też jej przypisanie może nastąpić zarówno w kodzie jak i XAML za pomocą właściwości Content.
Właściwość Content jest typu object, tak więc można przypisać jej dowolny obiekt. Wyświetlenie takiej zagnieżdżonej kontrolki zależy od tego czy dziedziczy po klasie UIElement:
  • jeżeli nie dziedziczy po UIElement, zostanie zastosowana metoda ToString i jej rezultat będzie wyświetlony w kontrolce
  • jeżeli dziedziczy po UIElement, zostanie wyświetlona w taki sposób, jaki zostało to wyspecyfikowanie w kontrolce
Należy zauważyć, że pomimo tego, iż Content control, może zawierać tylko jeden zagnieżdżony element, za pomocą odpowiedniego kontenera (np. StackPanel) można wyświetlić więcej obiektów.


Label control - Mnemonic keys
Kontrolka ta wykorzystywana jest do wyświetlania tekstu. Kontrolka ta pozwala na korzystanie z tzw. mnemonic keys, czyli predefiniowanych skrótów, które definiują która kontrolka ma stać się aktywna w przypadku ich użycia.
Przykładowe użycie: Label oraz obok TextBox. Chcemy aby po naciśnięciu ALT + I został przeniesiony kursor na TextBox. Realizacja w kodzie:

Code:
            <Label Target="{Binding ElementName=tbFirstName}">Naciśnij Alt+_I</Label>
            <TextBox Name="tbFirstName" Height="30" Width="100" />

Mnemonic Key (skrót ustawiamy poprzez dodanie znaku podkreślenia "_" przed wybraną literą do skrótu. Kontrolkę docelową wskazujemy za pomocą bindowania i nazwy elementu na który wskazujemy.


Button control
Przycisk jest dobrze znaną kontrolkę więc nie będę się nad nim rozwodził.
Dwie ważne właściwości, które zwykle ustawiamy podczas tworzenia interfejsu użytkownika to:
  • IsDefault - determinuje, który klawisz zareaguje po naciśnięciu Entera na formatce
  • IsCanceled - determinuje który klawisz zareaguje po naciśnięciu klawisza Esc
Przyciski definiują także tzw. access keys (przyciski dostępu). Jest to funkcjonalność podobna do Mnemonic keys i w taki sam sposób jest definiowana (poprzez znak podkreślenia przed wybraną literą). Klawisz skrótu to także ALT + wybrana litera.


CheckBox
Pozwala na wybór opcji. Zawiera właściwość IsChecked która jest typu bool?. Przybiera więc 3 stany:
  • po otwarciu formatki: null
  • po zaznaczeniu: true
  • po odznaczeniu: false

RadioButton
RadioButton pozwala na wybór jednej opcji z wielu. Jeżeli chcemy pogrupować RadioButton-y mamy dwie możliwości:
  • w kontenerze (Grid, StackPanel) są one automatycznie grupowane do jednej grupy
  • Można użyć właściwości GroupName w celu przypisania RadioButtonowi innej grupy 

TextBlock
Kontrolka służąca do wyświetlania tekstu.


ImageControl
Kontrolka służąca do wyświetlania plików graficznych. Najważniejsze właściwości:
  • Source - źródło - tzw URI Uniform Resource Identifier - ścieżka do miejsca gdzie jest załadowana grafika
  • Stretch - format wyświetlania grafiki, do dyspozycji mamy jeden z czterech:
    • None - zawartość pliku graficznego jest wyświetlana w oryginalnym rozmiarze, w razie potrzeby przycinana jeżeli się nie zmieści w kontrolce
    • Fill - zawartość pliku graficznego jest powiększana/zmniejszana w celu dopasowania do powierzchni w kontrolce ImageControl
    • Uniform - dostosowanie wielkości grafiki do kontrolki z proporcji obrazu
    • UniformToFill - plik graficzny jest odpowiednio powiększany/zmniejszany aby dopasować się do wielkości kontrolki. Zostają zachowane proporcje obrazu, w razie nie zmieszczenia obrazu zostaje on obcięty.

TextBox
Kontrolka umożliwia użytkownikowi wprowadzanie teksu. Najważniejsze właściwości:
  • IsReadOnly - tylko do odczytu
  • TextWrapping - umożliwia wprowadzanie wielu linii tekstu po ustawieniu tej właściwości na "Wrap"
  • VerticalScrollBarVisibility - dodaje pasek przewijania z góry na dół w kontrolce

ProgressBar
Daje możliwość wizualnego przedstawienia użytkownikowi postępu podczas wykonywania czasochłonnych operacji. Podstawowe właściwości:
  • IsIndeterminate - jeżeli ustawione na False pasek postępu wyświetla wartość z pola Value, w przeciwnym przypadku pasek postępu będzie "krążył" nie oddając do końca obecnego postępu
  • Maximum - maksymalna wartość właściwości Value (oznacza 100%)
  • Minimum - minimalna wartość Value (oznacza 0%)
  • Orientation - położne paska postępu pionowe lub poziome
  • Value - aktualna wartość postępu

Slider
Pozwana użytkownikowi w graficzny sposób zmienić wartość. Podstawowe właściwości:
  • IsDirectionReversed - determinuje czy kierunek jest odwrócony (od prawa do lewa)
  • LargeChange - maksymalna zmiana w przypadku kliknięcia myszką na końcu paska
  • Orientation - orientacja (pozioma lub pionowa)
  • SmallChange - najmniejsza zmiana w przypadku kliknięcia myszką na pasku
  • TickFrequency - determinuje podziałkę (co ile ma być)
  • TickPlacement - położenie podziałki
  • Ticks - ręczne przypisanie wartości podziałki i położenia
  • Value - aktualna wartość

Kolejność przechodzenia po kontrolkach za pomocą klawisza TAB
  • Ustawiamy za pomocą właściwości TabIndex
  • Kontrolki które nie mają zdefiniowanej wartości TabIndex otrzymują aktywność po przejściu wszystkich kontrolek, które mają ją ustawioną
  • Jeżeli kontrolki maja ustawiony ten sam TabIndex, kolejność przejścia jest determinowana kolejnością zadeklarowania w XAML-u
Jeżeli nie chcemy aby któraś kontrolka otrzymała aktywność podczas używania klawisza TAB, należy ustawić właściwość KeyboardNavigation.IsTabStop na False