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.

niedziela, 5 sierpnia 2012

[C#|Visual Studio] Generowanie kodu za pomocą plików T4

Pliki T4 (Text Template Transformation Toolkit) służą do generacji plików o dowolnym rozszerzeniu z bloków tekstu połączonych z blokami kodu pisanych w C# lub Visual Basic. Obowiązuje pewna prosta do opanowania składnia umożliwiająca oddzielenie kodu od tekstu oraz obliczanie wartości które chcemy wstawić w danym miejscu. Transformacja pliku następuje w momencie, gdy zapiszemy plik o rozszerzeniu .tt.

Aby dodać nowy plik, wybieramy Add...New Item... lub ctrl + shift + A. Z listy wybieramy Text Template:


Wygenerowane zostaną dwa pliki : .tt i .txt. Rozszerzenie możemy zmienić w nagłówku pliku .tt, podobnie jak język, w którym chcemy pisać. Kod od tekstu i ustawień oddziela się różnymi rodzajami tagów:
Tag otwierający ustawienia : <#@
Tag otwierający kod : <#
Zwykły tekst nie jest zamykany w żadnych tagach.

Poniższy przykład pokazuje możliwości szablonów T4.
Załóżmy, że chcemy na podstawie pewnej tablicy wygenerować klasy z konstruktorami. Poniższy kod pokazuje, jak to uczynić.


<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".cs" #>

namespace MyNamespace 
{

<#  var Countries = new string[]{"Poland","England","Ukraine"};
 foreach (var country in Countries)
    {#>
 public class <#=country#>
 {
  public <#=country#>()
  {
  }
 }
                 
<#}#>
}

W wyniku otrzymamy plik .cs o następującej treści :

namespace MyNamespace 
{

 public class Poland
 {
  public Poland()
  {
  }
 }
                 
 public class England
 {
  public England()
  {
  }
 }
                 
 public class Ukraine
 {
  public Ukraine()
  {
  }
 }
                 
}