• 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ś temat multimediów. WPF jako nastawiony na interface użytkownika ma dużo do powiedzenia w tej kwestii. Zacznijmy zatem po kolei przyglądać się co mamy do dyspozycji.

SoundPlayer

To najprostsza klasa, którą mamy do dyspozycji jeśli chcemy odtworzyć dźwięki.

SoundPlayer player = new SoundPlayer(“plik.wav”);

player.Play();

Ze względu na swoją prostotę ma też swoje ograniczenia.

  • odtwarza tylko pliki .wav

  • nie ma wsparcia dla wielu dźwięków odtwarzanych jednocześnie

  • nie ma możliwości zmiany głośności

SystemSounds

Jeśli chcemy odegrać proste dźwięki systemowe, możemy skorzystać z tej klasy. definiuje ona kilka podstawowych dźwięków, każdy z których udostępnia metodę Play.

SystemSounds.Asterisk.Play();

SoundPlayerAction

Aby umożliwić deklaratywne odtwarzanie dźwięków powstała ta klasa. Dzięki niej możemy bezpośrednio w XAMLu zdefiniować jaki dźwięk ma zostać odgrany, gdy jakieś zdarzenie zajdzie. Zdefiniujmy odtwarzanie pliku, gdy przycisk zostanie naciśnięty:

       <Button Content=”play”>

            <Button.Triggers>

                <EventTrigger RoutedEvent=”Button.Click”>

                    <EventTrigger.Actions>

                        <SoundPlayerAction Source=”click.wav” />

                    EventTrigger.Actions>

                EventTrigger>

            Button.Triggers>

        Button>

MediaPlayer

To klasa dużo bardziej rozbudowana niż wspomniany wcześniej SoundPlayer. Nie tylko wspiera więcej formatów niż poprzednik (audio i wideo) to dodatkowo posiada funkcje umożliwiające sterowanie odtwarzanym zasobem jak i odpowiednią jego konfigurację – np. przewijanie, regulacja głośności, pobranie długości klipu itp. Jak tego użyć. Wyłącznie z kodu:

MediaPlayer player = new MediaPlayer();

player.Open(new Uri(“roar.wma”, UriKind.Relative));

player.Play();

Tak jak wspomniałem, MediaPlayer ma dosyć duże możliwości, ale jest używalna tylko z kodu. Aby wykorzystać pełną moc XAML’a musimy skorzystać z następnych klas.

MediaElement oraz MediaTimeline

MediaElement to XAMLowy odpowiednik klasy MediaPlayer. Umożliwia nam te sam właściwości a przy okazji jest dostępny jako element języka XAML.

<StackPanel>

    <MediaElement Source=”roar.wma” />

StackPanel>

MediaTimeline natomiast, jest tym dla klasy MediaElement czym SoundPlayerAction jest dla SoundPlayer. Możemy zatem użyć jej w eventach i animacjach.

<MediaElement x:Name=”audio” />

<Button Content=”Hover”>

    <Button.Triggers>

        <EventTrigger RoutedEvent=”Button.MouseEnter”>

            <BeginStoryboard x:Name=”playStoryboard”>

                <Storyboard>

                    <MediaTimeline Source=”roar.wma” Storyboard.TargetName=”audio” />

                Storyboard>

            BeginStoryboard>

        EventTrigger>

        <EventTrigger RoutedEvent=”Button.Click”>

            <StopStoryboard BeginStoryboardName=”playStoryboard” />                   

        EventTrigger>

    Button.Triggers>

Button>

Wideo

Do wideo używamy ostatnio omawianej klasy MediaElement jednak w przypadku wideo, mamy do dyspozycji kilka dodatkowych właściwości, które warto omówić.
Możemy dla przykładu przyciąć wideo oraz wyświetlać je z półprzeźroczystością.

<Button Content=”I’m below the video” />

<MediaElement x:Name=”video” Source=”walker.wmv” Opacity=”0.7″ >

    <MediaElement.Clip>

        <EllipseGeometry Center=”80,80″ RadiusX=”180″ RadiusY=”180″ />

    MediaElement.Clip>

MediaElement>

Możemy też reagować na błędy związane z danymi za pomocą eventu MediaFailed.

Speech

WPF daje nam też możliwość zamiany tekstu w mowę oraz operację odwrotną czyli rozpoznawanie mowy i zamianę jej na formę tekstową. Aby skorzystać z tej funkcjonalności należy dodać do projektu System.Speech.dll.
Aby zmusić naszą aplikcję do wypowiedzenia pierwszych słów, należy skorzystać z klasy SpeechSynthesizer.

SpeechSynthesizer speech = new SpeechSynthesizer();

speech.Speak(“I’m talking”);

Można także robić to asynchronicznie za pomocą funkcji SpeakAsync. Oczywiście oprócz tego mamy jeszcze dużo więcej możliwości jak chociażby wybór głosu.

speech.SelectVoiceByHints(VoiceGender.Female, VoiceAge.Teen);

Oczywiście odpowidnie głosy musimy ściągnąć – są one dostępne w Speech SDK.
Oczywiście mamy dużo większe możliwości konfiguracyjne. Za pomocą klasy PromptBuilder. Umożliwia ona zbudowanie całej wypowiedzi wraz z odpowiednią intonacją: przerwami, przyśpieszaniem wypowiedzi lub jej zwalnianiem.

PromptBuilder builder = new PromptBuilder(CultureInfo.CurrentUICulture);

builder.AppendTextWithHint(“WPF”, SayAs.SpellOut);

builder.AppendText(“jest”);

builder.AppendBreak(PromptBreak.Small);

builder.AppendText(“ekstra”, PromptEmphasis.Strong);

 

SpeechSynthesizer speech = new SpeechSynthesizer();

speech.Speak(builder);

To tylko mały pokaz możliwości tej klasy.

Rozpoznawanie mowy

WPF udostępnia także rozpoznawanie głosu:

SpeechRecognizer recognizer = new SpeechRecognizer();

recognizer.SpeechRecognized += recognizer_SpeechRecognized;

Oczywiście i w tym przypadku możemy więcej kontrolować za pomocą gramatyki. Jednak jest to dosyć zaawansowana sprawa i nie sądzę, aby jej znajomość była bardzo wymagana na egzaminie – MSDN on GrammerBuilder

Na dziś tyle. Następnym razem będzie o zasobach w WPF.

Załóżmy, że mam sobie taką oto strukturę elementów na stronie WWW:

    <div id=”outer1″>

        <div id=”inner” />

    div>

    <div id=”outer2″>div>

Wygląda, że wszystko jest w porządku, jednak gdy uruchomimy taką stronę efekt wynikowy będzie zupełnie nie taki, jakiego byśmy się mogli spodziewać. Wynik w Firebugu:

    <div id=”outer1″>

        <div id=”inner”> div>

        <div id=”outer2″/>

    div>

Co jest? Nasz zewnętrzny element div (outer2) został wciągnięty do środka pierwszego zewnętrznego elementu div (outer1).
Co powoduje takie dziwnie zachowanie? Po kilku chwilach zdziwienia, zwątpienia i rozczarowania udało się ustalić, że za złe zachowanie odpowiedzialny jest ten element:

    <div id=”inner” />

Tak zamknięty element powodował to zachowanie. Wystarczy zmienić go na

&lt/div> a wszystko wróci do normy. Można powiedzieć, że mały to problem, ale często męczący. Ja np. praktycznie z automatu dodaję “niepoprawnie” zamknięty tag jak tworzę layout i potrzebuję mieć element, który będzie miał ustawiony clear:both.

Zawszę piszę po prostu:

i oczywiście wpadam w tę pułapkę. Mam nadzieję, że może komuś to pomoże i nie straci za dużo czasu na zastanawianiu się gdzie jest błąd. A może to tylko u mnie występuje ten efekt? Ktoś potwierdzi powtarzalność?

Dziś, zgodnie z zapowiedzią z poprzedniego posta, miało być o Materials. Jednak o tym nie będzie. Dlaczego? Posty te traktuję jako przygotowanie do egzaminu, i po przyjrzeniu się stwierdziłem, że tak wnikliwe opisywanie tematu nie jest konieczne przy egzaminie. Oby da decyzja się nie zemściła :).

Documents

Dziś zatem będzie o dokumentach. Dokumenty definiuje się za pomocą znacznika FlowDocument.

    <FlowDocument>

        <Paragraph FontSize=”22″>70-502 (WPF) Przygotowania…nr. 13Paragraph>

        <Paragraph>Dziś, zgodnie z zapowiedzią z poprzedniego posta, miało być o

            <Span FontFamily=”Consolas”>MaterialSpan> s. Jednak o tym nie będzie. Dlaczego? Posty te traktuję jako przygotowanie do egzaminu, i po przyjrzeniu się stwierdziłem, że tak wnikliwe opisywanie tematu nie jest konieczne przy egzaminie. Oby da decyzja się nie zemściła :).

        Paragraph>

        <Paragraph FontSize=”22″>DocumentsParagraph>

        <Paragraph>Dziś zatem będzie o dokumentach.Paragraph>

    FlowDocument>

Jak widzimy definicja dokumentu jest banalna. Definiujemy po prostu kolejne paragrafy, które zawierają tekst, który ma się pojawić w dokumencie. Po uruchomieniu naszym oczom ukaże się takie okno.

Okno standardowo zawiera przyciski do manipulacji dokumentem takie jak zmiana strony, powiększanie czy pomniejszanie. Jakie elementy mamy dostępne do budowania dokumentów?

Klocki

  • Paragraph – podstawowy element tworzący dokumenty. Składa się z podrzędnych elementów. W przypadku, gdy paragraf zawiera tylko tekst, tworzony jest element Run, który zawiera nasz tekst.

  • Section – grupuje elementy bez definiowania odgórnej ich struktury. Przydatne jeśli chcemy nadać identyczne właściwości większej ilości elementów w dokumencie.

  • List – umożliwia utworzenie listy elementów w dokumencie. Dużo możliwości ustalenia numeracji elementów – właściwość MarkStyle.

  • Table – odpowiednik znacznika TABLE z HTML

  • BlockUIContainer – umożliwia hostowanie elementu UIElement wewnątrz dokumentu

Zobaczmy jak to wygląda w praktyce:

    <FlowDocument>

        <Section FontFamily=”Consolas” FontSize=”12″>

            <Paragraph>

                <Span>Jakiś tekst w pierwszej linii.Span>

                <Span>Ta linia będzie miała takie same parametry jak poprzednia linia.Span>

            Paragraph>

        Section>

        <List MarkerStyle=”Box”>

            <ListItem>

                <Paragraph>Pozycja 1Paragraph>

            ListItem>

            <ListItem>

                <Paragraph>Pozycja 2Paragraph>

            ListItem>

            <ListItem>

                <Paragraph>Pozycja 3Paragraph>

            ListItem>

        List>

        <Table>

            <TableRowGroup>

                <TableRow>

                    <TableCell>

                        <Paragraph>Cell 1,1Paragraph>

                    TableCell>

                    <TableCell>

                        <Paragraph>Cell 1,2Paragraph>

                    TableCell>

                TableRow>

                <TableRow>

                    <TableCell>

                        <Paragraph>Cell 2,1Paragraph>

                    TableCell>

                    <TableCell>

                        <Paragraph>Cell 2,2Paragraph>

                    TableCell>

                TableRow>

            TableRowGroup>

        Table>

        <BlockUIContainer>

            <Button Content=”Przycisk w dokumencie” />

        BlockUIContainer>

    FlowDocument>

Przycisk oczywiście nadal działa, tak jak w normalnej aplikacji.

Elementy inline

Prócz elementu Span mamy do dyspozycji kilka predefiniowanych elementów, które przyśpieszają pisanie dokumentu.

        <Paragraph>

            <Bold>Jakiś tekst boldemBold>

            <Italic>KursywaItalic>

            <Underline>PodkreślenieUnderline>

            <Hyperlink>HyperlinkHyperlink>

            <Span BaselineAlignment=”Subscript”>indeks dolnySpan>

            <Span BaselineAlignment=”Superscript”>indeks górnySpan>

            <Span>

                <Span.TextDecorations>

                    <TextDecoration Location=”Strikethrough” />

                Span.TextDecorations>

                przekreślenie

            Span>

        Paragraph>

W przypadku dokumentów mamy możliwość umieszczenia “dokowalnych” elementów. Robimy to za pomocą Figure oraz Floater. Figure umożliwia przypięcie elementu do odpowiedniej części dokumentu.

            <Figure VerticalAnchor=”ContentTop”>

                <BlockUIContainer>

                    <Image Source=”braille.gif” />

                BlockUIContainer>

            Figure>

Wyświetlanie dokumentu

Jak możemy wyświetlić taki dokument? Do dyspozycji mamy trzy klasy.

  • FlowDocumentScrollViewer – wyświetla dokument jako dokument wraz z paskami przewijania.

  • FlowDocumentPageViewer – dokument wyświetlany jako pojedyncze strony z możliwością nawigacji pomiędzy nimi.

  • FlowDocumentReader – łączy możliwości poprzednich dwóch sposobów wyświetlania dodając kika zaawansowanych właściwości jak np. możliwość wyszukiwania tekstu.

Adnotacje

WPF umożliwia tworzenie adnotacji, do umieszczenia w dokumencie. WPF udostępnia kilka poleceń, które umożliwiają tworzenie notatek.

  • CreateTextStickyNoteCommand – umożliwia stworzenie tekstowej notatki dla zaznaczanego tekstu

  • CreateInkStickyNoteCommand – tworzy notatkę, na której możemy pisać ręcznie.

  • DeleteStickyNotestCommand – usuwa aktualnie zaznaczoną notatkę

  • CreateHightlightCommand – zaznacza tekst na kolor przekazany jako parametr.

  • ClearHighlightsCommand – usuwa zaznaczenie

        <StackPanel Orientation=”Horizontal”>

            <Button Command=”a:AnnotationService.CreateInkStickyNoteCommand” CommandTarget=”{Binding ElementName=reader}”>Nowa notatkaButton>

            <Button Command=”a:AnnotationService.DeleteStickyNotesCommand” CommandTarget=”{Binding ElementName=reader}”>Usuń notatkęButton>

            <Button Command=”a:AnnotationService.CreateHighlightCommand” CommandTarget=”{Binding ElementName=reader}”

                   CommandParameter=”{x:Static Brushes.Yellow}”>Zaznacz tekst na zielonoButton>

            <Button Command=”a:AnnotationService.ClearHighlightsCommand” CommandTarget=”{Binding ElementName=reader}”>Usuń zaznaczenieButton>

        StackPanel>

Musimy jeszcze aktywować adnotacje.

            AnnotationService service = AnnotationService.GetService(reader);

            if (service == null)

            {

                stream = new FileStream(“storage.xml”, FileMode.OpenOrCreate);

                service = new AnnotationService(reader);

                AnnotationStore store = new XmlStreamStore(stream);

                store.AutoFlush = true;

                service.Enable(store);

            }

Przy wychodzeniu z aplikacji, należy jeszcze zamknąć strumień.

Na dziś tyle, następnym razem będzie trochę o multimediach.

Dziś będzie trochę informacji odnośnie światła w scenach 3D w WPF. Światło jest dosyć ważne, gdyż to od niego zależy jak nasz obiekt będzie się prezentował. W WPF dostępnych mamy kilka ich rodzajów:

  • DirectionalLight

  • PointLight

  • SpotLight

  • AmbientLight

Jak ich używać? Światło definiujemy jak element w tagach ModelVisual3D.Content

<ModelVisual3D>

    <ModelVisual3D.Content>

        <AmbientLight />

    ModelVisual3D.Content>

ModelVisual3D>

DirectionalLight

Światło to symuluje źródło światła oddalone tak bardzo, że promienie są praktycznie równoległe.

<DirectionalLight Direction=”-0.612372,-0.5,-0.612372″ Color=”Red” />

W tym przypadku wystarczy zdefiniować kierunek “świecenia” oraz kolor.

PointLight

W tym przypadku definiujemy światło punktowe rozchodzące się we wszystkich kierunkach.

<PointLight ConstantAttenuation=”.1″

          LinearAttenuation=”0″

          QuadraticAttenuation=”0.0125″

          Color=”Red”

          Position=”2,2,2″ />

Ponownie pozycję i kolor definiujemy przez właściwości Position oraz Color. Jednak w tym przypadku definiujemy dodatkowo jak bardzo światło ma przygasać wraz z odległością. Dokładny wzór można znaleźć tu. Tak zdefiniowane światło da nam ładny gradient na naszej powierzchni.

SpotLight

Ten typ światła to można powiedzieć typ “lampki”. Skupione w wiązkę światła typu PointLights. Prócz właściwości dostępnych jak przy PointLight mamy dostępne InnerConeAngle oraz OuterConeAngle definiujące odpowiednio wewnętrzny i zewnętrzny kąt rozpraszania naszej wiązki.

AmbientLight

Ostatni typ światła. Stosowany zwykle jako dodatkowe jego źródło. Pozwala utworzyć efekt światła rozproszonego na różnych powierzchniach. Dzięki jego dodaniu uzyskujemy ładniejszy wygląd elementów na naszej scenie poprzez bardziej naturalne jej oświetlenie. W tym przypadku definiujemy tylko kolor.

<AmbientLight Color=”Pink”/>

Na dziś tyle. W następnej części będzie o materiałach, z których są tworzone nasze elementy na scenie 3D. klasie FlowDocument

Po kolejnej przerwie powracamy do kursu o WPF a konkretniej dokończenia fragmentu o grafice 3D.

Przekształcenia 3D

Dostępne mamy te same transformacje co w przypadku dwuwymiarowych grafik: TranslateTransform3D, ScaleTransform3D, RotateTransform3D, MatrixTransform3D oraz Transform3DGroup.

Jak ich użyć?

<ModelVisual3D>

    <ModelVisual3D.Transform>

        <x:Static Member=”Transform3D.Identity” />

    ModelVisual3D.Transform>

Tak więc po prostu tworzymy podrzędny tag o nazwie Transform, w którym będziemy definiować nasze przekształcenia. Dodatkowo widzimy jak możemy użyć przekształcenia tożsamościowego.

Myślę, że nad większością transformat nie ma co się rozpisywać. Dochodzi dodatkowo koordynata Z. (ScaleZ, OffsetZ, CenterZ). Ciekawy natomiast jest Obrót. Możemy go definiować za pomocą obrotu wokół osi (AxisAngleRotation3D) lub jako kwaternion (QuaternionRotation3D). Obrót definiujemy za pomocą:

<RotateTransform3D>

    <RotateTransform3D.Rotation>

        <AxisAngleRotation3D Angle=”40″ Axis=”0,1,0″ />

    RotateTransform3D.Rotation>

RotateTransform3D>

gdzie Axis definiujemy wokół, której osi obracamy. Z kwaternionem będzie tak:

<RotateTransform3D.Rotation>

    <QuaternionRotation3D Quaternion=”0.3,0.1,0,0.1″ />

RotateTransform3D.Rotation>

definiując odpowiednie wartości kwaternionu.

Na dziś tyle o przekształceniach 3D. W następnym wpisie będzie o świetle.

Temat grafiki 3D jeszcze jeszcze bardziej rozległy w porównaniu do 2D. A już myślałem, że będzie z górki. Niestety w moich krótkich lekcjach wstępu do 3D nie będzie, więc jeśli coś jest niezrozumiałe z podstaw to proszę pisać. Postaram się znaleźć jakiś kurs. Zaczynajmy. Aby skorzystać z 3D w WPF musimy zaznajomić się z elementem Viewport3D.

<Viewport3D>

    <Viewport3D.Camera>

        <PerspectiveCamera LookDirection=”-1,-1,-1″ Position=”5,5,5″/>

    Viewport3D.Camera>

    <Viewport3D.Children>

        <ModelVisual3D x:Name=”light”>

            <ModelVisual3D.Content>

                <AmbientLight />

            ModelVisual3D.Content>

        ModelVisual3D>

        <ModelVisual3D>

            <ModelVisual3D.Content>

                <Model3DGroup>

                    <GeometryModel3D>

                        <GeometryModel3D.Material>

                            <DiffuseMaterial Brush=”Blue” />

                        GeometryModel3D.Material>

                        <GeometryModel3D.Geometry>

                            <MeshGeometry3D Positions=”0,2,1 1,1,1 1,1,-1 0,2,-1″ TriangleIndices=”0 1 2 0 2 3″/>

                        GeometryModel3D.Geometry>

                    GeometryModel3D>

                Model3DGroup>

            ModelVisual3D.Content>

        ModelVisual3D>

    Viewport3D.Children>

Viewport3D>

Sporo tego, a efekt nie będzie oszołamiający.

Co na tym przykładzie widzimy? Głównym elementem jaszego okna jest Viewport3D. W nim definiujemy poszczególne elementy: Camera oraz Children. W elemencie Camera definiujemy, co się można domyśleć tryb kamery, która będzie obserwować naszą scenę 3D. Do dyspozycji mamy OrtographicCamera oraz PerspectiveCamera.
W naszym przypadku skorzystaliśmy z tej ostatniej opcji i ustawiliśmy jej pozycję (Position) oraz kierunek patrzenia (LookDirection).

W elemencie Children definiujemy naszą scenę. Rządzi się ona trochę innymi prawami niż w przypadku 2D. Jeśli nie zdefiniujemy światła na naszej scenie (AmbientLight), wszystko będzie czarne. Nasze elementy definiujemy jako GeometryModel3D, który ma kilka interesujących właściwości. Za pomocą Material ustawiamy materiał dla naszej bryły. Za pomocą właściwości Geometry definiujemy wygląd. Przy pomocy Positions definiujemy wierzchołki. Każda trójka definiuje jeden. Za pomocą TriangleIndices definiujemy połączenia pomiędzy wierzchołkami definiujące trójkąty.

System współrzędnych

Osie układu współrzędnych w WPF wyznaczane są przez tzw. regułę prawej dłoni. Warto o tym pamiętać, gdyż niezastosowanie się do tego może dać nieoczekiwane efekty na naszej scenie.

W następnym odcinku, będzie o transformacjach w świecie 3D.

Dziś będzie o klasa Brush i pochodnych. Zaczniemy od krótkiego omówienia struktury System.Windows.Media.Color.

Podałem pełny namespace, aby dokładnie zaznaczyć, iż to o tej strukturze Color będzie dziś mowa. Kolor możemy podać za pomocą dwóch przestrzeni kolorów. Standardowy ARGB podawany za pomocą liczby całkowitej z przedziału 0-255 (obustronnie domknięty). Za wartości z tego przedziału odpowiadają właściwości A, R, G oraz B. Drugą przestrzenią jest przestrzeń scARGB. Przechowującą wartości poszczególnych składowych jako liczby zmiennoprzecinkowe i dzięki temu pozwalające na przekroczenie dopuszczalnego zakresu (0.0f – 1.0f – obustronnie domknięty). Za wartości z tej przestrzeni odpowiadają właściwości scA, scR, scG oraz scB.

Dodatkowo klasa ta ma kilka ciekawych metod. Prócz standardowych FromArgb, FromScRgb mamy także metody AreClose, Add oraz Multiply.

SolidColorBrush

To najprostszy ze sposobów wypełnienia używający jednego koloru, aby wypełnić dostępną przestrzeń. Wspiera powyżej opisane sposoby ustawiania koloru jak również za pomocą nazwy kolory.

<StackPanel>

    <Button Content=”ScRgb”>

        <Button.Background>

            <SolidColorBrush Color=”sc#0.0 1.9 1.0″ />

        Button.Background>

    Button>

    <Button Content=”Nazwa”>

        <Button.Background>

            <SolidColorBrush Color=”Khaki” />

        Button.Background>

    Button>

    <Button Content=”ARGB”>

        <Button.Background>

            <SolidColorBrush Color=”#FF129212″ />

        Button.Background>

    Button>

StackPanel>

LinearGradientBrush

Wypełnia dostępną przestrzeń za pomocą gradientu zdefiniowanego za pomocą elementów GradientStops. Przykład:

<StackPanel>

    <StackPanel.Background>

        <LinearGradientBrush StartPoint=”0.5 0″ EndPoint=”0.5 1″>

            <GradientStop Color=”sc#1.0 0.5 0.0 1.0″ Offset=”0″ />

            <GradientStop Color=”sc#0.5 0.5 1.0 1.0″ Offset=”0.75″/>

        LinearGradientBrush>

    StackPanel.Background>       

StackPanel>

Każdy GradientStop definiuje offset w którym rozpoczyna się dany kolor. Pomiędzy zdefiniowanymi kolorami kolor zostanie interpolowany. Oczywiście możemy dać więcej niż dwa elementy GradientStop. Na elemencie LinearGradientBrush możemy ustawić kilka właściwości, które determinują wygląd wypełnienia. StartPoint, EndPoint definiują, po jakiej linii będzie przebiegał nasz gradient. Jeśli ich nie uzupełnimy przyjmą domyślne wartości odpowiednio jako: (0,0) (1,1). Zobaczmy jak będzie wyglądał nasz StackPanel.

Gradient przebiega diagonalnie. Właściwością ColorInterpolation możemy sterować jak kolory są interpolowane. Wartość ScRgbLinearInterpolation da nam łagodniejsze zmiany pomiędzy kolorami niż SRgbLinearInterpolation. Właściwość SpreadMethod pozwala na określenie co dzieje się z wypełnieniem, gdy dotrzemy do puntu określonego za pomocą EndPoint. Dostępne wartości to Pad, Repeat i Reflect. Polecam pobawić się z nimi. Dają ciekawe efekty.

RadialColorBrush

Podobny do LinearGradientBrush z kilkoma różnicami. Zamiast dwóch punktów, początkowego i końcowego, możemy zdefiniować jeden – Center, który definiuje centrum rozchodzenia się tego wypełnienia. Dodatkowo za pomocą właściwości GradientOrigin możemy zdefiniować, w którym miejscu gradient się rozpoczyna. Co za pomocą tego gradientu możemy uzyskać?

<RadialGradientBrush Center=”.3,.3″ GradientOrigin=”.1,.1″>

    <GradientStop Color=”Red” Offset=”0″ />

    <GradientStop Color=”Green” Offset=”0.9″/>

RadialGradientBrush>

DrawingBrush

Za pomocą tej klasy możemy użyć poznanych już klas GeometryDrawing.

ImageBrush

Za pomocą tej klasy możemy ustawić obrazek jako tło kontrolki. Obrazek ustawiamy za pomocą właściwości ImageSource

<StackPanel.Background>

    <ImageBrush ImageSource=”clouds_XSmall.jpg” />

StackPanel.Background>

VisualBrush

To jest chyba najciekawsza klasa, umożliwia ustawienia jako tło innych kontrolek.

<StackPanel.Background>

    <VisualBrush>

        <VisualBrush.Visual>

            <Button Content=”OK” />

        VisualBrush.Visual>

    VisualBrush>

StackPanel.Background>

Jednak prawdziwa siła ukazuje się wraz z Bindingiem. Możemy napisać tak:

<StackPanel>

    <TextBox x:Name=”textBox” FontSize=”20″/>

    <Rectangle Height=”{Binding ElementName=textBox, Path=ActualHeight}”

              Width=”{Binding ElementName=textBox, Path=ActualWidth}”>

        <Rectangle.Fill>

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

            VisualBrush>

        Rectangle.Fill>           

        <Rectangle.LayoutTransform>

            <ScaleTransform ScaleY=”-.8″ />

        Rectangle.LayoutTransform>

    Rectangle>

StackPanel>

Nieźle, nieźle…

Uwagi

Pen jako, że używa obiektu Brush zamiast po prostu Color może używać wszystkiego co zostało opisane powyżej. Po drugie, jeśli chcemy uzyskać linię w wypełnieniu, wystarczy dodać dwa elementy GradientStop z różnym kolorem a tą samą wartością pola Offset.

Na dziś tyle…

Shapes

Shapes umożliwiają to samo GeometryDrawing jednak z racji tego, że dziedziczą bezpośrednio po FrameworkElement mogą być użyte bezpośrednio w UI. Wystarczy napisać:

<Button>

    <Ellipse Fill=”Pink” Width=”200″ Height=”100″ Stroke=”Black” StrokeThickness=”5″/>

Button>

Jakie mamy dostępne elementy? Podobnie jak w przypadku GeometryDrawing są to: Rectangle, Ellipse, Line, Polyline, Polygon oraz Path. Zobaczmy co ciekawego udostępniają nam niektóre z tych kontrolek.

Rectangle

Prócz standardowych właściwości pozwalających na ustawienie rozmiaru prostokąta mamy możliwość ustawienia poziomy zaokrąglenia jego rogów.

<Rectangle Fill=”Pink” Width=”200″ Height=”100″ Stroke=”Black” StrokeThickness=”5″ RadiusX=”20″ RadiusY=”50″/>

Polyline

Polyline umożliwia utworzenie łamanej z odcinków. Punkty definiujemy za pomocą właściwości Points.

<Polyline Points=”0,0 100,20 10,40 90,60 20,80 80,100 30,118 70,136 40,154 60,172 50,180″ Stroke=”Black” StrokeThickness=”4″ />

Co da nam taka definicja?

Polygon

Polygon to w zasadzie Polyline, który dodatkowo za nas tworzy linię łączącą ostatni i pierwszy punkt z naszej krzywej, zamykając naszą figurę.

Path

Wszystkie powyższe kształty da się reprezentować za pomocą tego elementu.

<Path Data=”M0,0 L 100,20 10,40 90,60 20,80 80,100 30,118 70,136 40,154 60,172 50,180″ Stroke=”Black” StrokeThickness=”4″/>

Da nam taki sam wynik jak użycie Polyline.
Na koniec mała uwaga. Użycie tych klas może być dużym obciążeniem dla naszej aplikacji więc zalecane jest ich rozsądne użycie.

W następnym odcinku omówimy klasę Brush i pochodne.

Dziś odcinek o klasach Visuals. Czym się one różnią od omówionych już klas Gemoetry oraz jak ich możemy użyć. Visuals są trochę dziwnym tworem w WPF, rezydującym na znacznie niższym poziomie niż większość elementów, z którymi się stykami. Trzeba się zatem mocno napracować aby coś z tej klasy wydobyć. Napiszmy więc kawałek kodu:

DrawingGroup group = FindResource(“drawingObject”) as DrawingGroup;

DrawingVisual visual = new DrawingVisual();

using(var context = visual.RenderOpen())

{

    context.DrawDrawing(group);

}

a w XAMLu dodatkowo

<Window.Resources>

    <DrawingGroup x:Key=”drawingObject”>

        <GeometryDrawing Brush=”Pink” Geometry=”M0 0S 50,50 100,0 l10,30″>

            <GeometryDrawing.Pen>

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

            GeometryDrawing.Pen>

        GeometryDrawing>

    DrawingGroup>

Window.Resources>

gdy jednak uruchomimy program zobaczymy puste okienko. Niestety nie ma tak prosto. Czego zatem nam brakuje? Po pierwsze musimy zarejestrować nasz obiekt w drzewie logicznym i wizualnym.

AddVisualChild(visual);

AddLogicalChild(visual);

Jednak to nadal nie wszystko 🙁

protected override int VisualChildrenCount

{

    get

    {

        return 1;

    }

}

 

protected override Visual GetVisualChild(int index)

{

    if (index!=0)

        throw new ArgumentOutOfRangeException();

    return visual;

}

A na ekranie ukaże się nam taki oto widok.

Jak zatem tego używać w sensowny sposób i czy jest jakiś zysk z tej klasy?

Zwykle robi się to tak, że tę klasę wykorzystuje się w oddzielnym elemencie typu UIElement i to w nim przeciąża się te dwie metody odpowiednio. Czy jest z tego zatem jakiś zysk? odpowiedź oczywiście jest twierdząca. Dzięki temu, że klasa ta jest bardzo nisko w hierarchii unika się całego narzutu związanego z tworzeniem “ciężkich” elementów Drawing. Klasa DrawingContext wspiera multum metod do rysowania bezpośrednio na niej, podobnie jak znany z WinForms obiekt Graphics. Co zatem jeszcze za wykonać?

HitTesting

Jak to sprawdzić? Wystarczy użyć pomocniczej klasy VisualTreeHelper.

protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)

{

    base.OnMouseLeftButtonDown(e);

 

    Point point = e.GetPosition(null);

    HitTestResult result = VisualTreeHelper.HitTest(visual, point);

    if(result.VisualHit.GetType() == typeof(DrawingVisual))

    {

        //do sth.

    }

}

W przypadku gdy mamy leżące na sobie elementy i wszystkie powinny reagować na kliknięcia należy posłużyć się trochę inną techniką.

protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)

{

    base.OnMouseLeftButtonDown(e);

 

    Point point = e.GetPosition(null);           

    VisualTreeHelper.HitTest(this, null, HitTestCallback, new PointHitTestParameters(point));

}

 

private HitTestResultBehavior HitTestCallback(HitTestResult result)

{

    if (result.VisualHit.GetType() == typeof(DrawingVisual))

    {

        DrawingVisual drawingVisual = result.VisualHit as DrawingVisual;

    }

    return HitTestResultBehavior.Continue;

}

Możemy oczywiście przerwać dalsze poszukiwania zwracając z metody HitTestResultBehavior.Stop

Następny odcinek będzie o Shapes.

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.