Creazione di un controllo dall'aspetto personalizzabile
Windows Presentation Foundation (WPF) consente di creare un controllo il cui aspetto può essere personalizzato. Ad esempio, è possibile modificare l'aspetto di un oggetto CheckBox oltre a quale impostazione verranno eseguite le proprietà creando un nuovo ControlTemplateoggetto . Nella figura seguente viene illustrato un CheckBox oggetto che usa un valore predefinito ControlTemplate e un CheckBox oggetto che usa un oggetto personalizzato ControlTemplate.
Controllo CheckBox che usa il modello di controllo predefinito
Controllo CheckBox che usa un modello di controllo personalizzato
Se si seguono le parti e il modello di stati quando si crea un controllo, l'aspetto del controllo sarà personalizzabile. Gli strumenti di progettazione, ad esempio Blend per Visual Studio supportano il modello di parti e stati, pertanto quando si segue questo modello il controllo sarà personalizzabile in questi tipi di applicazioni. In questo argomento vengono illustrati i modelli di parti e stati e come seguirlo quando si crea un controllo personalizzato. In questo argomento viene usato un esempio di controllo personalizzato, NumericUpDown
, per illustrare la filosofia di questo modello. Il NumericUpDown
controllo visualizza un valore numerico, che un utente può aumentare o diminuire facendo clic sui pulsanti del controllo. Nella figura seguente viene illustrato il NumericUpDown
controllo descritto in questo argomento.
Controllo NumericUpDown personalizzato
Questo argomento include le sezioni seguenti:
Prerequisiti
In questo argomento si presuppone che si sappia come creare un nuovo ControlTemplate per un controllo esistente, conoscere gli elementi di un contratto di controllo e comprendere i concetti descritti in Creare un modello per un controllo.
Nota
Per creare un controllo che può avere l'aspetto personalizzato, è necessario creare un controllo che eredita dalla classe o una delle relative sottoclassi diverse da ControlUserControl. Un controllo che eredita da UserControl è un controllo che può essere creato rapidamente, ma non usa e ControlTemplate non è possibile personalizzarne l'aspetto.
Parti e stati del modello
Il modello parti e stati specifica come definire la struttura visiva e il comportamento visivo di un controllo. Per seguire il modello di parti e stati, eseguire le operazioni seguenti:
Definire la struttura visiva e il comportamento visivo in ControlTemplate di un controllo .
Seguire alcune procedure consigliate quando la logica del controllo interagisce con parti del modello di controllo.
Specificare un contratto di controllo per specificare gli elementi da includere in ControlTemplate.
Quando si definiscono la struttura visiva e il comportamento visivo in ControlTemplate di un controllo, gli autori dell'applicazione possono modificare la struttura visiva e il comportamento visivo del controllo creando un nuovo ControlTemplate anziché scrivere codice. È necessario fornire un contratto di controllo che indica agli autori dell'applicazione quali FrameworkElement oggetti e stati devono essere definiti in ControlTemplate. È consigliabile seguire alcune procedure consigliate quando si interagisce con le parti in ControlTemplate in modo che il controllo gestisca correttamente un oggetto incompleto ControlTemplate. Se si seguono questi tre principi, gli autori di applicazioni saranno in grado di creare un ControlTemplate oggetto per il controllo così facilmente possibile per i controlli forniti con WPF. La sezione seguente illustra in dettaglio ognuna di queste raccomandazioni.
Definizione della struttura visiva e del comportamento visivo di un controllo in un controlTemplate
Quando si crea il controllo personalizzato usando il modello di parti e stati, si definisce la struttura visiva e il comportamento visivo del controllo anziché ControlTemplate nella logica. La struttura visiva di un controllo è la composizione di FrameworkElement oggetti che costituiscono il controllo. Il comportamento visivo è il modo in cui il controllo viene visualizzato quando si trova in un determinato stato. Per altre informazioni sulla creazione di un ControlTemplate oggetto che specifica la struttura visiva e il comportamento visivo di un controllo, vedere Creare un modello per un controllo.
Nell'esempio del NumericUpDown
controllo la struttura visiva include due RepeatButton controlli e un oggetto TextBlock. Se si aggiungono questi controlli nel codice del NumericUpDown
controllo nel relativo costruttore, ad esempio le posizioni di tali controlli non saranno modificabili. Anziché definire la struttura visiva e il comportamento visivo del controllo nel codice, è necessario definirlo in ControlTemplate. Quindi uno sviluppatore di applicazioni per personalizzare la posizione dei pulsanti e TextBlock specificare il comportamento che si verifica quando Value
è negativo perché ControlTemplate può essere sostituito.
Nell'esempio seguente viene illustrata la struttura visiva del NumericUpDown
controllo , che include un RepeatButton oggetto per aumentare Value
, un RepeatButton oggetto per ridurre Value
e un TextBlock oggetto da visualizzare Value
.
<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>
Un comportamento visivo del NumericUpDown
controllo è che il valore è in un tipo di carattere rosso se è negativo. Se si modifica l'oggetto TextBlockForeground di nel codice quando è Value
negativo, l'oggetto NumericUpDown
mostrerà sempre un valore negativo rosso. È possibile specificare il comportamento visivo del controllo in ControlTemplate aggiungendo VisualState oggetti a ControlTemplate. Nell'esempio seguente vengono illustrati gli VisualState oggetti per gli Positive
stati e Negative
. Positive
e Negative
si escludono a vicenda (il controllo si trova sempre esattamente in uno dei due), quindi l'esempio inserisce gli VisualState oggetti in un singolo VisualStateGroupoggetto . Quando il controllo entra nello Negative
stato, l'oggetto dell'oggetto ForegroundTextBlock diventa rosso. Quando il controllo è nello Positive
stato , Foreground restituisce al valore originale. La definizione di VisualState oggetti in un ControlTemplate oggetto è illustrata in Creare un modello per un controllo .
Nota
Assicurarsi di impostare la VisualStateManager.VisualStateGroups proprietà associata nella radice FrameworkElement dell'oggetto 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>
Uso di parti di ControlTemplate nel codice
Un ControlTemplate autore potrebbe omettere o VisualState oggettiFrameworkElement, intenzionalmente o per errore, ma la logica del controllo potrebbe richiedere il corretto funzionamento di tali parti. Il modello parti e stati specifica che il controllo deve essere resiliente a un oggetto mancante FrameworkElement o VisualState a oggettiControlTemplate. Il controllo non deve generare un'eccezione o segnalare un errore se FrameworkElementun oggetto , VisualStateo VisualStateGroup non è presente nell'oggetto ControlTemplate. In questa sezione vengono descritte le procedure consigliate per interagire con FrameworkElement oggetti e stati di gestione.
Prevedere oggetti FrameworkElement mancanti
Quando si definiscono FrameworkElement oggetti in ControlTemplate, la logica del controllo potrebbe dover interagire con alcune di esse. Ad esempio, il NumericUpDown
controllo sottoscrive l'evento dei Click pulsanti per aumentare o diminuire Value
e imposta la Text proprietà di TextBlock su Value
. Se un oggetto personalizzato ControlTemplate omette i TextBlock pulsanti o , è accettabile che il controllo perda alcune funzionalità, ma è necessario assicurarsi che il controllo non causi un errore. Ad esempio, se un oggetto ControlTemplate non contiene i pulsanti per modificare Value
, perde NumericUpDown
tale funzionalità, ma un'applicazione che usa l'oggetto ControlTemplate continuerà a essere eseguita.
Le procedure seguenti assicurano che il controllo risponda correttamente agli oggetti mancanti FrameworkElement :
Impostare l'attributo
x:Name
per ogni FrameworkElement elemento a cui è necessario fare riferimento nel codice.Definire le proprietà private per ogni FrameworkElement oggetto con cui è necessario interagire.
Sottoscrivere e annullare la sottoscrizione a tutti gli eventi gestiti dal controllo nella FrameworkElement funzione di accesso set della proprietà.
Impostare le FrameworkElement proprietà definite nel passaggio 2 nel OnApplyTemplate metodo . Questo è il primo elemento FrameworkElement in ControlTemplate cui è disponibile per il controllo . Usare l'oggetto
x:Name
FrameworkElement di per ottenerlo da ControlTemplate.Verificare che non
null
sia prima di FrameworkElement accedere ai relativi membri. Se ènull
, non segnalare un errore.
Negli esempi seguenti viene illustrato come il NumericUpDown
controllo interagisce con FrameworkElement gli oggetti in base alle raccomandazioni nell'elenco precedente.
Nell'esempio che definisce la struttura visiva del NumericUpDown
controllo in ControlTemplate, l'oggetto che aumenta ha Value
il RepeatButton relativo x:Name
attributo impostato su UpButton
. Nell'esempio seguente viene dichiarata una proprietà denominata UpButtonElement
che rappresenta l'oggetto RepeatButton dichiarato in ControlTemplate. La set
funzione di accesso annulla prima di tutto l'evento del Click pulsante se UpDownElement
non null
è , quindi imposta la proprietà e quindi sottoscrive l'evento Click . Esiste anche una proprietà definita, ma non illustrata qui, per l'altro RepeatButton, denominato 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
Nell'esempio seguente viene illustrato l'oggetto OnApplyTemplate per il NumericUpDown
controllo . Nell'esempio viene utilizzato il GetTemplateChild metodo per ottenere gli FrameworkElement oggetti da ControlTemplate. Si noti che l'esempio protegge i casi in cui GetTemplateChild trova un FrameworkElement oggetto con il nome specificato che non è del tipo previsto. È anche consigliabile ignorare gli elementi con l'oggetto specificato x:Name
, ma sono di tipo errato.
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
Seguendo le procedure illustrate negli esempi precedenti, assicurarsi che il controllo continui a essere eseguito quando manca ControlTemplate un FrameworkElementoggetto .
Usare VisualStateManager per gestire gli stati
VisualStateManager Tiene traccia degli stati di un controllo ed esegue la logica necessaria per la transizione tra stati. Quando si aggiungono VisualState oggetti a ControlTemplate, aggiungerli a un VisualStateGroup oggetto e aggiungerli VisualStateGroup alla VisualStateManager.VisualStateGroups proprietà associata in modo che l'oggetto VisualStateManager abbia accesso a tali oggetti.
Nell'esempio seguente viene ripetuto l'esempio precedente che mostra gli VisualState oggetti che corrispondono agli Positive
stati e Negative
del controllo . L'oggetto Storyboard nell'oggettoVisualStateNegative
diventa il Foreground rossoTextBlock. Quando il NumericUpDown
controllo è nello Negative
stato , inizia lo Negative
storyboard nello stato . Quindi, nello StoryboardNegative
stato si arresta quando il controllo torna allo Positive
stato . L'oggetto Positive
VisualState non deve contenere un oggetto Storyboard perché, quando l'oggetto Storyboard per l'oggetto Negative
si arresta, restituisce il Foreground colore originale.
<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>
Si noti che all'oggetto TextBlock viene assegnato un nome, ma TextBlock non è presente nel contratto di controllo per NumericUpDown
perché la logica del controllo non fa mai riferimento a TextBlock. Gli elementi a cui viene fatto riferimento in ControlTemplate hanno nomi, ma non devono far parte del contratto di controllo perché un nuovo ControlTemplate oggetto per il controllo potrebbe non dover fare riferimento a tale elemento. Ad esempio, un utente che crea un nuovo ControlTemplate oggetto per NumericUpDown
potrebbe decidere di non indicare che Value
è negativo modificando .Foreground In tal caso, né il codice né il fa riferimento all'oggetto ControlTemplate in base al TextBlock nome.
La logica del controllo è responsabile della modifica dello stato del controllo. Nell'esempio seguente viene illustrato che il NumericUpDown
controllo chiama il GoToState metodo per passare Positive
allo stato quando Value
è 0 o maggiore e lo Negative
stato quando Value
è minore di 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
Il GoToState metodo esegue la logica necessaria per avviare e arrestare gli storyboard in modo appropriato. Quando un controllo chiama GoToState per modificarne lo stato, VisualStateManager esegue le operazioni seguenti:
Se l'oggetto VisualState che il controllo avrà , Storyboardinizia lo storyboard. Quindi, se l'oggetto VisualState da cui proviene il controllo ha un Storyboardoggetto , lo storyboard termina.
Se il controllo è già nello stato specificato, GoToState non esegue alcuna azione e restituisce
true
.Se lo stato specificato non esiste in ControlTemplate ,
control
GoToState non esegue alcuna azione e restituiscefalse
.
Procedure consigliate per l'uso di VisualStateManager
È consigliabile eseguire le operazioni seguenti per mantenere gli stati del controllo:
Utilizzare le proprietà per tenere traccia dello stato.
Creare un metodo helper per eseguire la transizione tra stati.
Il NumericUpDown
controllo usa la relativa Value
proprietà per tenere traccia se si trova nello Positive
stato o Negative
. Il NumericUpDown
controllo definisce anche gli Focused
stati e UnFocused
, che tiene traccia della IsFocused proprietà . Se si utilizzano stati che non corrispondono naturalmente a una proprietà del controllo, è possibile definire una proprietà privata per tenere traccia dello stato.
Un singolo metodo che aggiorna tutti gli stati centralizza le chiamate a VisualStateManager e mantiene gestibile il codice. Nell'esempio seguente viene illustrato il NumericUpDown
metodo helper del controllo , UpdateStates
. Quando Value
è maggiore o uguale a 0, è Control nello Positive
stato . Quando Value
è minore di 0, il controllo si trova nello Negative
stato . Quando IsFocused è true
, il controllo è nello Focused
stato; in caso contrario, si trova nello Unfocused
stato . Il controllo può chiamare UpdateStates
ogni volta che deve modificarne lo stato, indipendentemente dallo stato modificato.
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
Se si passa un nome di stato a GoToState quando il controllo è già in questo stato, GoToState non esegue alcuna operazione, quindi non è necessario verificare lo stato corrente del controllo. Ad esempio, se Value
cambia da un numero negativo a un altro numero negativo, lo storyboard per lo Negative
stato non viene interrotto e l'utente non visualizzerà una modifica nel controllo.
VisualStateManager Usa oggetti VisualStateGroup per determinare quale stato uscire quando si chiama GoToState. Il controllo è sempre in uno stato per ogni VisualStateGroup oggetto definito nel relativo ControlTemplate e lascia solo uno stato quando entra in un altro stato dallo stesso VisualStateGroupoggetto . Ad esempio, l'oggetto NumericUpDown
ControlTemplate del controllo definisce gli Positive
oggetti eVisualStateNegative
in uno VisualStateGroup e gli Focused
oggetti e Unfocused
VisualState in un altro. È possibile visualizzare Focused
e Unfocused
VisualState definito nella sezione Esempio completo in questo argomento Quando il controllo passa dallo Positive
stato allo Negative
stato o viceversa, il controllo rimane nello Focused
stato o Unfocused
.
Esistono tre posizioni tipiche in cui lo stato di un controllo può cambiare:
Quando l'oggetto ControlTemplate viene applicato a Control.
Quando una proprietà viene modificata.
Quando si verifica un evento.
Negli esempi seguenti viene illustrato l'aggiornamento dello stato del NumericUpDown
controllo in questi casi.
È necessario aggiornare lo stato del controllo nel OnApplyTemplate metodo in modo che il controllo venga visualizzato nello stato corretto quando ControlTemplate viene applicato . Nell'esempio seguente viene chiamato UpdateStates
per OnApplyTemplate assicurarsi che il controllo si trova negli stati appropriati. Si supponga, ad esempio, di creare un NumericUpDown
controllo e quindi impostarlo Foreground su verde e Value
su -5. Se non si chiama UpdateStates
quando l'oggetto ControlTemplate viene applicato al NumericUpDown
controllo, il controllo non è nello Negative
stato e il valore è verde anziché rosso. È necessario chiamare UpdateStates
per inserire il controllo nello Negative
stato .
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
Spesso è necessario aggiornare gli stati di un controllo quando viene modificata una proprietà. Nell'esempio seguente viene illustrato l'intero ValueChangedCallback
metodo. Poiché ValueChangedCallback
viene chiamato quando Value
cambia, il metodo chiama UpdateStates
nel caso Value
cambi da positivo a negativo o viceversa. È accettabile chiamare UpdateStates
quando Value
le modifiche ma rimangono positive o negative perché in tal caso il controllo non modificherà gli stati.
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
Potrebbe anche essere necessario aggiornare gli stati quando si verifica un evento. Nell'esempio seguente viene illustrato che le NumericUpDown
chiamate UpdateStates
su Control per gestire l'evento GotFocus .
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
VisualStateManager Consente di gestire gli stati del controllo. Usando , VisualStateManagerassicurarsi che il controllo passi correttamente da uno stato all'altro. Se si seguono le raccomandazioni descritte in questa sezione per l'uso di , il VisualStateManagercodice del controllo rimarrà leggibile e gestibile.
Fornire il contratto di controllo
Si fornisce un contratto di controllo in modo che ControlTemplate gli autori sappiano cosa inserire nel modello. Un contratto di controllo include tre elementi:
Gli elementi visivi usati dalla logica del controllo.
Gli stati del controllo e il gruppo a cui appartiene ogni stato.
Le proprietà pubbliche che influiscono visivamente sul controllo.
Un utente che crea un nuovo ControlTemplate oggetto deve conoscere gli FrameworkElement oggetti usati dalla logica del controllo, il tipo di ogni oggetto e il relativo nome. Un ControlTemplate autore deve anche conoscere il nome di ogni possibile stato in cui il controllo può trovarsi e in quale VisualStateGroup stato si trova.
Tornando all'esempio NumericUpDown
, il controllo prevede che l'oggetto ControlTemplate abbia gli oggetti seguenti FrameworkElement :
Oggetto RepeatButton denominato
UpButton
.Oggetto RepeatButton denominato
DownButton.
Il controllo può trovarsi negli stati seguenti:
Nel
ValueStates
VisualStateGroupPositive
Negative
Nel
FocusStates
VisualStateGroupFocused
Unfocused
Per specificare gli FrameworkElement oggetti previsti dal controllo, utilizzare TemplatePartAttribute, che specifica il nome e il tipo degli elementi previsti. Per specificare gli stati possibili di un controllo, utilizzare , TemplateVisualStateAttributeche specifica il nome dello stato e a cui VisualStateGroup appartiene. Inserire e TemplatePartAttributeTemplateVisualStateAttribute nella definizione della classe del controllo .
Anche qualsiasi proprietà pubblica che influisce sull'aspetto del controllo fa parte del contratto di controllo.
Nell'esempio seguente vengono specificati l'oggetto FrameworkElement e gli stati per il NumericUpDown
controllo .
[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
Esempio completo
L'esempio seguente è l'intero ControlTemplate per il NumericUpDown
controllo .
<!--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>
Nell'esempio seguente viene illustrata la logica per .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
Vedi anche
.NET Desktop feedback