niedziela, maj 09, 2010

Obrazkowy przycisk - WPF

Ostatnio męczyłem się z jednym tematem z WPF’a a mianowicie stworzyć przycisk, który jako swoje stany (Normal, Pressed, Disabled, Hover) będzie miał obrazki. Chciałem to zrobić ze zwykłego przycisku odpowiednio tworząc jego Template i wykorzystując potęgę Triggerów. Wszystkie moje próby spaliły na panewce bo i problem już się pojawiał w momencie gdzie przechowywać ścieżki do 4 obrazków.
W końcu przyszedł pomysł (wsparty przejrzeniem SO – dzięki @Gutek) i zabrałem się za robienie CustomControl. Ten post jest na przyszłość no i dla innych jakby ktoś potrzebował – taki Step By Step.

Zaczynamy zatem od stworzenia nowej klasy – nazwijmy ją MenuButton. Możemy też dodać od razu CustomControl (WPF). Wtedy od razu dostaniemy odpowiedni wpis w konstruktorze:

            DefaultStyleKeyProperty.OverrideMetadata(typeof(MenuButton),

                        new FrameworkPropertyMetadata(typeof(MenuButton)));

Oraz odpowiedni wpis w Generic.xaml w folderze Themes.

    <Style TargetType="{x:Type local:MenuButton}">

        <Setter Property="Template">

            <Setter.Value>

                <ControlTemplate TargetType="{x:Type local:MenuButton}">

                    <Border Background="{TemplateBinding Background}"

                           BorderBrush="{TemplateBinding BorderBrush}"

                           BorderThickness="{TemplateBinding BorderThickness}">

                    </Border>

                </ControlTemplate>

            </Setter.Value>

        </Setter>

    </Style>

Dzięki temu nasza kontrolka straciła właśnie swój wygląd. I dobrze. Zabierzmy się zatem z powrotem do pracy. Tak więc podsumowując nasza klasa na chwilę obecną wygląda tak:

    public class MenuButton : Control

    {

        static MenuButton()

        {

            DefaultStyleKeyProperty.OverrideMetadata(typeof(MenuButton),

                        new FrameworkPropertyMetadata(typeof(MenuButton)));

        }

    }

Teraz potrzebujemy mieć miejsce gdzie będziemy przechowywać ścieżki do naszych obrazków. Aby móc je ładnie bindować i wykorzystywać całą potęgę WPFa zrobimy je jako DependencyProprty.

        public static DependencyProperty NormalStateImageProperty;

 

        public string NormalStateImage

        {

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

            set { SetValue(NormalStateImageProperty, value); }

        }

Oraz zarejestrujmy ją w konstruktorze.

NormalStateImageProperty = DependencyProperty.Register("NormalStateImage",

    typeof(string), typeof(MenuButton));

I powtarzamy operację z pozostałymi 3-ma stanami: Hover, Pressed oraz Disabled. Zanim jednak przejdziemy do definiowania wyglądu zobaczmy jak będziemy mogli dodawać nasz przycisk:

<controls:MenuButton Height="24" Width="24" 

                    NormalStateImage="/Images/zoom_in_normal.png"

                    HoverStateImage="/Images/zoom_in_hover.png"

                    PressedStateImage="/Images/zoom_in_clicked.png"

                    DisabledStateImage="/Images/zoom_in_disabled.png" x:Name="btn" />

Czyli dość intuicyjnie. Ok. Przejdźmy do wyglądu. Z tego kodu nie jestem 100% zadowolony więc jakby ktoś chciał pomóc w poprawieniu to zapraszam do napisania komentarza lub maila, na adres który jest w profilu. Zacznijmy od samego wyglądu. Robimy to w pliku Generic.xaml

<Setter Property="Template">

    <Setter.Value>

        <ControlTemplate TargetType="{x:Type local:MenuButton}">

            <Grid x:Name="grid">

                <Image Name="DefaultImage" Source="{TemplateBinding NormalStateImage,

                                           Converter={StaticResource StringToImage}}" />

                <Image Name="HoverImage"   Source="{TemplateBinding HoverStateImage,

                                           Converter={StaticResource StringToImage}}"

                                           Visibility="Hidden" />                       

                <Image Name="ClickImage"   Source="{TemplateBinding PressedStateImage,

                                           Converter={StaticResource StringToImage}}"

                                           Visibility="Hidden" />

                <Image Name="DisabledImage" Source="{TemplateBinding DisabledStateImage,

                                           Converter={StaticResource StringToImage}}"

                                           Visibility="Hidden" />

            </Grid>                   

        </ControlTemplate>

    </Setter.Value>

</Setter>

4 obrazki. Tu jest minus tego rozwiązania. Odnośnie konwertera to jest to prosty – weź-łańcuch-znaków-i-zamień-na-bitmapę konwerter. Pominę go. Jeśli ktoś będzie go potrzebował niech napisze maila – podeślę.

Na koniec zostały nam same smaczki czyli zmiana stanów. W moim przypadku wyglądają one następująco:

<ControlTemplate.Triggers>

    <Trigger Property="IsMouseOver" Value="True">

        <Setter TargetName="ClickImage" Property="Visibility" Value="Hidden" />

        <Setter TargetName="DefaultImage" Property="Visibility" Value="Hidden" />

        <Setter TargetName="DisabledImage" Property="Visibility" Value="Hidden" />

        <Setter TargetName="HoverImage" Property="Visibility" Value="Visible" />

    </Trigger>

    <Trigger Property="IsPressed" Value="True">

        <Setter TargetName="grid" Property="RenderTransformOrigin" Value=".5,.5" />

        <Setter TargetName="grid" Property="RenderTransform">

            <Setter.Value>

                <ScaleTransform ScaleX=".9" ScaleY=".9" />

            </Setter.Value>

        </Setter>

    </Trigger>

    <Trigger Property="IsEnabled" Value="False">

        <Setter TargetName="ClickImage" Property="Visibility" Value="Hidden" />

        <Setter TargetName="DefaultImage" Property="Visibility" Value="Hidden" />

        <Setter TargetName="HoverImage" Property="Visibility" Value="Hidden" />

        <Setter TargetName="DisabledImage" Property="Visibility" Value="Visible" />

    </Trigger>

</ControlTemplate.Triggers>

Jak widać cały trik polega na odpowiednim ukrywaniu i pokazywaniu obrazków. Nie jest to może bardzo wyszukane rozwiązanie ale działa. Dodatkowo w trybie Pressed zrezygnowałem z obrazka na rzecz skalowania grida. Ot i cała filozofia. Może komuś się przyda! Całość do ściągnięcia z http://plukasik.eu/files/MenuButton.zip

Uwaga: Dla czytających tylko RSS’a tego bloga. Zacząłem ćwierkać :) - @pawel_lukasik. Można śledzić - zapraszam.

4 komentarze:

Gutek pisze...

A ten z BOT (przyklady ktore mi wyslales) przycisk Ci nie zalatwia tego? bo tam byl taki przycisk plus jesczze byla obserwacja gdzie na nim jest myszka i odpowiednie zdjecia sie ladowaly

pawlos pisze...

A ten przycisk był w tych dll'kach? Próbowałem go odszukać pod Reflektorem ale bez skutku. No i jak mam swój kod to mogę go zmodyfikować na 100% tak jak chce (np. ten ScaleTransform).

No i jakbym go tam znalazł to nie byłoby tego posta :)

Pozdrawiam,
Paweł

Anonimowy pisze...

Chyba lepiej zrobić to na VSM:

http://msdn.microsoft.com/en-us/library/system.windows.visualstatemanager%28VS.95%29.aspx

Anonimowy pisze...

chyba lepiej użyć VSM:

http://msdn.microsoft.com/en-us/library/system.windows.visualstatemanager%28VS.95%29.aspx