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

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

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

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

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

Dziś dokończenie (ale nie zakończenie) tematu grafiki 2D w WPF. Zaczniemy od omówienia Geometries. Tak więc zaczynamy.

Geometries

To sposób na wyabstrahowanie kształtu lub ścieżki dostępne w WPF. Co mamy dostępne?

  • RectangleGeometry – pozwala na tworzenie (tak, tak) prostokątów, także z zaokrąglonymi rogami

  • EllipseGeometry – elipsy i okręgi

  • LineGeometry – wszelkiej maści odcinki

  • PathGemoetry – wszystko co powyżej i jeszcze wiele więcej

Ten ostatni przypadek, omówimy sobie dokładniej, gdyż jest najbardziej złożony.

PathGeometry

PathGeometry składa się z jednego lub więcej PathFigure a ten z kolei składa się z PathSegment. Rodzajów segmentów mamy 7 i są to: LineSegment, PolyLineSegment, ArcSegment, BezierSegment, PolyBezierSegment, QuadriaticBezierSegment oraz PolyQadraticBezierSegment. Jak tego użyć?

<GeometryDrawing>

    <GeometryDrawing.Geometry>

        <PathGeometry>

            <PathFigure>

                <LineSegment Point=”0,100″ />

                <LineSegment Point=”100,100″ />

            PathFigure>

            <PathFigure>                                       

                <ArcSegment Point=”100,100″ RotationAngle=”0″ Size=”100,100″ />

            PathFigure>

        PathGeometry>

    GeometryDrawing.Geometry>

    <GeometryDrawing.Pen>

        <Pen Thickness=”5″ Brush=”Black” />

    GeometryDrawing.Pen>

GeometryDrawing>

Dodatkowo w prosty sposób możemy naszą figurę wypełnić kolorem. Wystarczy na elemencie GeometryDrawing ustawić właściwość Brush.

<GeometryDrawing Brush=”Pink”>

Możemy też w bardzo prosty sposób sprawić, aby nasz obiekt był domknięty. Właściwość IsClosed

<PathFigure IsClosed=”True”>

Mając już tak “skomplikowaną” strukturę możemy pobawić się trochę sposobem wypełniania. Steruje tym parametr FillRule. Domyślną wartością jest EvenOdd i obszar jest zapełniany jeśli przekroczona by była nieparzysta liczba segmentów na drodze do tego segmentu. Działanie tego trybu można zobaczyć na obrazku powyżej. Tryb NonZero jest bardziej złożony i w większości przypadków wypełniona będzie cała figura. Przykład działania NonZero

Dodatkowo za pomocą właściwości IsSmoothJoin możemy kontrolować w jaki sposób linie są ze sobą połączone.

Wydajność

Tworzenie elementów za pomocą XAML’a nie jest najwydajniejszym sposobem. Jeśli ktoś chce uzyskać większą wydajność, a raz utworzonych struktur nie ma potrzeby zmieniać warto popatrzeć na StreamGeometry.

Agregacje

Dostępne są dwie: GeometryGroup oraz CombinedGeometry. Ta pierwsza pozwala dodatkowo na ustawienie kilku właściwości naraz do wielu klas Geometry. Ten ostatni pozwala na przeprowadzenie kilku operacji na swoich elementach. Tryb ustawiamy za pomocą właściwości GeometryCombinedMode a dostępne tryby to: Union, Intersect, Xor oraz Exclude. Przykład użycia:

<GeometryDrawing.Geometry>

    <CombinedGeometry GeometryCombineMode=”Intersect”>

        <CombinedGeometry.Geometry1>

            <PathGeometry FillRule=”Nonzero”>

                <PathFigure IsClosed=”True”>

                    <LineSegment Point=”0,100″/>

                    <LineSegment Point=”100,100″ />

                PathFigure>

            PathGeometry>

        CombinedGeometry.Geometry1>

        <CombinedGeometry.Geometry2>

            <PathGeometry>

                <PathFigure>

                    <ArcSegment Point=”100,100″ RotationAngle=”0″ Size=”100,100″ />

                PathFigure>

            PathGeometry>

        CombinedGeometry.Geometry2>

    CombinedGeometry>

GeometryDrawing.Geometry>

Polecam po eksperymentować z tymi klasami. Można uzyskać ciekawe efekty.

W następnej części pokażemy jak można tworzyć klasy Geometry za pomocą łańcuchów znaków.

Pierwsz post wprowadzający do świada grafiki 2D i 3D w WPF. Tak, tak świata, gdyż w tej kwestii sporo zostało zmienione w stosunku do WinForms. Zaczniemy od 2D. Co zatem nowego daje nam WPF?

Drawings

Reprezentują obrazek 2D, ale ponieważ dziedziczą z klasy Animatable, można do nich DataBindować i animować je. Jakie mamy klasy dostępne?

  • GeometryDrawing – pozwala połączyć obiekt klasy Geometry (więcej o tym poźniej) z obiektem Brush oraz Pen.

    <Button Content=”Przycisk”>

        <Button.Background>

            <DrawingBrush>

                <DrawingBrush.Drawing>

                    <GeometryDrawing>

                        <GeometryDrawing.Geometry>

                            <EllipseGeometry RadiusX=”100″ RadiusY=”100″ />

                        GeometryDrawing.Geometry>

                        <GeometryDrawing.Pen>

                            <Pen Thickness=”5″ Brush=”Black” />

                        GeometryDrawing.Pen>

                    GeometryDrawing>

                DrawingBrush.Drawing>

            DrawingBrush>

        Button.Background>

    Button>

  • ImageDrawing – pozwala na użycie obrazka.

    <Button Content=”Przycisk”>

        <Button.Background>

            <ImageBrush>

                <ImageBrush.ImageSource>clouds_XSmall.jpgImageBrush.ImageSource>

            ImageBrush>

        Button.Background>

    Button>

  • VideoDrawing – podobnie jak powyżej z tą różnicą, że pokazujemy wideo.

    <MediaElement x:Name=”video” Source=”short.wmv” />

    <Button>

        <Button.Background>

            <VisualBrush Visual=”{Binding ElementName=video}”>                   

            VisualBrush>

        Button.Background>

    Button>

    Tak, to jest wideo i bez komentarzy proszę 😛
  • GlyphRunDrawing – pozwala na narysowanie obiektu klasy GlyphRun (typografia). Ktoś potrafi tego użyć? Dostaję wyjątek, iż nie można utworzyć GlyphTypeface..

  • DrawingGroup – pozwala zebrać powyższe elementy w grupę i dodatkowo nią sterować.

Gdzie tego użyć?

Widzimy, już z poprzednich przykładów, że elementy te trzeba użyć w konkretnych pojemnikach. Wyróżniamy 3:

  • DrawingImage – może być użyte wewnątrz obiektu Image

  • DrawingBrush – może być zastosowane w takich miejscach jak Foreground, Background itp.

  • DrawingVisual – gdy potrzebujemy czegoś wizualnego (będzie omówione w dalszych częściach).

Taka krótka rozgrzewka przed przyjrzeniem się dalej elementom 2D w WPF a będą to Gemoetries.

Dziś o tym co to jest i jak możemy stworzyć CustomControl.

Co to takiego

Czy zatem różni się CustomControl od omówionej już UserControl? Ta pierwsza jest kompletnie oddzielona od swojego wyglądu. Jest to tak zwana lookles-control. Spróbujmy sobie taką przygotować. Jako przykład spróbujmy stworzyć sobie kontrolkę i przygotować dla niej wygląd, która posłuży jako prosty ColorPicker

Kontrolka taka dziedziczy z jakiejś klasy bazowej. W naszym przypadku będzie to po prostu Control

public class ColorPicker : System.Windows.Controls.Control

{

}

Musimy także “powiedzieć”, że to my dostarczymy domyślny styl dla kontrolki. Robimy to przez odpowiednią deklarację w statycznym konstruktorze.

static ColorPicker()

{

    DefaultStyleKeyProperty.OverrideMetadata(typeof (ColorPicker),

                                            new FrameworkPropertyMetadata(typeof (ColorPicker)));

}

Aby zdefiniować wygląd naszej kontrolki, musimy utworzyć w katalogu Themes plik generic.xaml, którym to wygląd ten zdefiniujemy. Zanim zaczniemy tworzyć wygląd, warto nadmienić, iż oczywiście Visual Studio wspiera nas w procesie tworzenia CustomControlki (dodaje oba pliki). Wystarczy wybrań Add New i wybrać Custom Control.

Wygląd

Nasz kontrolka musi jakoś wyglądać, tak więc zdefinujmy sobie jej podstawowy wygląd.

<Style TargetType=”{x:Type local:ColorPicker}”>

    <Setter Property=”Template”>

        <Setter.Value>

            <ControlTemplate TargetType=”{x:Type local:ColorPicker}”>

                <Border Background=”{TemplateBinding Background}”

                      BorderBrush=”{TemplateBinding BorderBrush}”

                      BorderThickness=”{TemplateBinding BorderThickness}”>

                    <Grid>

                        <Grid.ColumnDefinitions>

                            <ColumnDefinition Width=”5*”/>

                            <ColumnDefinition Width=”*”/>

                        Grid.ColumnDefinitions>

                        <StackPanel Grid.Column=”0″>

                            <Slider Minimum=”0″ Maximum=”255″ Value=”{Binding Path=Red,

                                                    RelativeSource={RelativeSource TemplatedParent}}”/>

                            <Slider Minimum=”0″ Maximum=”255″ Value=”{Binding Path=Green,

                                                    RelativeSource={RelativeSource TemplatedParent}}”/>

                            <Slider Minimum=”0″ Maximum=”255″ Value=”{Binding Path=Blue,

                                                    RelativeSource={RelativeSource TemplatedParent}}”/>

                        StackPanel>

                        <Rectangle Grid.Column=”1″ Stroke=”Black” StrokeThickness=”2″>

                            <Rectangle.Fill>

                                <SolidColorBrush Color=”{Binding Path=Color,

                                                    RelativeSource={RelativeSource TemplatedParent}}” />

                            Rectangle.Fill>

                        Rectangle>

                    Grid>

                Border>

            ControlTemplate>

        Setter.Value>

    Setter>

Style>

Wiem, że jeszcze styli i szablonów nie omawiałem, ale pomińmy “nieistotne” w tej chwili szczegóły. Na co warto zwrócić uwagę to jak bindujemy dane. Używamy do tego celu TemplatedParent. A jaki wygląd to zdefiniuje? 3 slidery + prostokąt po prawej stronie, aby na bieżąco pokazywał aktualny kolor.

A jak to wygląda w CodeBehind? Też nic trudnego.

public static readonly DependencyProperty RedProperty =

    DependencyProperty.Register(“Red”, typeof (byte), typeof (ColorPicker),

                                new PropertyMetadata(OnValueChanged));

public byte Red

{

    get

    {

        return (byte) GetValue(RedProperty);

    }

    set

    {

        SetValue(RedProperty, value);

    }

}

 

public static readonly DependencyProperty GreenProperty =

    DependencyProperty.Register(“Green”, typeof(byte), typeof(ColorPicker),

                                new PropertyMetadata(OnValueChanged));

public byte Green

{

    get

    {

        return (byte)GetValue(GreenProperty);

    }

    set

    {

        SetValue(GreenProperty, value);

    }

}

 

public static readonly DependencyProperty BlueProperty =

    DependencyProperty.Register(“Blue”, typeof(byte), typeof(ColorPicker),

                                new PropertyMetadata(OnValueChanged));

public byte Blue

{

    get

    {

        return (byte)GetValue(BlueProperty);

    }

    set

    {

        SetValue(BlueProperty, value);

    }

}

 

public static readonly DependencyProperty ColorProperty =

    DependencyProperty.Register(“Color”, typeof(Color), typeof(ColorPicker));

 

public Color Color

{

    get

    {

        return (Color)GetValue(ColorProperty);

    }

    set

    {

        SetValue(ColorProperty, value);

    }

 

}

Z nowości to dodaliśmy przy rejestracji DepenedncyProperty funkcję, która będzie wołana przy zmianie tej właściwości. Kod tej funkcji to:

private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

    var cp = (ColorPicker) d;

    cp.RecalculateColor();

}

 

private void RecalculateColor()

{

    Color = new Color { A = 255, R = Red, G = Green, B = Blue };           

}

Tak więc widzimy, że przy każdej zmianie jednej z wartości R,G,B tworzymy nowy kolor i go zmieniamy. Od razu zaznaczę, że nie wiem czy nie da się tego zrobić “bardziej” WPFowo. Jeśli ktoś zna ładniejsze rozwiązanie, niech pisze.

Nasze dzieło po uruchomieniu wygląda następująco:

Motywy

Windowsy już od jakiegoś czasu posiadają coś takiego jak Motywy (Themes), w zależności od którego to zmienia się wygląd systemu. Dobrze, gdyby nasza kontrolka również wspierała tę funkcjonalność. Na szczęście jest to dość proste do osiągnięcia. Wystarczy stworzyć odpowiednio nazwany plik XAML, które będzie definiował wygląd naszej kontrolki w danym motywie. I tak odpowiednio (w kolejności System, Motyw, Kolorystyka):

  • Vista, Aero, NormalColor – Aero.NormalColor.xaml

  • XP (niebieski), Luna, NormalColor – Luna.NormalColor.xaml

  • XP (zielony), Luna, Homestead – Luna.Homested.xaml

  • XP (srebrny), Luba, Metalic – Luna.Metalic.xaml

  • XP Media Center, Royale, Normal – Royale.NormalColor.xaml

  • XP (Zune), Zune, NormalColor – Zune.NormalColor.xaml

  • XP lub Vista, Classic – Classic.xaml

Dzięki odpowiedniemu przygotowaniu, nasza kontrolka będzie odpowiednio się prezentować w każdym ze styli. Ale to już raczej zadanie dla jakiegoś “czarnego kołnierzyka” :).

Części

nasza kontrolka jest już gotowa i każdy może z niej korzystać i zmieniać jej wygląd za pomocą nowego szablonu. Jednak możemy zmianę szablonu naszej kontrolki jeszcze uprościć. WPF wprowadza pojęcie cześci (Part), za pomocą, które ułatwimy modyfikację. Aktualnie, każdy kto chciałby stworzyć nową wygląd za pomocą, którego zmieniamy wartość np. koloru czerwonego będzie musiał zadbać także o jej podłączenie do właściwości Red naszej klasy. Ułatwmy to. Nadajmy naszej sliderowi nazwę.

<Slider Minimum=”0″ Maximum=”255″ Value=”{Binding Path=Red,

                        RelativeSource={RelativeSource TemplatedParent}}”

      x:Name=”PART_RedSliver”/>

Trzymamy się konwencji nazewniczej i nazwę rozpoczynamy od: PART_. Dodajmy jeszcze w kodzie odpowiednie bindowanie:

public override void OnApplyTemplate()

{

    base.OnApplyTemplate();

 

    var redSlider = GetTemplateChild(“PART_RedSlider”) as RangeBase;

    if (redSlider == null) return;

    var b = new System.Windows.Data.Binding(“Red”) {Source = this, Mode = BindingMode.TwoWay};

    redSlider.SetBinding(RangeBase.ValueProperty, b);

}

Dzięki temu, przy tworzeniu nowego szablonu, kontrolki nie będziemy musieli się martwić o ustawianie odpowiedniego bindowania. Wystarczy, że kontrolkę nazwiemy PART_RedSlider.Jeśli zrobimy sobie nowy szablon kontrolki. np. taki:

<WpfExamDemo:ColorPicker.Template>

    <ControlTemplate>

        <Grid>

            <Grid.ColumnDefinitions>

                <ColumnDefinition Width=”*” />

                <ColumnDefinition Width=”3*” />

            Grid.ColumnDefinitions>

            <StackPanel Grid.Column=”1″>

                <Controls:RadNumericUpDown Minimum=”0″ Maximum=”255″ x:Name=”PART_RedSlider” SmallChange=”1″ />                           

                <Controls:RadNumericUpDown Minimum=”0″ Maximum=”255″ x:Name=”PART_GreenSlider” SmallChange=”1″ />

                <Controls:RadNumericUpDown Minimum=”0″ Maximum=”255″ x:Name=”PART_BlueSlider” SmallChange=”1″ />

            StackPanel>

            <Ellipse Stroke=”Black” StrokeThickness=”3″>

                <Ellipse.Fill>

                    <SolidColorBrush x:Name=”PART_View”>

                    SolidColorBrush>

                Ellipse.Fill>

            Ellipse>

        Grid>

    ControlTemplate>

WpfExamDemo:ColorPicker.Template>

To nasze bindingi ładne będą działać, a kontrolka będzie wyglądać tak:

Jedyne co nam pozostaje to udokumentować, że nasza kontrolka ma jakieś części. Robimy to za pomocą atrybutu TemplatePart

[TemplatePart(Name = “PART_RedSlider”, Type=typeof(RangeBase))]

[TemplatePart(Name = “PART_GreenSlider”, Type = typeof(RangeBase))]

[TemplatePart(Name = “PART_BlueSlider”, Type = typeof(RangeBase))]

[TemplatePart(Name = “PART_View”, Type= typeof(SolidColorBrush))]

public class ColorPicker : System.Windows.Controls.Control

{

Na ten odcinek to tyle. W następnym przejdziemy do kolejnego tematu i zarazem działu. Create and display 2D- and 3D- graphics. Temat rzeka w WPF, wiec pewnie będzie trochę zawężony i na pewno podzielony na części.

Dziś będzie o tym jak w WPF, możemy stworzyć sobie user kontrolkę. Kontrolka ta zawiera zarówno logikę jak i wygląd. Głównie składa się ją z już istniejących kontrolek udostępnianych przez WPF.

Nowa kontrolka

Visual Studio daje nam wsparcie do tworzenia tego typu kontrolek, więc wystarczy, że dodamy nowy element tego typu do projektu.

Po ustaleniu nazwy naszej nowej kontrolki możemy przystąpić do pracy (z braku ciekawego przykładu, posłużę się tym zawartym w książce – WPF Unleashed)

Zakodujmy sobie zatem kontrolkę, która pokaże nam przycisk umożliwiający wybór pliku oraz pole TextBox, które po wybraniu pliki będzie zawierać ścieżkę do niego.

<DockPanel>

    <Button Content=”Wybierz …” DockPanel.Dock=”Right” x:Name=”button” Click=”button_Click”/>

    <TextBox MinWidth=”{Binding ActualWidth, ElementName=button}” Margin=”0,0,2,0″ x:Name=”fileNameTextBox”/>       

DockPanel>

Tyle w XAMLu. W CodeBehind też niewiele musimy napisać.

private void button_Click(object sender, RoutedEventArgs e)

{

    using(var ofd = new OpenFileDialog())

    {

        if (ofd.ShowDialog() != DialogResult.OK) return;

        FileName = ofd.FileName;

    }

}

 

public string FileName

{

    get { return fileNameTextBox.Text; }

    set { fileNameTextBox.Text = value; }

}

I to już w zasadzie tyle, ale…tak przygotowana kontrolka nie pozwala użyć wielu z ciekawych rozwiązań dostępnych w WPF. Dopracujmy ją zatem.

Wartość domyślna

Nasza kontrolka “ładnie działa”, ale ktoś może się bardzo zdziwić jej używając. Załóżmy, że ktoś napisze tak:

<WpfExamDemo:FileOpen x:Name=”fip” Content=”C:\autoexec.bat”/>

lub tak

<WpfExamDemo:FileOpen x:Name=”fip”>c:\autoexec.batWpfExamDemo:FileOpen>

Ponieważ kontrolka jest typu Content, jest to jak najbardziej poprawne jej użycie. Jednak efekt może nie być zadowalający.

Stało się tak, gdyż umieszczony w szablonie klasy bazowej ContentPresenter nadpisał nasze obiekty. Jak temu zaradzić? Na drugi przypadek istnieje eleganckie rozwiązanie. Wystarczy dodać atrybut na naszej klasie i wskazać, jaka właściwość na być “odpowiedzialna” za zawartość.

[ContentProperty(“FileName”)]

public partial class FileOpen : UserControl

Pierwszy przypadek nie jest taki prosty. Musimy przeładować metodę OnContentChanged

protected override void OnContentChanged(object oldContent, object newContent)

{

    if (oldContent != null)

        throw new InvalidOperationException(“Content cannot be set explicitly.”);

    base.OnContentChanged(oldContent, newContent);

}

Nie jest to rozwiązanie ładne ni eleganckie, ale działa.

Więcej zależności…

Jeśli jednak właściwość FileName będziemy chcieli użyć w DataBindingu na zewnątrz naszej kontrolki, spotka nasz rozczarowanie. Nie zadziała. Musimy naszą właściwość zamienić na dependency property. Robimy to w kilku krokach:

  1. Rejestrujemy nową dependency property:

    public static readonly DependencyProperty FileNameProperty =

                DependencyProperty.Register(“FileName”,

                typeof(string),

                typeof(FileOpen));

    Nazwa, typ i wartość to elementy, które musimy podać.

  2. Tworzymy właściwość, która ustawia i odczytuję wartość z niej

    public string FileName

    {

        get { return (string)GetValue(FileNameProperty); }

        set { SetValue(FileNameProperty, value); }

    }

    Uwaga:Nie twórz żadnej logiki w get i set za wyjątkiem ustawienia i odczytania wartości! WPF czasem ją omija i ustawia wartość bezpośrednio a zatem twoja logika, może czasem nie być wykonana.

  3. “Naprawienie” funkcjonalności pokazania wybranego pliku w kontrolce TextBox

    Text=”{Binding FileName, ElementName=root}”

    Nazwę root nadajemy całej User Controlce

Po takich zabiegach, możemy już naszą właściwość FileName używać w DataBindingu.

Rutowalne zdarzenia

Możemy też zdefiniować rutowalny event, który będzie informować o zmianie ścieżki. Robimy to następująco:

  1. Definiujemy event:

    public static readonly RoutedEvent FileNameChangedEvent =

        EventManager.RegisterRoutedEvent(“FileNameChanged”,

        RoutingStrategy.Bubble,

        typeof (RoutedEventHandler),

        typeof (FileOpen));

  2. Add i Get

    public event RoutedEventHandler FileNameChanged

    {

        add { AddHandler(FileNameChangedEvent, value);}

        remove { RemoveHandler(FileNameChangedEvent, value); }

    }

  3. Wołamy go z odpowiedniego miejsca. TextChanged na TextBox brzmi odpowiednio.

    void fileName_TextChanged(object sender, System.Windows.Controls.TextChangedEventArgs e)

    {

        var args = new RoutedEventArgs(FileNameChangedEvent);

        RaiseEvent(args);

    }

I gotowe. Możemy już z niego korzystać.

<StackPanel WpfExamDemo:FileOpen.FileNameChanged=”StackPanel_FileNameChanged”>

    <WpfExamDemo:FileOpen x:Name=”fip”>WpfExamDemo:FileOpen> 

    <TextBlock Text=”{Binding FileName, ElementName=fip}” />

StackPanel>

Na dziś tyle. W następnym odcinku będzie o ContentControls CustomControls.

Interoperacyjność

Z WPF do WinForms

WPF przychodzi z możliwością użycia kontrolek WPF w aplikacji WinForms. Możemy nasze WinFormsowe aplikacje pomału przebudowywać korzystając z dobrodziejstw WPF. Robimy to za pomocą kontrolki ElementHost. Przykład:

var expander  = new Expander();           

var panel = new StackPanel();

expander.Content = panel;

panel.Children.Add(new Button {Content = new TextBlock {Text = “Button1”}});

panel.Children.Add(new Button { Content = new TextBlock { Text = “Button2” } });

panel.Children.Add(new Button { Content = new TextBlock { Text = “Button3” } });

Tworzymy w nim kontrolkę Expander wraz ze StackPanelem, który zawiera kilka przycisków. Standardowe kontrolki WPF. Teraz zacznie się magia :). Wrzućmy sobie na formę WinForms ElementHost i przypiszmy naszą kontrolkę:

elementHost1.Child = expander; 

A po uruchomieniu będzie to wyglądać następująco:

Oczywiście możemy podpinać się do event’ów kontrolek WPF’a i reagować na nie.

expander.Expanded += expander_Expanded;

void expander_Expanded(object sender, System.Windows.RoutedEventArgs e)

{

    MessageBox.Show(“Expanded”);

}

Z WinForms do WPF

Jeśli mamy potrzebę użycia czegoś co dostępne jest w WPF, również możemy to uczynić. WindowsFormsHost na to nam pozwala. Używamy tegop w następujący sposób. Dodajemy referencję do dll’ki i rejestrujemy namespace w XAMLu.

xmlns:Forms=”clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms”

A następnie po prostu używamy:

<WindowsFormsHost Name=”windowsFormsHost1″ >

    <Forms:MaskedTextBox Mask=”99-999″ />

WindowsFormsHost>

I po uruchomieniu możemy używać kontrolki MaskedTextBox w WPF.

Jeśli chcemy użyć akceleratorów to standardowo do ich deklarowania w WinForms używa się znaku &. Oczywiście w XAMLu nie możemy tego zrobić. Musimy posłużyć się & Jak w przykładzie poniżej:

<Forms:Button Text=”Use Alt+&A” Click=”Button_Click”/>

Kilka dodatków

Używając WPF w WinForm i vice versa musimy liczyć się z tym, że niektóre właściwości będą się różnic np. w WinForms mamy BackColor w WPF Backgroud. Musimy pamiętać o takich rozbieżnościach bawiąc się w łączenie WPF i WinForms.

W następnym odcinku będzie o user kontrolkach.

Dziś będzie o kontrolce Grid. Najbardziej zaawansowanej i dającej największe możliwości konfiguracyjne spośród wszystkich dostępnych standardowo paneli. Zaczynamy.

Najprostszy Grid uzyskamy po prostu deklarując

<Grid>

 

Grid>

Jednak w takim przypadku efekt nie będzie oszołamiający. Wszystkie dodawane kontrolki będą układane jedna na drugiej. Aby zmienić to zachowanie musimy zdefiniować wiersze i/lub kolumny.

RowDefinitions & ColumnDefinition

Wspomniane wiersze i kolumny, definiujemy w obrębie powyższych tagów. Oczywiście można zdefiniować różne ich ilości w zależności od potrzeb

<Grid.RowDefinitions>

    <RowDefinition />

    <RowDefinition />

    <RowDefinition />

Grid.RowDefinitions>

<Grid.ColumnDefinitions>

    <ColumnDefinition />

    <ColumnDefinition />           

Grid.ColumnDefinitions>

Powyższy kod, jak można się domyślać, tworzy 3 wiersze i 2 kolumny. Domyślnie są one o takim samym rozmiarze. Później pokażemy sobie jak możemy je różnicować. Teraz powiemy sobie jak możemy umieszczać elementy w poszczególnych komórkach

Attached properties

Grid udostępnia kilka attached properties (“doczepiane właściwości”??). Za pomocą Grid.Row oraz Grid.Column możemy zdefiniować wiersz i kolumnę, która będzie zajmowana przez naszą kontrolkę.

<Image Grid.Column=”0″ Grid.Row=”1″ Source=”clouds_Xsmall.jpg” />

I nasz obrazek znajdzie się w wierszu nr 2 (numerowanie od 0) i w 1. kolumnie.

Jeśli chcemy, aby kontrolka zajmowała więcej niż jedną komórkę wystarczy użyć Grid.ColumnSpan i/lub Grid.RowSpan i nasza kontrolka będzie zajmowac np. dwie kolumny.

Rozmiary

Domyślnie wiersze i kolumny, przyjmują taki rozmiar, aby zagospodarować całą przestrzeń jednakowo. oczywiście takie zachowanie często nie jest pożądane. Jak zatem możemy zmienić szerokość lub wysokość?

Pierwszym ze sposób, jest użycie trybu Auto. Jeśli ustawimy wysokość wiersza ustawimy na taką wartość to dopasuje się on do wysokości kontrolki, którą w sobie zawiera. Podobnie będzie z kolumną, ale w tym przypadku, możemy tylko dopasować szerokość (co jest w sumie oczywiste). Przykład.

<Grid.RowDefinitions>

    <RowDefinition />

    <RowDefinition Height=”Auto”/>

    <RowDefinition />

Grid.RowDefinitions>

Porównajcie z poprzednim przykładem

Inny sposobem, na ustalenie rozmiaru jest podanie wartości explicite.

<RowDefinition Height=”40″/>

Trzecią możliwością jest użycie notacji ‘gwiazdkowej’. O co chodzi?

Gwiazdki

Jako rozmiar (Height, Width) można wpisać gwiazdkę (*) lub jej wielokrotność (np. 4*). Jak to zadziała? Przyjmijmy, że mamy:

<Grid.RowDefinitions>

    <RowDefinition Height=”2*”/>

    <RowDefinition Height=”40″/>

    <RowDefinition Height=”3*”/>

Grid.RowDefinitions>

Środkowy wiersz, jako że ma wielkość ustawioną explicite na piksele, będzie miał taką właśnie wysokość. Pozostała część zostanie wzięta pod uwagę i podzielona na 5 części (3* + 2*). Dwie części zostaną przydzielone na pierwszy wiersz. Pozostałe 3 na ostatni. Chyba wszystko jasne? Jeszcze jak to zrobić z kodu:

GridLength lengthPixels = new GridLength(100); //explicite

GridLength lengthAuto = new GridLength(0, GridUnitType.Auto); //Auto

GridLength lengthStar = new GridLength(2, GridUnitType.Star); //Gwiazdka

Splitter’y

Grid umożliwia jeszcze ustalanie rozmiarów ręcznie w runtime. Służy do tego element GridSplitter. Wystarczy go umieścić w XAMLu i określić którego wiersza czy kolumny dotyczy. Jednak należy pamiętać, że musi on mieć swoją definicję wiersza lub kolumny, w której będzie osadzony. Trochę to uciążliwe.

<GridSplitter Grid.Row=”2″ Grid.ColumnSpan=”2″ HorizontalAlignment=”Stretch”/>

I na koniec omawiania splitterów jeszcze jedna właściwość. Umożliwiają one tworzenie grup, które będą współdzielić rozmiar. Myślę, że przykład najlepiej to wyjaśni. Definicja wierszy:

<Grid.RowDefinitions>

    <RowDefinition Height=”20″ />

    <RowDefinition Height=”3″/>

    <RowDefinition />           

    <RowDefinition />

Grid.RowDefinitions>

Dodajmy teraz do pierwszego i ostatniego wiersza właściwość

SharedSizeGroup=”myGroupd”

oraz na samej kontrolce grid

Grid.IsSharedSizeScope=”True”

Teraz jeśli będziemy mieli splittera, który będzie kontrolował rozmiar pierwszego wiersza, ostatni wiersz będzie automatycznie zmieniany, aby jego rozmiar dopasowywał się do tego pierwszego. Filmik?

Ciekawostki

Jeśli chcesz w run-time widzieć linie podziału na wiersze i kolumny ustaw ShowGridLines na True

Przy pisaniu tego posta zauważyłem ciekawą właściwość VS 2008. Wydawało mi si, że Desginer nie działa dla WPF a ja mogłem sobie w grid’a wrzucać kontrolki i ustawiać za pomocą myszki wielkość kolumn i wierszy. Czy to jakiś plugin czy coś przegapiłem?

Tyle na dziś. Następnym razem o Integrate Windows Forms controls into a WPF application

Źródłem do napisania tego posta był wpis na blogu CONFidence odnośnie “Czy programista powinien posiadać wiedzę na temat bezpiecznego programowania” oraz brak przedstawicieli naszej technologii na konferencji CONFidence 2009. Może termin “bezpieczne programowanie” nie jest zbyt fortunny, ale chyba wiadomo o co chodzi – o pisanie bezpiecznego kodu, wystrzeganie się SQLInjection i innych “niebezpiecznych” rzeczy (XSS na przykład).

Brakuje mi w .NET takiej przykładowej aplikacji, którą można by “złamać”, aby na własne oczy przekonać się, że kod, który piszemy na wpływ na bezpieczeństwo naszej aplikacji (a może ktoś wie o czymś takim a jeśli nie ma to może warto coś takiego napisać?). To nasza i tylko nasza odpowiedzialność

  • Jakie jest podejście do bezpieczeństwa w .NET w waszej firmie? Ktoś się nad bezpieczeństwem zastanawia czy jakoś jest implementowane mimochodem?
  • Czy znacie jakichś polskich bloggerów piszących o bezpieczeństwie w .NET?
  • Może jakieś zagraniczne nazwiska poza Dinis’a Cruz’a?

Ja bardzo chciałbym dać jakąś wartość dodaną do .NET’owej części na OWASP – może ktoś jest chętny mnie wspomóc w tym wysiłku?

WPF oferuje kilka pojemników, które pomagają przy tworzeniu wyglądu naszego okna. W tym poście omówimy je sobie. Zaczynajmy.

Canvas

To podstawowy pojemnik na kontrolki. Udostępnia cztery attached properties za pomocą, których możemy ustawić pozycję kontrolki. Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom. Left, Top mają pierwszeństwo nad Bottom, Right.Używa się tego tak:

<Canvas>                       

    <Button Content=”Przycisk” Canvas.Bottom=”100″ Canvas.Right=”90″/>

    <Button Content=”Przycisk” Canvas.Left=”100″ Canvas.Bottom=”90″/>

Canvas>

Dodatkowo Canvas definiuje właściwość ZIndex, za pomocą, której możemy ustawić kolejność kontrolek na osi Z.

StackPanel

StackPanel pozwala w łatwy sposób ułożyć kontrolki w stos. Wspiera zarówno stos pionowy (domyślnie) jak i poziomy. Tryb zmieniamy za pomocą właściwości Orientation.

<StackPanel>                       

    <Button Content=”Przycisk 1″ />

    <Button Content=”Przycisk 2″ />

    <Button Content=”Przycisk 3″ />

    <Button Content=”Przycisk 4″ />

StackPanel>

Jeśli zmienimy Orientation na Horizontal będzie to wyglądało tak:

WrapPanel

WrapPanel pozwala na ułożenie kontrolek kolejno jedna za drugą a dodatkowo w przypadku, gdy ilość kontrolek jest większa niż może pomieścić pojemnik w jednym rzędzie, “zawija je” i są one układane w kolejnym wierszu. Wygląda to tak:

<WrapPanel>                       

    <Button Content=”Przycisk 1″ />

    <Button Content=”Przycisk 2″ />

    <Button Content=”Przycisk 3″ />

    <Button Content=”Przycisk 4″ />

    <Button Content=”Przycisk 5″ />

    <Button Content=”Przycisk 6″ />

WrapPanel>

Elementy układane są standardowo od lewej do prawej. Można to zmienić właściwością Orientation i mieć je ułożone od góry do dołu. Jeśli ustawimy FlowDirection na RightToLeft, kontrolki są układane od prawej do lewej.

DockPanel

Ten pojemnik umożliwia “przyczepienie” kontrolki, do jednej z czterech stron. Udostępnia on właściwość DockPanel.Dock za pomocą, której definiujemy do które ze stron kontrolka ma być przyczepiona.

<DockPanel>                       

    <Button Content=”Przycisk 1″ DockPanel.Dock=”Top” />

    <Button Content=”Przycisk 2″ DockPanel.Dock=”Left”/>       

    <Button Content=”Przycisk 4″ DockPanel.Dock=”Right” />       

    <Button Content=”Przycisk 6″ DockPanel.Dock=”Bottom”/>

    <Button Content=”Przycisk 5″ />

DockPanel>

Widzimy, że żadna z kontrolek nie ma ustawionej wartości na Fill a jest wypełniona cała przestrzeń. Domyślnie ostatnio dodana kontrolka wypełnia całą pozostałą przestrzeń. Możemy to zachowanie zmienić poprzez ustawienie false dla właściwości LastChildFill.

Grid’a ze względu na rozległość tematu omówię w oddzielnym odcinku.

Dziś przesłuchałem 48 odcinek podcastu StackOverflow, w którym to Jeff & Joel omawiają “problem” portalu cnprog.com. Gdyby ktoś nie był w temacie, to w pewnym okresie (teraz już został zmieniony) był kompletną kopią strony StackOverflow. Graficznie wyglądał identycznie.

Pada w tym podcaście, również zdanie, iż fakt, że ktoś stara się cię skopiować powinieneś uważać za przejaw tego, że jesteś dobry na tyle, że ktoś chce twoją pracę powielić. Powinienem się chyba zatem cieszyć, że powstała strona http://techphoto.pl. Co prawda kopia jest ciut nieudolna, linki nie działają, layout trochę niedostosowany do tematu strony, ale co tam 🙂

Tak czy inaczej mail do firmy hostingowej z zapytaniem poszedł – zobaczymy co odpowiedzą 🙂

Dziś będzie ciekawy (przynajmniej dla mnie) temat. Transformacje.

WPF wprowadza możliwość zastosowania transformacji na kontrolkach. Wyróżniamy dwa rodzaje transformacji: LayoutTransform oraz RenderTransform. LayoutTransform jest stosowany przed tym jak kontrolka zostanie rozmieszczona. Drugi tj. RenderTransform po, ale przed tym jak ma zostać ona narysowana. Poniższy rysunek powinien wszystko wyjaśnić.

    <Button Content=”Przycisk 1″

          Background=”Orange”>

        <Button.LayoutTransform>

            <ScaleTransform CenterX=”.5″ CenterY=”.5″ ScaleY=”2″ />

        Button.LayoutTransform>

    Button>

    <Button Content=”Przycisk 2″

          Background=”Red”>

    Button>

    <Button Content=”Przycisk 3″

          Background=”Orange”>

        <Button.RenderTransform>

            <ScaleTransform CenterX=”.5″ CenterY=”.5″ ScaleY=”2″ />

        Button.RenderTransform>

    Button>

    <Button Content=”Przycisk 4″

          Background=”Red”>

    Button>

StackPanel>

Widzimy, że przycisk 1 jest całkowicie widoczny w odróżnieniu od przycisku 3, który został przysłonięty przez ostatni. Zanim przejdziemy do omówienia poszczególnych transformacji warto jeszcze wspomnieć, że punktem centralnym transformacji możemy sterować za pomocą właściwości RenderTransformOrigin

<Button Content=”Przycisk 3″

      Background=”Orange” RenderTransformOrigin=”1,0″>

    <Button.RenderTransform>

        <ScaleTransform  ScaleY=”2″ ScaleX=”2″ />

    Button.RenderTransform>

Button>

<Button Content=”Przycisk 4″

      Background=”Red”>

Button>

Obroty

Umożliwiają obrócenie kontrolki o zadany kąt (w stopniach). Najistotniejszy jest fakt, że kontrolka po obrocie nadal jest w pełni funkcjonalna. No może trochę trudniej się jej używa. Przykład:

<GroupBox Header=”Wybór”>

    <GroupBox.LayoutTransform>

        <RotateTransform Angle=”45″ />

    GroupBox.LayoutTransform>

    <StackPanel>

        <RadioButton Content=”Opcja 1″ />

        <RadioButton Content=”Opcja 2″ />

        <RadioButton Content=”Opcja 3″ />

    StackPanel>

GroupBox>

Dodatkowo możemy określić punkt środkowy obrotu za pomocą właściwości CenterX, CenterY. Wartości z przedziału 0-1 (obustronnie domknięty przedział).

Skalowanie

To rozciąganie elementu w pionie i/lub poziomie. Wystarczy dodać taki kod:

<ScaleTransform ScaleX=”1″ ScaleY=”2″ />

Przekrzywienie

To ciekawa transformacja pozwalająca uzyskać “ciekawe” wizualnie efekty. Możemy przekrzywiać oddzielnie względem osi X i Y. Zastosowanie

<SkewTransform AngleX=”15″ AngleY=”11″ />

na przycisku da nam następujący efekt końcowy

A przycisk oczywiście nadal jest klikalny.

Translacja

Ta transformacja ma widoczny efekt tylko dla RenderTransform i objawia się przesunięciem kontrolki o zadany wektor. Użycie jest proste:

<TranslateTransform X=”20″ Y=”10″ />

Transformacja macierzowa

Jeśli ktoś jest zaznajomiony z podstawami algebry, to wszystkie powyższe transformacji (i ich kombinacje) może uzyskać za pomocą tej.

Definiujemy ją w poniższy sposób:

<MatrixTransform>

    <MatrixTransform.Matrix>

        <Matrix M11=”1″ M12=”2″ M21=”.5″ M22=”.3″ OffsetX=”.3″ OffsetY=”2″ />

    MatrixTransform.Matrix>

MatrixTransform>

lub szybciej:

<Button Content=”OK” Width=”80″ Height=”80″ RenderTransform=”1,2,.5,.3,.3,2″>

Łączenie transformacji

LayoutTransform oraz RenderTransform są typu ContentControl i jako swoje dziecko pozwalają ustawić tylko jedną transformację. Jeśli chcemy połączyć ich kilka to musimy skorzystać z elementu TransformGroup. Będziemy mogli wtedy użyć większej ich ilości. Przykład:

<Button.RenderTransform>

    <TransformGroup>

        <ScaleTransform ScaleX=”.5″ ScaleY=”.5″ />

        <RotateTransform Angle=”45″ CenterX=”.5″ CenterY=”.5″/>

        <TranslateTransform X=”45″ Y=”45″ />

    TransformGroup>

Button.RenderTransform>

Uwaga dla osób, które pierwszy raz z tym się stykają. Złączenie np. obrotu i przesunięcia to nie to samo co złączenie przesunięcia i obrotu. Kolejność ma znaczenie.

Do zapamiętania

Wszelkie te operacje, choć tak to wygląda nie zmieniają rozmiarów kontrolki odczytanych za pomocą Height, Width, ActualWidth, ActualHeight. Mimo np. przeskalowania przycisku 20×20 dwa razy, wartości, które odczytamy będą nadal wskazywały na rozmiar 20×20.

Na dziś tyle. Następnym razem już w końcu omówienie paneli.