Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Windows Presentation Foundation (WPF) consente di creare un controllo il cui aspetto può essere personalizzato. Ad esempio, è possibile modificare l'aspetto di un CheckBox oltre quello che consentono le impostazioni delle proprietà creando un nuovo ControlTemplate oggetto. 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
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 le parti e il modello degli stati, quindi 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 contiene 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.
Annotazioni
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 può essere creato rapidamente, ma non usa un ControlTemplate e non è possibile personalizzarne l'aspetto.
Modello di parti e stati
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 di un controllo in ControlTemplate.
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 per il controllo altrettanto facilmente come fanno 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 un controllo personalizzato usando il modello di parti e stati, si può definire la struttura visiva e il comportamento visivo del controllo in ControlTemplate invece che 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 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. Poi uno sviluppatore di applicazioni può personalizzare la posizione dei pulsanti e TextBlock specificare quale comportamento si verifica quando Value
è negativo, poiché 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 aspetto visivo del comando NumericUpDown
è che il valore appare in rosso se è negativo. Se si modifica il Foreground del TextBlock nel codice quando il Value
è negativo, il 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 oggetti VisualState per gli stati Positive
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 stato Negative
, l'oggetto ForegroundTextBlock diventa rosso. Quando il controllo è nello Positive
stato , Foreground restituisce al valore originale. La definizione di VisualState oggetti in un ControlTemplate è ulteriormente discussa in Creare un modello per un controllo.
Annotazioni
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 autore ControlTemplate potrebbe omettere oggetti FrameworkElement o VisualState, intenzionalmente o per errore, ma la logica del controllo potrebbe richiedere tali parti per funzionare correttamente. Il modello parti e stati specifica che il controllo deve essere resiliente a un ControlTemplate che manca di oggetti FrameworkElement o VisualState. Il controllo non dovrebbe generare un'eccezione o segnalare un errore se FrameworkElement, VisualState o VisualStateGroup non è presente nel ControlTemplate. In questa sezione vengono descritte le procedure consigliate per interagire con FrameworkElement oggetti e stati di gestione.
Anticipare gli 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 si sottoscrive all'evento dei Click pulsanti per aumentare o diminuire Value
e imposta la proprietà Text di TextBlock su Value
. Se un oggetto personalizzato ControlTemplate omette i pulsanti TextBlock o altre funzioni, è accettabile che il controllo perda alcune delle sue funzionalità, ma è necessario assicurarsi che il controllo non provochi 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à.
Imposta le proprietà FrameworkElement che hai definito nel passaggio 2 nel metodo OnApplyTemplate. Questa è la prima volta che FrameworkElement nel ControlTemplate è disponibile per il controllo. Usa il
x:Name
del FrameworkElement per ottenerlo dal ControlTemplate.Verificare che FrameworkElement non sia
null
prima di 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 controllo NumericUpDown
in ControlTemplate, il RepeatButton che aumenta Value
ha il suo attributo x:Name
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 mostrato OnApplyTemplate per il controllo NumericUpDown
. 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 una buona pratica ignorare gli elementi che hanno il x:Name
specificato 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, assicuri che il controllo continui a essere eseguito quando il ControlTemplate manca un FrameworkElement.
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 gli oggetti VisualState a ControlTemplate, li si colloca in un VisualStateGroup e si assegna VisualStateGroup alla proprietà associata VisualStateManager.VisualStateGroups affinché VisualStateManager possa accedervi.
Nell'esempio seguente viene ripetuto l'esempio precedente che mostra gli VisualState oggetti che corrispondono agli stati Positive
e Negative
del controllo. Il Storyboard nel Negative
VisualState rende il Foreground del TextBlock rosso. Quando il NumericUpDown
controllo è nello Negative
stato, inizia il Negative
storyboard. Il Storyboard nello stato Negative
si arresta quando il controllo torna nello stato Positive
. Il Positive
VisualState non deve contenere un Storyboard perché, quando il Storyboard per il Negative
si arresta, il Foreground ritorna al 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 per NumericUpDown
potrebbe decidere di non indicare che Value
è negativo modificando il Foreground. In tal caso, né il codice né il ControlTemplate fa riferimento all'oggetto TextBlock per 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 il VisualState a cui sta andando il controllo ha un Storyboard, inizia lo storyboard. Quindi, se l'oggetto VisualState da cui proviene il controllo ha un Storyboard, 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 controllo NumericUpDown
definisce gli stati Focused
e anche UnFocused
, che tengono traccia della proprietà IsFocused. 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 ausiliario del controllo UpdateStates
. Quando Value
è maggiore o uguale a 0, il Control è nello stato di Positive
. 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 da quale stato uscire quando si chiama GoToState. Il controllo è sempre in uno stato per ciascun VisualStateGroup definito nel relativo ControlTemplate e lascia uno stato solo quando entra in un altro stato dallo stesso VisualStateGroup. Ad esempio, il ControlTemplate del controllo NumericUpDown
definisce gli oggetti Positive
e Negative
VisualState in uno VisualStateGroup, e gli oggetti Focused
e Unfocused
VisualState in un altro. È possibile visualizzare Focused
e Unfocused
VisualState definiti 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
stato.
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, UpdateStates
viene chiamato all'interno di OnApplyTemplate per assicurarsi che il controllo si trovi 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 ControlTemplate è applicato al controllo NumericUpDown
, il controllo non si trova nello stato Negative
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
cambia ma rimane positivo o negativo 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 NumericUpDown
chiama 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 aiuta a gestire gli stati del controllo. Usando il VisualStateManager, si assicuri che il controllo passi correttamente da uno stato all'altro. Se si seguono le raccomandazioni descritte in questa sezione per lavorare con il VisualStateManager, il codice 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 ha tre elementi:
Elementi visivi usati dalla logica del controllo.
Gli stati del controllo e il gruppo a cui appartiene ogni stato.
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 :
Un RepeatButton chiamato
UpButton
.Un RepeatButton chiamato
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 TemplatePartAttribute e TemplateVisualStateAttribute 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 l'elemento di controllo 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>
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
Vedere anche
.NET Desktop feedback