Compartilhar via


Visão geral da autoria de controle

A extensibilidade do modelo de controle WPF (Windows Presentation Foundation) reduz consideravelmente a necessidade de criar um novo controle. No entanto, em determinados casos, talvez você ainda precise criar um controle personalizado. Este tópico discute os recursos que minimizam sua necessidade de criar um controle personalizado e os diferentes modelos de criação de controle no WPF (Windows Presentation Foundation). Este tópico também demonstra como criar um novo controle.

Alternativas para escrever um novo controle

Historicamente, se você quisesse obter uma experiência personalizada de um controle existente, estava limitado a alterar as propriedades padrão do controle, como cor da tela de fundo, largura da borda e tamanho da fonte. Se você quisesse estender a aparência ou o comportamento de um controle além desses parâmetros predefinidos, precisaria criar um novo controle, geralmente herdando de um controle existente e substituindo o método responsável por desenhar o controle. Embora isso ainda seja uma opção, o WPF permite personalizar controles existentes usando seu modelo de conteúdo avançado, estilos, modelos e gatilhos. A lista a seguir fornece exemplos de como esses recursos podem ser usados para criar experiências personalizadas e consistentes sem precisar criar um novo controle.

  • Conteúdo avançado. Muitos dos controles padrão do WPF dão suporte a conteúdo avançado. Por exemplo, a propriedade de conteúdo de um Button é do tipo Object, portanto, teoricamente, qualquer coisa pode ser exibida em um Button. Para que um botão exiba uma imagem e um texto, você pode adicionar uma imagem e um TextBlock a um StackPanel e atribuir o StackPanel à propriedade Content. Como os controles podem exibir elementos visuais do WPF e dados arbitrários, há menos necessidade de criar um novo controle ou modificar um controle existente para dar suporte a uma visualização complexa. Para obter mais informações sobre o modelo de conteúdo para Button e outros modelos de conteúdo no WPF, consulte Modelo de Conteúdo do WPF.

  • Estilos. A Style é uma coleção de valores que representam propriedades para um controle. Usando estilos, você pode criar uma representação reutilizável de uma aparência e comportamento de controle desejados sem escrever um novo controle. Por exemplo, suponha que você queira que todos os controles TextBlock tenham uma fonte Arial vermelha com um tamanho de fonte de 14. Você pode criar um estilo como um recurso e definir as propriedades apropriadas adequadamente. Então, cada TextBlock que você adicionar ao seu aplicativo terá a mesma aparência.

  • Modelos de dados. Um DataTemplate permite personalizar como os dados são exibidos em um controle. Por exemplo, um DataTemplate pode ser usado para especificar como os dados são exibidos em um ListBox. Para obter um exemplo disso, consulte Visão geral da Modelagem de Dados. Além de personalizar a aparência dos dados, é DataTemplate possível incluir elementos de interface do usuário, o que proporciona muita flexibilidade na criação de interfaces personalizadas. Por exemplo, usando um DataTemplate, você pode criar um ComboBox no qual cada item contém uma caixa de seleção.

  • Modelos de controle. Muitos controles no WPF usam um ControlTemplate para definir a estrutura e a aparência do controle, que separa a aparência de um controle da funcionalidade do controle. Você pode alterar drasticamente a aparência de um controle redefinindo seu ControlTemplate. Por exemplo, suponha que você queira um controle que se pareça com um semáforo. Esse controle tem uma interface e funcionalidade de usuário simples. O controle é de três círculos, apenas um deles pode ser iluminado de cada vez. Depois de alguma reflexão, você pode perceber que um RadioButton oferece a funcionalidade de apenas um elemento ser selecionado de cada vez, mas a aparência padrão do RadioButton não tem nada a ver com as luzes de um semáforo. Como o RadioButton usa um modelo de controle para definir sua aparência, é fácil redefinir o ControlTemplate para atender aos requisitos do controle e usar botões de rádio para fazer o semáforo.

    Observação

    Embora um RadioButton possa usar um DataTemplate, um DataTemplate não é suficiente neste exemplo. O DataTemplate define a aparência do conteúdo de um controle. No caso de um RadioButton, o conteúdo é o que aparece à direita do círculo que indica se ele RadioButton está selecionado. No exemplo do semáforo, o botão de opção precisa ser apenas um círculo que possa "acender". Como o requisito de aparência para o semáforo é tão diferente da aparência padrão do RadioButton, é necessário redefinir o ControlTemplate. Em geral, um DataTemplate é usado para definir o conteúdo (ou dados) de um controle e um ControlTemplate é usado para definir como um controle é estruturado.

  • Gatilhos. Um Trigger permite alterar dinamicamente a aparência e o comportamento de um controle sem criar um novo controle. Por exemplo, suponha que você tenha vários ListBox controles em seu aplicativo e queira que os itens dentro de cada ListBox fiquem em negrito e vermelho quando forem selecionados. Seu primeiro instinto pode ser criar uma classe que herda de ListBox e substituir o método OnSelectionChanged para alterar a aparência do item selecionado, mas uma abordagem melhor é adicionar um gatilho a um estilo de ListBoxItem que altera a aparência do item selecionado. Um gatilho permite alterar valores de propriedade ou executar ações com base no valor de uma propriedade. Um EventTrigger permite que você execute ações quando ocorrer um evento.

Para obter mais informações sobre estilos, modelos e gatilhos, consulte Estilo e Modelagem.

Em geral, se o seu controle reflete a funcionalidade de um controle existente, mas você quer que ele aparente ser diferente, primeiro considere se é possível usar algum dos métodos discutidos nesta seção para alterar a aparência do controle existente.

Modelos para criação de controle

O modelo de conteúdo avançado, estilos, modelos e gatilhos minimizam a necessidade de criar um novo controle. No entanto, se você precisar criar um novo controle, é importante entender os diferentes modelos de criação de controle no WPF. O WPF fornece três modelos gerais para criar um controle, cada um deles fornece um conjunto diferente de recursos e nível de flexibilidade. As classes base para os três modelos são UserControl, Controle FrameworkElement.

Derivando de UserControl

A maneira mais simples de criar um controle no WPF é derivar de UserControl. Quando você cria um controle do qual herda UserControl, você adiciona componentes existentes ao nome dos componentes e faz referência a UserControlmanipuladores de eventos em XAML. Em seguida, você pode referenciar os elementos nomeados e definir os manipuladores de eventos no código. Esse modelo de desenvolvimento é muito semelhante ao modelo usado para o desenvolvimento de aplicativos no WPF.

Se criado corretamente, é UserControl possível aproveitar os benefícios de conteúdo avançado, estilos e gatilhos. No entanto, se o controle herdar de UserControlpessoas que usam seu controle não poderão usar um DataTemplate ou ControlTemplate personalizar sua aparência. É necessário derivar da Control classe ou de uma de suas classes derivadas (diferente UserControl) para criar um controle personalizado que dê suporte a modelos.

Benefícios de Derivar de UserControl

Considere derivar de UserControl se todas as seguintes condições forem aplicáveis.

  • Você deseja criar seu controle de forma semelhante à forma como você cria um aplicativo.

  • Seu controle consiste apenas em componentes existentes.

  • Você não precisa dar suporte à personalização complexa.

Derivando do Controle

A derivação da classe Control é o modelo adotado pela maioria dos controles WPF existentes. Ao criar um controle que herda da classe Control, você define sua aparência usando templates. Ao fazer isso, você separa a lógica operacional da representação visual. Você também pode garantir o desacoplamento da UI e da lógica usando comandos e ligações em vez de eventos e evitando referenciar elementos no ControlTemplate sempre que possível. Se a interface do usuário e a lógica do seu controle estiverem desacopladas corretamente, um usuário do seu controle poderá modificar o controle ControlTemplate para personalizar sua aparência. Embora a criação de um personalizado Control não seja tão simples quanto criar um UserControl, um personalizado Control fornece a maior flexibilidade.

Benefícios de derivar do controle

Considere derivar de Control em vez de usar a classe UserControl se qualquer uma das seguintes situações:

  • Você deseja que a aparência do controle seja personalizável por meio do ControlTemplate.

  • Você deseja que seu controle dê suporte a temas diferentes.

Derivando do FrameworkElement

Controles que derivam de UserControl ou Control dependem de compor elementos existentes. Para muitos cenários, essa é uma solução aceitável, porque qualquer objeto que herda FrameworkElement pode estar em um ControlTemplate. No entanto, há momentos em que a aparência de um controle requer mais do que a funcionalidade da composição de elemento simples. Para esses cenários, basear um componente FrameworkElement é a escolha certa.

Há dois métodos padrão para criar FrameworkElementcomponentes baseados: renderização direta e composição de elemento personalizado. A renderização direta envolve sobrescrever o método OnRender de FrameworkElement e fornecer operações DrawingContext que definem explicitamente os visuais do componente. Esse é o método usado por Image e Border. A composição de elemento personalizado envolve o uso de objetos do tipo Visual para compor a aparência do componente. Para obter um exemplo, consulte Como usar objetos DrawingVisual. Track é um exemplo de um controle no WPF que usa composição personalizada de elementos. Também é possível misturar renderização direta e composição de elemento personalizado no mesmo controle.

Benefícios de Derivar do FrameworkElement

Considere derivar de FrameworkElement se qualquer uma das seguintes condições se aplicar:

  • Você deseja ter controle preciso sobre a aparência do seu controle além do que é fornecido pela composição de elementos simples.

  • Você deseja definir a aparência do seu controle definindo sua própria lógica de renderização.

  • Você deseja compor elementos existentes de maneiras novas que vão além do que é possível com UserControl e Control.

Noções básicas de criação de controle

Conforme discutido anteriormente, um dos recursos mais poderosos do WPF é a capacidade de ir além de definir propriedades básicas de um controle para alterar sua aparência e comportamento, mas ainda não precisa criar um controle personalizado. Os recursos de estilo, associação de dados e gatilho são possíveis pelo sistema de propriedades do WPF e pelo sistema de eventos WPF. As seções a seguir descrevem algumas práticas que você deve seguir, independentemente do modelo que você usa para criar o controle personalizado, para que os usuários do seu controle personalizado possam usar esses recursos da mesma forma que fariam para um controle incluído no WPF.

Utilize "Propriedades de Dependência"

Quando uma propriedade é uma propriedade de dependência, é possível fazer o seguinte:

  • Defina a propriedade em um estilo específico.

  • Associe a propriedade a uma fonte de dados.

  • Use um recurso dinâmico como o valor da propriedade.

  • Faça a animação da propriedade.

Se você quiser que uma propriedade do seu controle dê suporte a qualquer uma dessas funcionalidades, você deverá implementá-la como uma propriedade de dependência. O exemplo a seguir define uma propriedade de dependência nomeada Value fazendo o seguinte:

  • Defina um DependencyProperty identificador nomeado ValueProperty como um publicstaticreadonly campo.

  • Registre o nome da propriedade com o sistema de propriedades, chamando DependencyProperty.Register, para especificar o seguinte:

    • O nome da propriedade.

    • O tipo da propriedade.

    • O tipo que possui a propriedade.

    • Os metadados da propriedade. Os metadados contêm o valor padrão da propriedade, a CoerceValueCallback e a PropertyChangedCallback.

  • Defina uma propriedade de wrapper CLR chamada Value, que tem o mesmo nome usado para registrar a propriedade de dependência, implementando os acessadores get e set da propriedade. Observe que os acessadores get e set só chamam GetValue e SetValue respectivamente. É recomendável que os acessores de propriedades de dependência não contenham lógica adicional, pois os clientes e o WPF podem ignorar os acessores e chamar GetValue e SetValue diretamente. Por exemplo, quando uma propriedade é associada a uma fonte de dados, o acessador da set propriedade não é chamado. Em vez de adicionar lógica adicional aos acessadores get e set, use os delegates ValidateValueCallback, CoerceValueCallback e PropertyChangedCallback para responder ou verificar o valor quando ele for alterado. Para obter mais informações sobre esses callbacks, consulte Retornos de chamada e validação de propriedade de dependência.

  • Definir um método para o CoerceValueCallback nomeado CoerceValue. CoerceValue garante que Value seja maior ou igual a MinValue e menor que ou igual a MaxValue.

  • Definir um método para o PropertyChangedCallback, chamado OnValueChanged. OnValueChanged cria um objeto RoutedPropertyChangedEventArgs<T> e se prepara para desencadear o evento roteado ValueChanged. Os eventos roteados são discutidos na próxima seção.

/// <summary>
/// Identifies the Value dependency property.
/// </summary>
public static readonly DependencyProperty ValueProperty =
    DependencyProperty.Register(
        "Value", typeof(decimal), typeof(NumericUpDown),
        new FrameworkPropertyMetadata(MinValue, new PropertyChangedCallback(OnValueChanged),
                                      new CoerceValueCallback(CoerceValue)));

/// <summary>
/// Gets or sets the value assigned to the control.
/// </summary>
public decimal Value
{
    get { return (decimal)GetValue(ValueProperty); }
    set { SetValue(ValueProperty, value); }
}

private static object CoerceValue(DependencyObject element, object value)
{
    decimal newValue = (decimal)value;
    NumericUpDown control = (NumericUpDown)element;

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue));

    return newValue;
}

private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
    NumericUpDown control = (NumericUpDown)obj;			

    RoutedPropertyChangedEventArgs<decimal> e = new RoutedPropertyChangedEventArgs<decimal>(
        (decimal)args.OldValue, (decimal)args.NewValue, ValueChangedEvent);
    control.OnValueChanged(e);
}
''' <summary>
''' Identifies the Value dependency property.
''' </summary>
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(Decimal), GetType(NumericUpDown), New FrameworkPropertyMetadata(MinValue, New PropertyChangedCallback(AddressOf OnValueChanged), New CoerceValueCallback(AddressOf CoerceValue)))

''' <summary>
''' Gets or sets the value assigned to the control.
''' </summary>
Public Property Value() As Decimal
    Get
        Return CDec(GetValue(ValueProperty))
    End Get
    Set(ByVal value As Decimal)
        SetValue(ValueProperty, value)
    End Set
End Property

Private Shared Overloads Function CoerceValue(ByVal element As DependencyObject, ByVal value As Object) As Object
    Dim newValue As Decimal = CDec(value)
    Dim control As NumericUpDown = CType(element, NumericUpDown)

    newValue = Math.Max(MinValue, Math.Min(MaxValue, newValue))

    Return newValue
End Function

Private Shared Sub OnValueChanged(ByVal obj As DependencyObject, ByVal args As DependencyPropertyChangedEventArgs)
    Dim control As NumericUpDown = CType(obj, NumericUpDown)

    Dim e As New RoutedPropertyChangedEventArgs(Of Decimal)(CDec(args.OldValue), CDec(args.NewValue), ValueChangedEvent)
    control.OnValueChanged(e)
End Sub

Para obter mais informações, consulte Propriedades de Dependência Personalizadas.

Usar eventos roteados

Assim como as propriedades de dependência estendem a noção de propriedades CLR com funcionalidade adicional, os eventos roteados estendem a noção de eventos CLR padrão. Quando você cria um novo controle WPF, também é uma boa prática implementar seu evento como um evento roteado porque um evento roteado dá suporte ao seguinte comportamento:

  • Os eventos podem ser tratados em um pai de vários controles. Se um evento for um evento de propagação, um único pai na árvore de elementos poderá subscrever ao evento. Em seguida, os autores do aplicativo podem usar um manipulador para responder ao evento de vários controles. Por exemplo, se o seu controle fizer parte de cada item em um ListBox (porque ele está incluído em um DataTemplate), o desenvolvedor de aplicativos poderá definir o manipulador de eventos para o evento do seu controle no ListBox. Sempre que o evento ocorre em qualquer um dos controles, o manipulador de eventos é chamado.

  • Eventos roteados podem ser usados em um EventSetter, o que permite que os desenvolvedores de aplicativos especifiquem o manipulador de um evento em um estilo.

  • Eventos roteados podem ser usados em um EventTrigger, que é útil para animar propriedades usando XAML. Para obter mais informações, consulte Visão geral da animação.

O exemplo a seguir define um evento roteado fazendo o seguinte:

  • Defina um RoutedEvent identificador nomeado ValueChangedEvent como um publicstaticreadonly campo.

  • Registre o evento roteado chamando o método EventManager.RegisterRoutedEvent. O exemplo especifica as seguintes informações quando chama RegisterRoutedEvent:

    • O nome do evento é ValueChanged.

    • A estratégia de roteamento é Bubble, o que significa que um manipulador de eventos na origem (o objeto que aciona o evento) é chamado primeiro e, em seguida, os manipuladores de eventos nos elementos pai da origem são chamados sucessivamente, começando com o manipulador de eventos no elemento pai mais próximo.

    • O tipo do manipulador de eventos é RoutedPropertyChangedEventHandler<T>, e é construído com um tipo Decimal.

    • O tipo do evento proprietário é NumericUpDown.

  • Declare um evento público nomeado ValueChanged e inclua declarações do acessador de eventos. O exemplo chama AddHandler na declaração de acessador add e RemoveHandler na declaração de acessador remove para usar os serviços de eventos do WPF.

  • Crie um método virtual protegido chamado OnValueChanged que gera o ValueChanged evento.

/// <summary>
/// Identifies the ValueChanged routed event.
/// </summary>
public static readonly RoutedEvent ValueChangedEvent = EventManager.RegisterRoutedEvent(
    "ValueChanged", RoutingStrategy.Bubble,
    typeof(RoutedPropertyChangedEventHandler<decimal>), typeof(NumericUpDown));

/// <summary>
/// Occurs when the Value property changes.
/// </summary>
public event RoutedPropertyChangedEventHandler<decimal> ValueChanged
{
    add { AddHandler(ValueChangedEvent, value); }
    remove { RemoveHandler(ValueChangedEvent, value); }
}

/// <summary>
/// Raises the ValueChanged event.
/// </summary>
/// <param name="args">Arguments associated with the ValueChanged event.</param>
protected virtual void OnValueChanged(RoutedPropertyChangedEventArgs<decimal> args)
{
    RaiseEvent(args);
}
''' <summary>
''' Identifies the ValueChanged routed event.
''' </summary>
Public Shared ReadOnly ValueChangedEvent As RoutedEvent = EventManager.RegisterRoutedEvent("ValueChanged", RoutingStrategy.Bubble, GetType(RoutedPropertyChangedEventHandler(Of Decimal)), GetType(NumericUpDown))

''' <summary>
''' Occurs when the Value property changes.
''' </summary>
Public Custom Event ValueChanged As RoutedPropertyChangedEventHandler(Of Decimal)
    AddHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.AddHandler(ValueChangedEvent, value)
    End AddHandler
    RemoveHandler(ByVal value As RoutedPropertyChangedEventHandler(Of Decimal))
        MyBase.RemoveHandler(ValueChangedEvent, value)
    End RemoveHandler
    RaiseEvent(ByVal sender As System.Object, ByVal e As RoutedPropertyChangedEventArgs(Of Decimal))
    End RaiseEvent
End Event

''' <summary>
''' Raises the ValueChanged event.
''' </summary>
''' <param name="args">Arguments associated with the ValueChanged event.</param>
Protected Overridable Sub OnValueChanged(ByVal args As RoutedPropertyChangedEventArgs(Of Decimal))
    MyBase.RaiseEvent(args)
End Sub

Para obter mais informações, consulte Visão geral de eventos roteados e Crie um evento roteado personalizado.

Usar Vinculação

Para desacoplar a interface do usuário do controle de sua lógica, considere usar a associação de dados. Isso é particularmente importante se você definir a aparência do seu controle usando um ControlTemplate. Ao usar a associação de dados, você poderá eliminar a necessidade de fazer referência a partes específicas da interface do usuário do código. É uma boa ideia evitar referenciar elementos que estão no ControlTemplate porque quando o código faz referência a elementos que estão no ControlTemplate e ControlTemplate é alterado, o elemento referenciado precisa ser incluído no novo ControlTemplate.

O exemplo a TextBlock seguir atualiza o NumericUpDown controle, atribuindo um nome a ele e fazendo referência à caixa de texto pelo nome no código.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">
  <TextBlock Name="valueText" Width="60" TextAlignment="Right" Padding="5"/>
</Border>
private void UpdateTextBlock()
{
    valueText.Text = Value.ToString();
}
Private Sub UpdateTextBlock()
    valueText.Text = Value.ToString()
End Sub

O exemplo a seguir usa vinculação para alcançar o mesmo resultado.

<Border BorderThickness="1" BorderBrush="Gray" Margin="2" 
        Grid.RowSpan="2" VerticalAlignment="Center" HorizontalAlignment="Stretch">

    <!--Bind the TextBlock to the Value property-->
    <TextBlock 
        Width="60" TextAlignment="Right" Padding="5"
        Text="{Binding RelativeSource={RelativeSource FindAncestor, 
                       AncestorType={x:Type local:NumericUpDown}}, 
                       Path=Value}"/>

</Border>

Para obter mais informações sobre a associação de dados, consulte Visão geral da associação de dados.

Design para Designers

Para receber suporte para controles WPF personalizados no Designer do WPF para Visual Studio (por exemplo, edição de propriedade com a janela Propriedades), siga estas diretrizes. Para obter mais informações sobre como desenvolver para o Designer do WPF, consulte Design XAML no Visual Studio.

Propriedades de dependência

Certifique-se de implementar CLR get e set acessadores, conforme descrito anteriormente, em "Usar propriedades de dependência". Os designers podem usar o wrapper para detectar a presença de uma propriedade de dependência, mas eles, como o WPF e os clientes do controle, não são obrigados a chamar os acessadores ao obter ou definir a propriedade.

Propriedades anexadas

Você deve implementar propriedades anexadas em controles personalizados usando as seguintes diretrizes:

  • Tenha um publicstaticreadonlyDependencyProperty do formulário PropertyNameProperty que foi criado usando o método RegisterAttached. O nome da propriedade que é passado deve corresponder a RegisterAttachedPropertyName.

  • Implemente um par de publicstatic métodos CLR chamados SetPropertyName e GetPropertyName. Ambos os métodos devem aceitar uma classe derivada de DependencyProperty como seu primeiro argumento. O Set método PropertyName também aceita um argumento cujo tipo corresponde ao tipo de dados registrado para a propriedade. O Get método PropertyName deve retornar um valor do mesmo tipo. Se o Set método PropertyName estiver ausente, a propriedade será marcada como somente leitura.

  • Set PropertyName e GetPropertyName devem rotear diretamente para os métodos e GetValue para SetValue o objeto de dependência de destino, respectivamente. Os designers podem acessar a propriedade anexada chamando por meio do wrapper de método ou fazendo uma chamada direta para o objeto de dependência de destino.

Para obter mais informações sobre propriedades anexadas, consulte Visão geral de propriedades anexadas.

Definir e usar recursos compartilhados

Você pode incluir seu controle no mesmo assembly que seu aplicativo ou empacotar seu controle em um assembly separado que pode ser usado em vários aplicativos. Na maioria das vezes, as informações discutidas neste tópico se aplicam independentemente do método usado. No entanto, há uma diferença que vale a pena observar. Quando você coloca um controle no mesmo assembly que um aplicativo, você é livre para adicionar recursos globais ao arquivo App.xaml. Mas um assembly que contém apenas controles não tem um Application objeto associado a ele, portanto, um arquivo App.xaml não está disponível.

Quando um aplicativo procura um recurso, ele examina três níveis na seguinte ordem:

  1. O nível do elemento.

    O sistema começa com o elemento que faz referência ao recurso e, em seguida, pesquisa recursos do pai lógico e assim por diante até que o elemento raiz seja atingido.

  2. O nível do aplicativo.

    Recursos definidos pelo Application objeto.

  3. O nível do tema.

    Dicionários no nível do tema são armazenados em uma subpasta chamada Temas. Os arquivos na pasta Temas correspondem a temas. Por exemplo, você pode ter Aero.NormalColor.xaml, Luna.NormalColor.xaml, Royale.NormalColor.xaml e assim por diante. Você também pode ter um arquivo chamado generic.xaml. Quando o sistema procura um recurso no nível de temas, ele primeiro procura-o no arquivo específico do tema e, em seguida, procura-o em generic.xaml.

Quando o controle está em um assembly separado do aplicativo, você deverá colocar seus recursos globais no nível de elemento ou de tema. Ambos os métodos têm suas vantagens.

Definindo recursos no nível do elemento

Você pode definir recursos compartilhados no nível do elemento criando um dicionário de recursos personalizado e mesclando-o com o dicionário de recursos do controle. Ao usar esse método, você pode nomear o arquivo de recurso como desejar e ele pode estar na mesma pasta que seus controles. Os recursos no nível do elemento também podem usar cadeias de caracteres simples como chaves. O exemplo a seguir cria um LinearGradientBrush arquivo de recurso chamado Dictionary1.xaml.

<ResourceDictionary 
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
  <LinearGradientBrush 
    x:Key="myBrush"  
    StartPoint="0,0" EndPoint="1,1">
    <GradientStop Color="Red" Offset="0.25" />
    <GradientStop Color="Blue" Offset="0.75" />
  </LinearGradientBrush>
  
</ResourceDictionary>

Depois de definir seu dicionário, você precisará mesclar com o dicionário de recursos do controle. Você pode fazer isso usando XAML ou código.

O exemplo a seguir mescla um dicionário de recursos usando XAML.

<UserControl.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Dictionary1.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</UserControl.Resources>

A desvantagem dessa abordagem é que um ResourceDictionary objeto é criado sempre que você faz referência a ele. Por exemplo, se você tiver 10 controles personalizados em sua biblioteca e mesclar os dicionários de recursos compartilhados para cada controle usando XAML, criará 10 objetos idênticos ResourceDictionary . Você pode evitar isso criando uma classe estática que mescla os recursos no código e retorna o resultado ResourceDictionary.

O exemplo a seguir cria uma classe que retorna um objeto ResourceDictionary compartilhado.

internal static class SharedDictionaryManager
{
    internal static ResourceDictionary SharedDictionary
    {
        get
        {
            if (_sharedDictionary == null)
            {
                System.Uri resourceLocater =
                    new System.Uri("/ElementResourcesCustomControlLibrary;component/Dictionary1.xaml",
                                    System.UriKind.Relative);

                _sharedDictionary =
                    (ResourceDictionary)Application.LoadComponent(resourceLocater);
            }

            return _sharedDictionary;
        }
    }

    private static ResourceDictionary _sharedDictionary;
}

O exemplo a seguir mescla o recurso compartilhado com os recursos de um controle personalizado no construtor do controle antes de chamar InitializeComponent. Como a SharedDictionaryManager.SharedDictionary propriedade é estática, ela ResourceDictionary é criada apenas uma vez. Como o dicionário de recursos foi mesclado antes InitializeComponent de ser chamado, os recursos estão disponíveis para o controle em seu arquivo XAML.

public NumericUpDown()
{
    this.Resources.MergedDictionaries.Add(SharedDictionaryManager.SharedDictionary);
    InitializeComponent();
}

Definindo recursos no nível do tema

O WPF permite que você crie recursos para diferentes temas do Windows. Como autor de controle, você pode definir um recurso para um tema específico para alterar a aparência do controle, dependendo de qual tema está em uso. Por exemplo, a aparência de um Button no tema clássico do Windows (o tema padrão para Windows 2000) difere de um Button no tema Windows Luna (o tema padrão para Windows XP) porque o Button usa um ControlTemplate diferente para cada tema.

Os recursos específicos de um tema são mantidos em um dicionário de recursos com um nome de arquivo específico. Esses arquivos devem estar em uma pasta nomeada Themes que seja uma subpasta da pasta que contém o controle. A tabela a seguir lista os arquivos de dicionário de recursos e o tema associado a cada arquivo:

Nome do arquivo do dicionário de recursos Tema do Windows
Classic.xaml Aparência clássica do Windows 9x/2000 no Windows XP
Luna.NormalColor.xaml Tema azul padrão no Windows XP
Luna.Homestead.xaml Tema de azeitona no Windows XP
Luna.Metallic.xaml Tema silver no Windows XP
Royale.NormalColor.xaml Tema padrão no Windows XP Media Center Edition
Aero.NormalColor.xaml Tema padrão no Windows Vista

Você não precisa definir um recurso para cada tema. Se um recurso não for definido para um tema específico, o controle verificará Classic.xaml o recurso. Se o recurso não estiver definido no arquivo que corresponde ao tema atual ou no Classic.xaml, o controle usará o recurso genérico, que está em um arquivo de dicionário de recursos chamado generic.xaml. O generic.xaml arquivo está localizado na mesma pasta que os arquivos de dicionário de recursos específicos do tema. Embora generic.xaml não corresponda a um tema específico do Windows, ele ainda é um dicionário no nível do tema.

O controle personalizado C# ou Visual Basic NumericUpDown com tema e exemplo de suporte à automação da interface do usuário contém dois dicionários de recursos para o NumericUpDown controle: um está em generic.xaml e o outro está em Luna.NormalColor.xaml.

Quando você coloca um ControlTemplate em qualquer um dos arquivos de dicionário de recursos específicos do tema, você deve criar um construtor estático para o controle e chamar o método OverrideMetadata(Type, PropertyMetadata) em DefaultStyleKey, conforme mostrado no exemplo a seguir.

static NumericUpDown()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(NumericUpDown),
               new FrameworkPropertyMetadata(typeof(NumericUpDown)));
}
Shared Sub New()
    DefaultStyleKeyProperty.OverrideMetadata(GetType(NumericUpDown), New FrameworkPropertyMetadata(GetType(NumericUpDown)))
End Sub
Definindo e referenciando chaves para recursos de tema

Ao definir um recurso no nível do elemento, você pode atribuir uma cadeia de caracteres como sua chave e acessar o recurso por meio da cadeia de caracteres. Ao definir um recurso no nível do tema, você deve usar um ComponentResourceKey como chave. O exemplo a seguir define um recurso em generic.xaml.

<LinearGradientBrush 
     x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:Painter}, 
                                  ResourceId=MyEllipseBrush}"  
                                  StartPoint="0,0" EndPoint="1,0">
    <GradientStop Color="Blue" Offset="0" />
    <GradientStop Color="Red" Offset="0.5" />
    <GradientStop Color="Green" Offset="1"/>
</LinearGradientBrush>

O exemplo a seguir faz referência ao recurso especificando como ComponentResourceKey a chave.

<RepeatButton 
    Grid.Column="1" Grid.Row="0"
    Background="{StaticResource {ComponentResourceKey 
                        TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                        ResourceId=ButtonBrush}}">
    Up
</RepeatButton>
<RepeatButton 
    Grid.Column="1" Grid.Row="1"
    Background="{StaticResource {ComponentResourceKey 
                    TypeInTargetAssembly={x:Type local:NumericUpDown}, 
                    ResourceId=ButtonBrush}}">
    Down
 </RepeatButton>
Especificando o local dos recursos de tema

Para localizar os recursos de um controle, o aplicativo de hospedagem precisa saber que o assembly contém recursos específicos do controle. Você pode fazer isso adicionando o ThemeInfoAttribute ao assembly que contém o controle. A ThemeInfoAttribute propriedade tem uma GenericDictionaryLocation propriedade que especifica o local dos recursos genéricos e uma ThemeDictionaryLocation propriedade que especifica o local dos recursos específicos do tema.

O exemplo a seguir define as propriedades GenericDictionaryLocation e ThemeDictionaryLocation como SourceAssembly, para especificar que os recursos genéricos e os específicos de tema estão no mesmo assembly que o controle.

[assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly,
           ResourceDictionaryLocation.SourceAssembly)]
<Assembly: ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)>

Consulte também