Erstellen eines Steuerelements mit anpassbarer Darstellung
Windows Presentation Foundation (WPF) bietet Ihnen die Möglichkeit, ein Steuerelement zu erstellen, dessen Darstellung angepasst werden kann. Sie können z. B. die Darstellung einer CheckBox über die Einstellungseigenschaften hinaus ändern, indem Sie eine neue ControlTemplate erstellen. Die folgende Abbildung zeigt eine CheckBox, die eine Standard-ControlTemplate verwendet, und eine CheckBox, die eine benutzerdefinierte ControlTemplate verwendet.
Ein CheckBox, das die Standardsteuerelementvorlage verwendet
CheckBox, das eine benutzerdefinierte Steuerelementvorlage verwendet
Wenn Sie beim Erstellen eines Steuerelements das Teile- und Zustandsmodell befolgen, kann das Erscheinungsbild des Steuerelements angepasst werden. Designertools wie Blend für Visual Studio unterstützen das Teile- und Zustandsmodell. Wenn Sie diesem Modell folgen, kann das Steuerelement in diesen Anwendungstypen angepasst werden. In diesem Artikel werden das Teile- und Zustandsmodell und wie Sie es beim Erstellen Ihres eigenen Steuerelements verwenden. In diesem Thema wird ein Beispiel für ein benutzerdefiniertes Steuerelement, NumericUpDown
, verwendet, um die Philosophie dieses Modells zu veranschaulichen. Das NumericUpDown
-Steuerelement zeigt einen numerischen Wert an, den ein Benutzer vergrößern oder verkleinern kann, indem er auf die Schaltflächen des Steuerelements klickt. Die folgende Abbildung zeigt das NumericUpDown
-Steuerelement, das in diesem Artikel behandelt wird.
Ein benutzerdefiniertes NumericUpDown-Steuerelement
Dieses Thema enthält die folgenden Abschnitte:
Voraussetzungen
In diesem Thema wird davon ausgegangen, dass Sie wissen, wie Sie eine neue ControlTemplate für ein vorhandenes Steuerelement erstellen. Außerdem sollten Sie mit den Elementen eines Steuerelementvertrags vertraut sein und die Konzepte verstehen, die in Erstellen einer Vorlage für ein Steuerelementerläutert werden.
Anmerkung
Um ein Steuerelement zu erstellen, das seine Darstellung anpassen kann, müssen Sie ein Steuerelement erstellen, das von der Klasse Control oder einer ihrer Unterklassen erbt, mit Ausnahme von UserControl. Ein Steuerelement, das von UserControl erbt, ist ein Steuerelement, das schnell erstellt werden kann, aber es verwendet keine ControlTemplate und Sie können die Darstellung nicht anpassen.
Teile- und Zustandsmodell
Das Teile- und Zustandsmodell gibt an, wie die visuelle Struktur und das visuelle Verhalten eines Steuerelements definiert werden. Gehen Sie wie folgt vor, um das Teile- und Zustandsmodell zu befolgen:
Definieren Sie die visuelle Struktur und das visuelle Verhalten in der ControlTemplate-Instanz eines Steuerelements.
Befolgen Sie bestimmte bewährte Methoden, wenn die Logik Ihres Steuerelements mit Teilen der Steuerelementvorlage interagiert.
Stellen Sie einen Steuerelementvertrag bereit, um festzulegen, was in die ControlTemplate einbezogen werden soll.
Wenn Sie die visuelle Struktur und das visuelle Verhalten in der ControlTemplate eines Steuerelements definieren, können Anwendungsautoren die visuelle Struktur und das visuelle Verhalten Ihres Steuerelements ändern, indem Sie eine neue ControlTemplate erstellen, anstatt Code zu schreiben. Sie müssen einen Steuerelementvertrag bereitstellen, durch den die Erstellenden der Anwendung wissen, welche FrameworkElement-Objekte und -Zustände in der ControlTemplate definiert werden sollten. Sie sollten einige bewährte Methoden befolgen, wenn Sie mit den Teilen in der ControlTemplate-Instanz interagieren, sodass Ihr Steuerelement eine unvollständige ControlTemplate-Instanz ordnungsgemäß behandelt. Wenn Sie diese drei Prinzipien befolgen, können Anwendungserstellende für Ihr Steuerelement genauso einfach eine ControlTemplate erstellen wie für die Steuerelemente, die mit WPF ausgeliefert werden. Im folgenden Abschnitt werden die einzelnen Empfehlungen ausführlich erläutert.
Definieren der visuellen Struktur und des visuellen Verhaltens eines Steuerelements in einer ControlTemplate
Wenn Sie Ihr benutzerdefiniertes Steuerelement mithilfe des Teile- und Zustandsmodells erstellen, definieren Sie die visuelle Struktur und das visuelle Verhalten des Steuerelements in seiner ControlTemplate und nicht in seiner Logik. Die visuelle Struktur eines Steuerelements ist die Kombination aus FrameworkElement Objekten, aus denen das Steuerelement besteht. Das visuelle Verhalten ist die Art und Weise, wie das Steuerelement angezeigt wird, wenn es sich in einem bestimmten Zustand befindet. Weitere Informationen zum Erstellen einer ControlTemplate, die die visuelle Struktur und das visuelle Verhalten eines Steuerelements angibt, finden Sie unter Erstellen einer Vorlage für ein Steuerelement.
Im Beispiel des NumericUpDown
-Steuerelements enthält die visuelle Struktur zwei RepeatButton-Steuerelemente und ein TextBlock-Steuerelement. Wenn Sie diese Steuerelemente im Code des Steuerelements NumericUpDown
hinzufügen, zum Beispiel in seinem Konstruktor, wären die Positionen dieser Steuerelemente nicht veränderbar. Anstatt die visuelle Struktur und das visuelle Verhalten des Steuerelements in seinem Code zu definieren, sollten Sie es in der ControlTemplate definieren. Anschließend kann ein Anwendungsentwickler die Position der Schaltflächen und TextBlock anpassen und angeben, welches Verhalten auftritt, wenn Value
negativ ist, da die ControlTemplate ersetzt werden kann.
Das folgende Beispiel zeigt die visuelle Struktur des NumericUpDown
-Steuerelements, das eine RepeatButton enthält, um Value
zu steigern, eine RepeatButton, die Value
verringern soll, und eine TextBlock, um Value
anzuzeigen.
<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>
Ein visuelles Verhalten des Steuerelements NumericUpDown
besteht darin, dass der Wert in roter Schrift dargestellt wird, wenn er negativ ist. Wenn Sie den Foreground von TextBlock im Code ändern, wenn Value
negativ ist, wird NumericUpDown
immer einen roten negativen Wert anzeigen. Sie legen das visuelle Verhalten des Steuerelements in der ControlTemplate-Instanz fest, indem Sie VisualState-Objekte zu ControlTemplate hinzufügen. Das folgende Beispiel zeigt die VisualState-Objekte für die Zustände Positive
und Negative
an. Positive
und Negative
schließen sich gegenseitig aus (das Steuerelement befindet sich immer in genau einem der beiden), sodass das Beispiel die VisualState-Objekte in eine einzelne VisualStateGroup-Instanz platziert. Wenn das Steuerelement in den Zustand Negative
wechselt, wird der Foreground von TextBlock rot. Wenn sich das Steuerelement im Zustand Positive
befindet, kehrt der Foreground zu seinem ursprünglichen Wert zurück. Das Definieren von VisualState-Objekten in einer ControlTemplate wird unter Erstellen einer Vorlage für ein Steuerelement näher erläutert.
Anmerkung
Stellen Sie sicher, dass Sie die an angefügte Eigenschaft VisualStateManager.VisualStateGroups für das Stamm-FrameworkElement der ControlTemplate festlegen.
<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>
Verwenden von Teilen der ControlTemplate-Instanz im Code
Der Ersteller einer ControlTemplate-Instanz kann absichtlich oder versehentlich FrameworkElement- oder VisualState-Objekte auslassen, aber die Logik Ihres Steuerelements benötigt diese Teile möglicherweise, um richtig zu funktionieren. Das Teile- und Zustandsmodell legt fest, dass Ihr Steuerelement resilient auf ein ControlTemplate reagieren sollte, dem FrameworkElement- oder VisualState-Objekte fehlen. Ihr Steuerelement sollte keine Ausnahme ausgeben oder einen Fehler melden, wenn FrameworkElement, VisualState oder VisualStateGroup in der ControlTemplate fehlt. In diesem Abschnitt werden die empfohlenen Praktiken für die Interaktion mit den FrameworkElement-Objekten und die Verwaltung von Zuständen beschrieben.
Erwarten fehlender FrameworkElement-Objekte
Wenn Sie FrameworkElement-Objekte in der ControlTemplate-Instanz definieren, muss die Logik des Steuerelements möglicherweise mit einigen von ihnen interagieren. Beispielsweise abonniert das NumericUpDown
-Steuerelement das Click-Ereignis der Schaltflächen, um Value
zu erhöhen oder zu verringern und die Text-Eigenschaft der TextBlock-Instanz auf Value
festzulegen. Wenn eine benutzerdefinierte ControlTemplate das TextBlock-Element oder Schaltflächen auslässt, ist es akzeptabel, dass das Steuerelement einen Teil seiner Funktionalität verliert, aber Sie sollten sicher sein, dass Ihr Steuerelement keinen Fehler verursacht. Wenn beispielsweise ein ControlTemplate nicht die Schaltflächen zum Ändern von Value
enthält, verliert NumericUpDown
diese Funktion, aber eine Anwendung, die ControlTemplate verwendet, wird weiterhin funktionieren.
Die folgenden Methoden stellen sicher, dass Ihr Steuerelement ordnungsgemäß auf fehlende FrameworkElement-Objekte reagiert:
Legen Sie das Attribut
x:Name
für jede FrameworkElement fest, auf die Sie im Code verweisen müssen.Definieren Sie für jedes FrameworkElement, mit dem Sie interagieren müssen, private Eigenschaften.
Sie können alle Ereignisse, die Ihr Steuerelement verarbeitet, über den festgelegten Accessor der FrameworkElement-Eigenschaft abonnieren und abbestellen.
Legen Sie die Eigenschaften FrameworkElement fest, die Sie in Schritt 2 in der Methode OnApplyTemplate definiert haben. Dies ist der früheste Zeitpunkt, an dem das FrameworkElement in der ControlTemplate für das Steuerelement verfügbar ist. Verwenden Sie
x:Name
der FrameworkElement-Instanz, um es von der ControlTemplate abzurufen.Vergewissern Sie sich, dass FrameworkElement nicht
null
ist, bevor Sie auf dessen Member zugreifen. Wenn diesnull
ist, melden Sie keinen Fehler.
Die folgenden Beispiele zeigen, wie das NumericUpDown
-Steuerelement mit FrameworkElement-Objekten in Übereinstimmung mit den Empfehlungen in der obigen Liste interagiert.
In dem Beispiel, in dem die visuelle Struktur des NumericUpDown
-Steuerelements in der ControlTemplate definiert wird, ist für die RepeatButton, die Value
erhöht, das x:Name
-Attribut auf UpButton
festgelegt. Das folgende Beispiel deklariert eine Eigenschaft mit dem Namen UpButtonElement
, die die RepeatButton-Instanz darstellt, die in der ControlTemplate-Instanz deklariert ist. Die set
-Zugriffsmethode bestellt zunächst das Click-Ereignis der Schaltfläche ab, wenn UpDownElement
nicht null
ist. Dann legt sie die Eigenschaft fest und abonniert dann das Click-Ereignis. Es gibt auch eine Eigenschaft, die aber hier nicht gezeigt wird, für die andere RepeatButton-Instanz namens 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
Das folgende Beispiel zeigt OnApplyTemplate für das NumericUpDown
-Steuerelement. Im Beispiel wird die GetTemplateChild-Methode verwendet, um die FrameworkElement-Objekte aus dem ControlTemplateabzurufen. Beachten Sie, dass das Beispiel vor Fällen schützt, in denen GetTemplateChild ein FrameworkElement mit dem angegebenen Namen findet, das nicht den erwarteten Typ aufweist. Es ist auch eine bewährte Methode, Elemente zu ignorieren, die die angegebene x:Name
haben, aber vom falschen Typ sind.
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
Indem Sie die in den vorherigen Beispielen gezeigten Praktiken befolgen, stellen Sie sicher, dass Ihr Steuerelement weiterhin ausgeführt wird, wenn der ControlTemplate ein FrameworkElement fehlt.
Verwenden Sie den VisualStateManager zum Verwalten von Zuständen
Die VisualStateManager verfolgt die Zustände einer Steuerung und führt die Logik aus, die für den Zustandswechsel erforderlich ist. Wenn Sie VisualState-Objekte zur ControlTemplate hinzufügen, fügen Sie sie zu einer VisualStateGroup hinzu und fügen die VisualStateGroup zu der an angefügten Eigenschaft VisualStateManager.VisualStateGroups hinzu, sodass VisualStateManager Zugriff auf sie hat.
Im folgenden Beispiel wird das vorherige Beispiel wiederholt, in dem die VisualState Objekte angezeigt werden, die den Positive
und Negative
Status des Steuerelements entsprechen. Das Storyboard im Negative
VisualState, führ dazu, dass Foreground von TextBlock rot ist. Wenn das NumericUpDown
-Steuerelement den Zustand Negative
aufweist, beginnt das Storyboard mit dem Zustand Negative
. Dann wird das Storyboard im Zustand Negative
beendet, wenn das Steuerelement in den Zustand Positive
zurückkehrt. Die Positive
VisualState-Instanz muss kein Storyboard enthalten, denn wenn das Storyboard für Negative
beendet wird, kehrt der Foreground zu seiner ursprünglichen Farbe zurück.
<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>
Beachten Sie, dass TextBlock einen Namen erhält, aber TextBlock ist nicht im Steuerelementvertrag für NumericUpDown
enthalten, da die Logik des Steuerelements niemals auf TextBlock verweist. Elemente, auf die in der ControlTemplate verwiesen wird, haben Namen, müssen jedoch nicht Teil des Kontrollvertrags sein, da eine neue ControlTemplate für die Steuerung möglicherweise keinen Verweis auf dieses Element benötigt. Jemand, der ein neues ControlTemplate für NumericUpDown
erstellt, könnte beispielsweise entscheiden, nicht anzugeben, dass Value
negativ ist, indem er die Foregroundändert. In diesem Fall verweisen weder Code noch ControlTemplate auf über den Namen auf TextBlock.
Die Logik des Steuerelements ist für das Ändern des Zustands des Steuerelements verantwortlich. Das folgende Beispiel zeigt, dass das NumericUpDown
-Steuerelement die GoToState-Methode aufruft, um in den Positive
-Zustand zu wechseln, wenn Value
0 oder größer ist, und in den Negative
-Zustand, wenn Value
kleiner als 0 ist.
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
Die GoToState-Methode führt die Logik aus, die erforderlich ist, um die Storyboards ordnungsgemäß zu starten und zu beenden. Wenn ein Steuerelement GoToState aufruft, um seinen Zustand zu ändern, führt die VisualStateManager die folgenden Aktionen aus:
Wenn der VisualState, in den das Steuerelement wechselt, über ein Storyboard verfügt, dann beginnt das Storyboard. Wenn der VisualState, aus dem das Steuerelement wechselt, über ein Storyboard verfügt, wird das Storyboard beendet.
Wenn sich das Steuerelement bereits im angegebenen Zustand befindet, führt GoToState keine Aktion aus und gibt
true
zurück.Wenn der angegebene Zustand im ControlTemplate von
control
nicht vorhanden ist, führt GoToState keine Aktion aus und gibtfalse
zurück.
Bewährte Methoden für das Arbeiten mit visualStateManager
Es wird empfohlen, dass Sie wie folgt vorgehen, um die Zustände Ihres Steuerelements beizubehalten:
Verwenden Sie Eigenschaften, um den Zustand nachzuverfolgen.
Erstellen Sie eine Hilfsmethode für den Übergang zwischen Zuständen.
Das NumericUpDown
-Steuerelement verwendet seine Value
-Eigenschaft, um nachzuverfolgen, ob es sich im Zustand Positive
oder Negative
befindet. Das NumericUpDown
-Steuerelement definiert auch die Zustände Focused
und UnFocused
, wodurch die IsFocused-Eigenschaft nachverfolgt wird. Wenn Sie Zustände verwenden, die nicht natürlich einer Eigenschaft des Steuerelements entsprechen, können Sie eine private Eigenschaft definieren, um den Zustand nachzuverfolgen.
Eine einzelne Methode, die alle Zustände aktualisiert, zentralisiert die Aufrufe an VisualStateManager und hält Ihren Code überschaubar. Das folgende Beispiel zeigt die Hilfsmethode des NumericUpDown
-Steuerelements, UpdateStates
. Wenn Value
größer oder gleich 0 ist, befindet sich der Control im Positive
Zustand. Wenn Value
kleiner als 0 ist, befindet sich das Steuerelement im Negative
Zustand. Wenn IsFocusedtrue
ist, befindet sich das Steuerelement im Focused
Zustand; andernfalls befindet es sich im Unfocused
Zustand. Das Steuerelement kann UpdateStates
aufrufen, wenn er seinen Zustand ändern muss, unabhängig davon, welcher Zustand geändert wird.
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
Wenn Sie einen Statusnamen an GoToState übergeben und sich das Steuerelement bereits in diesem Zustand befindet, führt GoToState nichts aus, sodass Sie den aktuellen Zustand des Steuerelements nicht überprüfen müssen. Wenn Value
z. B. von einer negativen Zahl zu einer anderen negativen Zahl wechselt, wird das Storyboard für den Zustand Negative
nicht unterbrochen und die benutzende Person sieht keine Änderung am Steuerelement.
Der VisualStateManager verwendet VisualStateGroup-Objekte, um zu bestimmen, welcher Zustand verlassen werden soll, wenn Sie GoToState aufrufen. Das Steuerelement befindet sich immer in einem Zustand für jede VisualStateGroup-Instanz, die in seiner ControlTemplate-Instanz definiert ist und verlässt einen Zustand nur, wenn es in einen anderen Zustand aus derselben VisualStateGroup-Instanz wechselt. Beispielsweise definiert die ControlTemplate-Instanz des NumericUpDown
-Steuerelements die Positive
- und Negative
VisualState-Objekte in einer VisualStateGroup-Instanz und die Focused
- und Unfocused
VisualState-Objekte in einer anderen Instanz. Sie können Focused
und Unfocused
VisualState im Abschnitt Vollständiges Beispiel in diesem Artikel sehen. Wenn das Steuerelement vom Zustand Positive
in den Zustand Negative
wechselt oder umgekehrt, bleibt das Steuerelement entweder im Zustand Focused
oder im Zustand Unfocused
.
Es gibt drei typische Orte, an denen sich der Zustand eines Steuerelements ändern kann:
Wenn ControlTemplate auf Control angewendet wird
Wenn eine Eigenschaft geändert wird
Wenn ein Ereignis auftritt.
Die folgenden Beispiele zeigen, wie Sie den Zustand des NumericUpDown
-Steuerelements in diesen Fällen aktualisieren.
Sie sollten den Status des Steuerelements in der OnApplyTemplate-Methode aktualisieren, sodass das Steuerelement im richtigen Zustand angezeigt wird, wenn die ControlTemplate angewendet wird. Das folgende Beispiel ruft UpdateStates
in OnApplyTemplate auf, um sicherzustellen, dass sich das Steuerelement in den passenden Zuständen befindet. Nehmen Sie z. B. an, dass Sie ein NumericUpDown
-Steuerelement erstellen und dann seinen Foreground auf „Grün“ und den Value
auf „-5“ festlegen. Wenn Sie UpdateStates
nicht aufrufen, wenn ControlTemplate auf das NumericUpDown
-Steuerelement angewendet wird, befindet sich das Steuerelement nicht im Zustand Negative
, und der Wert ist grün statt rot. Sie müssen UpdateStates
aufrufen, um die Kontrolle in den Zustand Negative
zu versetzen.
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
Häufig müssen Sie die Zustände eines Steuerelements aktualisieren, wenn sich eine Eigenschaft ändert. Das folgende Beispiel zeigt die gesamte ValueChangedCallback
-Methode. Da ValueChangedCallback
aufgerufen wird, wenn sich Value
ändert, ruft die Methode UpdateStates
auf, falls Value
von positiv in negativ oder umgekehrt geändert wurde. Es ist akzeptabel, UpdateStates
aufzurufen, wenn sich Value
ändert, aber positiv oder negativ bleibt, denn in diesem Fall ändert das Steuerelement seinen Zustand nicht.
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
Möglicherweise müssen Sie auch Zustände aktualisieren, wenn ein Ereignis eintritt. Das folgende Beispiel zeigt, dass NumericUpDown
entsprechend UpdateStates
für das Control aufruft, um das GotFocus-Ereignis zu behandeln.
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
Der VisualStateManager hilft Ihnen beim Verwalten der Zustände Ihres Steuerelements. Mit VisualStateManager stellen Sie sicher, dass Ihr Steuerelement ordnungsgemäß zwischen Zuständen wechselt. Wenn Sie den in diesem Abschnitt beschriebenen Empfehlungen für die Arbeit mit dem VisualStateManagerfolgen, bleibt der Code Ihres Steuerelements lesbar und wartbar.
Bereitstellen des Steuerelementvertrags
Sie stellen einen Steuerelementvertrag bereit, sodass die ControlTemplate-Erstellenden wissen, was sie in die Vorlage einfügen müssen. Ein Kontrollvertrag besteht aus drei Elementen:
Die visuellen Elementen, die die Logik des Steuerelements verwendet
Den Zuständen des Steuerelements und den Gruppen, zu denen die einzelnen Zustände gehören.
Die öffentlichen Eigenschaften, die die visuelle Darstellung des Steuerelements beeinflussen
Jemand, der ein neues ControlTemplate erstellt, muss wissen, welche FrameworkElement-Objekte die Logik des Steuerelements verwendet, welchen Typ jedes Objekt hat und wie sein Name lautet. Ein ControlTemplate-Autor muss auch den Namen jedes möglichen Zustands kennen, in dem sich das Steuerelement befinden kann, und zu welchem VisualStateGroup dieser Zustand gehört.
Um auf das NumericUpDown
-Beispiel zurückzukommen: Das Steuerelement erwartet, dass ControlTemplate die folgenden FrameworkElement-Objekte umfasst:
Eine RepeatButton namens
UpButton
.RepeatButton namens
DownButton.
Das Steuerelement kann sich in den folgenden Zuständen befinden:
In
ValueStates
VisualStateGroupPositive
Negative
In
FocusStates
VisualStateGroupFocused
Unfocused
Um anzugeben, welche FrameworkElement Objekte das Steuerelement erwartet, verwenden Sie die TemplatePartAttribute, die den Namen und Typ der erwarteten Elemente angibt. Um die möglichen Zustände eines Steuerelements anzugeben, verwenden Sie die TemplateVisualStateAttribute, die den Namen des Zustands angibt und zu welchem VisualStateGroup es gehört. Platzieren Sie TemplatePartAttribute und TemplateVisualStateAttribute in der Klassendefinition des Steuerelements.
Jede öffentliche Eigenschaft, die sich auf die Darstellung Ihres Steuerelements auswirkt, ist ebenfalls Teil des Steuerelementvertrags.
Im folgenden Beispiel werden das FrameworkElement-Objekt und die Zustände für das NumericUpDown
-Steuerelement angegeben.
[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
Vollständiges Beispiel
Das folgende Beispiel ist die gesamte ControlTemplate für das NumericUpDown
-Steuerelement.
<!--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>
Das folgende Beispiel zeigt die Logik für die NumericUpDown
-Instanz.
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
Weitere Informationen
.NET Desktop feedback