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.
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