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.
Founder of Octal Solutions a .NET software house.
Passionate dev, blogger, occasionally speaker, one of the leaders of Wroc.NET user group. Microsoft MVP. Podcaster – Ostrapila.pl