Visão geral da criação de controle

A extensibilidade do modelo de controle do Windows Presentation Foundation (WPF) reduz consideravelmente 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 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 ter de criar um novo controle.

  • Conteúdo sofisticado. Muitos dos controles WPF padrão oferecem suporte a conteúdo avançado. Por exemplo, a propriedade content de um é do tipo Object, portanto, teoricamente, qualquer coisa pode ser exibida em um ButtonButtonarquivo . 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 WPF e dados arbitrários, há menos necessidade de criar um novo controle ou modificar um controle existente para oferecer suporte a uma visualização complexa. Para obter mais informações sobre o modelo de conteúdo para e outros modelos de conteúdo no WPF, consulte Modelo de Button conteúdo do WPF.

  • Estilos. A Style é uma coleção de valores que representam propriedades de 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ê deseja que todos os seus TextBlock controles tenham 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. Em seguida, cada TextBlock um 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, um pode ser usado para especificar como os dados são exibidos em um DataTemplateListBoxarquivo . Para obter um exemplo disso, consulte Visão geral de modelagem de dados. Além de personalizar a aparência dos dados, um DataTemplate pode incluir elementos de interface do usuário, o que oferece muita flexibilidade em interfaces de usuário personalizadas. Por exemplo, usando um , você pode criar um DataTemplateComboBox no qual cada item contém uma caixa de seleção.

  • Modelos de controle. Muitos controles no WPF usam a para definir a estrutura e a aparência do controle, que separa a ControlTemplate 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 um oferece a funcionalidade de apenas um ser selecionado por vez, mas a aparência padrão do não se parece em nada com as luzes em um RadioButton semáforo RadioButton . Como o usa um modelo de controle para definir sua aparência, é fácil redefinir o RadioButtonControlTemplate para se ajustar aos requisitos do controle e usar botões de opção para fazer seu semáforo.

    Observação

    Embora um possa usar um RadioButtonDataTemplate, 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 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, a é usado para definir o conteúdo (ou dados) de um controle, e a DataTemplateControlTemplate é 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 sejam 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. Um EventTrigger permite que você execute ações quando um evento ocorre.

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, Controle 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 os manipuladores de eventos de UserControlreferência em XAML. Em seguida, você pode referenciar os elementos nomeados e definir os manipuladores de eventos no código. Este modelo de desenvolvimento é muito semelhante ao modelo usado para o desenvolvimento de aplicações no WPF.

Se criado corretamente, um UserControl pode aproveitar os benefícios de conteúdo rico, estilos e gatilhos. No entanto, se seu controle herdar do 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 ofereça 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

Derivando da Control classe é o modelo usado pela maioria dos controles WPF existentes. Quando você cria 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 elementos de referência 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 para ControlTemplate personalizar sua aparência. Embora criar um personalizado não seja tão simples quanto criar um UserControl, um personalizado ControlControl fornece a maior flexibilidade.

Benefícios de derivar de Control

Considere derivar de em vez de Control usar a UserControl classe se qualquer uma das seguintes opçõ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 de ou Control dependem da composição de UserControl elementos existentes. Para muitos cenários, essa é uma solução aceitável, porque qualquer objeto que herda de FrameworkElement pode estar em um ControlTemplatearquivo . 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.

Há dois métodos padrão para a criação de componentes baseados em compilação FrameworkElement: renderização direta e composição de elementos personalizados. A renderização direta envolve substituir o método e fornecer OnRenderDrawingContext operações que definem explicitamente os visuais do FrameworkElement componente. Este é 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 Usando objetos DrawingVisual. Track é um exemplo de um controle no WPF que usa composição de elemento personalizado. 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 alguma das seguintes situações 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ê quer 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

Como discutido anteriormente, um dos recursos mais poderosos do WPF é a capacidade de ir além da definição de 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 possibilitados pelo sistema de propriedades WPF e pelo sistema de eventos 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 seu 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 identificador nomeado como um DependencyPropertypublicstaticreadonly campo.ValueProperty

  • 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 e a CoerceValueCallbackPropertyChangedCallback.

  • Defina uma propriedade wrapper CLR chamada Value, que é o mesmo nome usado para registrar a propriedade de dependência, implementando as propriedades get e set os acessadores. Note que os get e set acessadores só chamam GetValue e SetValue 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 o , CoerceValueCallbacke PropertyChangedCallback delegados para responder ou verificar o ValidateValueCallbackvalor 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 nome 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 oferece 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 seu controle for uma parte de cada item em um (porque ele está incluído em um ListBoxDataTemplate), 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 , que permite que os desenvolvedores de aplicativos especifiquem o manipulador de um evento dentro de um EventSetterestilo.

  • Os eventos roteados podem ser usados em um EventTrigger, 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 identificador nomeado como um RoutedEventpublicstaticreadonly campo.ValueChangedEvent

  • 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 é , o que significa que um manipulador de eventos na origem (o objeto que gera o evento) é Bubblechamado 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 na add declaração de acessor e RemoveHandler na declaração de remove acessor para usar os serviços de eventos 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 ControlTemplatearquivo . 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 referenciar elementos que estão no porque quando o código faz referência a elementos que estão no e o é alterado, o ControlTemplate elemento referenciado precisa ser incluído no ControlTemplateControlTemplate novo ControlTemplate.

O exemplo a seguir atualiza o TextBlock do controle, atribuindo um nome a ele e fazendo referência à 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 WPF Designer 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 WPF Designer, 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 WPF e 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 publicstaticDependencyPropertyreadonlydo formulário PropertyNameProperty que foi criado usando o RegisterAttached método. O nome da propriedade que é passado para RegisterAttached deve corresponder a PropertyName.

  • Implemente um par de métodos CLR publicstatic 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 PropertyName devem rotear diretamente para os GetValue métodos e SetValueGetno 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 objeto associado a ele, portanto, um Application 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 cada vez 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 ResourceDictionaryresultado .

O exemplo a seguir cria uma classe que retorna um ResourceDictionaryarquivo .

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 é uma propriedade estática, o SharedDictionaryManager.SharedDictionaryResourceDictionary é criado 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();
}

Definir recursos no nível de tema

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 um no tema clássico do Windows (o tema padrão para o Windows 2000) difere de um no tema Windows Luna (o tema padrão para o Windows XP) porque o Button usa um ButtonButton 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 exemplo de controle personalizado C# ou Visual Basic NumericUpDown com tema e suporte à automação da interface do usuário contém dois dicionários de recursos para o controle: um está em generic.xaml e o NumericUpDown outro está em Luna.NormalColor.xaml.

Quando você coloca um em qualquer um dos arquivos de dicionário de recursos específicos do tema, você deve criar um ControlTemplate construtor estático para seu controle e chamar o OverrideMetadata(Type, PropertyMetadata)DefaultStyleKeymé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 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 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 propriedade que especifica o local de recursos genéricos e uma ThemeDictionaryLocationGenericDictionaryLocation propriedade que especifica o local dos recursos específicos do tema.

O exemplo a seguir define as GenericDictionaryLocation propriedades e como SourceAssembly, para especificar que os recursos genéricos e ThemeDictionaryLocation 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