Compartilhar via


Criar um controle que tem uma aparência personalizável

Windows Presentation Foundation (WPF)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, criando um novo fará definindo propriedades de ControlTemplate. A ilustração a seguir mostra um CheckBox que usa um padrão de ControlTemplate e um CheckBox que usa um personalizado ControlTemplate.

Uma caixa de seleção que usa o modelo de controle padrão

Marca de verificação com o modelo de controle padrão.

Uma caixa de seleção que usa um modelo de controle personalizado

Marca de verificação com um modelo de controle personalizado.

Se você seguir o modelo de partes e estados quando você cria um controle, a aparência do controle será personalizável. Ferramentas de Designer, como o Microsoft Expression Blend suportam o modelo de partes e estados, portanto, quando você seguir esse modelo de seu controle ser personalizável nesses tipos de aplicativos. Este tópico aborda o modelo de partes e estados e como seguem quando você cria seu próprio controle. Este tópico usa um exemplo de um controle personalizado, NumericUpDown, para ilustrar a filosofia deste modelo. O NumericUpDown controle exibe um valor numérico, o qual um usuário pode aumentar ou diminuir, clicando em todos os botões. do controle A ilustração a seguir mostra a NumericUpDown controle que será discutido neste tópico.

Um controle personalizado do NumericUpDown

Controle personalizado NumericUpDown.

This topic contains the following sections:

  • Prerequisites

  • Partes e o modelo de estados

  • Definindo a estrutura Visual e o comportamento Visual de um controle em um ControlTemplate.

  • Usando partes do ControlTemplate no código

  • Fornecendo o contrato de controle

  • Complete Example

Prerequisites

Este tópico pressupõe que você saiba como criar uma nova ControlTemplate para um controle existente, estão familiarizados com quais são os elementos em um contrato de controle e entender os conceitos discutidos Personalizando a aparência de um controle existente, criando um ControlTemplate..

Observação

Para criar um controle que pode ter sua aparência personalizada, você deve criar um controle que herda do Control classe ou uma de suas subclasses diferente de UserControl.Um controle que herda de UserControl é um controle que pode ser criado rapidamente, mas não usa um ControlTemplate e não é possível personalizar sua aparência.

Partes e o modelo de 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, você deve fazer o seguinte:

  • Definir a estrutura visual e o comportamento visual no ControlTemplate de controle.

  • Quando a lógica do controle interage com partes do modelo de controle, siga algumas melhores práticas.

  • Oferecem um contrato de controle para especificar o que deve ser incluído o 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 uma nova ControlTemplate em vez de escrever código. Você deve fornecer um contrato de controle que informa ao aplicativo autores que FrameworkElement objetos e estados devem ser definidos na ControlTemplate. Você deve seguir algumas práticas recomendadas ao interagir com as partes na ControlTemplate para que o controle manipula adequadamente incompleto ControlTemplate. Se você seguir essas três princípios, os autores de aplicativos poderão criar um ControlTemplate para o seu controle apenas tão facilmente quanto eles pode para os controles que vêm com o WPF. A seção a seguir explica cada uma destas recomendações em detalhes.

Definindo a estrutura Visual e o comportamento Visual de um controle em um ControlTemplate.

Quando você cria seu controle personalizado usando o modelo de partes e estados, você define a estrutura visual do controle e o comportamento visual no seu ControlTemplate em vez de na lógica. A estrutura visual de um controle é a composição de FrameworkElement os objetos que compõem o controle. O comportamento de visual é a maneira como o controle aparece quando ele estiver 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 Personalizando a aparência de um controle existente, criando um ControlTemplate..

No exemplo do NumericUpDown o controle, a estrutura visual inclui dois RepeatButton controles e uma TextBlock. Se você adicionar esses controles no código da NumericUpDown o controle--em seu construtor, por exemplo-- as posições desses controles seria inalterável. Em vez de definir a estrutura visual e o comportamento do visual do controle em seu código, você deve defini-lo a ControlTemplate. Em seguida, um desenvolvedor de aplicativos para personalizar a posição dos botões e TextBlock e especifique o que ocorre quando Value é negativo porque a ControlTemplate podem ser substituídos.

O exemplo a seguir mostra a estrutura visual da NumericUpDown controle, que inclui um RepeatButton para aumentar a Value, um RepeatButton para diminuir o 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 da NumericUpDown controle é que o valor em uma fonte vermelha se for negativo. Se você alterar o Foreground da TextBlock no código quando o Value for negativo, o NumericUpDown sempre mostrará um valor negativo vermelho. Você especificar o comportamento visual do controle no ControlTemplate , adicionando VisualState objetos para o ControlTemplate. A exemplo a seguir mostra a VisualState objetos para o Positive e Negative Estados. Positivee Negative (o controle é sempre em exatamente um dos dois) são mutuamente exclusivo, portanto, o exemplo coloca o VisualState objetos em um único VisualStateGroup. Quando o controle entra na Negative estado, o Foreground da TextBlock transforma red. Quando o controle é a Positive estado, o Foreground retorna a ele valor original. Definindo VisualState objetos em um ControlTemplate será abordada posteriormente nas Personalizando a aparência de um controle existente, criando um ControlTemplate..

Observação

Certifique-se de definir o VisualStateManager.VisualStateGroups anexado a propriedade na raiz FrameworkElement da 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

A ControlTemplate autor pode omitir FrameworkElement ou VisualState objetos, intencionalmente ou por engano, mas a lógica do controle talvez precise essas partes funcione adequadamente. O modelo de partes e estados Especifica que o seu controle deve ser resiliente em relação a um ControlTemplate que está faltando FrameworkElement ou VisualState objetos. Seu controle deve não lançar uma exceção ou o relatório de erro se um FrameworkElement, VisualState, ou VisualStateGroup está faltando a partir de ControlTemplate. Esta seção descreve as práticas recomendadas para a interação com FrameworkElement objetos e gerenciamento de estados.

Prever a falta de objetos de FrameworkElement

Quando você define FrameworkElement objetos na ControlTemplate, a lógica do controle, talvez seja necessário interagir com alguns deles. Por exemplo, o NumericUpDown controle assina os botões Clickevento para aumentar ou diminuir Value e define o Text propriedade da TextBlock para Value. Se um personalizado ControlTemplate omite a TextBlock ou botões, é aceitável que o controle perde parte de sua funcionalidade, mas você deve certificar-se de que o controle não causa um erro. Por exemplo, se um ControlTemplate não contém os botões para alterar Value, o NumericUpDown perde essa funcionalidade, mas um aplicativo que usa o ControlTemplate continuará a executar.

As seguintes práticas garantirá que o seu controle responda corretamente à ausência de FrameworkElement objetos:

  1. Definir o x:Name atributo para cada FrameworkElement que você precisa referenciar em código.

  2. Definir propriedades particulares para cada FrameworkElement que você precisa interagir com.

  3. Se inscrever e cancelar a inscrição de todos os eventos que o controle manipula no FrameworkElement acessador de conjunto. da propriedade

  4. Definir o FrameworkElement a propriedades que você definiu na etapa 2 do OnApplyTemplate método. Esta é a mais antiga que a FrameworkElement na ControlTemplate está disponível para o controle. Uso o x:Name da FrameworkElement obtê-lo a partir de ControlTemplate.

  5. Verifique se a FrameworkElement não é null antes de acessar seus participantes. Se for null, que não reportam 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 da NumericUpDown controlar na ControlTemplate, o RepeatButton que aumenta a Value tem seu x:Name atributo definido como UpButton. O exemplo a seguir declara uma propriedade chamada UpButtonElement que representa o RepeatButton que é declarado na ControlTemplate. O set acessador primeiro cancela a inscrição para o botão Click evento se UpDownElement não é null, em seguida, define a propriedade e, em seguida, ele assina o Click de evento. Também é uma propriedade definida, mas não mostrados aqui, para os outros RepeatButton, chamado DownButtonElement.

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 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);
        }
    }
}

A exemplo a seguir mostra a OnApplyTemplate para o NumericUpDown de controle. O exemplo usa o GetTemplateChild método para obter o FrameworkElement objetos da ControlTemplate. Observe que o exemplo protege contra casos onde GetTemplateChild localiza um FrameworkElement com o nome especificado é que não é do tipo esperado. Também é uma prática recomendada para ignorar os elementos que possuem especificado x:Name , mas são do tipo errado.

Public Overloads Overrides Sub OnApplyTemplate()

    UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
    DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)

    UpdateStates(False)
End Sub
public override void OnApplyTemplate()
{
    UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
    DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
    //TextElement = GetTemplateChild("TextBlock") as TextBlock;

    UpdateStates(false);
}

Seguindo as práticas que são mostradas nos exemplos anteriores, garantir que seu controle continuará a ser executada quando o ControlTemplate está faltando uma FrameworkElement.

Use o VisualStateManager para gerenciar os estados

O VisualStateManager controla os estados de um controle e executa a lógica necessária para a transição entre estados. Quando você adiciona VisualState objetos para o ControlTemplate, adicioná-los a uma VisualStateGroup e adicionar o VisualStateGroup para o VisualStateManager.VisualStateGroups anexado a propriedade para que o VisualStateManager tem acesso a eles.

O exemplo a seguir se repete o exemplo anterior que mostra a VisualState objetos que corresponde à Positive e Negative Estados de controle. The Storyboard in the Negative VisualState turns the Foreground of the TextBlock red. Quando o NumericUpDown o controle está na Negative de estado no storyboard a Negative estado começa. Em seguida, a Storyboard na Negative estado pára quando o controle retorna para o Positive estado. O Positive VisualState não precisa conter um Storyboard porque quando o Storyboard para o Negative pára, 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>

Observe que o TextBlock recebe um nome, mas o TextBlock não estiver no contrato de controle para NumericUpDown porque a lógica do controle nunca faz referência a TextBlock. Elementos que são referenciados na ControlTemplate têm nomes, mas não precisa ser parte do contrato de controle, porque uma nova ControlTemplate para o controle talvez não precise fazer referência a esse elemento. Por exemplo, alguém que cria um novo ControlTemplate para NumericUpDown pode decidir não indicar que Value for negativo, alterando a Foreground. Nesse caso, nem o código nem o ControlTemplate referências a TextBlock pelo nome.

Lógica do controle é responsável pela alteração de estado do controle. O exemplo a seguir mostra que o NumericUpDown controlar chamadas a GoToState método para ir para o Positive estado quando Value é 0 ou maior e o Negative estado quando Value for menor que 0.

If Value >= 0 Then
    VisualStateManager.GoToState(Me, "Positive", useTransitions)
Else
    VisualStateManager.GoToState(Me, "Negative", useTransitions)
End If
if (Value >= 0)
{
    VisualStateManager.GoToState(this, "Positive", useTransitions);
}
else
{
    VisualStateManager.GoToState(this, "Negative", useTransitions);
}

O GoToState método executa a lógica necessária para iniciar e interromper os storyboards adequadamente. Quando um controle chama GoToState para alterar seu estado, o VisualStateManager faz o seguinte:

  • Se a VisualState que o controle será tem um Storyboard, o storyboard começa. Então, se a VisualState o controle é proveniente de tem um Storyboard, as extremidades do storyboard.

  • Se o controle já estiver no estado em que for especificado, GoToState não faz nada e retorna true.

  • Se o estado especificado não existe o ControlTemplate de control, GoToState não faz nada e retorna false.

Práticas recomendadas para trabalhar com o VisualStateManager.

É recomendável que você faça o seguinte para manter os estados do controle:

  • Use as propriedades para controlar seu estado.

  • Crie um método auxiliar para fazer a transição entre estados.

O NumericUpDown controle usa seu Value propriedade para controlar se ele está sendo o Positive ou Negative estado. O NumericUpDown o controle também define o Focused e UnFocused afirma, as faixas a IsFocused propriedade. Se você usar os estados que naturalmente não correspondem a uma propriedade do controle, você pode definir uma propriedade privada para controlar o estado.

Um único método que atualiza todos os estados centraliza chamadas para o VisualStateManager e mantém o seu código gerenciável. A exemplo a seguir mostra a NumericUpDown método do auxiliar do controle, UpdateStates. Quando Value é maior que ou igual a 0, o Control está sendo o Positive estado. Quando Value é menor que 0, o controle é a Negative estado. Quando IsFocused é true, o controle é a Focused estado; Caso contrário, trata-o Unfocused estado. O controle pode chamar UpdateStates sempre que ele precisa alterar seu estado, independentemente de quais alterações de estado.

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
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);
    }

}

Se você passar o nome de um estado para GoToState quando o controle está já nesse estado, GoToState não faz nada, portanto você não precisa procurar o estado atual. do controle Por exemplo, se Value muda de um número negativo para outro número negativo, o storyboard para o Negative estado não é interrompido e o usuário não verá uma alteração no controle.

O VisualStateManager usa VisualStateGroup objetos para determinar qual estado deseja sair quando você chamar GoToState. O controle é sempre em um estado para cada VisualStateGroup que está definido na sua ControlTemplate e deixa somente um estado quando ele vai para outro estado, da mesma VisualStateGroup. Por exemplo, o ControlTemplate da NumericUpDown controle define o Positive e Negative VisualState objetos em um VisualStateGroup e o Focused e Unfocused VisualState objetos em outro. (Você pode ver o Focused e Unfocused VisualState definido na Exemplo completo seção neste tópico, quando o controle vai da Positive estado para o Negative estado, ou vice-versa, o controle permanece em ambos o Focused ou Unfocused estado.

Há três locais típicos, onde o estado de um controle pode alterar:

  • Quando o ControlTemplate é aplicado a Control.

  • Quando uma propriedade é alterada.

  • Quando ocorre um evento.

Os exemplos a seguir demonstram a atualização do estado do NumericUpDown o controle desses casos.

Você deve atualizar o estado do controle no OnApplyTemplate método para que o controle aparece no correto estado quando o ControlTemplate é aplicado. O exemplo a seguir chama UpdateStates em OnApplyTemplate garantir que o controle está no estados apropriados. Por exemplo, suponha que você crie um NumericUpDown de controle e defina seu Foreground para verde e Value como -5. Se você não chamar UpdateStates quando o ControlTemplate é aplicado a NumericUpDown controle, o controle não está na Negative estado e o valor é verde, em vez de red. Você deve chamar UpdateStates para colocar o controle Negative estado.

Public Overloads Overrides Sub OnApplyTemplate()

    UpButtonElement = TryCast(GetTemplateChild("UpButton"), RepeatButton)
    DownButtonElement = TryCast(GetTemplateChild("DownButton"), RepeatButton)

    UpdateStates(False)
End Sub
public override void OnApplyTemplate()
{
    UpButtonElement = GetTemplateChild("UpButton") as RepeatButton;
    DownButtonElement = GetTemplateChild("DownButton") as RepeatButton;
    //TextElement = GetTemplateChild("TextBlock") as TextBlock;

    UpdateStates(false);
}

Geralmente, você precisa atualizar os estados de um controle quando uma propriedade é alterada. O exemplo a seguir mostra todo o ValueChangedCallback método. Porque ValueChangedCallback é chamado quando Value alterações, as chamadas de método UpdateStates no caso de Value alterado de positivo para negativo ou vice versa. É aceitável para chamar UpdateStates quando Value é alterado, mas permanece positivo ou negativo, nesse caso, o controle não será alterada estados.

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
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));
}

Talvez você precise atualizar os estados quando ocorre um evento. O exemplo a seguir mostra que o NumericUpDown chamadas UpdateStates sobre o Control para lidar com o GotFocus evento.

Protected Overloads Overrides Sub OnGotFocus(ByVal e As RoutedEventArgs)
    MyBase.OnGotFocus(e)
    UpdateStates(True)
End Sub
protected override void OnGotFocus(RoutedEventArgs e)
{
    base.OnGotFocus(e);
    UpdateStates(true);
}

O VisualStateManager o ajuda a gerenciar estados de. seu controle Usando o VisualStateManager, certifique-se que o seu controle corretamente as transições entre estados. Se você seguir as recomendações descritas nesta seção para trabalhar com o VisualStateManager, código do controle permanecerá legível e fácil manutenção.

Fornecendo o contrato de controle

Você fornecer um contrato de controle para que ControlTemplate autores saibam o que colocar no modelo. Um contrato de controle possui três elementos:

  • Os elementos visuais que usa a lógica do controle.

  • Os estados do controle e o grupo de cada estado pertence.

  • As propriedades públicas visualmente afetam o controle.

Alguém que cria um novo ControlTemplate precisa saber o que FrameworkElement objetos usa a lógica do controle, que tipo de cada objeto é e que seu nome is. A ControlTemplate autor também precisa saber o nome de cada estado possível, o controle pode ser e que VisualStateGroup o estado é pol.

Retornando para a NumericUpDown o controle de exemplo, espera a ControlTemplate para ter os seguintes FrameworkElement objetos:

O controle pode estar nos seguintes estados:

Para especificar o que FrameworkElement o controle de objetos de espera, se você usar o TemplatePartAttribute, que especifica o nome e o tipo de elementos esperados. Para especificar os estados possíveis de um controle, use o TemplateVisualStateAttribute, que especifica o nome do estado e que VisualStateGroup ele pertence. Colocar o TemplatePartAttribute e TemplateVisualStateAttribute na definição de classe de controle.

Qualquer propriedade pública que afeta a aparência de seu controle também é uma parte do contrato de controle.

O exemplo a seguir especifica o FrameworkElement objeto e estados para o NumericUpDown de controle.

<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 BackgroundProperty As DependencyProperty
    Public Shared ReadOnly BorderBrushProperty As DependencyProperty
    Public Shared ReadOnly BorderThicknessProperty As DependencyProperty
    Public Shared ReadOnly FontFamilyProperty As DependencyProperty
    Public Shared ReadOnly FontSizeProperty As DependencyProperty
    Public Shared ReadOnly FontStretchProperty As DependencyProperty
    Public Shared ReadOnly FontStyleProperty As DependencyProperty
    Public Shared ReadOnly FontWeightProperty As DependencyProperty
    Public Shared ReadOnly ForegroundProperty As DependencyProperty
    Public Shared ReadOnly HorizontalContentAlignmentProperty As DependencyProperty
    Public Shared ReadOnly PaddingProperty As DependencyProperty
    Public Shared ReadOnly TextAlignmentProperty As DependencyProperty
    Public Shared ReadOnly TextDecorationsProperty As DependencyProperty
    Public Shared ReadOnly TextWrappingProperty As DependencyProperty
    Public Shared ReadOnly VerticalContentAlignmentProperty As DependencyProperty


    Private _Background As Brush
    Public Property Background() As Brush
        Get
            Return _Background
        End Get
        Set(ByVal value As Brush)
            _Background = value
        End Set
    End Property

    Private _BorderBrush As Brush
    Public Property BorderBrush() As Brush
        Get
            Return _BorderBrush
        End Get
        Set(ByVal value As Brush)
            _BorderBrush = value
        End Set
    End Property

    Private _BorderThickness As Thickness
    Public Property BorderThickness() As Thickness
        Get
            Return _BorderThickness
        End Get
        Set(ByVal value As Thickness)
            _BorderThickness = value
        End Set
    End Property

    Private _FontFamily As FontFamily
    Public Property FontFamily() As FontFamily
        Get
            Return _FontFamily
        End Get
        Set(ByVal value As FontFamily)
            _FontFamily = value
        End Set
    End Property

    Private _FontSize As Double
    Public Property FontSize() As Double
        Get
            Return _FontSize
        End Get
        Set(ByVal value As Double)
            _FontSize = value
        End Set
    End Property

    Private _FontStretch As FontStretch
    Public Property FontStretch() As FontStretch
        Get
            Return _FontStretch
        End Get
        Set(ByVal value As FontStretch)
            _FontStretch = value
        End Set
    End Property

    Private _FontStyle As FontStyle
    Public Property FontStyle() As FontStyle
        Get
            Return _FontStyle
        End Get
        Set(ByVal value As FontStyle)
            _FontStyle = value
        End Set
    End Property

    Private _FontWeight As FontWeight
    Public Property FontWeight() As FontWeight
        Get
            Return _FontWeight
        End Get
        Set(ByVal value As FontWeight)
            _FontWeight = value
        End Set
    End Property

    Private _Foreground As Brush
    Public Property Foreground() As Brush
        Get
            Return _Foreground
        End Get
        Set(ByVal value As Brush)
            _Foreground = value
        End Set
    End Property

    Private _HorizontalContentAlignment As HorizontalAlignment
    Public Property HorizontalContentAlignment() As HorizontalAlignment
        Get
            Return _HorizontalContentAlignment
        End Get
        Set(ByVal value As HorizontalAlignment)
            _HorizontalContentAlignment = value
        End Set
    End Property

    Private _Padding As Thickness
    Public Property Padding() As Thickness
        Get
            Return _Padding
        End Get
        Set(ByVal value As Thickness)
            _Padding = value
        End Set
    End Property

    Private _TextAlignment As TextAlignment
    Public Property TextAlignment() As TextAlignment
        Get
            Return _TextAlignment
        End Get
        Set(ByVal value As TextAlignment)
            _TextAlignment = value
        End Set
    End Property

    Private _TextDecorations As TextDecorationCollection
    Public Property TextDecorations() As TextDecorationCollection
        Get
            Return _TextDecorations
        End Get
        Set(ByVal value As TextDecorationCollection)
            _TextDecorations = value
        End Set
    End Property

    Private _TextWrapping As TextWrapping
    Public Property TextWrapping() As TextWrapping
        Get
            Return _TextWrapping
        End Get
        Set(ByVal value As TextWrapping)
            _TextWrapping = value
        End Set
    End Property

    Private _VerticalContentAlignment As VerticalAlignment
    Public Property VerticalContentAlignment() As VerticalAlignment
        Get
            Return _VerticalContentAlignment
        End Get
        Set(ByVal value As VerticalAlignment)
            _VerticalContentAlignment = value
        End Set
    End Property
End Class
[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; }
}

Complete Example

O exemplo a seguir é de todo o ControlTemplate para o NumericUpDown de controle.

<!--This is the contents of the themes/generic.xaml file.-->
<ResourceDictionary
    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="https://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 para a NumericUpDown.

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
    Private _value As Integer

    Public Sub New(ByVal id As RoutedEvent,
                   ByVal num As Integer)

        _value = num
        RoutedEvent = id
    End Sub

    Public ReadOnly Property Value() As Integer
        Get
            Return _value
        End Get
    End Property
End Class
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; }
        }
    }
}

Consulte também

Conceitos

Personalizando a aparência de um controle existente, criando um ControlTemplate.

Outros recursos

Control Customization