Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
O WPF (Windows Presentation Foundation) oferece a capacidade de criar um controle cuja aparência pode ser personalizada. Por exemplo, você pode alterar a aparência de um CheckBox além do que as propriedades de configuração farão criando um novo ControlTemplate. A ilustração a seguir mostra um CheckBox que usa um padrão ControlTemplate e um CheckBox que usa um personalizado ControlTemplate.
Uma Caixa de Seleção que usa o modelo de controle padrão
Uma Caixa de Seleção que usa um modelo de controle personalizado
Se você seguir o modelo de partes e estados ao criar um controle, a aparência do controle será personalizável. Ferramentas de designer como o Blend para Visual Studio dão suporte ao modelo de partes e estados, portanto, quando você seguir esse modelo, seu controle será personalizável nesses tipos de aplicativos. Este tópico discute o modelo de partes e estados e como segui-lo quando você cria seu próprio controle. Este tópico usa um exemplo de controle NumericUpDownpersonalizado para ilustrar a filosofia desse modelo. O NumericUpDown controle exibe um valor numérico, que um usuário pode aumentar ou diminuir clicando nos botões do controle. A ilustração a seguir mostra o NumericUpDown controle discutido neste tópico.
Um controle NumericUpDown personalizado
Este tópico contém as seguintes seções:
Pré-requisitos
Este tópico pressupõe que você sabe como criar um novo ControlTemplate para um controle existente, está familiarizado com o que são os elementos em um contrato de controle e entende os conceitos discutidos em Criar um modelo para um controle.
Observação
Para criar um controle que possa ter sua aparência personalizada, você deve criar um controle que herda da classe Control ou de uma de suas subclasses, exceto UserControl. Um controle que herda UserControl é um controle que pode ser criado rapidamente, mas ele não usa um ControlTemplate e você não pode personalizar sua aparência.
Modelo de partes e estados
O modelo de partes e estados especifica como definir a estrutura visual e o comportamento visual de um controle. Para seguir o modelo de partes e estados, faça o seguinte:
Defina a estrutura visual e o comportamento visual no ControlTemplate de um controle.
Siga determinadas práticas recomendadas quando a lógica do controle interagir com partes do modelo de controle.
Forneça um contrato de controle para especificar o que deve ser incluído no ControlTemplate.
Quando você define a estrutura visual e o comportamento visual no ControlTemplate de um controle, os autores do aplicativo podem alterar a estrutura visual e o comportamento visual do seu controle criando um novo ControlTemplate em vez de escrever código. Você deve fornecer um contrato de controle que informe aos autores do aplicativo quais FrameworkElement objetos e estados devem ser definidos no ControlTemplate. Você deve seguir algumas práticas recomendadas ao interagir com os componentes no ControlTemplate de modo que seu controle manipule corretamente um ControlTemplate incompleto. Se você seguir esses três princípios, os autores de aplicativos poderão criar um ControlTemplate para seu controle tão facilmente quanto para os controles que vêm com o WPF. A seção a seguir explica cada uma dessas recomendações em detalhes.
Definindo a estrutura visual e o comportamento visual de um controle em um ControlTemplate
Ao criar seu controle personalizado usando o modelo de partes e estados, você define a estrutura e o comportamento visual do controle no ControlTemplate, em vez de em sua lógica. A estrutura visual de um controle é a composição de FrameworkElement objetos que compõem o controle. O comportamento visual é a maneira como o controle aparece quando está em um determinado estado. Para obter mais informações sobre como criar um ControlTemplate que especifica a estrutura visual e o comportamento visual de um controle, consulte Criar um modelo para um controle.
No exemplo do NumericUpDown controle, a estrutura visual inclui dois RepeatButton controles e um TextBlock. Se você adicionar esses controles no código do NumericUpDown controle, em seu construtor, por exemplo, as posições desses controles serão inalteráveis. Em vez de definir a estrutura visual e o comportamento visual do controle em seu código, você deve defini-la no ControlTemplate. Em seguida, um desenvolvedor de aplicativos deve personalizar a posição dos botões e TextBlock especificar qual comportamento ocorre quando Value é negativo porque ControlTemplate pode ser substituído.
O exemplo a seguir mostra a estrutura visual do NumericUpDown controle, que inclui um RepeatButton para aumentar Value, um RepeatButton para diminuir Valuee um TextBlock para exibir 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>
Um comportamento visual do NumericUpDown controle é que o valor está em uma fonte vermelha se for negativo. Se você alterar o Foreground do TextBlock no código quando o Value for negativo, o NumericUpDown sempre mostrará um valor negativo vermelho. Você especifica o comportamento visual do controle no ControlTemplate ao adicionar objetos VisualState ao ControlTemplate. O exemplo a seguir mostra os objetos VisualState para os estados Positive e Negative.
Positive e Negative são mutuamente exclusivos (o controle está sempre exatamente em um dos dois), portanto, o exemplo coloca os VisualState objetos em um único VisualStateGroup. Quando o controle entra no estado Negative, o ForegroundTextBlock fica vermelho. Quando o controle está no Positive estado, o Foreground retorna ao seu valor original. A definição VisualState de objetos em um ControlTemplate é discutida em Criar um modelo para um controle.
Observação
Certifique-se de definir a VisualStateManager.VisualStateGroups propriedade anexada na raiz FrameworkElement do 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>
Usando partes do ControlTemplate no código
Um ControlTemplate autor pode omitir FrameworkElement ou VisualState objetos, intencionalmente ou por engano, mas a lógica do controle pode precisar dessas partes para funcionar corretamente. O modelo de partes e estados especifica que seu controle deve ser resiliente a um ControlTemplate que possa ficar ausente de objetos FrameworkElement ou VisualState. Seu controle não deve gerar uma exceção ou relatar um erro se um FrameworkElement, VisualStateou VisualStateGroup estiver ausente do ControlTemplate. Esta seção descreve as práticas recomendadas para interagir com FrameworkElement objetos e gerenciar estados.
Prever objetos FrameworkElement ausentes
Quando você define FrameworkElement objetos no ControlTemplate, a lógica do controle pode precisar interagir com alguns deles. Por exemplo, o NumericUpDown controle assina o Click evento dos botões para aumentar ou diminuir Value e define a propriedade Text de TextBlock para Value. Se um personalizado ControlTemplate omite os TextBlock botões ou, é aceitável que o controle perca parte de sua funcionalidade, mas você deve ter certeza de que seu controle não causa um erro. Por exemplo, se um ControlTemplate não contiver os botões para alterar Value, o NumericUpDown perderá essa funcionalidade, mas um aplicativo que usa o ControlTemplate continuará em execução.
As práticas a seguir garantirão que seu controle responda corretamente a objetos ausentes FrameworkElement :
Defina o atributo
x:Namepara cada FrameworkElement que você precisa referenciar no código.Defina propriedades privadas para cada FrameworkElement com as quais você precisa interagir.
Inscreva-se e cancele a inscrição de qualquer evento que seu controle manipule no acessor de definição da propriedade FrameworkElement.
Defina as propriedades FrameworkElement que você definiu na etapa 2 no método OnApplyTemplate. Este é o momento mais cedo em que o FrameworkElement no ControlTemplate está disponível para o controle. Use o
x:Namedo FrameworkElement para obtê-lo do ControlTemplate.Verifique se FrameworkElement não é
nullantes de acessar seus membros. Caso sejanull, não reporte um erro.
Os exemplos a seguir mostram como o NumericUpDown controle interage com FrameworkElement objetos de acordo com as recomendações na lista anterior.
No exemplo que define a estrutura visual do NumericUpDown controle no ControlTemplate, o RepeatButton que aumenta Value tem seu atributo x:Name definido como UpButton. O exemplo a seguir declara uma propriedade chamada UpButtonElement que representa a RepeatButton, que é declarada no ControlTemplate. O acessador set primeiro cancela a assinatura do evento Click se UpDownElement não for null, depois define a propriedade e então se inscreve no evento Click. Também há uma propriedade definida, mas não mostrada aqui, para a outra RepeatButtonchamada 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
O exemplo a seguir mostra o OnApplyTemplate para o controle NumericUpDown. O exemplo usa o GetTemplateChild método para obter os FrameworkElement objetos do ControlTemplate. Observe que o exemplo protege contra casos onde GetTemplateChild encontra um FrameworkElement com o nome especificado que não é do tipo esperado. Também é uma prática recomendada ignorar os elementos que têm o especificado x:Name , mas que são do tipo errado.
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
Seguindo as práticas mostradas nos exemplos anteriores, você garante que seu controle continuará a ser executado quando estiver ControlTemplate faltando um FrameworkElement.
Usar o VisualStateManager para gerenciar estados
O VisualStateManager acompanha os estados de um dispositivo e executa a lógica necessária para fazer a transição entre esses estados. Quando você adiciona VisualState objetos ao ControlTemplate, adicione-os a um VisualStateGroup e adicione-os VisualStateGroupVisualStateManager.VisualStateGroups à propriedade anexada para que ele VisualStateManager tenha acesso a eles.
O exemplo a seguir repete o exemplo anterior que mostra os VisualState objetos que correspondem aos estados Positive e Negative do controle. O Storyboard no NegativeVisualState faz com que o Foreground do TextBlock fique vermelho. Quando o controle NumericUpDown está no estado Negative, o storyboard Negative começa. Em seguida, o estado Storyboard no estado Negative é interrompido quando o controle retorna ao estado Positive. O PositiveVisualState não precisa conter um Storyboard porque quando o Storyboard para o Negative, o Foreground retorna à sua cor original.
<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>
Nota que um nome é atribuído ao TextBlock, mas o TextBlock não está no contrato de controle para NumericUpDown porque a lógica do controle nunca faz referência ao TextBlock. Os elementos referenciados nos ControlTemplate nomes têm, mas não precisam fazer parte do contrato de controle, pois um novo ControlTemplate para o controle pode não precisar referenciar esse elemento. Por exemplo, alguém que cria um novo ControlTemplate para NumericUpDown pode decidir não indicar que Value isso é negativo alterando o Foreground. Nesse caso, nem o código nem as referências ao ControlTemplate o mencionam pelo nome.
A lógica do controle é responsável por alterar o estado do controle. O exemplo a seguir mostra que o controle NumericUpDown chama o método GoToState para entrar no estado Positive quando Value for 0 ou maior, e no estado Negative quando Value for menor que 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
O GoToState método executa a lógica necessária para iniciar e parar os storyboards adequadamente. Quando um controle chama GoToState para alterar seu estado, o VisualStateManager faz o seguinte:
Se o VisualState ao qual o controle vai ter um Storyboard, o storyboard começará. Em seguida, se o VisualState de onde o controle está vindo tiver um Storyboard, o storyboard termina.
Se o controle já estiver no estado especificado, GoToState não executará nenhuma ação e retornará
true.Se o estado especificado não existir no ControlTemplate de
control, GoToState não executará nenhuma ação e retornaráfalse.
Práticas recomendadas para trabalhar com o VisualStateManager
É recomendável que você faça o seguinte para manter os estados do controle:
Use propriedades para acompanhar seu estado.
Crie um método auxiliar para fazer a transição entre estados.
O NumericUpDown controle usa sua Value propriedade para rastrear se ele está no estado Positive ou Negative. O NumericUpDown controle também define os estados Focused e UnFocused, que rastreiam a propriedade IsFocused. Se você usar estados que não correspondem naturalmente a uma propriedade do controle, poderá definir uma propriedade privada para monitorar o estado.
Um único método que atualiza todos os estados centraliza as chamadas para o VisualStateManager e mantém seu código gerenciável. O exemplo a seguir mostra o NumericUpDown método auxiliar do controle. UpdateStates Quando Value for maior ou igual a 0, ele Control estará no Positive estado. Quando Value é menor que 0, o controle está no Negative estado. Quando IsFocused está true, o controle está no Focused estado; caso contrário, ele está no Unfocused estado. O controle pode chamar UpdateStates sempre que precisar alterar seu estado, independentemente de qual estado seja alterado.
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 você passar um nome de estado para GoToState quando o controle já estiver nesse estado, GoToState não fará nada, portanto, não será necessário verificar o estado atual do controle. Por exemplo, se Value mudar de um número negativo para outro número negativo, o storyboard do Negative estado não é interrompido e o usuário não vê uma mudança no controle.
O VisualStateManager usa objetos VisualStateGroup para determinar de qual estado sair quando você chama GoToState. O controle está sempre em um estado para cada VisualStateGroup que é definido em seu ControlTemplate e só deixa um estado quando ele entra em outro estado dentro do mesmo VisualStateGroup. Por exemplo, o ControlTemplate do NumericUpDown controle define os objetos Positive e NegativeVisualState em um VisualStateGroup e os objetos Focused e UnfocusedVisualState em outro. (Você pode ver os Focused e UnfocusedVisualState definidos na seção Exemplo Completo neste tópico, quando o controle vai do estado Positive para o estado Negative, ou vice-versa, o controle permanece no estado Focused ou Unfocused.
Há três locais típicos em que o estado de um controle pode mudar:
Quando a opção ControlTemplate é aplicada ao Control.
Quando uma propriedade é alterada.
Quando ocorre um evento.
Os exemplos a seguir demonstram a atualização do estado do NumericUpDown controle nesses casos.
Você deve atualizar o estado do controle no OnApplyTemplate método para que o controle apareça no estado correto quando o ControlTemplate for aplicado. O exemplo a seguir chama UpdateStates em OnApplyTemplate para garantir que o controle esteja nos estados apropriados. Por exemplo, suponha que você crie um NumericUpDown controle e, em seguida, defina-o Foreground como verde e Value como -5. Se você não chamar UpdateStates quando ControlTemplate for aplicado ao controle NumericUpDown, o controle não estará no estado Negative e o valor ficará verde em vez de vermelho. Você deve chamar UpdateStates para colocar o estado de controle em Negative.
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
Geralmente, você precisa atualizar os estados de um controle quando uma propriedade é alterada. O exemplo a seguir mostra o método ValueChangedCallback todo. Como ValueChangedCallback é chamado quando Value muda, o método chama UpdateStates no caso de Value ter mudado de positivo para negativo ou vice-versa. É aceitável chamar UpdateStates quando Value muda, mas continua positivo ou negativo, porque nesse caso o controle não mudará de estado.
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
Talvez você também precise atualizar estados quando ocorrer um evento. O exemplo a seguir mostra que NumericUpDown invoca UpdateStates em Control para lidar com o 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
O VisualStateManager ajuda você a gerenciar os estados do comando. Usando o controle VisualStateManager, você assegura que o controle transicione corretamente entre estados. Se você seguir as recomendações descritas nesta seção para trabalhar com o VisualStateManager, o código do controle permanecerá legível e mantido.
Fornecendo o contrato de controle
Você fornece um contrato de controle para que ControlTemplate os autores saibam o que colocar no modelo. Um contrato de controle tem três elementos:
Os elementos visuais que a lógica do controle usa.
Os estados do controle e do grupo ao qual cada estado pertence.
As propriedades públicas que afetam visualmente o controle.
Alguém que cria um novo ControlTemplate precisa saber quais FrameworkElement objetos a lógica do controle usa, que tipo cada objeto é e qual é o nome dele. Um ControlTemplate autor também precisa saber o nome de cada estado possível em que o controle pode estar e em qual VisualStateGroup estado está.
Retornando ao exemplo de NumericUpDown, o controle espera que o ControlTemplate tenha os seguintes objetos FrameworkElement.
Um RepeatButton chamado
UpButton.Um RepeatButton chamado
DownButton.
O controle pode estar nos seguintes estados:
No
ValueStatesVisualStateGroupPositiveNegative
No
FocusStatesVisualStateGroupFocusedUnfocused
Para especificar quais FrameworkElement objetos o controle espera, use o TemplatePartAttribute, que especifica o nome e o tipo dos elementos esperados. Para especificar os estados possíveis de um controle, use o TemplateVisualStateAttribute, que especifica o nome do estado e ao qual VisualStateGroup ele pertence. Coloque o TemplatePartAttribute e o TemplateVisualStateAttribute na definição de classe do controle.
Qualquer propriedade pública que afete a aparência do seu controle também faz parte do contrato de controle.
O exemplo a seguir especifica o objeto FrameworkElement e os estados para o controle NumericUpDown.
[TemplatePart(Name = "UpButtonElement", Type = typeof(RepeatButton))]
[TemplatePart(Name = "DownButtonElement", Type = typeof(RepeatButton))]
[TemplateVisualState(Name = "Positive", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Negative", GroupName = "ValueStates")]
[TemplateVisualState(Name = "Focused", GroupName = "FocusedStates")]
[TemplateVisualState(Name = "Unfocused", GroupName = "FocusedStates")]
public class NumericUpDown : Control
{
public static readonly DependencyProperty BackgroundProperty;
public static readonly DependencyProperty BorderBrushProperty;
public static readonly DependencyProperty BorderThicknessProperty;
public static readonly DependencyProperty FontFamilyProperty;
public static readonly DependencyProperty FontSizeProperty;
public static readonly DependencyProperty FontStretchProperty;
public static readonly DependencyProperty FontStyleProperty;
public static readonly DependencyProperty FontWeightProperty;
public static readonly DependencyProperty ForegroundProperty;
public static readonly DependencyProperty HorizontalContentAlignmentProperty;
public static readonly DependencyProperty PaddingProperty;
public static readonly DependencyProperty TextAlignmentProperty;
public static readonly DependencyProperty TextDecorationsProperty;
public static readonly DependencyProperty TextWrappingProperty;
public static readonly DependencyProperty VerticalContentAlignmentProperty;
public Brush Background { get; set; }
public Brush BorderBrush { get; set; }
public Thickness BorderThickness { get; set; }
public FontFamily FontFamily { get; set; }
public double FontSize { get; set; }
public FontStretch FontStretch { get; set; }
public FontStyle FontStyle { get; set; }
public FontWeight FontWeight { get; set; }
public Brush Foreground { get; set; }
public HorizontalAlignment HorizontalContentAlignment { get; set; }
public Thickness Padding { get; set; }
public TextAlignment TextAlignment { get; set; }
public TextDecorationCollection TextDecorations { get; set; }
public TextWrapping TextWrapping { get; set; }
public VerticalAlignment VerticalContentAlignment { get; set; }
}
<TemplatePart(Name:="UpButtonElement", Type:=GetType(RepeatButton))>
<TemplatePart(Name:="DownButtonElement", Type:=GetType(RepeatButton))>
<TemplateVisualState(Name:="Positive", GroupName:="ValueStates")>
<TemplateVisualState(Name:="Negative", GroupName:="ValueStates")>
<TemplateVisualState(Name:="Focused", GroupName:="FocusedStates")>
<TemplateVisualState(Name:="Unfocused", GroupName:="FocusedStates")>
Public Class NumericUpDown
Inherits Control
Public Shared ReadOnly TextAlignmentProperty As DependencyProperty
Public Shared ReadOnly TextDecorationsProperty As DependencyProperty
Public Shared ReadOnly TextWrappingProperty As DependencyProperty
Public Property TextAlignment() As TextAlignment
Public Property TextDecorations() As TextDecorationCollection
Public Property TextWrapping() As TextWrapping
End Class
Exemplo completo
O exemplo a seguir é todo o ControlTemplate para o controle 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>
O exemplo a seguir mostra a lógica do 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
Consulte também
.NET Desktop feedback