Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este tópico explica o sistema de propriedades de dependência que está disponível quando se escreve uma aplicação WinUI com definições XAML para UI.
O que é uma propriedade de dependência?
Uma propriedade de dependência é um tipo especializado de propriedade. Especificamente, é uma propriedade onde o valor da propriedade é monitorizado e influenciado por um sistema dedicado de propriedades que faz parte do WinUI.
Para suportar uma propriedade de dependência, o objeto que define a propriedade deve ser um DependencyObject (ou seja, uma classe que tenha a classe base DependencyObject em algum lugar da sua herança). Muitos dos tipos que usas para as definições de UI de uma aplicação WinUI com XAML serão uma subclasse DependencyObject e suportarão propriedades de dependência. No entanto, qualquer tipo que venha de um espaço de nomes Windows Runtime que não tenha "XAML" no nome não suportará propriedades de dependência; propriedades desses tipos são propriedades normais que não terão o comportamento de dependência do sistema de propriedades.
O objetivo das propriedades de dependência é fornecer uma forma sistémica de calcular o valor de uma propriedade com base noutras entradas (outras propriedades, eventos e estados que ocorrem dentro da sua aplicação enquanto esta está a correr). Estes outros inputs podem incluir:
- Entrada externa, como preferência do utilizador
- Mecanismos de determinação de propriedades just-in-time, como associação de dados, animações e storyboards
- Padrões de modelação de múltiplos usos, como recursos e estilos,
- Valores conhecidos através das relações pai-filho com outros elementos da árvore de objetos
Uma propriedade de dependência representa ou suporta uma funcionalidade específica do modelo de programação para definir uma aplicação WinUI com XAML para UI. Estas funcionalidades incluem:
- Vinculação de dados
- Estilos
- Animações com storyboard
- comportamento "PropertyChanged"; uma propriedade de dependência pode ser implementada para fornecer callbacks que podem propagar alterações a outras propriedades de dependência.
- Usar um valor predefinido que provém dos metadados da propriedade
- Utilidade geral do sistema de propriedades, como ClearValue e consulta de metadados
Propriedades de dependência e propriedades do Windows Runtime
As propriedades de dependência estendem a funcionalidade básica das propriedades do Windows Runtime ao fornecer um armazenamento global e interno de propriedades que apoia todas as propriedades de dependência numa aplicação em tempo de execução. Esta é uma alternativa ao padrão padrão de apoiar uma propriedade com um terreno privado que é privado na classe de definição de propriedade. Pode pensar neste armazenamento interno de propriedades como um conjunto de identificadores e valores de propriedade que existem para qualquer objeto em particular (desde que seja um DependencyObject). Em vez de ser identificada pelo nome, cada propriedade no armazenamento é identificada por uma instância DependencyProperty. No entanto, o sistema de propriedades esconde maioritariamente este detalhe de implementação: normalmente pode acess propriedades de dependência usando um nome simples (o nome da propriedade programática na linguagem de código que está a usar, ou um nome de atributo quando está a escrever XAML).
O tipo base que fornece a base do sistema de propriedades de dependência é DependencyObject. DependencyObject define métodos que podem aceder à propriedade de dependência, e instâncias de uma classe derivada de DependencyObject suportam internamente o conceito de armazenamento de propriedades que mencionámos anteriormente.
Aqui está um resumo da terminologia que usamos na documentação ao discutir propriedades de dependência:
| Term | Description |
|---|---|
| Propriedade de dependência | Uma propriedade que existe num identificador DependencyProperty (ver abaixo). Normalmente, este identificador está disponível como um membro estático da classe derivada definidora do DependencyObject . |
| Identificador de propriedade de dependência | Um valor constante para identificar a propriedade, tipicamente é público e apenas leitura. |
| Invólucro de propriedades | As implementações executáveis get e set para uma propriedade do Windows Runtime. Ou, a projeção específica de linguagem da definição original. Uma implementação do wrapper de propriedade get chama o GetValue, passando o identificador de propriedade de dependência relevante. |
O empacotador de propriedades não é apenas uma conveniência para os utilizadores, mas também expõe a propriedade de dependência a qualquer processo, ferramenta ou projeção que use definições do Windows Runtime para propriedades.
O exemplo seguinte define uma propriedade de dependência personalizada conforme definida para C#, e mostra a relação do identificador de propriedade de dependência com o envelope de propriedades.
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 propriedades de dependência para quem prefira aprender conceitos através do código. Para uma explicação mais completa deste exemplo, veja Propriedades de dependência personalizadas.
Precedência do valor da propriedade de dependência
Quando obtém o valor de uma propriedade de dependência, está a obter um valor que foi determinado para essa propriedade através de qualquer uma das entradas que participam no sistema de propriedades do Windows Runtime. A precedência de 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 esteja igualmente familiarizado com a ordem básica de precedência. Caso contrário, pode encontrar-se numa situação em que tenta definir uma propriedade num nível de precedência, mas outra coisa (o sistema, chamadas de terceiros, algum do seu próprio código) está a defini-la noutro nível, e vai ficar frustrado a tentar perceber qual o valor da propriedade usado e de onde veio esse valor.
Por exemplo, estilos e modelos destinam-se a ser um ponto de partida partilhado para definir os valores das propriedades e, assim, as aparências dos controlos. Mas numa instância de controlo particular podes querer alterar o seu valor em relação ao valor padrão padrão, como atribuir a esse controlo uma cor de fundo diferente ou uma cadeia de texto diferente como conteúdo. O sistema de propriedades Windows Runtime considera valores locais com precedência superior aos fornecidos por estilos e modelos. Isso permite que valores específicos da aplicação substituam os modelos, tornando os controlos úteis para o seu próprio uso na interface da aplicação.
Lista de precedência de propriedades de dependência
A ordem definitiva que o sistema de propriedades utiliza para atribuir o valor em tempo de execução a uma propriedade de dependência é a seguinte. A precedência mais alta é listada primeiro. Encontrará explicações mais detalhadas logo após esta lista.
- Valores animados: Animações ativas, animações de estado visual, ou animações que têm comportamento HoldEnd. Para ter algum efeito prático, uma animação aplicada a uma propriedade deve ter precedência sobre o valor base (unanime), mesmo que esse valor tenha sido definido localmente.
- Valor local: Um valor local pode ser definido pela conveniência do wrapper de propriedades, que também equivale a definir como um atributo ou elemento de propriedade em XAML, ou por uma chamada ao método SetValue usando uma propriedade de uma instância específica. Se definir um valor local usando um binding ou um recurso estático, cada um atua em precedência como se um valor local tivesse sido definido, e bindings ou referências de recursos são apagadas se um novo valor local for definido.
- Propriedades modeladas: Um elemento tem estes elementos se foi criado como parte de um modelo (a partir de um ControlTemplate ou DataTemplate).
- Colocadores de estilo: Valores de um Setter dentro de estilos de páginas ou recursos de aplicação.
- Valor padrão: Uma propriedade de dependência pode ter um valor padrão como parte dos seus metadados.
Propriedades templateadas
Propriedades templateadas como item de precedência não se aplicam a nenhuma propriedade de um elemento que declare diretamente na marcação de página em XAML. O conceito de propriedade modelada existe apenas para objetos criados quando o WinUI aplica um modelo XAML a um elemento da UI e, assim, define a sua aparência.
Todas as propriedades definidas a partir de um template de controlo têm valores de algum tipo. Estes valores são quase como um conjunto alargado de valores padrão para o controlo e estão frequentemente associados a valores que podes redefinir mais tarde definindo diretamente os valores das propriedades. Assim, os valores do conjunto de modelos devem ser distinguíveis de um valor local verdadeiro, para que qualquer novo valor local possa sobrescrevê-lo.
Observação
Em alguns casos, o template pode sobrescrever até valores locais, caso o template falhe em expor referências de extensão de marcação {TemplateBinding} para propriedades que deveriam ser configuráveis nas instâncias. Isto é normalmente feito apenas se a propriedade não for realmente destinada a ser definida em instâncias, por exemplo, se for relevante apenas para visuais e comportamento do template e não para a função pretendida ou a lógica de runtime do controlo que usa o template.
Ligações e precedência
As operações de binding têm a precedência apropriada para o âmbito em que 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 property setter aplica-se como um style setter. Como as ligações têm de esperar até ao tempo de execução para obter valores das fontes de dados, o processo de determinar a precedência de valor de qualquer propriedade estende-se também ao tempo de execução.
Não só as associações operam com a mesma precedência que um valor local, como são realmente um valor local, onde a associação é o marcador de posição para um valor que é adiado. Se tiver uma associação para um valor de propriedade e definir um valor local durante a execução, esse valor substitui completamente a associação. De forma semelhante, se chamar SetBinding para definir uma ligação que só é criada em tempo de execução, substitui qualquer valor local que possa ter aplicado em XAML ou por código previamente executado.
Animações com storyboard e valor base
As animações com storyboard atuam sobre um conceito de valor base. O valor base é o valor determinado pelo sistema de propriedades usando a sua precedência, mas omitindo esse último passo de procurar animações. Por exemplo, um valor base pode vir do template de um controlo, ou pode vir de definir um valor local numa instância de um controlo. ** De qualquer forma, aplicar uma animação irá sobrescrever esse valor base e aplicar o valor animado enquanto a sua animação continuar a ser executada.
Para uma propriedade animada, o valor base pode ainda ter um efeito no comportamento da animação, se essa animação não especificar explicitamente tanto From como To, ou se a animação reverter a propriedade para o seu valor base quando concluída. Nestes casos, quando uma animação deixa de ser executada, a ordem de precedência restante é aplicada novamente.
No entanto, uma animação que especifica um To com um comportamento HoldEnd pode sobrescrever um valor local até que a animação seja removida, mesmo quando visualmente parece estar parada. Conceptualmente, isto é como uma animação que está a correr para sempre, mesmo que não haja uma animação visual na interface.
Múltiplas animações podem ser aplicadas a uma única propriedade. Cada uma destas animações pode ter sido definida para substituir valores base que vieram de diferentes pontos na precedência de valor. No entanto, estas animações estarão todas a correr simultaneamente em tempo de execução, o que muitas vezes significa que têm de combinar os seus valores porque cada animação tem influência igual sobre o valor. Isto depende exatamente de como as animações são definidas e do tipo de valor que está a ser animado.
Para mais informações, veja Animações com storyboards.
Valores padrão
O estabelecimento do valor padrão para uma propriedade de dependência com um valor PropertyMetadata é explicado com mais detalhe no tópico Propriedades de dependência personalizadas .
As propriedades de dependência continuam a ter valores padrão mesmo que esses valores não estivessem explicitamente definidos nos metadados dessa propriedade. A menos que tenham sido alterados por metadados, os valores predefinidos para as propriedades de dependência do Windows Runtime são geralmente um dos seguintes:
- Uma propriedade que utiliza um objeto em tempo de execução ou o tipo básico de Objeto (um tipo de referência) tem um valor padrão de nulo. Por exemplo, DataContext é nulo até ser deliberadamente definido ou herdado.
- Uma propriedade que utiliza um valor básico como números ou um valor booleano ( um tipo de valor) usa um padrão esperado para esse valor. Por exemplo, 0 para inteiros e números de ponto flutuante, falso para um booleano.
- Uma propriedade que utiliza uma estrutura do Windows Runtime tem um valor predefinido que é obtido ao chamar o construtor padrão implícito dessa estrutura. Este construtor utiliza os valores padrão de cada um dos campos de valor básicos da estrutura. Por exemplo, um valor padrão para um valor de Pontos é inicializado com os seus valores X e Y como 0.
- Uma propriedade que utiliza uma enumeração tem um valor padrão do primeiro membro definido nessa enumeração. Verifique a referência para enumerações específicas e veja qual é o valor padrão.
- Uma propriedade que usa uma cadeia (System.String para .NET, Platform::String para C++/CX) tem como valor padrão uma cadeia vazia ("").
- As propriedades de coleção normalmente não são implementadas como propriedades de dependência, por razões discutidas mais adiante neste tópico. Mas se implementar uma propriedade de coleção personalizada e quiser que ela seja uma propriedade de dependência, certifique-se de evitar um singleton não intencional, como descrito perto do final das propriedades de dependência personalizadas.
Funcionalidade de uma propriedade proporcionada por uma propriedade dependente
Vinculação de dados
Uma propriedade de dependência pode ter o seu valor definido através da aplicação de uma ligação de dados. Data binding utiliza a sintaxe da 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 databound, a determinação final do valor da propriedade é adiada até ao tempo de execução. Nesse momento, o valor é obtido a partir de uma fonte de dados. O papel que o sistema de propriedades de dependência desempenha aqui é permitir um comportamento temporário para operações como carregar XAML quando o valor ainda não é conhecido, e depois fornecer o valor em tempo de execução interagindo com o motor de data binding do Windows Runtime.
O exemplo seguinte define o valor Text para um elemento TextBlock , usando uma ligação em XAML. A ligação utiliza um contexto de dados herdado e uma fonte de dados de objetos. (Nenhum destes é mostrado no exemplo abreviado; para uma amostra mais completa que mostre contexto e fonte, veja Ligação de dados em profundidade.)
<Canvas>
<TextBlock Text="{Binding Team.TeamName}"/>
</Canvas>
Também podes estabelecer bindings usando código em vez de XAML. Ver SetBinding.
Observação
Ligações como esta são tratadas como um valor local para efeitos de precedência de valor de propriedade de dependência. Se definir outro valor local para uma propriedade que originalmente detinha um valor de ligação , irá sobrescrever completamente a ligação, não apenas o valor em tempo de execução da ligação. {x:Bind} As associações são implementadas usando código gerado que definirá um valor local para a propriedade. Se definir um valor local para uma propriedade que usa {x:Bind}, esse valor será substituído na próxima vez que a ligação for avaliada, como quando observa uma alteração de propriedade no seu objeto de origem.
Fontes de ligação, alvos de ligação, o papel do FrameworkElement
Para ser a fonte de uma ligação, uma propriedade não precisa de ser uma propriedade de dependência; Geralmente podes usar qualquer propriedade como fonte de binding, embora isso dependa da tua linguagem de programação e cada uma tenha certos casos limites. No entanto, para ser o alvo de uma extensão de marcação {Binding} ou Binding, essa propriedade deve ser uma propriedade de dependência. {x:Bind} não tem este requisito, pois utiliza código gerado para aplicar os seus valores de ligação.
Se estiver a criar uma ligação em código, note que a API SetBinding está definida apenas para FrameworkElement. No entanto, pode criar uma definição de ligação usando BindingOperations em vez disso, e assim referenciar qualquer propriedade DependencyObject .
Para código ou XAML, lembre-se que o DataContext é uma propriedade FrameworkElement . Ao usar uma forma de herança de propriedade pai-filho (tipicamente estabelecida na marcação XAML), o sistema de ligação pode resolver um DataContext que existe num elemento pai. Esta 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 detenha o seu próprio valor DataContext . No entanto, o elemento pai herdado deve ser um FrameElement para definir e manter o DataContext. Em alternativa, deve definir a ligação de modo a que possa funcionar com um valor nulo para o DataContext.
Configurar a ligação não é a única coisa necessária na maioria dos cenários de vinculação de dados. Para que uma ligação unidirecional ou bidirecional seja eficaz, a propriedade de origem deve suportar notificações de alteração que se propagam para o sistema de ligação e, assim, para o destino. Para fontes de ligação personalizadas, isto significa que a propriedade deve ser uma propriedade de dependência, ou o objeto deve suportar INotifyPropertyChanged. As coleções devem suportar INotifyCollectionChanged. Certas classes suportam estas interfaces nas suas implementações para que sejam úteis como classes base para cenários de ligação de dados; um exemplo de tal classe é a ObservableCollection<T>. Para mais informações sobre ligação de dados e como a ligação de dados se relaciona com o sistema de propriedades, consulte ligação de dados em detalhe.
Observação
Os tipos aqui listados suportam fontes de dados Microsoft .NET. As fontes de dados em C++/CX utilizam diferentes interfaces para notificação de alterações ou comportamento observável, veja Ligação de dados em detalhe.
Estilos e modelos
Estilos e templates são dois dos cenários para propriedades definidas como propriedades de dependência. Os estilos são úteis para definir propriedades que definem a interface da aplicação. Os estilos são definidos como recursos em XAML, seja como uma entrada numa coleção de Recursos , ou em ficheiros XAML separados, como dicionários de recursos de temas. 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: define a maior parte da aparência visual e do estado visual de um Control. Para mais informações sobre estilos, e alguns exemplos de XAML que definem um Estilo e usam setters, veja Controlos de Estilo.
Valores que vêm de estilos ou modelos são valores adiados, semelhantes a ligações. Isto serve para que os utilizadores possam readaptar os componentes de controlo ou redefinir os estilos. E é por isso que os definidores de propriedades em estilos só podem definir propriedades de dependência, não propriedades comuns.
Animações com storyboard
Podes animar o valor de uma propriedade de dependência usando uma animação com storyboard. As animações com storyboard no Windows Runtime não são meramente decorações visuais. É mais útil pensar nas animações como uma técnica de máquina de estados que permite definir os valores de propriedades individuais ou de todas as propriedades e visuais de um controlo, e alterar esses valores ao longo do tempo.
Para ser animada, 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 alvo deve ser suportado por um dos tipos de animação existentes derivados da Timeline. Os valores de Cor, Duplo e Ponto podem ser animados usando técnicas de interpolação ou keyframes. A maioria dos outros valores pode ser animada usando fotogramas-chave discretos do Objeto .
Quando uma animação é aplicada e está em execução, o valor animado tem precedência sobre qualquer valor (como um valor local) que a propriedade teria de outra forma. As animações também têm um comportamento de HoldEnd opcional que pode fazer com que as animações se apliquem a valores de propriedade mesmo que a animação pareça visualmente estar parada.
O princípio da máquina de estados é incorporado pelo uso de animações em storyboard como parte do modelo de estado do VisualStateManager para controlos. Para mais informações sobre animações com storyboard, veja Animações com storyboard. Para mais informações sobre o VisualStateManager e definição de estados visuais para controlos, consulte Animações com storyboard para estados visuais ou modelos de Controlo.
Comportamento de mudança de propriedade
O comportamento de alteração de propriedades é a origem da expressão "dependência" na terminologia de propriedades 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 muitos quadros. No sistema de propriedades Windows Runtime, cada propriedade de dependência pode especificar um callback que é invocado sempre que o valor da sua propriedade muda. Este callback pode ser usado para notificar ou alterar valores de propriedades relacionadas, de forma geralmente síncrona. Muitas propriedades de dependência existentes têm um comportamento de mudança de propriedade. Também pode adicionar comportamentos semelhantes de callback a propriedades de dependência personalizadas e implementar os seus próprios callbacks de alteração de propriedade. Veja Propriedades de dependência personalizadas para um exemplo.
Windows 10 introduz o método RegisterPropertyChangedCallback. Isto permite que o código da aplicação se registre para notificações de alterações quando a propriedade de dependência especificada é alterada numa instância do DependencyObject.
Valor padrão e ClearValue
Uma propriedade de dependência pode ter um valor padrão definido como parte dos metadados da sua propriedade. Para uma propriedade de dependência, o seu valor padrão não se torna irrelevante depois de a propriedade ter sido definida pela primeira vez. O valor padrão pode aplicar-se novamente em tempo de execução sempre que algum outro determinante na precedência de valor desaparece. (A precedência de valor de propriedades de dependência é discutida na secção seguinte.) Por exemplo, podes remover deliberadamente um valor de estilo ou uma animação que se aplica a uma propriedade, mas queres que o valor seja um padrão razoável depois disso. O valor padrão da propriedade de dependência pode fornecer este valor, sem necessidade de definir especificamente o valor de cada propriedade como um passo extra.
Pode definir deliberadamente uma propriedade para o valor padrão mesmo depois de já a ter definido com um valor local. Para reinicializar um valor para ser novamente o padrão e permitir que outros participantes com precedência possam sobrepor o valor padrão sem afetar um valor local, chame o método ClearValue (especifique a propriedade a limpar como parâmetro do método). Nem sempre quer que a propriedade use literalmente o valor padrão, mas limpar o valor local e voltar ao valor predefinido pode ativar outro item em precedência que deseja aplicar agora, como usar o valor que veio de um definidor de estilos em um modelo de controle.
DependencyObject e encadeamento
Todas as instâncias de DependencyObject devem ser criadas no thread UI que está associado à Janela atual mostrada por uma aplicação WinUI. Embora cada DependencyObject deva ser criado na thread principal da interface, os objetos podem ser acedidos usando uma referência dispatcher de outros threads, acedendo à propriedade DispatcherQueue . Depois podes chamar métodos como TryEnqueue e executar o teu código dentro das restrições de threads no thread da interface de utilizador.
Observação
Para aplicações UWP, acesse a propriedade Dispatcher. Depois podes chamar métodos como RunAsync no objeto CoreDispatcher e executar o teu código dentro das regras de restrição do thread no thread UI. Para mais informações sobre as diferenças entre o UWP e o WinUI 3 para o Windows App SDK, veja Migração de funcionalidades de threading.
Os aspetos de threading de DependencyObject são relevantes porque geralmente significam que apenas 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. Os problemas de threading geralmente podem ser evitados no código típico da interface do usuário que faz uso correto de padrões assíncronos e threads de trabalho em segundo plano. Normalmente, você só enfrenta 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 seja necessariamente apropriado.
Tópicos relacionados
Material conceptual
- Propriedades de dependência personalizadas
- Visão geral das propriedades anexadas
- Vinculação de dados em profundidade
- Animações criadas com storyboard
APIs relacionadas com propriedades de dependência
Windows developer