czwartek, 30 sierpnia 2012

[WPF] ControlTemplate

Choć jesteśmy przyzwyczajeni do WPF - owych kontrolek w takiej postaci, w jakiej występują one w naszym systemie operacyjnym, nie jesteśmy w żaden sposób ograniczeni do takiego ich wyglądu. Aby w pełni zreorganizować wygląd kontrolek, należy zdefiniować dla nich ControlTemplate. Dzięki temu nie stracimy dostępu do DependencyProperties czy Eventów charakterystycznych dla danej kontrolki, a nadamy jej unikalny wygląd. Mechanizm TemplateBinding umożliwia zbindowanie się do property z oryginalnej kontrolki.


<Window.Resources>
    <ControlTemplate x:Key="TriangleButton">
        <Grid Width="100" Height="100">
            <StackPanel Orientation="Vertical">
            <Path Data="M 30 10 L 50 40 10 40 Z"
                StrokeThickness="48"
                StrokeLineJoin="Round"
                Stroke="Blue"
                Fill="Blue" />
                <TextBlock FontSize="15" 
                            HorizontalAlignment="Left"
                            Text="{TemplateBinding Property=Button.Content}" />
            </StackPanel>
        </Grid>
            
    </ControlTemplate>
</Window.Resources>
<Grid>
    <Button Content="Exit" 
            Template="{StaticResource TriangleButton}" 
            Click="Button_Click" />
</Grid>

Efekt :


wtorek, 28 sierpnia 2012

[WPF] Triggers

Triggery stanowią kolejny bardzo użyteczny mechanizm WPF. Pojedynczy Trigger składa się z zestawy setterów, podobnie jak styl, oraz z warunku, jaki musi zostać spełniony, aby te settery zostały zastosowane.Zatem dzięki zastosowaniu triggerów, mamy okazję przenieść sporo kodu dotyczącego wyglądu z code behind do xaml. Wprowadzono trzy rodzaje triggerów :

  • Property Trigger

Warunek wyzwolenia takiego triggera zależy od tego, czy któreś dependency property osiągnęło pewną wartość. W momencie, gdy property ponownie zmieni wartość, zmiany zostaną cofnięte. Przykład :

<Style TargetType="{x:Type Button}">
    <Setter Property="Background" Value="Red" />
    <Setter Property="Foreground" Value="White" />
    <Setter Property="BorderThickness" Value="2" />
    <Setter Property="BorderBrush" Value="Blue" />
    <Style.Triggers>
        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="Green" />
        </Trigger>
    </Style.Triggers>
</Style>

  • Data Trigger

Działa podobnie jak Property Trigger, ale może być wyzwolony przez dowolne .NETowe property. Przez to obowiązuje nieco trudniejsza składa przy definiowaniu tego Triggera. Przykładowo tworząc w CodeBehind property IsHidden, możemy sterować widocznością TextBlocka.


  public bool IsHidden
        {
            get { return _isHidden; }
            set
            {
                _isHidden = value;
                Debug.WriteLine(value);
                if(PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs("IsHidden"));
            }
        }

Teraz można zdefiniować DataTrigger.


<Style TargetType="{x:Type TextBlock}" >
    <Style.Triggers>
        <DataTrigger Value="True"
            Binding="{Binding ElementName=Window,Path=IsHidden}">
            <Setter Property="Visibility" Value="Collapsed" />
        </DataTrigger>
    </Style.Triggers>
</Style>

  • EventTrigger

Głównym ich zastosowaniem jest wyzwalanie animacji zapisanych w storyboardach w momencie, gdy zajdzie jakieś zdarzenie. Mając nagrany w Resources storyboard, możemy się posłużyć następującą składnią:


<Window.Triggers>
    <EventTrigger RoutedEvent="FrameworkElement.Loaded">
        <BeginStoryboard Storyboard="{StaticResource MyStoryboard}"/>
    </EventTrigger>
</Window.Triggers>

Dodatkowo często chcemy stworzyć bardziej złożoną logikę, niż zajście pojedynczego warunku. Mamy do dyspozycji konstrukcje będące ekwiwalentem logicznych funkcji AND i OR.

  • AND

Narzędziem, służącym do zbudowania tej funkcji logicznej jest MultiTrigger.


<Style.Triggers>    
    <MultiTrigger>
        <MultiTrigger.Conditions>
            <Condition Property="IsMouseOver" Value="True" />
            <Condition Property="IsVisible" Value="True" />
        </MultiTrigger.Conditions>
        <MultiTrigger.Setters>
            <Setter Property="Foreground" Value="Black"/>
        </MultiTrigger.Setters>
    </MultiTrigger>
</Style.Triggers>

  • OR

Aby stworzyć funkcję OR, można dodać kilka Triggerów obok siebie z tymi samymi setterami. Taka konstrukcja będzie odpowiadała alternatywie.

poniedziałek, 27 sierpnia 2012

[WPF] Implicit Styles

Podczas stylowania kontrolek czasami ciężko jest uniknąć ważnej zasady DRY - Don't Repeat Yourself. WPF daje sporo sposobów, na które możemy ostylować nasze kontrolki. Jednym z nich, bardzo wygodnym i zapewniającym reużywalność naszego stylu jest stylowanie domniemane. Idea polega na tym, że definiujemy styl dla danego typu kontrolki. Jeśli nie nadpiszemy określonych property to będą one ustawiane tak, jak w implicit style.

Przykładowo gdy chcemy, aby wszystkie przyciski w obrębie aplikacji były o określonej kolorystyce, dopisujemy styl do Application.Resources.


<Application.Resources>
    <Style TargetType="{x:Type Button}">
        <Setter Property="Background" Value="Purple" />
        <Setter Property="Foreground" Value="White" />
        <Setter Property="BorderThickness" Value="2" />
        <Setter Property="BorderBrush" Value="White" />
    </Style>
</Application.Resources>

Dzięki temu, każdy przycisk będzie domyślnie wyglądał jak powyżej.

[WPF] ValidationRule

Bindując się do kontrolek interfejsu użytkownika, powinno się z góry założyć, że wprowadzone mogą zostać niepoprawne dane. W niektórych przypadkach możemy takie dane przyjąć, a w innych możemy wymusić na użytkowniku wpisanie poprawnych danych. Pomocne w tym przypadku stają się reguły walidacyjne. Reguła walidacyjna jest warstwą pomiędzy źródłem i celem bindingu, która zawiera logikę sprawdzającą poprawność danych.

Aby zbudować swoją regułą, wystarczy stworzyć klasę dziedziczącą po ValidationRule.

 public class EmailValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            Debug.WriteLine("Validation Rule Active : " + value.ToString());
            var str = value.ToString();
            Regex regex = new Regex(@"^([\w.-]+)@([\w-]+)((.(\w){2,3})+)$",RegexOptions.IgnoreCase);
            var match = regex.Match(str);
            return match.Success ? new ValidationResult(true, null) : 
                new ValidationResult(false, "Email Address is not valid");
        }
    }

A następnie dodać instancję, do databindingu.

<TextBox Name="textBox8" >
    <TextBox.Text>
        <Binding>
            <Binding.ElementName>
                main
            </Binding.ElementName>
            <Binding.Path>
                Email
            </Binding.Path>
            <Binding.ValidationRules>
                <local:EmailValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Metoda Validate wywoła się w momencie próby aktualizacji źródła danych w przypadku Bindingu OneWay.

niedziela, 26 sierpnia 2012

[WPF] DataBinding : Mode, UpdateSourceTrigger

Data binding może być stosowany na wiele sposobów. WPF umożliwia określenie trybu, oraz sposobu uaktualniania danych. Przez tryb rozumiany jest głównie kierunek przepływu danych. Ustawia się go przez property Mode.

 <TextBox  Text="{Binding Mode=OneWay, ElementName=main, Path=MyText1}" />
 <TextBox  Text="{Binding Mode=TwoWay, ElementName=main, Path=MyText2}" />
 <TextBox  Text="{Binding Mode=OneWayToSource,ElementName=main, Path=MyText3}" />
 <TextBox  Text="{Binding Mode=OneTime,ElementName=main, Path=MyText4}" />


Tryby data bindingu :
  • OneWay
Cel jest aktualizowany przy każdej zmianie źródła, zmiany celu (np. zmiana teksu w TextBoxie) pozostaje bez wpływu na stan źródła.
  • TwoWay
Zarówno zmiana źródła danych, jak i celu spowoduje zmiany po drugiej stronie.
  • OneWayToSource
Odwroność OneWay. Zmiana celu powoduje zmianę źródła danych, w drugą stronę zmiany nie zachodzą.
  • OneTime
Target pobiera dane ze źródła w momencie bindowania, następnie połączenie zostaje niejako zerwane: zmiany źródła nie aktualizują celu, podobnie zmiany celu nie powodują zmian w źródle.

Jeśli chodzi o domyślny tryb, to dla większości DependencyProperties jest to OneWay. Wyjątkiem jest np. TextBox.Text, gdzie domyślnie ustawiony jest tryb TwoWay.

Drugą użyteczną opcją, jest UpdateSourceTrigger. Można w ten sposób ustawić, kiedy ma dojść do aktualizacji danych.


<TextBox Text="{Binding Mode=TwoWay, ElementName=main, Path=MyText1, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Text="{Binding Mode=TwoWay, ElementName=main, Path=MyText1, UpdateSourceTrigger=LostFocus}" />
<TextBox Text="{Binding Mode=TwoWay, ElementName=main, Path=MyText1, UpdateSourceTrigger=Explicit}" />

  • PropertyChanged
Aktualizacja następuje przy każdej zmianie property. Oznacza to, że np. wpisując do pustego TextBoxa słowo kot, nastąpią trzy zmiany źródła : przy dopisaniu każdej litery.
  • LostFocus
Domyślny tryb dla TextBox.Text. Zmiana zachodzi, gdy kontrolka będąca targetem straci focus. Oszczędza się wtedy niepotrzebnych zmian źródła.
  • Explicit
W tym przypadku, musimy jawnie aktualizować źródło w następujący sposób:


var bindingExpr = textBox7.GetBindingExpression(TextBox.TextProperty);
bindingExpr.UpdateSource();

[WPF] HierarchicalDataTemplate

Podczas stosowanie data bindingu, dane mogą być nam dostarczone w różnej postaci. Mogą to być pojedyncze wartości, kolekcje przeróżnych typów, ale często zdarzy się, że są dostarczone w postaci hierarchicznej. WPF ułatwia bindowanie się do plików XML, za pomocą specjalnych mechanizmów.Pierwszy z nich do XmlDataProvider zapewniający binding i notyfikację każdej zmiany, jaka zajdzie po obu stronach. Kolejnym jest HierarchicalDataTemplate, który umożliwia binding do hierarchicznej struktury.

Mając przykładowe dane w pliku Countries.xml:


<?xml version="1.0" encoding="utf-8" ?>
<Countries>
  <Country Name="Poland">
    <CapitalCity>Warsaw</CapitalCity>
    <Voivodeships>
      <Voivodeship Name="Masovian">
        <Cities>
          <City>Plock</City>
          <City>Radom</City>
          <City>Warsaw</City>
        </Cities>
      </Voivodeship>
      <Voivodeship Name="Lesser Poland">
        <Cities>
          <City>Cracow</City>
          <City>Tarnow</City>
        </Cities>
      </Voivodeship>
      <Voivodeship Name="Greater Poland">
        <Cities>
          <City>Kalisz</City>
          <City>Poznan</City>
        </Cities>
      </Voivodeship>      
    </Voivodeships>  
</Country>
</Countries>

Możemy do resourców dodać XmlProvider z ścieżką do pliku.

<XmlDataProvider Source="Countries.xml" XPath="Countries" x:Key="MyProvider" />

A następnie, również w resourcach dodać hierarchiczny szablon danych.


<HierarchicalDataTemplate DataType="Countries" ItemsSource="{Binding XPath=*}">
    <TextBlock Text="Countries" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="Country" ItemsSource="{Binding XPath=*}">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="{Binding XPath=@Name}" />
        <TextBlock Text=", Capital City : " />                
        <TextBlock Text="{Binding XPath=CapitalCity}" />
    </StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="Voivodeships" ItemsSource="{Binding XPath=*}">
    <TextBlock Text="Voivodeships :" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="Voivodeship" ItemsSource="{Binding XPath=*}">
    <TextBlock Text="{Binding XPath=@Name}" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="Cities" ItemsSource="{Binding XPath=*}">
    <TextBlock Text="Cities" />
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="City" >
    <TextBlock Text="{Binding XPath=.}" />
</HierarchicalDataTemplate>

Ostatecznie możemy zbindować dane z providera do kontrolki TreeView.


 <TreeView ItemsSource="{Binding Source={StaticResource ResourceKey=MyProvider}, XPath=.}"  />

I tym sposobem możemy przeglądać drzewo z naszymi danymi zapisanymi w takiej hierarchii, jak w pliku XML

sobota, 25 sierpnia 2012

[WPF] ICollectionView

WPF oferuje mechanizm wspierający podstawowe standardy dotyczące wyświetlania kolekcji, takie jak filtrowanie, sortowanie czy grupowanie. Mechanizm jest następujący : w momencie, gdy następuje data binding do obiektu typu IEnumerable, tworzony jest pomiędzy źródłem a celem tworzony jest niejawnie widok. Jest nim obiekt implementujący interfejs ICollectionView. Wspiera on wspomniane wcześniej operacje.
  • Sortowanie
Tworzymy obiekt klasy SortDescription, gdzie jako parametry konstruktora podawane jest property, po którym ma się odbyć sortowanie, oraz kierunek (rosnąco lub malejąco).


var sort1 = new SortDescription("Surname", ListSortDirection.Descending);   
c1.Items.SortDescriptions.Add(sort1);

  • Grupowanie
Aby móc w pełni wykorzystać grupowanie, należy nieco rozszerzyć wygląd ListBoxa o to, jak prezentowany będzie nagłówek grupowania.

<ListBox.GroupStyle>
    <GroupStyle>
        <GroupStyle.HeaderTemplate>
            <DataTemplate>
                <StackPanel Orientation="Horizontal">
                    <Label Content="Age = " />
                    <Label Content="{Binding}"/>
                </StackPanel>                                
            </DataTemplate>
        </GroupStyle.HeaderTemplate>
    </GroupStyle>
</ListBox.GroupStyle>

Po dodaniu wyglądu nagłówka, można przejść do dodania mechanizmu grupującego. Najbardziej przydatne wydaje się być grupowanie po property.


var group1 = new PropertyGroupDescription("Age");
c2.Items.GroupDescriptions.Add(group1);

  • Filtrowaanie
 Aby móc filtrować kolekcję należy stworzyć delegata zwracającego wartość logiczną.


c3.Items.Filter = o =>
                        {
                            var p = o as Person;
                            if (p == null)
                                return false;
                            return p.Age > 27;
                        };



piątek, 24 sierpnia 2012

[WPF] Data Binding : Value Converter

Używając data bindingu może zajść potrzeba zbindowania dwóch różnych typów. Innym razem możemy chcieć dodać pewne reguły walidacji bindowanych danych, np. saturację wartości liczbowych. Te zadania wymagają stworzenia własnego Value Convertera - specjalnej klasy, zawierającej logikę konwertującą, czy walidującą.

W pierwszym kroku należy zdefiniować klasę implementującą interfejs IValueConverter.

 public class DigitToBrushConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            float res = 0;
            bool valid = float.TryParse(value.ToString(), out res);
            if(valid)
            {
                var props = typeof(Colors).GetProperties();
                try
                {
                    return new SolidColorBrush(Color.FromRgb((byte)res, 0, 0));
                }
                catch (Exception)
                {
                    return new SolidColorBrush(Color.FromRgb(0, 0, 0));
                }
            }
            return Brushes.Wheat;

        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

Jeżeli interesuje nas binding w jedną stronę, metody ConvertBack nie trzeba implementować.

Następnie należy dodać do zasobów okna nasz konwerter.

<Window.Resources>
        <local:DigitToBrushConverter x:Key="myConverter" />
</Window.Resources>

Utworzona zostanie instancja konwertera, którą można przywoływać, jako StaticResource.

W końcu można przejść w xaml do stworzenia bindingu.


<Slider x:Name="colorSlider"  />
<Button Background="{Binding ElementName=colorSlider, Converter={StaticResource myConverter}, Path=Value}" 
Content="BoundBackground" />
   

Dodatkową opcją jest ConverterParameter - obiekt, który będzie przekazany do metody konwertera.jako parameter.

[WPF] Data Binding : Relative Source

Ustawiając w xaml data binding, możemy określić źródło korzystając z RelativeSource. Istnieje kilka sytuacji, w których warto użyć tej opcji.

  • Gdy chcemy, aby źródło i cel bindingu były tą samą kontrolką, np. zmiana wartości slidera powoduje zmianę jego nieprzezroczystości.
<Slider Minimum="0.1" Maximum="1" TickFrequency="0.1"
   Value="{Binding Path=Opacity, RelativeSource={RelativeSource Self}}" />

  • Podczas tworzenia CustomControl w WPF, dodajemy do niej styl w pliku Generic.xaml. Custom control może składać się z wielu mniejszych kontrolek i gdy chcemy aby taka kontrolka bindowała się do property z CustomControl, używamy RelativeSource TemplatedParent.

<Style TargetType="{x:Type local:MySimpleCustomControl}">
        <Setter Property="HorizontalAlignment" Value="Center"/>
        <Setter Property="VerticalAlignment" Value="Center"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MySimpleCustomControl}">
                    <Grid Margin="3">                     
                        <Border BorderThickness="1" BorderBrush="Gray" 
                            Margin="2" Grid.RowSpan="2" 
                            VerticalAlignment="Center" HorizontalAlignment="Stretch">
                            <TextBlock Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Margin}" 
                                Width="60" TextAlignment="Right" Padding="5"/>
                        </Border>                      

                    </Grid>

                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

...możemy stworzyć instancję takiej kontrolki, ustawić jej marginesy i zostaną one zbindowane do textblocka.
  • Czasami zachodzi potrzeba zbindowania się do property z kontrolki, która znajduje się wyżej w drzewie wizualnym. Możemy znaleźć najbliższego przodka danego typu.

<TextBlock Text="{Binding Path=ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}}" />

Istnieje także możliwość określenie, który z kolei przodek nas interesuje, dopisując AncestorLevel=...

czwartek, 23 sierpnia 2012

[WPF] Logical Resources

Zasobem logicznym może być np. kolor, brush czy czcionka. Mamy do dyspozycji dwa rodzaje zasobów logicznych.

StaticResource -statyczne, ładowane w momencie uruchamiania aplikacji, nie podlegają zmianom w czasie trwania aplikacji, ale też nie wymagają dodatkowych zasobów do śledzenia zmian.

DynamicResource - mogą być zmieniane wielokrotnie w czasie trwania aplikacji, ładowane są dopiero w momencie, kiedy zachodzi taka potrzeba, np. przy wejściu do danej strony.

Na poziomie okna zasoby definiowane są w następujący sposób :

    <Window.Resources>
        <SolidColorBrush x:Key="myBrush" Color="Azure" />
    </Window.Resources>

Mogą być także definiowane na innych poziomach, np. w pliku App.xaml jako zasoby globalne dla całej aplikacji.

   <Application.Resources>
        <ResourceDictionary>
            <SolidColorBrush x:Key="globalBrush" Color="DarkOliveGreen" />
        </ResourceDictionary>
    </Application.Resources>

Do zasobów odwołujemy się w następujący sposób:

<Rectangle Fill="{StaticResource myBrush}" Name="rectangle1" Stroke="Black" VerticalAlignment="Top" Width="491" />
<Rectangle Fill="{DynamicResource globalBrush}"  Name="rectangle2" Stroke="Black" VerticalAlignment="Top" Width="491" />
   

Istnieje możliwość używania tych samych kluczy na różnych poziomach. Podczas wczytywania zasobu, przeszukiwane jest drzewo wizualne, od najbardziej lokalnego poziomu. Jeśli zostanie znaleziony zasób na poziomie okna, to jest on ładowany, jeśli nie, to sprawdzany jest poziom aplikacji, później zasoby systemowe, a gdy również tam nie zostanie nic znalezione, rzucany jest wyjątek.

Warto także pamiętać o tym, że domyślnie, gdy zdefiniujemy zasób i odwołamy się do niego w wielu miejscach, to używana jest jedna instancja. Możemy wymusić tworzenie nowego zasobu poprzez ustawiania atrybutu x:Shared=”False”.

Zasoby można także definiować oraz przydzielać z poziomu Code Behind.


 private void rectangle2_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Resources.Add("addedResource", new SolidColorBrush(Colors.Khaki));
            App.Current.Resources.Add("newResource", new SolidColorBrush(Colors.Honeydew));
        }

        private void rectangle1_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            ((Rectangle)sender).Fill = (SolidColorBrush)Resources["addedResource"];
            ((Rectangle)sender).Stroke = (SolidColorBrush)App.Current.Resources["newResource"];
        }

poniedziałek, 20 sierpnia 2012

[WPF] Resources

Resourcem można nazwać wszystko, co nie zawiera kodu, przeważnie obrazy, pliki muzyczne czy filmy. Zasobami można zarządzać na kilka sposobów w WPFie. Pierwszy sposób to binary resources. Mogą być one przechowywane na kilka sposobów: wbudowane w assembly, luźne pliki znane aplikacji w momencie kompilacji lub luźnie pliki, które nie muszą być znane aplikacji w momencie kompilacji. Rodzaj zasobu definiujemy w Visual Studio we właściwościach pliku przez Build Action.


Wybierając Resource lub Content decydujemy się na zasób binarny. Build Action ustawione na Resource spowoduje, że plik zostanie wbudowany w assembly. W przypadku gdy wybierzemy Content, plik pozostanie poza assembly, ale w metadanych zostanie ustawiony atrybut AssemblyAssociatedContentFile, który rejestruje, czy plik istnieje. W przypadku opcji Content, zachodzi już konieczność ustawienie Copy to Output Directory. Dzięki temu, podobnie jak dla Build Action - Resource można w xaml podawać ścieżkę do pliku w wygodnej postaci , np . :

<Image Source="resource_image.png" HorizontalAlignment="Left" />
<Image Source="content_image.png" HorizontalAlignment="Right" />

Co ciekawe nie możemy się odwoływać ze skompilowanego xaml do plików, które nie są dołączone do projektu, nawet jeśli są w tym samym folderze.

W przypadku takich plików, można podać pełną ścieżkę do pliku, lub odwołać się do niego przez site of origin.

<Image Source="E:\Projects\Wpf\Resources\loose_file.png"/>
<Image Source="pack://siteOfOrigin:,,,/star.png" />

Można także odwoływać się do zasobów zaszytych w innych dll, za pomocą następującej składni :
AssemblyReference;Component/ResourceName, np. MyAssembly.dll;MyProject/image1.png.

Czasami zachodzi także potrzeba ustawienia zasobu z poziomu kodu. Ścieżkę podaje się "opakowaną" w klasę Uri. Np.


image1.Source = new BitmapImage(new Uri("pack://siteOfOrigin:,,,/star.png"));

sobota, 18 sierpnia 2012

[WPF] Aplikacje wykorzystujące NavigationWindow

WPF wspiera tworzenie aplikacji, które przypominają strony internetowe, a więc takich, gdzie możemy przechodzić między stronami, lub wracać do poprzednio odwiedzonych. Komponentem, który wspiera takie zachowanie jest Page. Strony mogą być osadzane w dwóch kontenerach : Navigation Window oraz Frame. Zachowują się one podobnie, z tą różnicą, że NavigationWindow zawiera pasek do nawigacji. Aby rozpocząć, zamiast obiektu typu Window w App.xaml jako StartupUri podajemy obiekt typu Navigation Window. W samym NavigationWindow w xaml podajemy jako Source URI do startowej strony, np. Source="FirstPage". Wewnątrz strony można zwyczajnie, jak w UserControl układać kontrolki wewnątrz grida, należy jedynie mieć świadomość o braku zdarzeń OnClosed i OnClosing.

<NavigationWindow x:Class="Navigation_Based_Applications.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Source="MainPage.xaml" Height="350" Width="525">
   
</NavigationWindow>


Pomiędzy stronami można przechodzić, korzystając z metody Navigate klasy NavigationService.

this.NavigationService.Navigate(new SecondPage());

Przechodzić można nie tylko do kontrolek typu Page, ale także do stron www, podając adres jako Uri :

this.NavigationService.Navigate(new Uri("http://www.codeproject.com"));

W efekcie uaktywni nam się pasek nawigacji w NavigationWindow. Będziemy mogli przechodzić do przodu / do tyłu, lub wybrać jedną z odwiedzonych stron z listy. Przechodzić do przodu i do tyłu można również z poziomu kodu, za pomocą metod:

            this.NavigationService.GoBack();
            this.NavigationService.GoForward();
        

Aby uniknąć wyjątku należy sprawdzić, czy okno ma gdzie nawigować (properties CanGoBack i CanGoForward z NavigationService).

piątek, 17 sierpnia 2012

[WPF] Freezable

Obiekty typu Freezable charakteryzują się tym, iż posiadają dwa stany : frozen oraz unfrozen. Kiedy znajdują się one w stanie unfrozen, zachowują się jak zwykłe obiekty, natomiast gdy ich stan zmieni się na frozen, nie można ich modyfikować. Taki stan ma dwie główne zalety : oszczędność zasobów, które w normalnym stanie odpowiadają za notyfikowanie zmian, oraz możliwość współdzielenia zamrożonego obiektu przez wiele wątków. Przykładem obiektów typu Freezable są Brush, czy Transform, które korzystają z niezarządzanych zasobów, których monitorowanie jest kosztowne.Klasa Freezable dzieidziczy z DependencyObject, tak więc dostajemy wszystkie zalety związne z Dependency Property. W przypadku, gdy nasz obiekt został zamrożony metodą Freeze(), nie możemy przeprowadzić operacji odwrotnej. Możemy natomiast stworzyć nowy, "odmrożony obiekt", klonując poprzedni.


 public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            FreezablesTest();
        }

        private void FreezablesTest()
        {
            SolidColorBrush myBrush = new SolidColorBrush(Colors.Gainsboro);
            LayoutRoot.Background = myBrush;
            myBrush.Freeze();
            try
            {
                myBrush.Color = Colors.Green;
                LayoutRoot.Background = myBrush;
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
            var newBrush = myBrush.Clone();
            newBrush.Color = Colors.Green;
            LayoutRoot.Background = newBrush;
        }
    }

Wyjątek :
Cannot set a property on object '#FFDCDCDC' because it is in a read-only state.

czwartek, 16 sierpnia 2012

[WPF] Commands

Komendy są pewnego rodzaju zdarzeniami opisywanymi na wyższym poziomie abstrakcji. Można też je nazwać akcjami niezależnymi od aktualnego interfejsu. Przykłady ? Kopiuj, wklej, cofnij i tak dalej. WPF wspiera znaczną ilość wbudowanych komend, a niektóre z nich są skojarzone ze skrótami klawiaturowymi systemu (np. Ctrl + C). Od strony kodu komenda musi implementować interfejs ICommand, który definiuje trzy podstawowe składniki: Execute (metoda w której zawiera się logika), CanExecute - zwraca true, jeśli można wywołać komendę oraz zdarzenie CanExecuteChanged, które jest wywoływane, gdy wartość CanExecute zmienia się. Niektóre kontrolki, takie jak np. button wystawiają property Command, gdzie przez Command Binding można zbindować się do jednej z dostępnych komend, lub stworzyć własną. Ponadto nic nie stoi na przeszkodzie, by zdefiniować własne skróry klawiaturowe do własnych komend.

Dostępne komendy w System.Windows.Input:
Poniższy przykład pokazuje jak wykorzystać kilka wbudowanych komend i zdefiniować dla nich skróty klawiszowe:

<Window x:Class="Commands.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.CommandBindings>
        <CommandBinding Command="Favorites" 
                        CanExecute="CommandBinding_CanExecute" 
                        Executed="CommandBinding_Executed" />
        <CommandBinding Command="Close" 
                        CanExecute="CommandBinding_CanExecute"
                        Executed="CommandBinding_Executed_1" />
    </Window.CommandBindings>
    <Window.InputBindings>
        <KeyBinding Command="Favorites" Key="F5" />
        <KeyBinding Command="Close" Gesture="Ctrl+q" CommandParameter="q" />
    </Window.InputBindings>
    <Grid>
        <CheckBox Content="myCheckbox" />
    </Grid>
</Window>

  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(e.Command.ToString());
        }

        private void CommandBinding_Executed_1(object sender, ExecutedRoutedEventArgs e)
        {
            MessageBox.Show("Will be closed now");
        }
    }

[WPF] Routed Events

Routed events to specjalny rodzaj zdarzeń, które świetnie nadają się do pracy z hierarchiczną strukturą kontrolek. Kiedy Routed Event jest wywoływany, może być propagowany w górę lub dół drzewa, co może się okazać w wielu sytuacjach bardzo użytecznym rozwiązaniem. Dzięki nim logika aplikacji nie musi pamiętać o strukturze drzewa wizualnego, co jest bardzo dobrą właściwością np. przy stylowaniu. Istnieje pewna analogia pomiędzy RE, a Dependency Property. Oba  są definiowane jako publiczne statyczne pole, rejestrowane w podobny sposób i oba można "opakować". Dla DP pobiera się lub ustawia wartość, dla RE dodaje lub zabiera Event Handler. Istnieją trzy strategie routingu :
  • Tunneling - zdarzenie jest wywoływane najpierw w korzeniu drzewa, a następnie na niższych poziomach aż do źródła wywołania lub obsługi zdarzenia
  • Bubbling - zdarzenie wołane u źródła przechodzi na wyższe poziomy do korzenia lub do miejsca, w którym zostaje obsłużone
  • Direct - zachowanie takie, jak u zwykłych .NETowych eventów (wołane tylko w źródle), ale można tworzyć dla tego zdarzenia event triggery.
 Obsługa RE przypomina obsługę zwykłych zdarzeń (metody z pierwszym parametrem typu object (sender), oraz drugim typu eventArgs lub pochodnym).

Klasa UI element oferuje wiele eventów ze zdefiniowaną strategią bubbling jak i tunneling. Przez konwencję, zdarzenia ze strategią tunneling nazywane są z przedrostkiem Preview. Np. PreviewMouseDown to zdarzenie ze strategią tunneling, podczas gdy MouseDown ma zdefiniowaną strategię typu bubbling.

Na poniższym przykładzie można zaobserwować kolejność, w jakiej są wywoływane zdarzenia, oraz warunki, w jakich routing jest przerwany.

<Window x:Class="Routed_Events.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid MouseLeftButtonDown="Grid_MouseLeftButtonDown"           
          PreviewMouseRightButtonDown="Grid_PreviewMouseRightButtonDown">
            <Button Content="Click Me"            
            PreviewMouseLeftButtonDown="Button_PreviewMouseLeftButtonDown" 
            MouseRightButtonDown="Button_MouseRightButtonDown" Margin="75,112,278,140" />

        </Grid>
       
    </Grid>
   
    
</Window>


  public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Grid_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("LeftMouseButton handled by grid1");
        }

        private void Grid_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("RightMouseButton handled by grid1");
        }

        private void Button_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("LeftMouseButton handled by button1");
        }

        private void Button_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            Debug.WriteLine("RightMousebutton handled by button1");
        }
     
    }


LeftMouseButton handled by button1
RightMouseButton handled by grid1
RightMousebutton handled by button1

Programista może definiować swoje Routed Events w następujący sposób:

public class MyControl : Button
    {
        public static readonly RoutedEvent EvenClickEvent;
        public static int Counter = 0;

        static MyControl()
        {

            MyControl.EvenClickEvent = EventManager.RegisterRoutedEvent("EvenClick",
                RoutingStrategy.Direct, typeof(RoutedEventHandler), typeof(MyControl));
        }

        public event RoutedEventHandler EvenClick
        {
            add { AddHandler(MyControl.EvenClickEvent, value); }
            remove { RemoveHandler(MyControl.EvenClickEvent, value); }
        }

        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
        {
            if(Counter++ % 2 != 0)
                RaiseEvent(new RoutedEventArgs(MyControl.EvenClickEvent, this));
        }
    }

Dzięki temu można obsługiwać taki event z poziomu xaml.

[WPF] Attached Property

Attached Property to nic innego jak Dependency Property dołączane do obiektów już istniejących. Jest to na przykład świetny sposób na rozszerzanie kontrolek z których nie można dziedziczyć, lub z których dziedziczyć się nie opłaca, bo chcemy dodać tylko jedną prostą funkcjonalność. Przykładem AP jest Grid.Column ustawiane dla elementów potomnych. Ustawia się je w celu poinformowania rodzica o tym, jak mają być ustawione dzieci. Ogólnie założeniem AP jest, by klasy zgromadzone w pewien sposób w hierarchii lub powiązane w pewien logiczny sposób mogły zgłaszać wspólną informację do typu, które definiuje Attached Property. Typ ten może być zdefiniowany, jako rodzic, zawierający wiele dzieci. Iterując po nich może on wyciągać informacje, które go interesują. Inne zastosowanie, to serwis, który jest powiadamiany w momencie, gdy AttachedProperty jest ustawiane.

W poniższym przykładzie stworzono serwis, kontrolujący to, który prostokąt został wybrany. Attached Property rejestrowane jest podobnie jak Dependency Property specjalną metodą RegisterAttached.

<Window x:Class="Attached_Property.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Attached_Property="clr-namespace:Attached_Property" Title="MainWindow" Height="200" Width="200">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
        </Grid.ColumnDefinitions>
        <Rectangle Width="100" Height="100" MouseEnter="Rect3_MouseEnter"
                   x:Name="Rect1" Stroke="Red" Grid.Column="0" Grid.Row="0" 
                   Fill="LightGreen" Attached_Property:RectangleService.IsChecked="True" />
        <Rectangle Width="100" Height="100"  MouseEnter="Rect3_MouseEnter"
                   x:Name="Rect2" Stroke="Red" Grid.Column="1" Grid.Row="0" />
        <Rectangle Width="100" Height="100" MouseEnter="Rect3_MouseEnter"
                   x:Name="Rect3" Stroke="Red" Grid.Column="0" Grid.Row="1" />
        <Rectangle Width="100" Height="100" MouseEnter="Rect3_MouseEnter"
                   x:Name="Rect4" Stroke="Red" Grid.Column="1" Grid.Row="1" />
    </Grid>
</Window>


 public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void Rect3_MouseEnter(object sender, MouseEventArgs e)
        {
            RectangleService.SetIsChecked(sender as DependencyObject, true);
        }
    }


 public class RectangleService
    {
        public static readonly DependencyProperty IsCheckedProperty;

        public static Rectangle CheckedRect { get; set; }

        static RectangleService()
        {
            IsCheckedProperty = DependencyProperty.RegisterAttached("IsChecked"
                              typeof (bool), typeof (Rectangle),
                                                new PropertyMetadata(false));
        }

        public static void SetIsChecked(DependencyObject element, bool value)
        {
            element.SetValue(RectangleService.IsCheckedProperty, value);
            //service logic
            if (!value) return;
            if(CheckedRect != null)
                CheckedRect.Fill = new SolidColorBrush(Colors.Yellow);
            CheckedRect = element as Rectangle;
            if(CheckedRect== null)
                return;
            CheckedRect.Fill = new SolidColorBrush(Colors.LightGreen);
        }

        public static bool GetIsChecked(DependencyObject element)
        {
            return (bool)element.GetValue(RectangleService.IsCheckedProperty);
        }

środa, 15 sierpnia 2012

[WPF] Dependency Property

Jedną z najważniejszych koncepcji w WPF jest Dependency Property. Umożliwiają one stylowanie, automatyczny data binding czy animowanie dowolnych właściwości. Dependency Property może być zmieniane przez wiele obiektów jednocześnie, a jego największą zaletą jest wbudowana zdolność do notyfikowania o zmianach wartości. DP mogą być także zmieniane z poziomu xaml co jest następną ich zaletą. Zatem przy tworzeniu własnych kontrolek jest to bardzo wygodne rozwiązanie. Kolejną korzyścią wynikającą z zastosowania DP jest możliwosć zdefiniowania Property Triggerów - warunkowych akcji wykonywanych, gdy property osiągnie pewną wartość

Od strony języka DP są propertiesami, budowanymi w dość nietypowy sposób. Należy dodać do klasy kontrolki statyczne publiczne pole tylko do odczytu, gdzie nazwa to nazwa property z dodanym "Property". Następnie w statycznym konstruktorze rejestruje się DP za pomocą metody DependencyProperty.Register. Przez parametry tej metody można określić domyślną wartość i metodę wołaną przy zmianie wartości. Można także zdefiniować pewien "wrapper", który będzie opakowywał ustawianie i odczyt wartości DP.

W poniższym przykładzie dla kontrolki tykającego zegarka zdefiniowano Dependency Property Ticks, które reprezentuje liczbę uderzeń zegarka.

 public partial class TickingTimer : UserControl
    {

        public static readonly DependencyProperty TicksProperty;

        public int Ticks
        {
            get { return (int)GetValue(TickingTimer.TicksProperty); }
            set { SetValue(TickingTimer.TicksProperty, value); }
        }

        static TickingTimer()
        {
            TickingTimer.TicksProperty = DependencyProperty.Register("Ticks",
                typeof(int), typeof(TickingTimer),
                new FrameworkPropertyMetadata(0,
                new PropertyChangedCallback(OnTicksChanged)));
        }

        private static void OnTicksChanged(DependencyObject o, DependencyPropertyChangedEventArgs eventArgs)
        {
            Console.Beep();
        }
//LOGIKA KONTROLKI
     }

Kontrolkę taką można dodać do okna i zbindować się do naszego Dependency Property Ticks.

<Window x:Class="Dependency_Property.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:Dependency_Property="clr-namespace:Dependency_Property" 
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Dependency_Property:TickingTimer x:Name="Timer" Ticks="5" />
        <Label Content="{Binding ElementName=Timer,  Path=Ticks}" />
    </Grid>
</Window>

[WPF] Logical Tree, Visual Tree

Dużą zaletą technologii WPF jest oddzielenie wyglądu od zachowania. Wygląd zapisujemy w plikach xaml, natomiast zachowanie w plikach C#. Może się tak zdarzyć, że z poziomu kodu będziemy chcieli modyfikować pewne własności kontrolek. W tym miejscu z pomocą przychodzą dwa terminy wprowadzane przez WPF : drzewo wizualne oraz drzewo logiczne. Drzewo logiczne, to układ strukturalny kontrolek w naszym widoku, tak, jak zdefinowane są w xaml. Do przeszukiwania tego drzewa służy klasa LogicalTreeHelper. Drzewo wizualne zawiera dodatkowe właściwości, takie jak zawartość poszczególnych kontrolek. Na poniższym przykładzie można zobaczyć, jakie właściwości są dostępne w drzewie wizualnym za pomocą klasy VisualTreeHelper.

Zbudowane zostało proste okno z kilkoma poziomami zagnieżdżenia kontrolek i trzema radiobuttonami, gdzie każdy ma ustawiony inny poziom widoczności. Do wypisywania drzewa zastosowano metody przedstawione w książce Adama Nathana, WPF Unleashed.

<Window x:Class="VisualTreeHelper_LogicalTreeHelper.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid x:Name="RootGrid">
        <StackPanel>
            <Grid Width="200" Height="100">
                <Button Content="Button1" Margin="30,30,46.5,31.5" />
                <CheckBox Content="Checkbox1" />
            </Grid>
            <Grid Width="200" Height="100">
             <ListBox Margin="21.5,8,26.5,30" RenderTransformOrigin="0.507,0.355" Background="#FFD24848">
              <ListBoxItem/>
             </ListBox>
                <RadioButton x:Name="rb_Visible" Content="RadioButton" Height="18.5"  Visibility="Visible"/>
                <RadioButton x:Name="rb_Hidden" Content="RadioButton" Height="18.5"  Visibility="Hidden"/>
             <RadioButton x:Name="rb_Collapsed" Content="RadioButton" Visibility="Collapsed"/>

            </Grid>
        </StackPanel>
    </Grid>
</Window>


 public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        protected override void OnContentRendered(EventArgs e)
        {
            base.OnContentRendered(e);
            Debug.WriteLine("Logical Tree");
            PrintLogicalTree(0, this);
            Debug.WriteLine("Visual Tree");
            PrintVisualTree(0, this);
        }

        public void PrintLogicalTree(int depth, object obj)
        {
            Debug.WriteLine(new string(' ', depth) + obj);

            if(obj as DependencyObject == null)
                return;

            foreach (object child in LogicalTreeHelper.GetChildren(obj as DependencyObject))
            {
                PrintLogicalTree(depth + 1, child);
            }
        }

        public void PrintVisualTree(int depth, DependencyObject obj)
        {
            Debug.WriteLine(new string(' ', depth) + obj)
            ;
            // Recursive call for each visual child
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                PrintVisualTree(depth + 1, VisualTreeHelper.GetChild(obj, i));
        }

    }

Po uruchomieniu programu output przedstawiał się następująco :

Logical Tree
VisualTreeHelper_LogicalTreeHelper.MainWindow
 System.Windows.Controls.Grid
  System.Windows.Controls.StackPanel
   System.Windows.Controls.Grid
    System.Windows.Controls.Button: Button1
     Button1
    System.Windows.Controls.CheckBox Content:Checkbox1 IsChecked:False
     Checkbox1
   System.Windows.Controls.Grid
    System.Windows.Controls.ListBox Items.Count:1
     System.Windows.Controls.ListBoxItem
    System.Windows.Controls.RadioButton Content:RadioButton IsChecked:False
     RadioButton
    System.Windows.Controls.RadioButton Content:RadioButton IsChecked:False
     RadioButton
    System.Windows.Controls.RadioButton Content:RadioButton IsChecked:False
     RadioButton
Visual Tree
VisualTreeHelper_LogicalTreeHelper.MainWindow
 System.Windows.Controls.Border
  System.Windows.Documents.AdornerDecorator
   System.Windows.Controls.ContentPresenter
    System.Windows.Controls.Grid
     System.Windows.Controls.StackPanel
      System.Windows.Controls.Grid
       System.Windows.Controls.Button: Button1
        Microsoft.Windows.Themes.ButtonChrome
         System.Windows.Controls.ContentPresenter
          System.Windows.Controls.TextBlock
       System.Windows.Controls.CheckBox Content:Checkbox1 IsChecked:False
        System.Windows.Controls.Primitives.BulletDecorator
         Microsoft.Windows.Themes.BulletChrome
         System.Windows.Controls.ContentPresenter
          System.Windows.Controls.TextBlock
      System.Windows.Controls.Grid
       System.Windows.Controls.ListBox Items.Count:1
        System.Windows.Controls.Border
         System.Windows.Controls.ScrollViewer
          System.Windows.Controls.Grid
           System.Windows.Shapes.Rectangle
           System.Windows.Controls.ScrollContentPresenter
            System.Windows.Controls.ItemsPresenter
             System.Windows.Controls.VirtualizingStackPanel
              System.Windows.Controls.ListBoxItem
               System.Windows.Controls.Border
                System.Windows.Controls.ContentPresenter
            System.Windows.Documents.AdornerLayer
           System.Windows.Controls.Primitives.ScrollBar Minimum:0 Maximum:0 Value:0
           System.Windows.Controls.Primitives.ScrollBar Minimum:0 Maximum:0 Value:0
       System.Windows.Controls.RadioButton Content:RadioButton IsChecked:False
        System.Windows.Controls.Primitives.BulletDecorator
         Microsoft.Windows.Themes.BulletChrome
         System.Windows.Controls.ContentPresenter
          System.Windows.Controls.TextBlock
       System.Windows.Controls.RadioButton Content:RadioButton IsChecked:False
        System.Windows.Controls.Primitives.BulletDecorator
         Microsoft.Windows.Themes.BulletChrome
         System.Windows.Controls.ContentPresenter
          System.Windows.Controls.TextBlock
       System.Windows.Controls.RadioButton Content:RadioButton IsChecked:False
   System.Windows.Documents.AdornerLayer

Warto zwrócić uwagę na fakt, iż dla radiobuttona z visibility ustawionym na collapsed cała zawartość została usunięta z drzewa wizualnego.Poza tym, dla każdej kontrolki wyświetlana jest każda wyrenderowana składowa, taka jak ramka, czy tekst w przycisku.