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 Windows Runtime com definições XAML para 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 é acompanhado e influenciado por um sistema de propriedades dedicado que faz parte do Windows Runtime.

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 proveniente de um namespace do Windows Runtime que não tenha "XAML" em seu nome não oferecerá suporte a propriedades de dependência; 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 durante a execução). Essas outras entradas podem incluir:

  • Entrada externa, como preferência do usuário
  • Mecanismos de determinação de propriedade just-in-time, como associação de dados, animações e storyboards
  • Padrões de modelagem de uso múltiplo, como recursos e estilos
  • Valores conhecidos por meio de relações 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 Windows Runtime com XAML para interface do usuário. Esses recursos incluem:

  • Vinculação de dados
  • Estilos
  • Animações de storyboard
  • Comportamento "PropertyChanged"; uma propriedade dependente pode ser implementada para fornecer retornos de chamada que podem propagar alterações para outras propriedades dependentes
  • 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 Windows Runtime

As propriedades de dependência estendem a funcionalidade básica de propriedade do Windows Runtime ao fornecer um repositório interno e global de propriedades que dá suporte a todas as propriedades de dependência de um aplicativo durante a execução. Essa é uma alternativa ao padrão padrão de backup de 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 sendo um conjunto de identificadores de propriedade e valores que existem para qualquer objeto específico (desde que seja um DependencyObject). Em vez de serem identificadas 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: você geralmente 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 ao escrever 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 DependencyObject dão suporte interno ao conceito de repositório de propriedades mencionado anteriormente.

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

Prazo Description
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 DependencyObject definidora.
Identificador de propriedade de dependência Um valor constante para identificar a propriedade, normalmente é pública e somente leitura.
Wrapper de propriedade get e set são implementações callable para uma propriedade do Windows Runtime. Ou a projeção específica do idioma da definição original. Uma implementação get property wrapper chama GetValue, passando o identificador de propriedade de dependência relevante.

O empacotador de propriedade não é apenas uma conveniência para os usuários, ele também expõe a propriedade de dependência a qualquer ferramenta, processo ou projeção que use as definições do Windows Runtime para propriedades.

O exemplo a seguir define uma propriedade de dependência personalizada como definido para C# e mostra a relação do identificador de propriedade de dependência com o encapsulador da 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 como 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 conceitos de aprendizagem por meio do 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

Quando você obtém 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 Windows Runtime. A precedência do valor da propriedade de dependência existe para que o sistema de propriedades do Windows Runtime possa calcular valores de maneira previsível e é importante que você também esteja familiarizado com a ordem de precedência básica. Caso contrário, você pode se ver em uma situação em que tenta definir uma propriedade em um nível de prioridade, mas algo mais (o sistema, chamadores de terceiros, parte do seu próprio código) está configurando-a em outro nível, e você ficará frustrado tentando descobrir qual valor de propriedade está sendo usado e de onde esse valor surgiu.

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 comparação com o 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 Windows Runtime considera valores locais com precedência maior do que os valores fornecidos por estilos e modelos. Isso possibilita que valores específicos de aplicativos substituam os modelos, tornando os controles úteis para sua utilização na interface do usuário do aplicativo.

Lista de precedência de propriedade de dependência

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

  1. Valores animados: Animações ativas, animações de estado visual, ou animações com o comportamento HoldEnd. Para ter qualquer efeito prático, uma animação aplicada a uma propriedade deve ter precedência sobre o valor base (unanimado), mesmo que esse valor tenha sido definido localmente.
  2. Valor local: Um valor local pode ser estabelecido pela funcionalidade conveniente do encapsulador de propriedade, que também corresponde à definição como um atributo ou elemento de propriedade em XAML, ou através de uma chamada para o método SetValue utilizando 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 atuará na precedência como se um valor local fosse 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 os terá se ele foi criado como parte de um modelo (de um ControlTemplate ou DataTemplate).
  4. Definidores de estilo: Valores de um Setter dentro de estilos nos recursos de página ou do aplicativo.
  5. Valor padrão: Uma propriedade de dependência pode ter um valor padrão como parte de seus metadados.

Propriedades modeladas

As propriedades de modelo pré-definidas 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 modelo existe apenas para objetos que são criados quando o Windows Runtime aplica um modelo XAML a um elemento de interface do usuário e, portanto, define seus visuais.

Todas as propriedades definidas de 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 são associados a valores que você pode redefinir mais tarde definindo os valores da propriedade diretamente. Portanto, os valores definidos pelo modelo 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 poderá substituir até mesmo valores locais, se o modelo não tiver exposto 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 ela é relevante apenas para visuais e comportamento de modelo e não para a função pretendida ou lógica de runtime do controle que usa o modelo.

Vinculações e precedência

As operações de vinculação têm precedência adequada para qualquer escopo em que sejam utilizadas. Por exemplo, um {Binding} aplicado a um valor local atua como valor local, e uma extensão de marcação {TemplateBinding} para um definidor de propriedade é aplicada como um definidor 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 ocorre em tempo de execução.

Não só as vinculações operam com a mesma precedência que um valor local, elas realmente são um valor local, onde a vinculação é o espaço reservado para um valor diferido. Se você tiver uma vinculação ativa para um valor de propriedade e definir um valor local nela em tempo de execução, isso substitui completamente a vinculação. Da mesma forma, se você chamar SetBinding para definir uma associação que só existe em tempo de execução, substitua qualquer valor local que você possa ter aplicado em XAML ou com código executado anteriormente.

Animações planejadas com storyboard e valor base

Animações com storyboard atuam em um conceito de valor base. O valor base é o valor determinado pelo sistema de propriedades usando sua precedência, mas omitindo essa última etapa de busca de 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 influenciar o comportamento da animação, caso a animação não especifique explicitamente tanto De quanto Para, ou caso a animação reverta a propriedade para seu valor base ao ser concluída. Nesses casos, uma vez que uma animação não está mais em execução, o restante da precedência é usado novamente.

No entanto, uma animação que especifica um Para com um comportamento HoldEnd pode substituir um valor local até que a animação seja removida, mesmo quando visualmente parece estar parada. Conceitualmente, isso é como uma animação que está em execução 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 pontos diferentes na precedência de 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 influência igual sobre o valor. Isso depende exatamente de como 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 predefinidos

Estabelecer o valor padrão de 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 Windows Runtime geralmente são um dos seguintes:

  • Uma propriedade que usa um objeto em tempo de execução ou o tipo de objeto básico (um tipo de referência) tem um valor padrão de nulo. 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 números inteiros e de ponto flutuante, false para um booliano.
  • Uma propriedade que usa uma estrutura do Windows Runtime 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 point é 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 da 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 fim das 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 da extensão de marcação {Binding} no XAML, a extensão de marcação {x:Bind} ou a classe Binding no código. Para uma propriedade de databound, 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 Windows Runtime.

O exemplo a seguir define o valor de Texto 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 obter um exemplo mais completo que mostra o contexto e a origem, consulte a associaçã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 de valor de propriedade de dependência. Se você definir outro valor local para uma propriedade que originalmente tinha um valor Binding, substituirá o Binding inteiramente, não apenas o valor de tempo de execução do Binding. {x:Bind} 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 esteja usando {x:Bind}, esse valor será substituído na próxima vez que a associação for avaliada, como quando observar 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; você geralmente pode usar qualquer propriedade como fonte de associação, embora isso dependa da linguagem de programação e cada uma delas tenha alguns casos de borda. 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 que o objeto filho (que tem a propriedade de destino) não seja um FrameworkElement e, portanto, não mantenha 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.

Configurar a vinculação não é a única coisa necessária para a maioria dos cenários de vinculação de dados. Para que uma associação unidirecional ou bidirecional seja eficaz, a propriedade de origem deve dar suporte a notificações de alteração que se propagam para o sistema de associação e, portanto, 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 a associação de dados detalhadamente.

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 a associação de dados em profundidade.

Estilos e modelos

Estilos e modelos são dois dos cenários para propriedades que estão sendo definidas como propriedades de dependência. 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, seja como uma entrada em uma coleção de recursos 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 Controle: ela define a maior parte da aparência visual e do estado visual de um Controle. Para obter mais informações sobre estilos e alguns exemplos de XAML que define um Estilo e usa setters, consulte Controles de estilo.

Valores provenientes de estilos ou modelos são valores adiados, semelhantes às associações. Isso é para que os usuários de controle possam reimplanter 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 de storyboard

Você pode animar o valor de uma propriedade de dependência usando uma animação com storyboard. Animações roteirizadas no Windows Runtime não são meramente 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 animar, a propriedade alvo 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 suportado por 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 de quadro-chave. A maioria dos outros valores pode ser animada usando quadros-chave discretos de Objeto.

Quando uma animação é aplicada e em execução, o valor animado opera com uma precedência maior do que qualquer valor (como um valor local) que a propriedade teria de outra forma. As animações também têm um comportamento HoldEnd opcional que pode fazer com que as animações se apliquem a valores de propriedade, mesmo que a animação pareça estar parada visualmente.

O princípio das máquinas de estado é refletido no uso de animações storyboard como parte do modelo de estados do VisualStateManager para controles. Para obter mais informações sobre animações baseadas em storyboard, consulte Animações Baseadas em 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 alterado pela propriedade é 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 Windows Runtime, cada propriedade de dependência pode especificar um retorno de chamada que é invocado sempre que o valor da propriedade é alterado. Esse callback 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 alterado pela propriedade. Você também pode adicionar um comportamento de retorno de chamada semelhante às propriedades de dependência personalizadas e implementar seus próprios retornos de chamada alterados por propriedade. Veja as propriedades de dependência personalizadas para obter um exemplo.

O 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, após essa remoção, o valor retorne a um padrão razoável. 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 para o valor padrão mesmo depois de já tê-la definido com um valor local. Para redefinir um valor para o padrão novamente e também habilitar outros participantes de prioridade que possam substituir o valor padrão, mas não um valor local, chame o método ClearValue (referencie a propriedade a ser limpa como um parâmetro do método). Você nem sempre deseja que a propriedade use literalmente o valor padrão, mas remover o valor local e reverter para o valor padrão pode permitir que outro item com prioridade atue no momento, como usar o valor que veio de um definidor de estilo em um modelo de controle.

DependencyObject e encadeamento

Todas as instâncias DependencyObject devem ser criadas no thread de UI associado à Janela atual exibida por um aplicativo do Windows Runtime. Embora cada DependencyObject deva ser criado no thread principal da interface do usuário, os objetos podem ser acessados usando uma referência de dispatcher de outros threads, acessando a propriedade DispatcherQueue . Em seguida, você pode chamar métodos como TryEnqueue e executar seu código de acordo com as regras de restrições de thread no thread de UI.

Observação

Para aplicativos UWP, acesse 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. Para obter mais informações sobre as diferenças entre UWP e WinUI para o SDK de Aplicativos do Windows, consulte a migração de funcionalidade do Threading.

Os aspectos de threading do DependencyObject são relevantes porque geralmente significa que somente o código executado na thread de UI pode alterar ou até mesmo ler o valor de uma propriedade dependente. Os problemas de threading geralmente podem ser evitados em 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ó encontrará problemas de encadeamento relacionados a DependencyObject se estiver definindo seus próprios tipos DependencyObject e tentar usá-los como fontes de dados ou em outros cenários em que um DependencyObject não seja necessariamente apropriado.

Material conceitual