Propriedades de dependência personalizadas
Este tópico descreve os motivos pelos quais os desenvolvedores de aplicativos do Windows Presentation Foundation (WPF) e os autores de componentes podem querer criar uma propriedade de dependência personalizada e descreve as etapas de implementação, bem como algumas opções de implementação que podem melhorar o desempenho, a usabilidade ou a versatilidade da propriedade.
Pré-requisitos
Este tópico pressupõe que você compreenda as propriedades de dependência da perspetiva de um consumidor de propriedades de dependência existentes em classes WPF e tenha lido o tópico Visão geral das propriedades de dependência
O que é uma propriedade de dependência?
Você pode habilitar o que seria uma propriedade CLR (Common Language Runtime) para oferecer suporte a estilo, vinculação de dados, herança, animações e valores padrão implementando-a como uma propriedade de dependência. Propriedades de dependência são propriedades que são registradas com o sistema de propriedades WPF chamando o método Register (ou RegisterReadOnly) e que são apoiadas por um campo identificador DependencyProperty. As propriedades de dependência podem ser usadas apenas por tipos DependencyObject, mas DependencyObject é bastante alto na hierarquia de classes do WPF, portanto, a maioria das classes disponíveis no WPF pode oferecer suporte a propriedades de dependência. Para obter mais informações sobre propriedades de dependência e algumas das terminologias e convenções usadas para descrevê-las neste SDK, consulte Visão geral de propriedades de dependência .
Exemplos de propriedades de dependência
Exemplos de propriedades de dependência que são implementadas em classes WPF incluem a propriedade Background, a propriedade Width e a propriedade Text, entre muitas outras. Cada propriedade de dependência exposta por uma classe tem um campo estático público correspondente do tipo DependencyProperty exposto nessa mesma classe. Este é o identificador da propriedade de dependência. O identificador é nomeado usando uma convenção: o nome da propriedade de dependência com a cadeia de caracteres Property
anexada a ela. Por exemplo, o campo identificador de DependencyProperty correspondente para a propriedade Background é BackgroundProperty. O identificador armazena as informações sobre a propriedade de dependência como ela foi registrada, e o identificador é usado posteriormente para outras operações envolvendo a propriedade de dependência, como chamar SetValue.
Conforme mencionado no Dependency Properties Overview, todas as propriedades de dependência no WPF (exceto a maioria das propriedades anexadas) também são propriedades CLR devido à implementação "wrapper". Portanto, a partir do código, você pode obter ou definir propriedades de dependência chamando acessadores CLR que definem os wrappers da mesma maneira que você usaria outras propriedades CLR. Como consumidor de propriedades de dependência estabelecidas, normalmente você não usa os métodos DependencyObject, GetValue e SetValue, que são o ponto de conexão com o sistema de propriedades subjacente. Em vez disso, a implementação existente das propriedades CLR já terá chamado GetValue e SetValue nas implementações de wrapper da propriedade get
e set
, usando o campo identificador apropriadamente. Se você estiver implementando uma propriedade de dependência personalizada por conta própria, estará definindo o wrapper de maneira semelhante.
Quando se deve implementar uma propriedade de dependência?
Quando você implementa uma propriedade em uma classe, desde que sua classe derive de DependencyObject, você tem a opção de apoiar sua propriedade com um identificador de DependencyProperty e, portanto, torná-la uma propriedade de dependência. Ter a sua propriedade como uma propriedade de dependência nem sempre é necessário ou apropriado, e dependerá das necessidades do seu cenário. Às vezes, a técnica comum de respaldar a propriedade com um campo privado é adequada. No entanto, você deve implementar sua propriedade como uma propriedade de dependência sempre que desejar que sua propriedade ofereça suporte a um ou mais dos seguintes recursos do WPF:
Você quer que a sua propriedade seja configurável em um estilo. Para obter mais informações, consulte Styling and Templating.
Você quer que seu estabelecimento ofereça suporte à vinculação de dados. Para obter mais informações sobre propriedades de dependência de vinculação de dados, consulte Vincular as Propriedades de Dois Controles.
Você deseja que a sua propriedade seja definível com uma referência de recurso dinâmico. Para obter mais informações, consulte Recursos XAML.
Você deseja herdar um valor de propriedade automaticamente de um elemento pai na árvore de elementos. Nesse caso, registre-se com o método RegisterAttached, mesmo que você também crie um wrapper de propriedade para acesso CLR. Para obter mais informações, consulte Property Value Inheritance.
Você quer que seu estabelecimento seja animável. Para obter mais informações, consulte Visão geral da animação .
Você deseja que o sistema de propriedades relate quando o valor anterior da propriedade foi alterado por ações tomadas pelo sistema de propriedades, pelo ambiente ou pelo usuário, ou lendo e usando estilos. Usando metadados de propriedade, sua propriedade pode especificar um método de retorno de chamada que será invocado sempre que o sistema de propriedades determinar que o valor da propriedade foi definitivamente alterado. Um conceito relacionado é a coerção do valor da propriedade. Para obter mais informações, consulte Retornos de chamada de propriedade de dependência e validação.
Você deseja usar convenções de metadados estabelecidas que também são usadas por processos WPF, como relatar se a alteração de um valor de propriedade deve exigir que o sistema de layout recomponha os elementos visuais de um elemento. Ou você deseja poder usar substituições de metadados para que as classes derivadas possam alterar características baseadas em metadados, como o valor padrão.
Você deseja que as propriedades de um controle personalizado recebam suporte do Visual Studio WPF Designer, como Propriedades edição de janela. Para obter mais informações, consulte Visão geral da criação de controle .
Ao examinar esses cenários, você também deve considerar se pode alcançar seu cenário substituindo os metadados de uma propriedade de dependência existente, em vez de implementar uma propriedade completamente nova. Se uma substituição de metadados é prática depende do seu cenário e de quão próximo esse cenário se assemelha à implementação em propriedades e classes de dependência do WPF existentes. Para obter mais informações sobre como substituir metadados em propriedades existentes, consulte Dependency Property Metadata.
Lista de verificação para definir uma propriedade de dependência
A definição de uma propriedade de dependência consiste em quatro conceitos distintos. Estes conceitos não são necessariamente etapas processuais estritas, porque alguns deles acabam sendo combinados como linhas únicas de código na implementação:
(Opcional) Crie metadados de propriedade para a propriedade de dependência.
Registe o nome da propriedade no sistema de propriedades, especificando um tipo de proprietário e o tipo do valor da propriedade. Especifique também os metadados da propriedade, se usados.
Defina um identificador de DependencyProperty como um campo
public
static
readonly
no tipo de proprietário.Defina uma propriedade CLR "wrapper" cujo nome corresponda ao nome da propriedade de dependência. Implemente os acessores
get
eset
da propriedade CLR "wrapper" para se conectar com a propriedade de dependência que a suporta.
Registo do Imóvel no Sistema de Propriedade
Para que sua propriedade seja uma propriedade de dependência, você deve registrar essa propriedade em uma tabela mantida pelo sistema de propriedades e dar-lhe um identificador exclusivo que é usado como qualificador para operações posteriores do sistema de propriedades. Essas operações podem ser operações internas ou o seu próprio código a chamar APIs do sistema de propriedade. Para registrar a propriedade, você chama o método Register dentro do corpo da sua classe (dentro da classe, mas fora de qualquer definição de membro). O campo identificador também é fornecido pela chamada do método Register, como o valor de retorno. O motivo pelo qual a chamada de Register é feita fora de outras definições de membro é porque você usa esse valor de retorno para atribuir e criar um campo public
static
readonly
do tipo DependencyProperty como parte de sua classe. Este campo torna-se o identificador da sua propriedade de dependência.
public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
"AquariumGraphic",
typeof(Uri),
typeof(AquariumObject),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnUriChanged)
)
);
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))
Convenções de Nome para Propriedades de Dependência
Existem convenções de nomenclatura estabelecidas em relação às propriedades de dependência que você deve seguir em todas as circunstâncias, exceto em circunstâncias excecionais.
A propriedade de dependência em si terá um nome básico, "AquariumGraphic", como neste exemplo, que é dado como o primeiro parâmetro de Register. Esse nome deve ser único dentro de cada tipo de registro. As propriedades de dependência herdadas através de tipos base são consideradas como já fazendo parte do tipo de registo; Os nomes das propriedades herdadas não podem ser registrados novamente. No entanto, há uma técnica para adicionar uma classe como proprietário de uma propriedade de dependência, mesmo quando essa propriedade de dependência não é herdada; para obter detalhes, consulte Dependency Property Metadata.
Ao criar o campo identificador, nomeie-o pelo nome da propriedade como você o registrou, além do sufixo Property
. Este campo é o seu identificador para a propriedade de dependência e será usado posteriormente como entrada para as chamadas SetValue e GetValue que você irá fazer nos encapsuladores, por qualquer outro acesso de código à propriedade pelo seu próprio código, por qualquer acesso de código externo que você permitir, pelo sistema de propriedades e, potencialmente, por processadores XAML.
Observação
Definir a propriedade de dependência no corpo da classe é a implementação típica, mas também é possível definir uma propriedade de dependência no construtor estático de classe. Essa abordagem pode fazer sentido se você precisar de mais de uma linha de código para inicializar a propriedade de dependência.
Implementando o "Wrapper"
Sua implementação de wrapper deve chamar GetValue na implementação get
e SetValue na implementação set
(a chamada de registro original e o campo são mostrados aqui também para clareza).
Em todas as circunstâncias, exceto em circunstâncias excecionais, suas implementações de wrapper devem executar apenas as ações GetValue e SetValue, respectivamente. O motivo para isso é discutido no tópico Carregamento de XAML e Propriedades de Dependência.
Todas as propriedades dependentes públicas existentes que são fornecidas nas classes WPF usam este modelo de implementação de wrapper simples. A maioria da complexidade de como as propriedades dependentes funcionam é intrínseca ao comportamento do sistema de propriedades ou é implementada através de outros conceitos, como coerções ou callbacks de alteração nas propriedades por meio de metadados de propriedade.
public static readonly DependencyProperty AquariumGraphicProperty = DependencyProperty.Register(
"AquariumGraphic",
typeof(Uri),
typeof(AquariumObject),
new FrameworkPropertyMetadata(null,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnUriChanged)
)
);
public Uri AquariumGraphic
{
get { return (Uri)GetValue(AquariumGraphicProperty); }
set { SetValue(AquariumGraphicProperty, value); }
}
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty = DependencyProperty.Register("AquariumGraphic", GetType(Uri), GetType(AquariumObject), New FrameworkPropertyMetadata(Nothing, FrameworkPropertyMetadataOptions.AffectsRender, New PropertyChangedCallback(AddressOf OnUriChanged)))
Public Property AquariumGraphic() As Uri
Get
Return CType(GetValue(AquariumGraphicProperty), Uri)
End Get
Set(ByVal value As Uri)
SetValue(AquariumGraphicProperty, value)
End Set
End Property
Novamente, por convenção, o nome da propriedade wrapper deve ser o mesmo que o nome escolhido e dado como primeiro parâmetro da chamada Register que registrou a propriedade. Se o seu estabelecimento não seguir a convenção, isso não necessariamente desativa todos os usos possíveis, mas você encontrará vários problemas notáveis:
Certos aspetos de estilos e modelos não funcionarão.
A maioria das ferramentas e designers deve confiar nas convenções de nomenclatura para serializar o XAML corretamente ou para fornecer assistência no ambiente do designer a nível de propriedade.
A implementação atual do carregador WPF XAML ignora totalmente os wrappers e depende da convenção de nomenclatura ao processar valores de atributo. Para obter mais informações, consulte Propriedades de Carregamento e Dependência XAML.
Metadados de propriedade para uma nova propriedade de dependência
Quando você registra uma propriedade de dependência, o registro por meio do sistema de propriedades cria um objeto de metadados que armazena as características da propriedade. Muitas dessas características têm valores padrão estabelecidos se a propriedade for registada com as assinaturas básicas de Register. Outras assinaturas de Register permitem que você especifique os metadados desejados ao registrar a propriedade. Os metadados mais comuns fornecidos para propriedades de dependência é dar-lhes um valor padrão que é aplicado em novas instâncias que usam a propriedade.
Se você estiver criando uma propriedade de dependência que existe em uma classe derivada de FrameworkElement, você pode usar a classe de metadados mais especializada FrameworkPropertyMetadata em vez da classe PropertyMetadata base. O construtor para a classe FrameworkPropertyMetadata tem várias assinaturas onde você pode especificar várias características de metadados em combinação. Se desejar especificar apenas o valor padrão, use a assinatura que usa um único parâmetro do tipo Object. Passe o parâmetro do objeto como um valor padrão específico do tipo para a sua propriedade (o valor padrão fornecido deve ser do tipo que você forneceu como o parâmetro propertyType
na chamada Register).
Para FrameworkPropertyMetadata, também podes especificar as opções de sinalização de metadados para a tua propriedade. Esses sinalizadores são convertidos em propriedades discretas nos metadados da propriedade após o registro e são usados para comunicar determinadas condicionais a outros processos, como o mecanismo de layout.
Definindo sinalizadores de metadados apropriados
Se sua propriedade (ou alterações em seu valor) afetar a interface do usuário (UI) e, em particular, afetar como o sistema de layout deve dimensionar ou renderizar seu elemento em uma página, defina um ou mais dos seguintes sinalizadores: AffectsMeasure, AffectsArrange, AffectsRender.
AffectsMeasure indica que uma alteração nessa propriedade requer uma alteração na renderização da interface, podendo o objeto contido exigir mais ou menos espaço dentro do pai. Por exemplo, uma propriedade "Width" deve ter esse sinalizador definido.
AffectsArrange indica que uma alteração nessa propriedade requer uma alteração na renderização da interface do usuário que normalmente não requer uma alteração no espaço dedicado, mas indica que o posicionamento dentro do espaço foi alterado. Por exemplo, uma propriedade "Alignment" deve ter esse sinalizador definido.
AffectsRender indica que ocorreu alguma outra alteração que não afetará o layout e a medida, mas requer outra renderização. Um exemplo seria uma propriedade que altera uma cor de um elemento existente, como "Background".
Esses indicadores são frequentemente usados como protocolo em metadados para as suas próprias implementações personalizadas do sistema de propriedades ou de retornos de chamada de layout. Por exemplo, pode-se ter um callback OnPropertyChanged que chamará InvalidateArrange se qualquer propriedade da instância relatar uma alteração de valor e tiver AffectsArrange como
true
nos seus metadados.
Algumas propriedades podem afetar as características de renderização do elemento pai que contém, de maneiras acima e além das alterações no tamanho necessário mencionadas acima. Um exemplo é a propriedade MinOrphanLines usada no modelo de documento de fluxo, onde as alterações nessa propriedade podem alterar a renderização geral do documento de fluxo que contém o parágrafo. Use AffectsParentArrange ou AffectsParentMeasure para identificar casos semelhantes em suas próprias propriedades.
Por padrão, as propriedades de dependência oferecem suporte à vinculação de dados. Você pode deliberadamente desabilitar a vinculação de dados, para casos em que não há um cenário realista para a vinculação de dados ou em que o desempenho na vinculação de dados para um objeto grande é reconhecido como um problema.
Por padrão, a vinculação de dados Mode para propriedades de dependência assume o valor OneWay. Você sempre pode alterar a associação para ser TwoWay por instância de vinculação; para obter detalhes, consulte Especificar a direção dode vinculação . Mas, como autor da propriedade de dependência, você pode optar por fazer com que a propriedade use TwoWay modo de vinculação por padrão. Um exemplo de uma propriedade de dependência existente é MenuItem.IsSubmenuOpen; O cenário para essa propriedade é que a lógica de configuração IsSubmenuOpen e a composição de MenuItem interajam com o estilo de tema padrão. A lógica de propriedade IsSubmenuOpen usa a vinculação de dados nativamente para manter o estado da propriedade de acordo com outras propriedades de estado e chamadas de método. Outra propriedade de exemplo que vincula TwoWay por padrão é TextBox.Text.
Você também pode habilitar a herança de propriedade em uma propriedade de dependência personalizada definindo o sinalizador Inherits. A herança de propriedade é útil para um cenário em que os elementos pai e os elementos filho têm uma propriedade em comum, e faz sentido que os elementos filho tenham esse valor de propriedade específico definido para o mesmo valor que o pai o definiu. Um exemplo de propriedade hereditária é DataContext, que é usada para associações que habilitam o importante cenário mestre-detalhe para apresentação de dados. Tornando DataContext herdável, todos os elementos filho herdam o mesmo contexto de dados também. Devido à herança do valor da propriedade, você pode especificar um contexto de dados na raiz da página ou do aplicativo e não precisa reespecificá-lo para associações em todos os elementos filho possíveis. DataContext também é um bom exemplo para ilustrar que a herança substitui o valor padrão, mas pode sempre ser definida localmente em qualquer elemento filho específico; para obter detalhes, consulte Usar o padrão Master-Detail com dados hierárquicos. A herança de valor de propriedade pode ter um custo de desempenho e deve ser usada com moderação; para obter detalhes, consulte Herança de Valor de Propriedade.
Defina o sinalizador Journal para indicar se sua propriedade de dependência deve ser detetada ou usada pelos serviços de registro no diário de navegação. Um exemplo é a propriedade SelectedIndex; Qualquer item selecionado em um controle de seleção deve ser mantido quando o histórico de registro no diário é navegado.
Read-Only Propriedades de dependência
Você pode definir uma propriedade de dependência que seja somente leitura. No entanto, os cenários para definir sua propriedade como somente leitura são um pouco diferentes, assim como o procedimento para registrá-la no sistema de propriedade e expor o identificador. Para obter mais informações, consulte Read-Only Propriedades de dependência.
Collection-Type Propriedades de dependência
As propriedades de dependência do tipo de coleção têm alguns problemas de implementação adicionais a serem considerados. Para obter detalhes, consulte Collection-Type Propriedades de dependência.
Considerações sobre segurança de propriedade de dependência
As propriedades de dependência devem ser declaradas como propriedades públicas. Os campos de identificador de propriedade de dependência devem ser declarados como campos estáticos públicos. Mesmo que você tente declarar outros níveis de acesso (como protegido), uma propriedade de dependência sempre pode ser acessada por meio do identificador em combinação com as APIs do sistema de propriedades. Até mesmo um campo de identificador protegido é potencialmente acessível devido a APIs de relatório de metadados ou determinação de valor que fazem parte do sistema de propriedades, como LocalValueEnumerator. Para obter mais informações, consulte Dependency Property Security.
Propriedades de dependência e construtores de classe
Há um princípio geral na programação de código gerenciado (geralmente imposto por ferramentas de análise de código como FxCop) de que os construtores de classe não devem chamar métodos virtuais. Isso ocorre porque os construtores podem ser chamados como inicialização de base de um construtor de classe derivada, e inserir o método virtual através do construtor pode ocorrer em um estado de inicialização incompleto da instância de objeto que está sendo construída. Quando você deriva de qualquer classe que já deriva de DependencyObject, você deve estar ciente de que o próprio sistema de propriedades chama e expõe métodos virtuais internamente. Esses métodos virtuais fazem parte dos serviços do sistema de propriedades WPF. A substituição dos métodos permite que as classes derivadas participem na determinação do valor. Para evitar possíveis problemas com a inicialização do tempo de execução, você não deve definir valores de propriedade de dependência dentro de construtores de classes, a menos que siga um padrão de construtor muito específico. Para obter detalhes, consulte Padrões de Construtores Seguros para DependencyObjects.
Ver também
- Visão geral das propriedades de dependência
- Metadados de Propriedade de Dependência
- Visão geral da autoria de controle
- Collection-Type Propriedades de dependência
- Segurança de Propriedade de Dependência
- Carregamento XAML e Propriedades de Dependência
- Padrões de construtor seguros para DependencyObjects
.NET Desktop feedback