Visão geral das propriedades de dependência (WPF .NET)

O WPF (Windows Presentation Foundation) fornece um conjunto de serviços que podem ser usados para estender a funcionalidade de uma propriedade de um tipo. Coletivamente, esses serviços são chamados de sistema de propriedades do WPF. Uma propriedade apoiada pelo sistema de propriedades do WPF é conhecida como uma propriedade de dependência. Esta visão geral descreve o sistema de propriedades do WPF e os recursos de uma propriedade de dependência, incluindo como usar propriedades de dependência existentes em XAML e no código. Esta visão geral também apresenta aspectos especializados de propriedades de dependência, como metadados de propriedades de dependência e como criar sua própria propriedade de dependência em uma classe personalizada.

Pré-requisitos

Este artigo pressupõe conhecimento básico do sistema de tipos .NET e programação orientada a objetos. Para seguir os exemplos neste artigo, é útil entender o XAML e saber como escrever aplicativos WPF. Para obter mais informações, consulte Tutorial: Criar um novo aplicativo WPF com .NET.

Propriedades de dependência e propriedades CLR

As propriedades do WPF normalmente são expostas como propriedades padrão do .NET. Você pode interagir com essas propriedades em um nível básico e nunca saber que elas são implementadas como uma propriedade de dependência. No entanto, a familiaridade com alguns ou todos os recursos do sistema de propriedades do WPF ajudará você a aproveitar esses recursos.

A finalidade das propriedades de dependência é fornecer uma maneira de calcular o valor de uma propriedade com base no valor de outras entradas, como:

  • Propriedades do sistema, como temas e preferência do usuário.
  • Mecanismos de determinação de propriedades just-in-time, como associação de dados e animações/storyboards.
  • Modelos de uso múltiplo, como recursos e estilos.
  • Valores conhecidos por meio de relacionamentos pai-filho com outros elementos na árvore de elementos.

Além disso, uma propriedade de dependência pode fornecer:

  • Validação independente.
  • Valores padrão.
  • Retornos de chamada que monitoram alterações em outras propriedades.
  • Um sistema que pode forçar valores de propriedade com base em informações de tempo de execução.

As classes derivadas podem alterar algumas características de uma propriedade existente substituindo os metadados de uma propriedade de dependência, em vez de substituir a implementação real de propriedades existentes ou criar novas propriedades.

Na referência do SDK, você pode identificar uma propriedade de dependência pela presença de uma seção Informações da Propriedade de Dependência na página de referência gerenciada dessa propriedade. A seção Informações da Propriedade de Dependência inclui um link para o DependencyProperty campo identificador dessa propriedade de dependência. Ele também inclui a lista de opções de metadados para essa propriedade, informações de substituição por classe e outros detalhes.

As propriedades de dependência dão suporte às propriedades CLR

As propriedades de dependência e o sistema de propriedades do WPF estendem a funcionalidade da propriedade fornecendo um tipo que dá suporte a uma propriedade, como uma alternativa ao padrão padrão de dar suporte a uma propriedade com um campo privado. O nome desse tipo é DependencyProperty. O outro tipo importante que define o sistema de propriedades do WPF é DependencyObject, que define a classe base que pode registrar e possuir uma propriedade de dependência.

Aqui estão algumas terminologias comumente usadas:

  • Dependency , que é uma propriedade apoiada por um DependencyProperty.

  • Identificador de propriedade de dependência, que é uma DependencyProperty instância obtida como um valor retornado ao registrar uma propriedade de dependência e, em seguida, armazenada como um membro estático de uma classe. Muitas das APIs que interagem com o sistema de propriedades do WPF usam o identificador de propriedade de dependência como um parâmetro.

  • CLR "wrapper", que é o get e set implementações para a propriedade. Essas implementações incorporam o identificador de propriedade de dependência usando-o GetValue nas chamadas and SetValue . Dessa forma, o sistema de propriedades do WPF fornece o suporte para a propriedade.

O exemplo a seguir define a IsSpinning propriedade de dependência para mostrar a relação do DependencyProperty identificador com a propriedade que ele dá suporte.

public static readonly DependencyProperty IsSpinningProperty = DependencyProperty.Register(
    "IsSpinning", typeof(bool),
    typeof(MainWindow)
    );

public bool IsSpinning
{
    get => (bool)GetValue(IsSpinningProperty);
    set => SetValue(IsSpinningProperty, value);
}

A convenção de nomenclatura da propriedade e de seu campo DependencyProperty de suporte é importante. O nome do campo é sempre o nome da propriedade, com o sufixo Property acrescentado. Para obter mais informações sobre essa convenção e os motivos para ela, consulte Propriedades de dependência personalizadas.

Configurando valores da propriedade

É possível definir propriedades no código ou em XAML.

Configurando valores da propriedade em XAML

O exemplo XAML a seguir define a cor da tela de fundo de um botão como vermelho. O valor da cadeia de caracteres para o atributo XAML é convertido pelo analisador XAML do WPF em um tipo WPF. No código gerado, o tipo WPF é um Color, por meio de um SolidColorBrush.

<Button Content="I am red" Background="Red"/>

O XAML dá suporte a vários formulários de sintaxe para definir propriedades. A sintaxe a ser usada para uma propriedade específica depende do tipo de valor que uma propriedade usa e de outros fatores, como a presença de um conversor de tipo. Para obter mais informações sobre a sintaxe XAML para definir propriedades, consulte XAML no WPF e Sintaxe XAML em detalhes.

O exemplo XAML a seguir mostra outro plano de fundo de botão que usa a sintaxe do elemento de propriedade em vez da sintaxe do atributo. Em vez de definir uma cor sólida simples, o XAML define a propriedade button Background como uma imagem. Um elemento representa essa imagem e um atributo do elemento aninhado especifica a origem da imagem.

<Button Content="I have an image background">
    <Button.Background>
        <ImageBrush ImageSource="stripes.jpg"/>
    </Button.Background>
</Button>

Configurando propriedades no código

A definição de valores de propriedade de dependência no código normalmente é apenas uma chamada para a set implementação exposta pelo "wrapper" CLR:

Button myButton = new();
myButton.Width = 200.0;

Obter um valor de propriedade é essencialmente uma chamada para a implementação " get wrapper":

double whatWidth = myButton.Width;

Você também pode chamar as APIs GetValue do sistema de propriedades e SetValue diretamente. Chamar as APIs diretamente é apropriado para alguns cenários, mas geralmente não quando você está usando propriedades existentes. Normalmente, os wrappers são mais convenientes e fornecem melhor exposição da propriedade para ferramentas de desenvolvedor.

As propriedades também podem ser definidas em XAML e, em seguida, acessadas no código posteriormente, por meio do code-behind. Para obter detalhes, consulte Code-behind e XAML no WPF.

Funcionalidade de propriedade fornecida por uma propriedade de dependência

Ao contrário de uma propriedade que é apoiada por um campo, uma propriedade de dependência estende a funcionalidade de uma propriedade. Muitas vezes, a funcionalidade adicionada representa ou suporta um destes recursos:

Recursos

Você pode definir um valor de propriedade de dependência referenciando um recurso. Os recursos geralmente são especificados como o Resources valor da propriedade de um elemento raiz de página ou do aplicativo, pois esses locais oferecem acesso conveniente ao recurso. Neste exemplo, definimos um SolidColorBrush recurso:

<StackPanel.Resources>
    <SolidColorBrush x:Key="MyBrush" Color="Gold"/>
</StackPanel.Resources>

Agora que o recurso está definido, podemos fazer referência ao recurso para fornecer um valor para a Background propriedade:

<Button Background="{DynamicResource MyBrush}" Content="I am gold" />

No WPF XAML, você pode usar uma referência de recurso estático ou dinâmico. Esse recurso específico é referenciado como um DynamicResource. Uma referência de recurso dinâmico só pode ser usada para definir uma propriedade de dependência, portanto, é especificamente o uso de referência de recurso dinâmico habilitado pelo sistema de propriedades do WPF. Para obter mais informações, consulte Recursos XAML.

Observação

Os recursos são tratados como um valor local, o que significa que, se você definir outro valor local, eliminará a referência do recurso. Para obter mais informações, consulte Precedência do valor da propriedade de dependência.

Vinculação de dados

Uma propriedade de dependência pode referenciar um valor por meio da vinculação de dados. A associação de dados funciona por meio de uma sintaxe de extensão de marcação específica em XAML ou pelo objeto Binding no código. Com a vinculação de dados, a determinação do valor da propriedade final é adiada até o tempo de execução, momento em que o valor é obtido de uma fonte de dados.

O exemplo a seguir define a propriedade para um Button, usando uma associação declarada Content em XAML. A associação usa um contexto de dados herdado e uma fonte de dados XmlDataProvider (não mostrada). A própria associação especifica a propriedade source dentro da fonte de dados por XPath.

<Button Content="{Binding Source={StaticResource TestData}, XPath=test[1]/@text}"/>

Observação

As associações são tratadas como um valor local, o que significa que, se você definir outro valor local, eliminará a associação. Para obter detalhes, consulte Precedência do valor da propriedade de dependência.

As propriedades de dependência, ou a DependencyObject classe, não dão suporte INotifyPropertyChanged nativo à notificação de alterações no DependencyObject valor da propriedade de origem para operações de associação de dados. Para obter mais informações sobre como criar propriedades para uso na associação de dados que podem relatar alterações em um destino de associação de dados, consulte Visão geral da associação de dados.

Estilos

Estilos e modelos são motivos convincentes para usar propriedades de dependência. Os estilos são particularmente úteis para definir propriedades que definem a interface do usuário do aplicativo. Normalmente, os estilos são definidos como recursos no XAML. Os estilos interagem com o sistema de propriedades porque normalmente contêm "setters" para propriedades específicas e "gatilhos" que alteram um valor de propriedade com base no valor de runtime de outra propriedade.

O exemplo a seguir cria um estilo simples, que seria definido dentro de um Resources dicionário (não mostrado). Em seguida, esse estilo é aplicado diretamente à Style propriedade de um Button. O setter no estilo define a propriedade Background de um Button com estilo como verde.

<Style x:Key="GreenButtonStyle">
    <Setter Property="Control.Background" Value="Green"/>
</Style>
<Button Style="{StaticResource GreenButtonStyle}" Content="I am green"/>

Para obter mais informações, confira Estilo e modelagem.

Animações

As propriedades de dependência podem ser animadas. Quando uma animação aplicada é executada, o valor animado tem maior precedência do que qualquer outro valor de propriedade, incluindo um valor local.

O exemplo a seguir anima a Background propriedade de um Button. Tecnicamente, a sintaxe do elemento de propriedade define um espaço em branco SolidColorBrush como , Backgrounde a SolidColorBrush Color propriedade do é animada.

<Button Content="I am animated">
    <Button.Background>
        <SolidColorBrush x:Name="AnimBrush"/>
    </Button.Background>
    <Button.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation
                        Storyboard.TargetName="AnimBrush" 
                        Storyboard.TargetProperty="(SolidColorBrush.Color)"
                        From="Blue" To="White" Duration="0:0:1" 
                        AutoReverse="True" RepeatBehavior="Forever" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Button.Triggers>
</Button>

Para obter mais informações sobre como animar propriedades, consulte Visão geral da animação e Visão geral dos storyboards.

Substituições de metadados

Você pode alterar comportamentos específicos de uma propriedade de dependência substituindo seus metadados ao derivar da classe que originalmente registrou a propriedade de dependência. A substituição de metadados depende do DependencyProperty identificador e não requer a reimplementação da propriedade. A alteração de metadados é tratada nativamente pelo sistema de propriedades. Cada classe potencialmente contém metadados individuais para todas as propriedades herdadas de classes base, por tipo.

O exemplo a seguir substitui metadados para uma DefaultStyleKey propriedade de dependência. A substituição de metadados para essa propriedade de dependência específica faz parte de um padrão de implementação para criar controles que podem usar estilos padrão de temas.

public class SpinnerControl : ItemsControl
{
    static SpinnerControl() => DefaultStyleKeyProperty.OverrideMetadata(
            typeof(SpinnerControl),
            new FrameworkPropertyMetadata(typeof(SpinnerControl))
        );
}

Para obter mais informações sobre como substituir ou acessar metadados para propriedades de dependência, consulte Substituir metadados para uma propriedade de dependência.

Herança do valor de propriedade

Um elemento pode herdar o valor de uma propriedade de dependência de seu pai na árvore de objetos.

Observação

O comportamento de herança de valor de propriedade não é habilitado globalmente para todas as propriedades de dependência, pois o tempo de cálculo da herança afeta o desempenho. A herança de valor de propriedade normalmente só é habilitada em cenários que sugerem aplicabilidade. Você pode verificar se uma propriedade de dependência herda examinando a seção Informações da Propriedade de Dependência dessa propriedade de dependência na referência do SDK.

O exemplo a seguir mostra uma associação que inclui a DataContext propriedade para especificar a origem da associação. Portanto, as associações em objetos filho não precisam especificar a origem e podem usar o valor herdado de DataContext no objeto pai StackPanel . Ou um objeto filho pode especificar diretamente seu próprio DataContext ou um Source no Binding, e não usar o valor herdado.

<StackPanel Canvas.Top="50" DataContext="{Binding Source={StaticResource TestData}}">
    <Button Content="{Binding XPath=test[2]/@text}"/>
</StackPanel>

Para obter mais informações, consulte Herança de valor de propriedade.

Integração com o WPF Designer

Controles personalizados com propriedades implementadas como propriedades de dependência integram-se bem ao WPF Designer para Visual Studio. Um exemplo é a capacidade de editar propriedades de dependência diretas e anexadas na janela Propriedades . Para obter mais informações, consulte Visão geral da criação de controle.

Precedência do valor da propriedade de dependência

Qualquer uma das entradas baseadas em propriedade dentro do sistema de propriedades do WPF pode definir o valor de uma propriedade de dependência. A precedência do valor da propriedade de dependência existe para que os vários cenários de como as propriedades obtêm seus valores interajam de maneira previsível.

Observação

A documentação do SDK às vezes usa o termo "valor local" ou "valor definido localmente" ao discutir propriedades de dependência. Um valor definido localmente é um valor de propriedade definido diretamente em uma instância de objeto no código ou como um atributo de elemento em XAML.

O próximo exemplo inclui um estilo que se aplica à Background propriedade de qualquer botão, mas especifica um botão com uma propriedade definida Background localmente. Tecnicamente, esse botão tem sua Background propriedade definida duas vezes, embora apenas um valor se aplique: o valor com a precedência mais alta. Um valor definido localmente tem a precedência mais alta, exceto para uma animação em execução, que não existe aqui. Portanto, o segundo botão usa o valor definido localmente para a Background propriedade, em vez do valor do setter de estilo. O primeiro botão não tem valor local ou outro valor com precedência maior do que um setter de estilo e, portanto, usa o valor do setter de estilo para a Background propriedade.

<StackPanel>
    <StackPanel.Resources>
        <Style x:Key="{x:Type Button}" TargetType="{x:Type Button}">
            <Setter Property="Background" Value="Orange"/>
        </Style>
    </StackPanel.Resources>
    <Button>I am styled orange</Button>
    <Button Background="Pink">I am locally set to pink (not styled orange)</Button>
</StackPanel>

Por que a precedência da propriedade de dependência existe?

Os valores definidos localmente têm precedência sobre os valores do setter de estilo, que dá suporte ao controle local das propriedades do elemento. Para obter detalhes, consulte Precedência do valor da propriedade de dependência.

Observação

Várias propriedades definidas em elementos do WPF não são propriedades de dependência, pois as propriedades de dependência normalmente eram implementadas somente quando um recurso do sistema de propriedades do WPF era necessário. Os recursos incluem associação de dados, estilo, animação, suporte a valor padrão, herança, propriedades anexadas e invalidação.

Mais informações sobre propriedades de dependência

  • Os desenvolvedores de componentes ou desenvolvedores de aplicativos podem querer criar sua própria propriedade de dependência para adicionar recursos, como associação de dados ou suporte a estilos, ou suporte a invalidação e coerção de valor. Para obter mais informações, consulte Propriedades de dependência personalizadas.

  • Considere as propriedades de dependência como propriedades públicas, acessíveis ou detectáveis por qualquer chamador com acesso a uma instância. Para obter mais informações, consulte Segurança de propriedade de dependência.

  • Uma propriedade anexada é um tipo de propriedade que dá suporte a uma sintaxe especializada em XAML. Uma propriedade anexada geralmente não tem uma correspondência 1:1 com uma propriedade Common Language Runtime e não é necessariamente uma propriedade de dependência. A principal finalidade de uma propriedade anexada é permitir que elementos filho relatem valores de propriedade para um elemento pai, mesmo que o elemento pai e o elemento filho não incluam essa propriedade como parte das listagens de membros da classe. Um cenário principal é permitir que um elemento filho informe aos elementos pai como apresentá-los na interface do usuário. Para exemplos, consulte Dock e Left. Para obter mais informações, consulte Visão geral das propriedades anexadas.

Confira também