środa, kwiecień 29, 2009

70-502 (WPF) Przygotowania...nr 12.3

Dziś będzie odcinek o tym jak możemy "uprościć" tworzenie klas typu Geometry i na koniec trochę o pisakach :). W WPFie dostępny jest tryb, dzięki któremu możemy tworzyć obiekty za pomocą ich reprezentacji w łańcuch znaków. Jak to robić?


<GeometryDrawing Brush="Pink" Geometry="M0,0 L 0,100 L100,100 A 100,100 0 0 10,10">



Spróbujmy omówić poszczególne znaczki.

  • F n - definiuje sposób wypełnienie figury. - tryb EvenOdd, 1 - NonZero. Jeśli użyte musi być na początku.

  • M x,y - rozpoczyna figurę ustawiając jej punkt początkowy (StartPoint na x,y. M jak move.

  • Z - kończy figurę i ustawia właściwość IsClosed na true. Jeśli chcesz zakończyć figurę, bez jej zamykania po prostu wprowadź kolejną definicję figury lub zakończ definicję całości bez znaku Z.

  • L x,y - rysuję linie do punktu (x,y)

  • A rx,ry d f1 f2 x,y - tworzy wycinek okręgu do punktu (x,y) w oparciu o elipsę o promieniach rx,ry obróconą o d stopni. Za pomocą flag f1,f2 (wartości 0 lub 1) kontrolują odpowiednio: IsLargeArc oraz Clocwise

  • C x1,x1 x2,y2 x,y - Tworzy krzywą Béziera do punktu (x,y) używając punktów kontrolnych (x1,y1) oraz (x2,y2)

  • Q x1,y1 x,y - tworzy Quadratic Bézier curves do punktu (x,y) z punktem kontrolnym x1,y1

  • H x - skrót umożliwiający narysowanie linii poziomej, gdzie x określa pozycję końcową.

  • V y - podobnie jak powyżej z tym razem linia pionowa

  • S x2,y2 x,y - tworzy krzywą Béziera do punktu x,y używając punktu kontrolnego x2,y2 a drugi punkt kontrolny jest wyliczany automatycznie, aby krzywa była gładka.

Litery można wpisywać duże i małe. Różnią się wtedy znaczeniem. W przypadku małych liter punkty są traktowane jako relatywne do ostatnio wprowadzonego punktu, a nie jako pozycje absolutne. Zobaczmy przykład:


<GeometryDrawing Brush="Pink" Geometry="M0 0S 50,50 100,0 L10,30">



Gdy teraz zamienimy tylko wielkość L na l otrzymamy:

Widać różnicę.

Jeszcze uwaga na koniec tego tematu. Spacje i przecinki są opcjonalne. Wymagane jest jedynie rozdzielenie parametrów.

Pisaki :)

Za pomocą pisaków (Pen) możemy kontrolować jak wygląda obwoluta naszego kształtu. Są one relatywnie proste w użyciu i posiadają niewiele właściwości umożliwiających konfigurowanie. Główną właściwością jest grubość czyli Thickness. Prócz tego dostępne jest jeszcze kilka innych takich jak:

  • StartLineCap, EndLineCap - określają początek i koniec linii. Dostępne wartości to: Flat, Square, Round oraz Traingle.

  • LineJoin - określa w jaki sposób łączone są stykające się linie. Dostępne wartości to: Miter, Round, Bevel. Tryb Miter możemy dodatkowo kontrolować za pomocą właściwości MiterLimit.

  • DashStyle - zmienia wygląd samej linii. Z linii ciągłej możemy utworzyć przerywaną. Dostępne wartości to Solid, Dash, Dot, DashDot oraz DashDotDot. Używa się tego w następujący sposób:


    <Pen Thickness="2" Brush="Black" DashStyle="{x:Static DashStyles.DashDot}" DashCap="Round" />


    DashCap ustawia sposób zakończenia odcinków w linii. Zobaczmy na obrazek:

Następnym razem podejmiemy temat Visuals.

wtorek, kwiecień 28, 2009

70-502 (WPF) Przygotowania...nr 12.2

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.

poniedziałek, kwiecień 27, 2009

70-502 (WPF) Przygotowania...nr 12.1

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.jpg</ImageBrush.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ę :P
  • 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.

sobota, kwiecień 25, 2009

70-502 (WPF) Przygotowania...nr 11.2

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.

70-502 (WPF) Przygotowania...nr 11.1

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.bat</WpfExamDemo: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.

czwartek, kwiecień 23, 2009

70-502 (WPF) Przygotowania...nr 10

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+&amp;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.

środa, kwiecień 22, 2009

70-502 (WPF) Przygotowania...nr 9.5

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