Tworzenie formantu, którego wygląd można dostosować
Program Windows Presentation Foundation (WPF) umożliwia utworzenie kontrolki, której wygląd można dostosować. Można na przykład zmienić wygląd elementu CheckBox poza właściwościami ustawienia, tworząc nowy ControlTemplateelement . Poniższa ilustracja przedstawia element CheckBox , który używa wartości domyślnej ControlTemplate i , CheckBox która używa niestandardowego ControlTemplateelementu .
Pole wyboru używające domyślnego szablonu kontrolki
Pole wyboru używające niestandardowego szablonu kontrolki
W przypadku korzystania z modelu części i stanów podczas tworzenia kontrolki wygląd kontrolki będzie możliwy do dostosowania. Projektant narzędzi, takich jak Blend for Visual Studio, obsługują model części i stanów, więc w przypadku korzystania z tego modelu kontrolka będzie można dostosowywać w tych typach aplikacji. W tym temacie omówiono części i stany modelu oraz sposób ich wykonywania podczas tworzenia własnej kontrolki. W tym temacie użyto przykładu kontrolki niestandardowej , NumericUpDown
aby zilustrować filozofię tego modelu. Kontrolka NumericUpDown
wyświetla wartość liczbową, którą użytkownik może zwiększyć lub zmniejszyć, klikając przyciski kontrolki. Poniższa ilustracja przedstawia kontrolkę NumericUpDown
, która została omówiona w tym temacie.
Niestandardowa kontrolka NumericUpDown
Ten temat zawiera następujące sekcje:
Wymagania wstępne
W tym temacie założono, że wiesz, jak utworzyć nową ControlTemplate dla istniejącej kontrolki, zapoznać się z elementami kontraktu sterowania i zrozumieć pojęcia omówione w temacie Tworzenie szablonu dla kontrolki.
Uwaga
Aby utworzyć kontrolkę, która może mieć dostosowany wygląd, należy utworzyć kontrolkę dziedziczą z Control klasy lub jednej z jej podklas innych niż UserControl. Kontrolka dziedziczona po UserControl kontrolce jest kontrolką, którą można szybko utworzyć, ale nie używa ControlTemplate elementu i nie można dostosować jej wyglądu.
Części i stany — model
Model części i stanów określa sposób definiowania struktury wizualizacji i zachowania wizualnego kontrolki. Aby postępować zgodnie z modelem części i stanów, należy wykonać następujące czynności:
Zdefiniuj strukturę wizualizacji i zachowanie wizualne w ControlTemplate kontrolce.
Postępuj zgodnie z pewnymi najlepszymi rozwiązaniami, gdy logika kontrolki wchodzi w interakcje z częściami szablonu kontrolki.
Podaj kontrakt kontrolny, aby określić, co należy uwzględnić w obiekcie ControlTemplate.
Podczas definiowania struktury wizualizacji i zachowania wizualnego w ControlTemplate kontrolce autorzy aplikacji mogą zmieniać strukturę wizualizacji i zachowanie wizualne kontrolki, tworząc nowy ControlTemplate , zamiast pisać kod. Należy podać kontrakt kontrolny, który informuje autorów aplikacji, które FrameworkElement obiekty i stany powinny być zdefiniowane w obiekcie ControlTemplate. Należy postępować zgodnie z niektórymi najlepszymi rozwiązaniami w przypadku interakcji z częściami w ControlTemplate programie , tak aby kontrolka prawidłowo obsługiwała niekompletny ControlTemplateelement . Jeśli zastosujesz się do tych trzech zasad, autorzy aplikacji będą mogli utworzyć dla ControlTemplate Twojej kontroli tak samo łatwo, jak w przypadku kontrolek, które wysyłają z WPF. W poniższej sekcji opisano szczegółowo każdą z tych zaleceń.
Definiowanie struktury wizualizacji i wizualnego zachowania kontrolki w kontrolce ControlTemplate
Podczas tworzenia kontrolki niestandardowej przy użyciu modelu części i stanów należy zdefiniować strukturę wizualizacji i zachowanie wizualne kontrolki w jej ControlTemplate zamiast w logice. Struktura wizualna kontrolki to złożone FrameworkElement obiekty tworzące kontrolkę. Zachowanie wizualizacji to sposób, w jaki kontrolka jest wyświetlana, gdy znajduje się w określonym stanie. Aby uzyskać więcej informacji na temat tworzenia obiektu ControlTemplate określającego strukturę wizualizacji i zachowanie wizualne kontrolki, zobacz Tworzenie szablonu dla kontrolki.
W przykładzie kontrolki NumericUpDown
struktura wizualizacji zawiera dwie RepeatButton kontrolki i .TextBlock W przypadku dodania tych kontrolek w kodzie kontrolki NumericUpDown
— na przykład jego konstruktora — pozycja tych kontrolek byłaby nie do pomyślenia. Zamiast definiować strukturę wizualizacji i zachowanie wizualne kontrolki w kodzie, należy zdefiniować ją w pliku ControlTemplate. Następnie deweloper aplikacji dostosuje położenie przycisków i TextBlock określ, jakie zachowanie występuje, gdy Value
jest ujemne, ponieważ ControlTemplate można je zamienić.
W poniższym przykładzie pokazano strukturę wizualizacji kontrolki NumericUpDown
, która zawiera element RepeatButton , który ma zwiększyć Value
wartość , a RepeatButton do zmniejszenia Value
wartości i , TextBlock aby wyświetlić Value
element .
<ControlTemplate TargetType="src:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border BorderThickness="1" BorderBrush="Gray"
Margin="7,2,2,2" Grid.RowSpan="2"
Background="#E0FFFFFF"
VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<!--Bind the TextBlock to the Value property-->
<TextBlock Name="TextBlock"
Width="60" TextAlignment="Right" Padding="5"
Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type src:NumericUpDown}},
Path=Value}"/>
</Border>
<RepeatButton Content="Up" Margin="2,5,5,0"
Name="UpButton"
Grid.Column="1" Grid.Row="0"/>
<RepeatButton Content="Down" Margin="2,0,5,5"
Name="DownButton"
Grid.Column="1" Grid.Row="1"/>
<Rectangle Name="FocusVisual" Grid.ColumnSpan="2" Grid.RowSpan="2"
Stroke="Black" StrokeThickness="1"
Visibility="Collapsed"/>
</Grid>
</Grid>
</ControlTemplate>
Wizualne zachowanie kontrolki NumericUpDown
polega na tym, że wartość znajduje się w czerwonej czcionki, jeśli jest ujemna. Jeśli zmienisz ForegroundTextBlock wartość w kodzie, gdy Value
wartość jest ujemna, NumericUpDown
zawsze będzie wyświetlana czerwona wartość ujemna. Zachowanie wizualne kontrolki w ControlTemplate obiekcie określa się przez dodanie VisualState obiektów do obiektu ControlTemplate. W poniższym przykładzie przedstawiono VisualState obiekty dla Positive
stanów i Negative
. Positive
i Negative
wzajemnie się wykluczają (kontrolka jest zawsze w dokładnie jednej z dwóch), więc przykład umieszcza VisualState obiekty w jednym VisualStateGroupobiekcie . Gdy kontrolka przejdzie w Negative
stan, kontrolka ForegroundTextBlock zmieni kolor na czerwony. Gdy kontrolka jest w Positive
stanie, Foreground funkcja zwraca wartość oryginalną. Definiowanie VisualState obiektów w obiekcie ControlTemplate zostało dokładniej omówione w temacie Tworzenie szablonu dla kontrolki.
Uwaga
Pamiętaj, aby ustawić dołączoną VisualStateManager.VisualStateGroups właściwość w katalogu głównym FrameworkElement elementu ControlTemplate.
<ControlTemplate TargetType="local:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValueStates">
<!--Make the Value property red when it is negative.-->
<VisualState Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="TextBlock"
Storyboard.TargetProperty="(Foreground).(Color)"/>
</Storyboard>
</VisualState>
<!--Return the TextBlock's Foreground to its
original color.-->
<VisualState Name="Positive"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
Używanie części kontrolkiTemplate w kodzie
Autor ControlTemplate może pominąć FrameworkElement lub VisualState obiekty, celowo lub przez pomyłkę, ale logika kontrolki może wymagać prawidłowego działania tych części. Model części i stanów określa, że kontrolka powinna być odporna na ControlTemplate brakujące FrameworkElement obiekty lub VisualState . Kontrolka nie powinna zgłaszać wyjątku ani zgłaszać błędu, jeśli FrameworkElementw obiekcie ControlTemplatebrakuje elementu , VisualStatelub VisualStateGroup . W tej sekcji opisano zalecane rozwiązania dotyczące interakcji z obiektami FrameworkElement i zarządzania stanami.
Przewidywanie brakujących obiektów FrameworkElement
Podczas definiowania FrameworkElement obiektów w ControlTemplateobiekcie logika kontrolki może wymagać interakcji z niektórymi z nich. Na przykład kontrolka NumericUpDown
subskrybuje zdarzenie przycisków Click w celu zwiększenia lub zmniejszenia Value
i ustawia Text właściwość na TextBlockValue
wartość . Jeśli niestandardowy ControlTemplateTextBlock pomija przyciski lub, dopuszczalne jest, aby kontrolka utraciła część jej funkcjonalności, ale upewnij się, że kontrolka nie powoduje błędu. Na przykład jeśli kontrolka ControlTemplate nie zawiera przycisków do zmiany Value
, NumericUpDown
funkcja utraci te funkcje, ale aplikacja korzystająca z tej ControlTemplate funkcji będzie nadal działać.
Poniższe rozwiązania zapewnią prawidłowe reagowanie kontrolki na brakujące FrameworkElement obiekty:
x:Name
Ustaw atrybut dla każdegoFrameworkElement, do którego należy odwołać się w kodzie.Zdefiniuj właściwości prywatne dla każdego FrameworkElement , z którym chcesz korzystać.
Subskrybuj i anuluj subskrypcję wszystkich zdarzeń obsługiwanych przez kontrolkę w FrameworkElement metodzie dostępu zestawu właściwości.
FrameworkElement Ustaw właściwości zdefiniowane w kroku 2 w metodzie OnApplyTemplate . Jest to najwcześniejsze, że FrameworkElement element w elemecie ControlTemplate jest dostępny dla kontrolki.
x:Name
Użyj elementu , FrameworkElement aby pobrać go z obiektu ControlTemplate.Sprawdź, czy element FrameworkElement nie
null
znajduje się przed uzyskaniem dostępu do jego członków. Jeśli jestnull
to , nie zgłaszaj błędu.
W poniższych przykładach pokazano, jak kontrolka NumericUpDown
współdziała z obiektami FrameworkElement zgodnie z zaleceniami na powyższej liście.
W przykładzie definiującym strukturę wizualizacji kontrolki NumericUpDown
w ControlTemplateobiekcie , RepeatButton który zwiększa Value
, ma jego x:Name
atrybut ustawiony na UpButton
wartość . Poniższy przykład deklaruje właściwość o nazwie UpButtonElement
, która reprezentuje RepeatButton zadeklarowany w obiekcie ControlTemplate. Akcesorium set
najpierw anuluje subskrypcję zdarzenia przycisku Click , jeśli UpDownElement
nie null
jest , a następnie ustawia właściwość, a następnie subskrybuje Click zdarzenie. Istnieje również zdefiniowana właściwość, ale nie jest wyświetlana w tym miejscu dla innego RepeatButtonelementu o nazwie DownButtonElement
.
private RepeatButton upButtonElement;
private RepeatButton UpButtonElement
{
get
{
return upButtonElement;
}
set
{
if (upButtonElement != null)
{
upButtonElement.Click -=
new RoutedEventHandler(upButtonElement_Click);
}
upButtonElement = value;
if (upButtonElement != null)
{
upButtonElement.Click +=
new RoutedEventHandler(upButtonElement_Click);
}
}
}
Private m_upButtonElement As RepeatButton
Private Property UpButtonElement() As RepeatButton
Get
Return m_upButtonElement
End Get
Set(ByVal value As RepeatButton)
If m_upButtonElement IsNot Nothing Then
RemoveHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
m_upButtonElement = value
If m_upButtonElement IsNot Nothing Then
AddHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
End Set
End Property
W poniższym przykładzie pokazano OnApplyTemplate dla kontrolki NumericUpDown
. W przykładzie użyto GetTemplateChild metody w celu pobrania FrameworkElement obiektów z obiektu ControlTemplate. Zwróć uwagę, że przykład chroni przed przypadkami, w których GetTemplateChild znajduje element FrameworkElement o określonej nazwie, który nie jest oczekiwanym typem. Najlepszym rozwiązaniem jest również ignorowanie elementów, które mają określony x:Name
typ, ale są nieprawidłowe.
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
//TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
UpdateStates(False)
End Sub
Postępując zgodnie z praktykami przedstawionymi w poprzednich przykładach, upewnij się, że kontrolka będzie nadal działać, gdy ControlTemplate brakuje FrameworkElementelementu .
Zarządzanie stanami za pomocą programu VisualStateManager
Funkcja VisualStateManager śledzi stany kontrolki i wykonuje logikę niezbędną do przejścia między stanami. Po dodaniu VisualState obiektów do ControlTemplateobiektu należy dodać je do VisualStateGroup obiektu i dodać VisualStateGroup do dołączonej VisualStateManager.VisualStateGroups właściwości, aby VisualStateManager obiekt miał do nich dostęp.
Poniższy przykład powtarza poprzedni przykład, który pokazuje VisualState obiekty, które odpowiadają Positive
stanom i Negative
kontrolki. ForegroundNegative
VisualState Na Storyboard kolei czerwonyTextBlock. Gdy kontrolka NumericUpDown
jest w Negative
stanie, rozpoczyna się scenorys w Negative
stanie . Następnie wartość Storyboard w Negative
stanie zatrzymuje się, gdy kontrolka powraca do Positive
stanu. Element Positive
VisualState nie musi zawierać elementu Storyboard , ponieważ gdy Storyboard dla zatrzymania Negative
, Foreground element powraca do oryginalnego koloru.
<ControlTemplate TargetType="local:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValueStates">
<!--Make the Value property red when it is negative.-->
<VisualState Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="TextBlock"
Storyboard.TargetProperty="(Foreground).(Color)"/>
</Storyboard>
</VisualState>
<!--Return the TextBlock's Foreground to its
original color.-->
<VisualState Name="Positive"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
Należy pamiętać, że TextBlock element ma nazwę, ale TextBlock nie znajduje się w kontrakcie kontrolnym, NumericUpDown
ponieważ logika kontrolki nigdy nie odwołuje się do TextBlockelementu . Elementy, do których odwołuje się ControlTemplate nazwa, ale nie muszą być częścią kontraktu sterowania, ponieważ nowy ControlTemplate element kontrolki może nie wymagać odwołowania się do tego elementu. Na przykład osoba tworząca nowy ControlTemplate element NumericUpDown
może zdecydować się nie wskazać, że Value
jest to negatywne, zmieniając element Foreground. W takim przypadku ani kod, ani ControlTemplate nazwa nie odwołuje się do nazwy TextBlock .
Logika kontrolki jest odpowiedzialna za zmianę stanu kontrolki. W poniższym przykładzie pokazano, że kontrolka NumericUpDown
wywołuje metodę , aby przejść do stanu Value
0 lub większegoPositive
, a Negative
stan, gdy Value
jest mniejszy niż GoToState 0.
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
If Value >= 0 Then
VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
Metoda GoToState wykonuje logikę niezbędną do odpowiedniego uruchamiania i zatrzymywania scenorysów. Gdy kontrolka wywołuje GoToState polecenie , aby zmienić jego stan, VisualStateManager wykonuje następujące czynności:
Jeśli kontrolka VisualState będzie zawierać Storyboardelement , rozpocznie się scenorys. Następnie, jeśli kontrolka VisualState pochodzi z elementu , Storyboardscenorys kończy się.
Jeśli kontrolka znajduje się już w określonym stanie, GoToState nie podejmuje żadnej akcji i zwraca wartość
true
.Jeśli określony stan nie istnieje w elemecie ControlTemplate
control
, GoToState nie podejmuje żadnej akcji i zwraca wartośćfalse
.
Najlepsze rozwiązania dotyczące pracy z visualStateManager
Zaleca się wykonanie następujących czynności w celu zachowania stanów kontroli:
Użyj właściwości, aby śledzić jego stan.
Utwórz metodę pomocnika, aby przejść między stanami.
Kontrolka NumericUpDown
używa jej Value
właściwości do śledzenia, czy jest w Positive
stanie lub Negative
. Kontrolka NumericUpDown
definiuje Focused
również stany i UnFocused
, które śledzą IsFocused właściwość . Jeśli używasz stanów, które nie odpowiadają naturalnie właściwości kontrolki, możesz zdefiniować właściwość prywatną do śledzenia stanu.
Pojedyncza metoda, która aktualizuje wszystkie stany, centralizuje wywołania metody VisualStateManager i zapewnia możliwość zarządzania kodem. W poniższym przykładzie przedstawiono metodę NumericUpDown
pomocnika kontrolki . UpdateStates
Gdy Value
wartość jest większa lub równa 0, Control wartość jest w Positive
stanie . Gdy Value
wartość jest mniejsza niż 0, kontrolka Negative
jest w stanie . Gdy IsFocused jest true
, kontrolka Focused
jest w stanie ; w przeciwnym razie jest w Unfocused
stanie . Kontrolka może wywoływać UpdateStates
zawsze, gdy musi zmienić swój stan, niezależnie od tego, jaki stan się zmienia.
private void UpdateStates(bool useTransitions)
{
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
if (IsFocused)
{
VisualStateManager.GoToState(this, "Focused", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Unfocused", useTransitions);
}
}
Private Sub UpdateStates(ByVal useTransitions As Boolean)
If Value >= 0 Then
VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
If IsFocused Then
VisualStateManager.GoToState(Me, "Focused", useTransitions)
Else
VisualStateManager.GoToState(Me, "Unfocused", useTransitions)
End If
End Sub
Jeśli przekażesz nazwę stanu do GoToState , gdy kontrolka jest już w tym stanie, nic nie robi, GoToState więc nie musisz sprawdzać bieżącego stanu kontrolki. Jeśli na przykład Value
zmieni się z jednej liczby ujemnej na inną liczbę ujemną, scenorys stanu Negative
nie zostanie przerwany, a użytkownik nie zobaczy zmiany w kontrolce.
Obiekt VisualStateManager używa VisualStateGroup obiektów do określenia, który stan ma zakończyć się po wywołaniu metody GoToState. Kontrolka jest zawsze w jednym stanie dla każdego VisualStateGroup zdefiniowanego w nim ControlTemplate i pozostawia stan tylko wtedy, gdy przechodzi do innego stanu z tego samego VisualStateGroup. Na przykład kontrolka definiuje obiekty iVisualStateNegative
w jednej VisualStateGroup i Unfocused
VisualStateFocused
i w innej.Positive
NumericUpDown
ControlTemplate (Możesz zobaczyć Focused
wartości i Unfocused
VisualState zdefiniowane w sekcji Kompletny przykład w tym temacie Gdy kontrolka przechodzi ze Positive
stanu do Negative
stanu lub na odwrót, kontrolka pozostaje w Focused
stanie lub Unfocused
.
Istnieją trzy typowe miejsca, w których stan kontrolki może ulec zmianie:
Gdy element ControlTemplate jest stosowany do .Control
Gdy właściwość ulegnie zmianie.
Gdy wystąpi zdarzenie.
W poniższych przykładach pokazano aktualizowanie stanu kontrolki NumericUpDown
w tych przypadkach.
Należy zaktualizować stan kontrolki w metodzie OnApplyTemplate , tak aby kontrolka pojawiała się w prawidłowym stanie po zastosowaniu ControlTemplate . Poniższy przykład wywołuje metodę UpdateStates
, OnApplyTemplate aby upewnić się, że kontrolka znajduje się w odpowiednich stanach. Załóżmy na przykład, że tworzysz kontrolkę, a następnie ustawiasz jej Foreground wartość na zieloną NumericUpDown
i Value
na -5. Jeśli kontrolka nie UpdateStates
jest wywoływana, gdy ControlTemplate kontrolka jest stosowana, NumericUpDown
kontrolka nie znajduje się w Negative
stanie, a wartość jest zielona, a nie czerwona. Należy wywołać UpdateStates
polecenie , aby umieścić kontrolkę Negative
w stanie .
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
//TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
UpdateStates(False)
End Sub
Często trzeba zaktualizować stany kontrolki po zmianie właściwości. Poniższy przykład przedstawia całą ValueChangedCallback
metodę. Ponieważ ValueChangedCallback
jest wywoływana, gdy Value
zmiany, metoda wywołuje UpdateStates
w przypadku Value
zmiany z dodatniego na ujemny lub odwrotnie. Dopuszczalne jest wywołanie UpdateStates
, gdy Value
zmiany pozostaną pozytywne lub negatywne, ponieważ w takim przypadku kontrolka nie zmieni stanów.
private static void ValueChangedCallback(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
NumericUpDown ctl = (NumericUpDown)obj;
int newValue = (int)args.NewValue;
// Call UpdateStates because the Value might have caused the
// control to change ValueStates.
ctl.UpdateStates(true);
// Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(
new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent,
newValue));
}
Private Shared Sub ValueChangedCallback(ByVal obj As DependencyObject,
ByVal args As DependencyPropertyChangedEventArgs)
Dim ctl As NumericUpDown = DirectCast(obj, NumericUpDown)
Dim newValue As Integer = CInt(args.NewValue)
' Call UpdateStates because the Value might have caused the
' control to change ValueStates.
ctl.UpdateStates(True)
' Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(New ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, newValue))
End Sub
Może być również konieczne zaktualizowanie stanów po wystąpieniu zdarzenia. W poniższym przykładzie pokazano, że NumericUpDown
wywołania UpdateStates
elementu w Control celu obsługi GotFocus zdarzenia.
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
UpdateStates(true);
}
Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
MyBase.OnGotFocus(e)
UpdateStates(True)
End Sub
Ułatwia VisualStateManager zarządzanie stanami kontrolki. Korzystając z elementu VisualStateManager, upewnij się, że kontrolka prawidłowo przechodzi między stanami. Jeśli zastosujesz się do zaleceń opisanych w tej sekcji na potrzeby pracy z VisualStateManagerkodem , kod kontrolki pozostanie czytelny i możliwy do utrzymania.
Zapewnianie kontraktu kontroli
Należy podać kontrakt kontrolny, ControlTemplate aby autorzy wiedzieli, co należy umieścić w szablonie. Kontrakt kontrolny ma trzy elementy:
Elementy wizualne używane przez logikę kontrolki.
Stany kontrolki i grupy, do których należy każdy stan.
Właściwości publiczne, które wizualnie wpływają na kontrolkę.
Ktoś, kto tworzy nowy ControlTemplate , musi wiedzieć, jakich FrameworkElement obiektów używa logika kontrolki, jakiego typu jest każdy obiekt i jaka jest jego nazwa. Autor ControlTemplate musi również znać nazwę każdego możliwego stanu, w którym może znajdować się kontrolka i w którym VisualStateGroup znajduje się stan.
Wracając do przykładu NumericUpDown
, kontrolka oczekuje ControlTemplate następujących FrameworkElement obiektów:
O RepeatButton nazwie
UpButton
.Wywołana RepeatButton
DownButton.
Kontrolka może być w następujących stanach:
W
ValueStates
VisualStateGroupPositive
Negative
W
FocusStates
VisualStateGroupFocused
Unfocused
Aby określić, jakich FrameworkElement obiektów oczekuje kontrolka, należy użyć TemplatePartAttributeelementu , który określa nazwę i typ oczekiwanych elementów. Aby określić możliwe stany kontrolki, należy użyć TemplateVisualStateAttributeelementu , który określa nazwę stanu i do którego VisualStateGroup należy. Umieść kontrolkę TemplatePartAttribute i TemplateVisualStateAttribute w definicji klasy kontrolki.
Każda właściwość publiczna, która ma wpływ na wygląd kontrolki, jest również częścią kontraktu kontroli.
W poniższym przykładzie określono FrameworkElement obiekt i stany dla kontrolki NumericUpDown
.
[TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
[TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
[TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
public class NumericUpDown : Control
{
public static readonly DependencyProperty BackgroundProperty;
public static readonly DependencyProperty BorderBrushProperty;
public static readonly DependencyProperty BorderThicknessProperty;
public static readonly DependencyProperty FontFamilyProperty;
public static readonly DependencyProperty FontSizeProperty;
public static readonly DependencyProperty FontStretchProperty;
public static readonly DependencyProperty FontStyleProperty;
public static readonly DependencyProperty FontWeightProperty;
public static readonly DependencyProperty ForegroundProperty;
public static readonly DependencyProperty HorizontalContentAlignmentProperty;
public static readonly DependencyProperty PaddingProperty;
public static readonly DependencyProperty TextAlignmentProperty;
public static readonly DependencyProperty TextDecorationsProperty;
public static readonly DependencyProperty TextWrappingProperty;
public static readonly DependencyProperty VerticalContentAlignmentProperty;
public Brush Background { get; set; }
public Brush BorderBrush { get; set; }
public Thickness BorderThickness { get; set; }
public FontFamily FontFamily { get; set; }
public double FontSize { get; set; }
public FontStretch FontStretch { get; set; }
public FontStyle FontStyle { get; set; }
public FontWeight FontWeight { get; set; }
public Brush Foreground { get; set; }
public HorizontalAlignment HorizontalContentAlignment { get; set; }
public Thickness Padding { get; set; }
public TextAlignment TextAlignment { get; set; }
public TextDecorationCollection TextDecorations { get; set; }
public TextWrapping TextWrapping { get; set; }
public VerticalAlignment VerticalContentAlignment { get; set; }
}
<TemplatePart(Name:="UpButtonElement", Type:=GetType(RepeatButton))>
<TemplatePart(Name:="DownButtonElement", Type:=GetType(RepeatButton))>
<TemplateVisualState(Name:="Positive", GroupName:="ValueStates")>
<TemplateVisualState(Name:="Negative", GroupName:="ValueStates")>
<TemplateVisualState(Name:="Focused", GroupName:="FocusedStates")>
<TemplateVisualState(Name:="Unfocused", GroupName:="FocusedStates")>
Public Class NumericUpDown
Inherits Control
Public Shared ReadOnly TextAlignmentProperty As DependencyProperty
Public Shared ReadOnly TextDecorationsProperty As DependencyProperty
Public Shared ReadOnly TextWrappingProperty As DependencyProperty
Public Property TextAlignment() As TextAlignment
Public Property TextDecorations() As TextDecorationCollection
Public Property TextWrapping() As TextWrapping
End Class
Kompletny przykład
Poniższy przykład dotyczy całej ControlTemplate kontrolki NumericUpDown
.
<!--This is the contents of the themes/generic.xaml file.-->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:VSMCustomControl">
<Style TargetType="{x:Type local:NumericUpDown}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NumericUpDown">
<Grid Margin="3"
Background="{TemplateBinding Background}">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValueStates">
<!--Make the Value property red when it is negative.-->
<VisualState Name="Negative">
<Storyboard>
<ColorAnimation To="Red"
Storyboard.TargetName="TextBlock"
Storyboard.TargetProperty="(Foreground).(Color)"/>
</Storyboard>
</VisualState>
<!--Return the control to its initial state by
return the TextBlock's Foreground to its
original color.-->
<VisualState Name="Positive"/>
</VisualStateGroup>
<VisualStateGroup Name="FocusStates">
<!--Add a focus rectangle to highlight the entire control
when it has focus.-->
<VisualState Name="Focused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="FocusVisual"
Storyboard.TargetProperty="Visibility" Duration="0">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!--Return the control to its initial state by
hiding the focus rectangle.-->
<VisualState Name="Unfocused"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border BorderThickness="1" BorderBrush="Gray"
Margin="7,2,2,2" Grid.RowSpan="2"
Background="#E0FFFFFF"
VerticalAlignment="Center"
HorizontalAlignment="Stretch">
<!--Bind the TextBlock to the Value property-->
<TextBlock Name="TextBlock"
Width="60" TextAlignment="Right" Padding="5"
Text="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type local:NumericUpDown}},
Path=Value}"/>
</Border>
<RepeatButton Content="Up" Margin="2,5,5,0"
Name="UpButton"
Grid.Column="1" Grid.Row="0"/>
<RepeatButton Content="Down" Margin="2,0,5,5"
Name="DownButton"
Grid.Column="1" Grid.Row="1"/>
<Rectangle Name="FocusVisual" Grid.ColumnSpan="2" Grid.RowSpan="2"
Stroke="Black" StrokeThickness="1"
Visibility="Collapsed"/>
</Grid>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
W poniższym przykładzie przedstawiono logikę dla elementu NumericUpDown
.
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
namespace VSMCustomControl
{
[TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
[TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
[TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
public class NumericUpDown : Control
{
public NumericUpDown()
{
DefaultStyleKey = typeof(NumericUpDown);
this.IsTabStop = true;
}
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(int), typeof(NumericUpDown),
new PropertyMetadata(
new PropertyChangedCallback(ValueChangedCallback)));
public int Value
{
get
{
return (int)GetValue(ValueProperty);
}
set
{
SetValue(ValueProperty, value);
}
}
private static void ValueChangedCallback(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
NumericUpDown ctl = (NumericUpDown)obj;
int newValue = (int)args.NewValue;
// Call UpdateStates because the Value might have caused the
// control to change ValueStates.
ctl.UpdateStates(true);
// Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(
new ValueChangedEventArgs(NumericUpDown.ValueChangedEvent,
newValue));
}
public static readonly RoutedEvent ValueChangedEvent =
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,
typeof(ValueChangedEventHandler), typeof(NumericUpDown));
public event ValueChangedEventHandler ValueChanged
{
add { AddHandler(ValueChangedEvent, value); }
remove { RemoveHandler(ValueChangedEvent, value); }
}
protected virtual void OnValueChanged(ValueChangedEventArgs e)
{
// Raise the ValueChanged event so applications can be alerted
// when Value changes.
RaiseEvent(e);
}
private void UpdateStates(bool useTransitions)
{
if (Value >= 0)
{
VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Negative", useTransitions);
}
if (IsFocused)
{
VisualStateManager.GoToState(this, "Focused", useTransitions);
}
else
{
VisualStateManager.GoToState(this, "Unfocused", useTransitions);
}
}
public override void OnApplyTemplate()
{
UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
//TextElement = GetTemplateChild("TextBlock") as TextBlock;
UpdateStates(false);
}
private RepeatButton downButtonElement;
private RepeatButton DownButtonElement
{
get
{
return downButtonElement;
}
set
{
if (downButtonElement != null)
{
downButtonElement.Click -=
new RoutedEventHandler(downButtonElement_Click);
}
downButtonElement = value;
if (downButtonElement != null)
{
downButtonElement.Click +=
new RoutedEventHandler(downButtonElement_Click);
}
}
}
void downButtonElement_Click(object sender, RoutedEventArgs e)
{
Value--;
}
private RepeatButton upButtonElement;
private RepeatButton UpButtonElement
{
get
{
return upButtonElement;
}
set
{
if (upButtonElement != null)
{
upButtonElement.Click -=
new RoutedEventHandler(upButtonElement_Click);
}
upButtonElement = value;
if (upButtonElement != null)
{
upButtonElement.Click +=
new RoutedEventHandler(upButtonElement_Click);
}
}
}
void upButtonElement_Click(object sender, RoutedEventArgs e)
{
Value++;
}
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
{
base.OnMouseLeftButtonDown(e);
Focus();
}
protected override void OnGotFocus(RoutedEventArgs e)
{
base.OnGotFocus(e);
UpdateStates(true);
}
protected override void OnLostFocus(RoutedEventArgs e)
{
base.OnLostFocus(e);
UpdateStates(true);
}
}
public delegate void ValueChangedEventHandler(object sender, ValueChangedEventArgs e);
public class ValueChangedEventArgs : RoutedEventArgs
{
private int _value;
public ValueChangedEventArgs(RoutedEvent id, int num)
{
_value = num;
RoutedEvent = id;
}
public int Value
{
get { return _value; }
}
}
}
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Controls.Primitives
Imports System.Windows.Input
Imports System.Windows.Media
<TemplatePart(Name:="UpButtonElement", Type:=GetType(RepeatButton))> _
<TemplatePart(Name:="DownButtonElement", Type:=GetType(RepeatButton))> _
<TemplateVisualState(Name:="Positive", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Negative", GroupName:="ValueStates")> _
<TemplateVisualState(Name:="Focused", GroupName:="FocusedStates")> _
<TemplateVisualState(Name:="Unfocused", GroupName:="FocusedStates")> _
Public Class NumericUpDown
Inherits Control
Public Sub New()
DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
Me.IsTabStop = True
End Sub
Public Shared ReadOnly ValueProperty As DependencyProperty =
DependencyProperty.Register("Value", GetType(Integer), GetType(NumericUpDown),
New PropertyMetadata(New PropertyChangedCallback(AddressOf ValueChangedCallback)))
Public Property Value() As Integer
Get
Return CInt(GetValue(ValueProperty))
End Get
Set(ByVal value As Integer)
SetValue(ValueProperty, value)
End Set
End Property
Private Shared Sub ValueChangedCallback(ByVal obj As DependencyObject,
ByVal args As DependencyPropertyChangedEventArgs)
Dim ctl As NumericUpDown = DirectCast(obj, NumericUpDown)
Dim newValue As Integer = CInt(args.NewValue)
' Call UpdateStates because the Value might have caused the
' control to change ValueStates.
ctl.UpdateStates(True)
' Call OnValueChanged to raise the ValueChanged event.
ctl.OnValueChanged(New ValueChangedEventArgs(NumericUpDown.ValueChangedEvent, newValue))
End Sub
Public Shared ReadOnly ValueChangedEvent As RoutedEvent =
EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Direct,
GetType(ValueChangedEventHandler), GetType(NumericUpDown))
Public Custom Event ValueChanged As ValueChangedEventHandler
AddHandler(ByVal value As ValueChangedEventHandler)
Me.AddHandler(ValueChangedEvent, value)
End AddHandler
RemoveHandler(ByVal value As ValueChangedEventHandler)
Me.RemoveHandler(ValueChangedEvent, value)
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As RoutedEventArgs)
Me.RaiseEvent(e)
End RaiseEvent
End Event
Protected Overridable Sub OnValueChanged(ByVal e As ValueChangedEventArgs)
' Raise the ValueChanged event so applications can be alerted
' when Value changes.
MyBase.RaiseEvent(e)
End Sub
#Region "NUDCode"
Private Sub UpdateStates(ByVal useTransitions As Boolean)
If Value >= 0 Then
VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
If IsFocused Then
VisualStateManager.GoToState(Me, "Focused", useTransitions)
Else
VisualStateManager.GoToState(Me, "Unfocused", useTransitions)
End If
End Sub
Public Overloads Overrides Sub OnApplyTemplate()
UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)
UpdateStates(False)
End Sub
Private m_downButtonElement As RepeatButton
Private Property DownButtonElement() As RepeatButton
Get
Return m_downButtonElement
End Get
Set(ByVal value As RepeatButton)
If m_downButtonElement IsNot Nothing Then
RemoveHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
End If
m_downButtonElement = value
If m_downButtonElement IsNot Nothing Then
AddHandler m_downButtonElement.Click, AddressOf downButtonElement_Click
End If
End Set
End Property
Private Sub downButtonElement_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Value -= 1
End Sub
Private m_upButtonElement As RepeatButton
Private Property UpButtonElement() As RepeatButton
Get
Return m_upButtonElement
End Get
Set(ByVal value As RepeatButton)
If m_upButtonElement IsNot Nothing Then
RemoveHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
m_upButtonElement = value
If m_upButtonElement IsNot Nothing Then
AddHandler m_upButtonElement.Click, AddressOf upButtonElement_Click
End If
End Set
End Property
Private Sub upButtonElement_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Value += 1
End Sub
Protected Overloads Overrides Sub OnMouseLeftButtonDown(ByVal e As MouseButtonEventArgs)
MyBase.OnMouseLeftButtonDown(e)
Focus()
End Sub
Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
MyBase.OnGotFocus(e)
UpdateStates(True)
End Sub
Protected Overloads Overrides Sub OnLostFocus(ByVal e As RoutedEventArgs)
MyBase.OnLostFocus(e)
UpdateStates(True)
End Sub
#End Region
End Class
Public Delegate Sub ValueChangedEventHandler(ByVal sender As Object,
ByVal e As ValueChangedEventArgs)
Public Class ValueChangedEventArgs
Inherits RoutedEventArgs
Public Sub New(ByVal id As RoutedEvent,
ByVal num As Integer)
Value = num
RoutedEvent = id
End Sub
Public ReadOnly Property Value() As Integer
End Class
Zobacz też
.NET Desktop feedback