Compartilhar via


Visão geral da criação de controles

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

Alternativas a escrever um novo controle

Historicamente, se você quisesse obter uma experiência personalizada de um controle existente, você era limitado a alterar as propriedades padrão do controle, como a cor da tela de fundo, a largura da borda e o tamanho da fonte. Se você quisesse estender a aparência ou o comportamento de um controle além desses parâmetros predefinidos, você precisaria criar um novo controle, geralmente herdando um controle existente e substituindo o método responsável por desenhar o controle. Embora isso ainda seja uma opção, o WPF permite que você personalize os 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 ter de criar um novo controle.

  • Conteúdo sofisticado. Muitos dos controles padrão do WPF dão suporte a conteúdo avançado. Por exemplo, a propriedade content de a Button é do tipo Object, então, 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 a e StackPanel atribuir o StackPanel à Content propriedade. 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 e Button 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 da aparência e comportamento de um controle desejado sem escrever um novo controle. Por exemplo, suponha que você queira que todos os seus TextBlock controles tenham uma fonte Arial vermelha com um tamanho de fonte de 14. Você pode criar um estilo como um recurso e definir as propriedades adequadas de acordo com isso. Então, tudo TextBlock o que você adicionar ao seu aplicativo terá a mesma aparência.

  • Modelos de dados. A DataTemplate permite que você personalize como os dados são exibidos em um controle. Por exemplo, a pode ser usado para especificar como os DataTemplate dados são exibidos em um ListBox. Para obter um exemplo disso, consulte Visão geral de modelagem de dados. Além de personalizar a aparência dos dados, a DataTemplate pode incluir elementos de interface do usuário, o que oferece muita flexibilidade em interfaces de usuário 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 a ControlTemplate para definir a estrutura e a aparência do controle, o 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ê deseja um controle que se pareça com um alerta. Esse controle tem uma interface do usuário e uma e funcionalidade simples. O controle é composto de três círculos, sendo que apenas um deles pode ser aceso por vez. Depois de alguma reflexão, você pode perceber que a RadioButton oferece a funcionalidade de apenas um ser selecionado por vez, mas a aparência padrão do não RadioButton se parece em nada 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 opção para fazer seu semáforo.

    Observação

    Embora a RadioButton possa usar um DataTemplate, a 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 o RadioButton está selecionado. No exemplo do semáforo, o botão de opção precisa ser apenas um círculo que pode "acender". Como o requisito de aparência para o semáforo é muito diferente da aparência padrão do RadioButton, é necessário redefinir o ControlTemplate. Em geral, a DataTemplate é usado para definir o conteúdo (ou dados) de um controle e a ControlTemplate é usado para definir como um controle é estruturado.

  • Gatilhos. A Trigger permite que você altere 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 em cada um ListBox estejam em negrito e vermelho quando forem selecionados. Seu primeiro instinto pode ser criar uma classe que herda ListBox e substitua o OnSelectionChanged método para alterar a aparência do item selecionado, mas uma abordagem melhor é adicionar um gatilho a um estilo de um ListBoxItem que altera a aparência do item selecionado. Um gatilho permite que você altere os valores da propriedade ou execute ações com base no valor de uma propriedade. An EventTrigger permite que você execute ações quando ocorre um evento.

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

Em geral, se o seu controle espelha a funcionalidade de um controle existente mas você deseja que seu controle tenha uma aparência diferente, você deve primeiro considerar se você pode ou não usar qualquer um dos métodos abordados nesta seção para alterar a aparência do controle existente.

Modelos para criação de controles

O modelo de conteúdo sofisticado, 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 dos quais fornece um conjunto diferente de recursos e nível de flexibilidade. As classes base para os três modelos são UserControl, Control, e FrameworkElement.

Derivar de UserControl

A maneira mais simples de criar um controle no WPF é derivar de UserControl. Ao criar um controle que herda do , você adiciona componentes existentes ao UserControl, nomeia os componentes e faz referência a manipuladores de UserControleventos 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 desenvolvimento de aplicativos no WPF.

Se criado corretamente, um UserControl pode aproveitar os benefícios de conteúdo, estilos e gatilhos avançados. No entanto, se o seu controle herdar de UserControl, as pessoas 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 de UserControl) para criar um controle personalizado que dê suporte a modelos.

Benefícios de derivar de UserControl

Considere derivar de UserControl se todos os itens a seguir se aplicarem:

  • Você deseja criar o controle da mesma forma como cria um aplicativo.

  • O controle consiste somente de componentes existentes.

  • Não é necessário dar suporte a personalização complexa.

Derivar de Control

Derivado da Control classe está o modelo usado pela maioria dos controles WPF existentes. Ao criar um controle que herda da Control classe, você define sua aparência usando modelos. Fazendo isso, você pode separar a lógica operacional da representação visual. Você também pode garantir o desacoplamento da interface do usuário e da lógica usando comandos e associações em vez de eventos e evitando referenciar elementos sempre ControlTemplate que possível. Se a interface do usuário e a lógica do controle estiverem desacopladas corretamente, um usuário do controle poderá redefinir o controle ControlTemplate para personalizar sua aparência. Embora a criação de um costume Control não seja tão simples quanto a criação de um UserControl, um personalizado Control oferece mais flexibilidade.

Benefícios de derivar de Control

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

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

  • Você quiser que o controle dê suporte a diferentes temas.

Derivar de FrameworkElement

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

Existem dois métodos padrão para a construção FrameworkElementde componentes baseados em: renderização direta e composição de elementos personalizados. A renderização direta envolve a substituição do método e o OnRender fornecimento FrameworkElement de DrawingContext operações que definem explicitamente os visuais do componente. Este é o método usado por Image e Border. A composição de elementos personalizados envolve o uso de objetos de tipo Visual para compor a aparência do componente. Para obter um exemplo, consulte Usando objetos DrawingVisual. Track é um exemplo de um controle no WPF que usa a composição de elementos personalizados. Também é possível misturar renderização direta e composição personalizada de elementos no mesmo controle.

Benefícios de derivar de FrameworkElement

Considere derivar de FrameworkElement se qualquer um dos seguintes se aplicar:

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

  • Você deseja definir a aparência de 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 controles

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

Usar propriedades de dependência

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

  • Defina a propriedade em um estilo.

  • Associe a propriedade a uma fonte de dados.

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

  • Anime a propriedade.

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

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

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

    • O nome da propriedade.

    • O tipo da propriedade.

    • O tipo que é proprietário da propriedade.

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

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

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

  • Defina um método para o PropertyChangedCallback, chamado OnValueChanged. OnValueChanged cria um RoutedPropertyChangedEventArgs<T> objeto e se prepara para gerar o ValueChanged evento roteado. Eventos roteados são abordados 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:

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

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

  • Os eventos roteados podem ser usados em um EventTrigger, o que é útil para animar propriedades usando XAML. Para obter mais informações, consulte Visão geral de 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 EventManager.RegisterRoutedEvent método. 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 gera 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>, construído com um Decimal tipo.

    • O tipo proprietário do evento é NumericUpDown.

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

  • Criar um método virtual protegido denominado OnValueChanged que aciona o evento ValueChanged.

/// <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 Criar um evento roteado personalizado.

Usar associação

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

O exemplo a seguir atualiza o TextBlock do controle, atribuindo um nome a ele e referenciando a caixa de NumericUpDown texto por 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 a associação para realizar a mesma coisa.

<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 mais informações sobre 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 o desenvolvimento para o Designer do WPF, consulte Projetar XAML no Visual Studio.

Propriedades de Dependência

Certifique-se de implementar o CLR get e set os 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 nos controles personalizados usando as diretrizes a seguir:

  • Tenha um publicstaticreadonlyDependencyProperty do formulário PropertyNameProperty que estava criando usando o RegisterAttached método. O nome da propriedade para o qual é 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 método SetPropertyName também aceita um argumento cujo tipo corresponde ao tipo de dados registrado para a propriedade. O método GetPropertyName deve retornar um valor do mesmo tipo. Se o método SetPropertyName estiver ausente, a propriedade será somente leitura.

  • SetPropertyName e GetPropertyName devem ser roteados diretamente para os GetValue métodos and SetValue no objeto de dependência de destino, respectivamente. Designers podem acessar a propriedade anexada chamando-a por meio dos métodos wrapper ou fazendo uma chamada direta para o objeto de dependência de destino.

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

Definir e usar recursos compartilhados

Você pode incluir seu controle no mesmo assembly que seu aplicativo ou então você pode empacotar seu controle em um assembly separado, que pode ser usado em vários aplicativos. Geralmente, as informações discutidas neste tópico são aplicáveis independentemente do método que você usar. No entanto, há uma diferença importante a observar. Quando você coloca um controle no mesmo assembly que um aplicativo, você fica 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 procura em três níveis na seguinte ordem:

  1. O nível de elemento.

    O sistema começa com o elemento que referencia o recurso e pesquisa por recursos do pai lógico e assim por diante até o elemento raiz ser alcançado.

  2. O nível de aplicativo.

    Recursos definidos pelo Application objeto.

  3. O nível de tema.

    Dicionários no nível de tema são armazenados em uma subpasta denominada Themes. Os arquivos na pasta Themes correspondem aos 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 por um recurso no nível de temas, ele primeiro procura por esse recurso no arquivo específico do tema e, em seguida, procura por ele em generic.xaml.

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

Definir recursos no nível de 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 da maneira que quiser, sendo que tais arquivos podem estar na mesma pasta que os controles. Recursos no nível de elemento também podem usar cadeias de caracteres simples como chaves. O exemplo a seguir cria um arquivo de LinearGradientBrush 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 ter definido seu dicionário, você precisa mesclá-lo 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 ResourceDictionary.

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

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 o SharedDictionaryManager.SharedDictionary é uma propriedade estática, o ResourceDictionary é criado apenas uma vez. Como o dicionário de recursos foi mesclado antes de InitializeComponent ser chamado, os recursos estão disponíveis para o controle em seu arquivo XAML.

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

Definir recursos no nível de tema

O WPF permite que você crie recursos para diferentes temas do Windows. Como um 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 a Button no tema Windows Classic (o tema padrão do Windows 2000) difere de a Button no tema Windows Luna (o tema padrão do Windows XP) porque o Button usa um diferente ControlTemplate para cada tema.

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 chamada Themes, que é uma subpasta da pasta que contém o controle. A tabela a seguir lista os arquivos de dicionário de recursos e o tema que está associado a cada arquivo:

Nome do arquivo do dicionário de recursos Tema do Windows
Classic.xaml Aparência do Tema Clássico do Windows 9x/2000 no Windows XP
Luna.NormalColor.xaml Tema azul padrão no Windows XP
Luna.Homestead.xaml Tema verde-oliva no Windows XP
Luna.Metallic.xaml Tema prateado 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 está definido para um tema específico, o controle verifica Classic.xaml em busca do recurso. Se o recurso não está definido no arquivo que corresponde ao tema atual nem em Classic.xaml, o controle usa o recurso genérico, que está em um arquivo de dicionário de recursos chamado generic.xaml. O arquivo generic.xaml 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 de nível de tema.

O controle personalizado C# ou Visual Basic NumericUpDown com exemplo de suporte a tema e 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.

Ao colocar a em qualquer um ControlTemplate dos arquivos de dicionário de recursos específicos do tema, você deve criar um construtor estático para o OverrideMetadata(Type, PropertyMetadata)controle e chamar o DefaultStyleKey método no , 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
Definir e referenciar chaves para recursos de tema

Quando você definir um recurso no nível de elemento, você poderá atribuir uma cadeia de caracteres como sua chave e acessar o recurso pela cadeia de caracteres. Ao definir um recurso no nível do tema, você deve usar a 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 o ComponentResourceKey como 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 host precisa ter certeza de que o assembly contém recursos específicos do controle. Você pode fazer isso adicionando o ThemeInfoAttribute ao assembly que contém o controle. O ThemeInfoAttribute 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 GenericDictionaryLocation propriedades and ThemeDictionaryLocation como SourceAssembly, para especificar que os recursos genéricos e específicos do tema estão no mesmo assembly que o controle.

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

Confira também