Partilhar via


Visão geral das propriedades de dependência

Este tópico explica o sistema de propriedades de dependência que está disponível quando você escreve um aplicativo do Tempo de Execução do Windows usando C++, C# ou Visual Basic junto com definições XAML para a interface do usuário.

O que é uma propriedade de dependência?

Uma propriedade de dependência é um tipo especializado de propriedade. Especificamente, é uma propriedade em que o valor da propriedade é rastreado e influenciado por um sistema de propriedades dedicado que faz parte do Tempo de Execução do Windows.

Para dar suporte a uma propriedade de dependência, o objeto que define a propriedade deve ser um DependencyObject (em outras palavras, uma classe que tem a classe base DependencyObject em algum lugar em sua herança). Muitos dos tipos que você usa para suas definições de interface do usuário para um aplicativo UWP com XAML serão uma subclasse DependencyObject e darão suporte a propriedades de dependência. No entanto, qualquer tipo que venha de um namespace do Tempo de Execução do Windows que não tenha "XAML" em seu nome não dará suporte a propriedades de dependência; As propriedades desses tipos são propriedades comuns que não terão o comportamento de dependência do sistema de propriedades.

A finalidade das propriedades de dependência é fornecer uma maneira sistêmica de calcular o valor de uma propriedade com base em outras entradas (outras propriedades, eventos e estados que ocorrem em seu aplicativo enquanto ele é executado). Essas outras entradas podem incluir:

  • Entrada externa, como preferência do usuário
  • Mecanismos de determinação de propriedades just-in-time, como vinculação de dados, animações e storyboards
  • Padrões de modelos de uso múltiplo, como recursos e estilos
  • Valores conhecidos por meio de relacionamentos pai-filho com outros elementos na árvore de objetos

Uma propriedade de dependência representa ou dá suporte a um recurso específico do modelo de programação para definir um aplicativo do Tempo de Execução do Windows com XAML para interface do usuário e extensões de componente C#, Microsoft Visual Basic ou Visual C++ (C++/CX) para código. Esses recursos incluem:

  • Vinculação de dados
  • Estilos
  • Animações com storyboard
  • Comportamento "PropertyChanged"; Uma propriedade de dependência pode ser implementada para fornecer retornos de chamada que podem propagar alterações para outras propriedades de dependência
  • Usando um valor padrão proveniente de metadados de propriedade
  • Utilitário geral do sistema de propriedades, como ClearValue e pesquisa de metadados

Propriedades de dependência e propriedades do Tempo de Execução do Windows

As propriedades de dependência estendem a funcionalidade básica da propriedade do Tempo de Execução do Windows, fornecendo um repositório de propriedades interno global que dá suporte a todas as propriedades de dependência em um aplicativo em tempo de execução. Essa é uma alternativa ao padrão padrão de dar suporte a uma propriedade com um campo privado que é privado na classe de definição de propriedade. Você pode pensar nesse repositório de propriedades interno como um conjunto de identificadores e valores de propriedade que existem para qualquer objeto específico (desde que seja um DependencyObject). Em vez de ser identificada pelo nome, cada propriedade no repositório é identificada por uma instância DependencyProperty. No entanto, o sistema de propriedades oculta principalmente esse detalhe de implementação: geralmente você pode acessar propriedades de dependência usando um nome simples (o nome da propriedade programática na linguagem de código que você está usando ou um nome de atributo quando você está escrevendo XAML).

O tipo base que fornece os fundamentos do sistema de propriedades de dependência é DependencyObject. DependencyObject define métodos que podem acessar a propriedade de dependência, e instâncias de uma classe derivada de DependencyObject dão suporte internamente ao conceito de repositório de propriedades que mencionamos anteriormente.

Aqui está um resumo da terminologia que usamos na documentação ao discutir as propriedades de dependência:

Termo Descrição
Propriedade de dependência Uma propriedade que existe em um identificador DependencyProperty (veja abaixo). Normalmente, esse identificador está disponível como um membro estático da classe derivada de definição de DependencyObject .
Identificador de propriedade de dependência Um valor constante para identificar a propriedade, normalmente é público e somente leitura.
Wrapper de propriedade As implementações get e set que podem ser chamadas para uma propriedade do Tempo de Execução do Windows. Ou a projeção específica do idioma da definição original. Uma implementação de wrapper de propriedade get chama GetValue, passando o identificador de propriedade de dependência relevante.

O wrapper de propriedade não é apenas conveniência para chamadores, ele também expõe a propriedade de dependência a qualquer processo, ferramenta ou projeção que use definições do Tempo de Execução do Windows para propriedades.

O exemplo a seguir define uma propriedade de dependência personalizada, conforme definido para C#, e mostra a relação do identificador de propriedade de dependência com o wrapper de propriedade.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  "Label",
  typeof(string),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);


public string Label
{
    get { return (string)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}

Observação

O exemplo anterior não se destina a ser o exemplo completo de como criar uma propriedade de dependência personalizada. Destina-se a mostrar conceitos de propriedade de dependência para qualquer pessoa que prefira aprender conceitos por meio de código. Para obter uma explicação mais completa deste exemplo, consulte Propriedades de dependência personalizadas.

Precedência do valor da propriedade de dependência

Ao obter o valor de uma propriedade de dependência, você está obtendo um valor que foi determinado para essa propriedade por meio de qualquer uma das entradas que participam do sistema de propriedades do Tempo de Execução do Windows. A precedência do valor da propriedade de dependência existe para que o sistema de propriedades do Tempo de Execução do Windows possa calcular valores de maneira previsível, e é importante que você também esteja familiarizado com a ordem básica de precedência. Caso contrário, você pode se encontrar em uma situação em que está tentando definir uma propriedade em um nível de precedência, mas outra coisa (o sistema, chamadores de terceiros, parte de seu próprio código) está definindo-a em outro nível, e você ficará frustrado tentando descobrir qual valor de propriedade é usado e de onde veio esse valor.

Por exemplo, estilos e modelos destinam-se a ser um ponto de partida compartilhado para estabelecer valores de propriedade e, portanto, aparências de um controle. Mas em uma instância de controle específica, talvez você queira alterar seu valor em relação ao valor de modelo comum, como dar a esse controle uma cor de plano de fundo diferente ou uma cadeia de caracteres de texto diferente como conteúdo. O sistema de propriedades do Tempo de Execução do Windows considera valores locais com precedência mais alta do que os valores fornecidos por estilos e modelos. Isso permite o cenário de ter valores específicos do aplicativo substituindo os modelos para que os controles sejam úteis para seu próprio uso deles na interface do usuário do aplicativo.

Lista de precedência de propriedade de dependência

Veja a seguir a ordem definitiva que o sistema de propriedades usa ao atribuir o valor de tempo de execução para uma propriedade de dependência. A precedência mais alta é listada primeiro. Você encontrará explicações mais detalhadas logo após esta lista.

  1. Valores animados: animações ativas, animações de estado visual ou animações com um comportamento HoldEnd . Para ter qualquer efeito prático, uma animação aplicada a uma propriedade deve ter precedência sobre o valor base (não animado), mesmo que esse valor tenha sido definido localmente.
  2. Valor local: um valor local pode ser definido por meio da conveniência do wrapper de propriedade, que também equivale à configuração como um atributo ou elemento de propriedade em XAML, ou por uma chamada para o método SetValue usando uma propriedade de uma instância específica. Se você definir um valor local usando uma associação ou um recurso estático, cada um deles agirá na precedência como se um valor local tivesse sido definido, e as associações ou referências de recurso serão apagadas se um novo valor local for definido.
  3. Propriedades de modelo: um elemento as terá se tiver sido criado como parte de um modelo (de um ControlTemplate ou DataTemplate).
  4. Setters de estilo: valores de um Setter dentro de estilos de recursos de página ou aplicativo.
  5. Valor padrão: uma propriedade de dependência pode ter um valor padrão como parte de seus metadados.

Propriedades de modelo

As propriedades de modelo como um item de precedência não se aplicam a nenhuma propriedade de um elemento que você declara diretamente na marcação de página XAML. O conceito de propriedade de modelo existe apenas para objetos criados quando o Tempo de Execução do Windows aplica um modelo XAML a um elemento de interface do usuário e, portanto, define seus visuais.

Todas as propriedades definidas em um modelo de controle têm valores de algum tipo. Esses valores são quase como um conjunto estendido de valores padrão para o controle e geralmente estão associados a valores que você pode redefinir posteriormente definindo os valores de propriedade diretamente. Assim, os valores do conjunto de modelos devem ser distinguíveis de um valor local verdadeiro, para que qualquer novo valor local possa substituí-lo.

Observação

Em alguns casos, o modelo pode substituir até mesmo valores locais, se o modelo não expor referências de extensão de marcação {TemplateBinding} para propriedades que deveriam ter sido configuráveis em instâncias. Isso geralmente é feito somente se a propriedade realmente não se destina a ser definida em instâncias, por exemplo, se for relevante apenas para visuais e comportamento de modelo e não para a função pretendida ou lógica de tempo de execução do controle que usa o modelo.

Associações e precedência

As operações de associação têm a precedência apropriada para qualquer escopo para o qual são usadas. Por exemplo, um {Binding} aplicado a um valor local atua como valor local e uma extensão de marcação {TemplateBinding} para um setter de propriedade se aplica como um setter de estilo. Como as associações devem aguardar até o tempo de execução para obter valores de fontes de dados, o processo de determinar a precedência do valor da propriedade para qualquer propriedade também se estende ao tempo de execução.

As associações não apenas operam com a mesma precedência que um valor local, mas também são realmente um valor local, em que a associação é o espaço reservado para um valor que é adiado. Se você tiver uma associação em vigor para um valor de propriedade e definir um valor local nele em tempo de execução, isso substituirá totalmente a associação. Da mesma forma, se você chamar SetBinding para definir uma associação que só passa a existir em tempo de execução, substituirá qualquer valor local que possa ter aplicado em XAML ou por código executado anteriormente.

Animações com storyboard e valor base

As animações com storyboard atuam em um conceito de um valor base. O valor base é o valor determinado pelo sistema de propriedades usando sua precedência, mas omitindo a última etapa de procurar animações. Por exemplo, um valor base pode vir do modelo de um controle ou pode vir da configuração de um valor local em uma instância de um controle. De qualquer forma, a aplicação de uma animação substituirá esse valor base e aplicará o valor animado enquanto a animação continuar a ser executada.

Para uma propriedade animada, o valor base ainda pode ter um efeito sobre o comportamento da animação, se essa animação não especificar explicitamente From e To, ou se a animação reverter a propriedade para seu valor base quando concluída. Nesses casos, quando uma animação não estiver mais em execução, o restante da precedência será usado novamente.

No entanto, uma animação que especifica um To com um comportamento HoldEnd pode substituir um valor local até que a animação seja removida, mesmo quando ela parece visualmente estar parada. Conceitualmente, isso é como uma animação que está sendo executada para sempre, mesmo que não haja uma animação visual na interface do usuário.

Várias animações podem ser aplicadas a uma única propriedade. Cada uma dessas animações pode ter sido definida para substituir valores base provenientes de diferentes pontos na precedência do valor. No entanto, todas essas animações serão executadas simultaneamente em tempo de execução, e isso geralmente significa que elas devem combinar seus valores porque cada animação tem igual influência no valor. Isso depende de como exatamente as animações são definidas e do tipo do valor que está sendo animado.

Para obter mais informações, consulte Animações com storyboard.

Valores padrão

Estabelecer o valor padrão para uma propriedade de dependência com um valor PropertyMetadata é explicado com mais detalhes no tópico Propriedades de dependência personalizadas.

As propriedades de dependência ainda têm valores padrão, mesmo que esses valores padrão não tenham sido explicitamente definidos nos metadados dessa propriedade. A menos que tenham sido alterados por metadados, os valores padrão para as propriedades de dependência do Tempo de Execução do Windows geralmente são um dos seguintes:

  • Uma propriedade que usa um objeto de tempo de execução ou o tipo Object básico (um tipo de referência) tem um valor padrão de null. Por exemplo, DataContext é nulo até que seja deliberadamente definido ou herdado.
  • Uma propriedade que usa um valor básico, como números ou um valor booliano (um tipo de valor), usa um padrão esperado para esse valor. Por exemplo, 0 para inteiros e números de ponto flutuante, false para um booleano.
  • Uma propriedade que usa uma estrutura do Tempo de Execução do Windows tem um valor padrão obtido chamando o construtor padrão implícito dessa estrutura. Esse construtor usa os padrões para cada um dos campos de valor básicos da estrutura. Por exemplo, um padrão para um valor de ponto é inicializado com seus valores X e Y como 0.
  • Uma propriedade que usa uma enumeração tem um valor padrão do primeiro membro definido nessa enumeração. Verifique a referência de enumerações específicas para ver qual é o valor padrão.
  • Uma propriedade que usa uma cadeia de caracteres (System.String para .NET, Platform::String para C++/CX) tem um valor padrão de uma cadeia de caracteres vazia ("").
  • As propriedades de coleção normalmente não são implementadas como propriedades de dependência, por motivos discutidos mais adiante neste tópico. Mas se você implementar uma propriedade de coleção personalizada e quiser que ela seja uma propriedade de dependência, evite um singleton não intencional, conforme descrito perto do final de Propriedades de dependência personalizadas.

Funcionalidade de propriedade fornecida por uma propriedade de dependência

Vinculação de dados

Uma propriedade de dependência pode ter seu valor definido por meio da aplicação de uma associação de dados. A associação de dados usa a sintaxe de extensão de marcação {Binding} em XAML, a extensão de marcação {x:Bind} ou a classe Binding no código. Para uma propriedade vinculada a dados, a determinação do valor da propriedade final é adiada até o tempo de execução. Nesse momento, o valor é obtido de uma fonte de dados. A função que o sistema de propriedades de dependência desempenha aqui é habilitar um comportamento de espaço reservado para operações como carregar XAML quando o valor ainda não é conhecido e, em seguida, fornecer o valor em tempo de execução interagindo com o mecanismo de associação de dados do Tempo de Execução do Windows.

O exemplo a seguir define o valor Text para um elemento TextBlock , usando uma associação em XAML. A associação usa um contexto de dados herdado e uma fonte de dados de objeto. (Nenhum deles é mostrado no exemplo abreviado; para uma amostra mais completa que mostra o contexto e a fonte, consulte Vinculação de dados em profundidade.)

<Canvas>
  <TextBlock Text="{Binding Team.TeamName}"/>
</Canvas>

Você também pode estabelecer associações usando código em vez de XAML. Consulte SetBinding.

Observação

Associações como essa são tratadas como um valor local para fins de precedência do valor da propriedade de dependência. Se você definir outro valor local para uma propriedade que originalmente continha um valor de associação , substituirá totalmente a associação, não apenas o valor de tempo de execução da associação. {x:Vincular} As associações são implementadas usando o código gerado que definirá um valor local para a propriedade. Se você definir um valor local para uma propriedade que está usando {x:Bind}, esse valor será substituído na próxima vez que a associação for avaliada, como quando ela observa uma alteração de propriedade em seu objeto de origem.

Fontes de associação, destinos de associação, a função de FrameworkElement

Para ser a origem de uma associação, uma propriedade não precisa ser uma propriedade de dependência; Geralmente, você pode usar qualquer propriedade como uma fonte de associação, embora isso dependa da sua linguagem de programação e cada uma tenha determinados casos extremos. No entanto, para ser o destino de uma extensão de marcação {Binding} ou Binding, essa propriedade deve ser uma propriedade de dependência. {x:Bind} não tem esse requisito, pois usa o código gerado para aplicar seus valores de associação.

Se você estiver criando uma associação no código, observe que a API SetBinding é definida apenas para FrameworkElement. No entanto, você pode criar uma definição de associação usando BindingOperations e, portanto, fazer referência a qualquer propriedade DependencyObject.

Para código ou XAML, lembre-se de que DataContext é uma propriedade FrameworkElement. Usando uma forma de herança de propriedade pai-filho (normalmente estabelecida na marcação XAML), o sistema de associação pode resolver um DataContext que existe em um elemento pai. Essa herança pode ser avaliada mesmo se o objeto filho (que tem a propriedade target) não for um FrameworkElement e, portanto, não tiver seu próprio valor DataContext. No entanto, o elemento pai que está sendo herdado deve ser um FrameworkElement para definir e manter o DataContext. Como alternativa, você deve definir a associação de modo que ela possa funcionar com um valor nulo para DataContext.

A conexão da associação não é a única coisa necessária para a maioria dos cenários de associação de dados. Para que uma associação unidirecional ou bidirecional seja eficaz, a propriedade source deve dar suporte a notificações de alteração que se propagam para o sistema de associação e, portanto, para o destino. Para fontes de associação personalizadas, isso significa que a propriedade deve ser uma propriedade de dependência ou o objeto deve dar suporte a INotifyPropertyChanged. As coleções devem dar suporte a INotifyCollectionChanged. Determinadas classes dão suporte a essas interfaces em suas implementações para que sejam úteis como classes base para cenários de associação de dados; um exemplo dessa classe é ObservableCollection<T>. Para obter mais informações sobre a associação de dados e como a associação de dados se relaciona com o sistema de propriedades, consulte Associação de dados em detalhes.

Observação

Os tipos listados aqui dão suporte a fontes de dados do Microsoft .NET. As fontes de dados C++/CX usam interfaces diferentes para notificação de alteração ou comportamento observável, consulte Associação de dados em detalhes.

Estilos e modelos

Estilos e modelos são dois dos cenários para propriedades que estão sendo definidas como propriedades de dependência. Os estilos são úteis para definir propriedades que definem a interface do usuário do aplicativo. Os estilos são definidos como recursos em XAML, como uma entrada em uma coleção Resources ou em arquivos XAML separados, como dicionários de recursos de tema. Os estilos interagem com o sistema de propriedades porque contêm setters para propriedades. A propriedade mais importante aqui é a propriedade Control.Template de um Control: ela define a maior parte da aparência visual e do estado visual de um Control. Para obter mais informações sobre estilos e alguns exemplos de XAML que definem um Style e usam setters, consulte Controles de estilo.

Os valores provenientes de estilos ou modelos são valores adiados, semelhantes às associações. Isso é para que os usuários de controle possam remodelar controles ou redefinir estilos. E é por isso que os setters de propriedade em estilos só podem agir em propriedades de dependência, não em propriedades comuns.

Animações com storyboard

Você pode animar o valor de uma propriedade de dependência usando uma animação com storyboard. As animações com storyboard no Tempo de Execução do Windows não são apenas decorações visuais. É mais útil pensar em animações como sendo uma técnica de máquina de estado que pode definir os valores de propriedades individuais ou de todas as propriedades e visuais de um controle e alterar esses valores ao longo do tempo.

Para ser animada, a propriedade de destino da animação deve ser uma propriedade de dependência. Além disso, para ser animado, o tipo de valor da propriedade de destino deve ser compatível com um dos tipos de animação derivados da linha do tempo existentes. Os valores de Cor, Duplo e Ponto podem ser animados usando técnicas de interpolação ou quadro-chave. A maioria dos outros valores pode ser animada usando quadros-chave de objeto discretos.

Quando uma animação é aplicada e executada, o valor animado opera em uma precedência mais alta do que qualquer valor (como um valor local) que a propriedade tem. As animações também têm um comportamento HoldEnd opcional que pode fazer com que as animações sejam aplicadas a valores de propriedade, mesmo que a animação pareça visualmente estar parada.

O princípio da máquina de estado é incorporado pelo uso de animações com storyboard como parte do modelo de estado VisualStateManager para controles. Para obter mais informações sobre animações com storyboard, consulte Animações com storyboard. Para obter mais informações sobre VisualStateManager e definir estados visuais para controles, consulte Animações com storyboard para estados visuais ou Modelos de controle.

Comportamento de alteração de propriedade

O comportamento de propriedade alterada é a origem da parte "dependência" da terminologia da propriedade de dependência. Manter valores válidos para uma propriedade quando outra propriedade pode influenciar o valor da primeira propriedade é um problema de desenvolvimento difícil em muitas estruturas. No sistema de propriedades do Tempo de Execução do Windows, cada propriedade de dependência pode especificar um retorno de chamada que é invocado sempre que seu valor de propriedade é alterado. Esse retorno de chamada pode ser usado para notificar ou alterar valores de propriedade relacionados, de maneira geralmente síncrona. Muitas propriedades de dependência existentes têm um comportamento de alteração de propriedade. Você também pode adicionar um comportamento de retorno de chamada semelhante a propriedades de dependência personalizadas e implementar seus próprios retornos de chamada alterados por propriedade. Consulte Propriedades de dependência personalizadas para obter um exemplo.

Windows 10 apresenta o método RegisterPropertyChangedCallback. Isso permite que o código do aplicativo se registre para notificações de alteração quando a propriedade de dependência especificada é alterada em uma instância de DependencyObject.

Valor padrão e ClearValue

Uma propriedade de dependência pode ter um valor padrão definido como parte de seus metadados de propriedade. Para uma propriedade de dependência, seu valor padrão não se torna irrelevante depois que a propriedade é definida pela primeira vez. O valor padrão pode ser aplicado novamente em tempo de execução sempre que algum outro determinante na precedência de valor desaparecer. (A precedência do valor da propriedade de dependência é discutida na próxima seção.) Por exemplo, você pode remover deliberadamente um valor de estilo ou uma animação que se aplica a uma propriedade, mas deseja que o valor seja um padrão razoável depois de fazer isso. O valor padrão da propriedade de dependência pode fornecer esse valor, sem a necessidade de definir especificamente o valor de cada propriedade como uma etapa extra.

Você pode definir deliberadamente uma propriedade como o valor padrão, mesmo depois de já tê-la definido com um valor local. Para redefinir um valor para ser o padrão novamente e também para habilitar outros participantes em precedência que podem substituir o padrão, mas não um valor local, chame o método ClearValue (faça referência à propriedade para clear como um parâmetro de método). Você nem sempre deseja que a propriedade use literalmente o valor padrão, mas limpar o valor local e reverter para o valor padrão pode habilitar outro item em precedência que você deseja agir agora, como usar o valor que veio de um setter de estilo em um modelo de controle.

DependencyObject e threading

Todas as instâncias de DependencyObject devem ser criadas no thread da interface do usuário associado à janela atual mostrada por um aplicativo do Tempo de Execução do Windows. Embora cada DependencyObject deva ser criado no thread principal da interface do usuário, os objetos podem ser acessados usando uma referência dispatcher de outros threads, acessando a propriedade Dispatcher. Em seguida, você pode chamar métodos como RunAsync no objeto CoreDispatcher e executar seu código dentro das regras de restrições de thread no thread da interface do usuário.

Os aspectos de threading de DependencyObject são relevantes porque geralmente significa que somente o código executado no thread da interface do usuário pode alterar ou até mesmo ler o valor de uma propriedade de dependência. Problemas de threading geralmente podem ser evitados no código de interface do usuário típico que faz uso correto de padrões assíncronos e threads de trabalho em segundo plano. Normalmente, você só se depara com problemas de threading relacionados a DependencyObject se estiver definindo seus próprios tipos de DependencyObject e tentar usá-los para fontes de dados ou outros cenários em que um DependencyObject não é necessariamente apropriado.

Material conceitual