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 perspectiva 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. Para seguir os exemplos deste tópico, você também deve ter noções básicas de XAML e saber como escrever aplicativos do WPF.

O que é uma propriedade de dependência?

Você pode habilitar o que seria uma propriedade CLR (Common Language Runtime) para oferecer suporte a estilo, associaçã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 Register método (ou RegisterReadOnly) e que são apoiadas por um DependencyProperty campo identificador. As propriedades de dependência podem ser usadas apenas por DependencyObject tipos, mas DependencyObject é bastante alta 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 das 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, a propriedade e a BackgroundWidthText propriedade, 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. Esse é o identificador da propriedade de dependência. O identificador é nomeado com uma convenção: o nome da propriedade de dependência com a cadeia de caracteres Property acrescentada a ela. Por exemplo, o campo identificador correspondente DependencyProperty para a Background propriedade é 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 na Visão geral das propriedades de dependência, 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, você normalmente não usa os DependencyObject métodos 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 dentro das get implementações e set wrapper da propriedade, usando o campo identificador apropriadamente. Se estiver implementando uma propriedade de dependência personalizada, você definirá um wrapper de maneira semelhante.

Quando é necessário implementar uma propriedade de dependência?

Quando você implementa uma propriedade em uma classe, desde que sua classe derive de , você tem a opção de DependencyObjectapoiar sua propriedade com um DependencyProperty identificador e, assim, torná-la uma propriedade de dependência. Transformar a propriedade em uma propriedade de dependência nem sempre é necessário ou apropriado e dependerá das necessidades do cenário. Às vezes, a técnica típica de dar suporte à 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ê deseja que a propriedade seja configurável em um estilo. Para obter mais informações, consulte Estilo e modelagem.

  • Você deseja que a propriedade dê suporte à vinculação de dados. Para obter mais informações sobre propriedades de dependência com a vinculação de dados, consulte Associar as propriedades de dois controles.

  • Você deseja que a propriedade seja configurável com uma referência de recurso dinâmico. Para obter mais informações, consulte Recursos XAML.

  • Você deseja herdar um valor da propriedade automaticamente de um elemento pai na árvore de elementos. Nesse caso, registre-se com o RegisterAttached método, mesmo se você também criar um wrapper de propriedade para acesso CLR. Para obter mais informações, consulte Herança do valor da propriedade.

  • Você deseja que a propriedade possa ser animada. Para obter mais informações, consulte Visão geral de animação.

  • Você deseja que o sistema de propriedades relate quando o valor anterior da propriedade for alterado por ações executadas pelo sistema de propriedades, ambiente ou usuário ou pela leitura e pelo uso de estilos. Ao usar metadados de propriedade, a 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 da 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 conseguir 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 a edição da janela Propriedades . 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 obter o 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 quão próximo esse cenário se assemelha à implementação em propriedades e classes de dependência WPF existentes. Para obter mais informações sobre a substituição de metadados em propriedades existentes, consulte Metadados de propriedades de dependência.

Lista de verificação para definição de uma propriedade de dependência

A definição de uma propriedade de dependência consiste em quatro conceitos distintos. Esses conceitos não são necessariamente etapas de procedimentos estritas, porque alguns deles acabam sendo combinados como linhas de código individuais na implementação:

  • (Opcional) Crie metadados de propriedade para a propriedade de dependência.

  • Registre 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 de propriedade, se forem usados.

  • Defina um identificador como um DependencyPropertypublicstaticreadonly campo no tipo de proprietário.

  • Defina uma propriedade "wrapper" CLR cujo nome corresponda ao nome da propriedade dependency. Implemente a propriedade "wrapper" do get CLR e set os acessadores para se conectar com a propriedade de dependência que a suporta.

Registrando a propriedade no sistema de propriedades

Para que a propriedade seja uma propriedade de dependência, é necessário registrá-la em uma tabela mantida pelo sistema de propriedades e fornecer a ela um identificador exclusivo, que é usado como o qualificador para operações posteriores do sistema de propriedades. Essas operações podem ser operações internas ou suas próprias APIs do sistema de propriedades de chamada de código. Para registrar a propriedade, você chama o Register método 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 de Register método, como o valor de retorno. O motivo pelo qual a Register chamada é feita fora de outras definições de membro é porque você usa esse valor de retorno para atribuir e criar umreadonlypublicstaticcampo de tipo DependencyProperty como parte de sua classe. Esse campo se torna o identificador da 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 nomenclatura para propriedades de dependência

Existem convenções de nomenclatura estabelecidas referentes a propriedades de dependência que sempre devem ser seguidas, exceto em circunstâncias excepcionais.

A própria propriedade dependency terá um nome básico, "AquariumGraphic" como neste exemplo, que é dado como o primeiro parâmetro de Register. Esse nome deve ser exclusivo dentro de cada tipo de registro. As propriedades de dependência herdadas por meio dos tipos base já são consideradas uma parte do tipo de registro; os nomes das propriedades herdadas não podem ser registrados novamente. No entanto, há uma técnica para adicionar uma classe como o proprietário de uma propriedade de dependência mesmo quando a propriedade de dependência não é herdada; para obter detalhes, consulte Metadados de propriedades de dependência.

Ao criar o campo de identificador, nomeie-o com o nome da propriedade registrado, mais o sufixo Property. Esse campo é seu identificador para a propriedade de dependência e será usado posteriormente como uma entrada para as SetValue chamadas e que você fará nos wrappers, por qualquer outro acesso de código à propriedade por seu próprio código, por qualquer acesso de código externo permitido, pelo sistema de propriedades e GetValue , potencialmente, por processadores XAML.

Observação

A definição da propriedade de dependência no corpo da classe é uma implementação típica, mas também é possível definir uma propriedade de dependência no construtor estático da classe. Essa abordagem poderá 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 e na getset implementação (a chamada de registro original e SetValue o campo são mostrados aqui também para maior clareza).

Em todas as circunstâncias, exceto em circunstâncias excepcionais, suas implementações de wrapper devem executar apenas as GetValueSetValue e ações, respectivamente. A razão para isso é abordada no tópico Carregamento de XAML e propriedades de dependência.

Todas as propriedades de dependência pública existentes que são fornecidas nas classes WPF usam esse modelo de implementação de wrapper simples; A maior parte da complexidade de como as propriedades de dependência funcionam é inerentemente um comportamento do sistema de propriedades ou é implementada por meio de outros conceitos, como coerção ou retornos de chamada de alteração de propriedade 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 Register chamada que registrou a propriedade. Se a propriedade não seguir a convenção, isso não necessariamente desabilitará todos os usos possíveis, mas você encontrará vários problemas notáveis:

  • Alguns aspectos de estilos e modelos não funcionarão.

  • A maioria das ferramentas e designers deve confiar nas convenções de nomenclatura para serializar corretamente o XAML ou para fornecer assistência ao ambiente de designer em um nível por propriedade.

  • A implementação atual do carregador XAML do WPF ignora totalmente os wrappers e depende da convenção de nomenclatura ao processar valores de atributo. Para obter mais informações, consulte Carregamento de XAML e propriedades de dependência.

Metadados de propriedade de uma nova propriedade de dependência

Quando uma propriedade de dependência é registrada, o registro cria um objeto de metadados por meio do sistema de propriedades que armazena as características da propriedade. Muitas dessas características têm padrões que são definidos se a propriedade for registrada com as assinaturas simples do 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 são fornecer a elas um valor padrão que é aplicado a novas instâncias que usam a propriedade.

Se você estiver criando uma propriedade de dependência que existe em uma classe derivada de , você pode usar a classe de FrameworkElementmetadados mais especializada em vez da classe FrameworkPropertyMetadata base PropertyMetadata . O construtor para a FrameworkPropertyMetadata classe tem várias assinaturas onde você pode especificar várias características de metadados em combinação. Se você quiser especificar somente o valor padrão, use a assinatura que usa um único parâmetro do tipo Object. Passe esse parâmetro de objeto como um valor padrão específico do tipo para sua propriedade (o valor padrão fornecido deve ser o tipo fornecido como o propertyType parâmetro na Register chamada).

Para FrameworkPropertyMetadata, você também pode especificar sinalizadores de opção de metadados para sua propriedade. Esses sinalizadores são convertidos em propriedades discretas nos metadados de propriedade após o registro e são usados para comunicar alguns condicionais para outros processos como o mecanismo de layout.

configurando 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, , AffectsArrangeAffectsRender.

    • AffectsMeasure indica que uma alteração nessa propriedade requer uma alteração na renderização da interface do usuário, onde o objeto que contém pode exigir mais ou menos espaço dentro do pai. Por exemplo, uma propriedade “Largura” 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 “Alinhamento” 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 a cor de um elemento existente, como “Tela de Fundo”.

    • Em geral, esses sinalizadores são usados como um protocolo em metadados de suas próprias implementações de substituição do sistema de propriedades ou de retornos de chamada do layout. Por exemplo, você pode ter um OnPropertyChanged retorno de chamada que chamará InvalidateArrange se qualquer propriedade da instância relatar uma alteração de valor e tiver AffectsArrange como true em seus metadados.

  • Algumas propriedades podem afetar as características de renderização do elemento pai recipiente, de maneiras que estão além das alterações no espaço necessário mencionado acima. Um exemplo é a propriedade usada no modelo de documento de fluxo, em que as alterações nessa propriedade podem alterar a MinOrphanLines renderização geral do documento de fluxo que contém o parágrafo. Use AffectsParentArrange ou AffectsParentMeasure identifique casos semelhantes em suas próprias propriedades.

  • Por padrão, as propriedades de dependência dão suporte à vinculação de dados. Você pode deliberadamente desabilitar a vinculação de dados, em casos nos quais não haja nenhum cenário realista para a vinculação de dados ou nos quais o desempenho na vinculação de dados de um objeto grande seja reconhecido como um problema.

  • Por padrão, a OneWayvinculação Mode de dados para propriedades de dependência tem como padrão . Você sempre pode alterar a associação para ser TwoWay por instância de vinculação, para obter detalhes, consulte Especificar a direção da associação. Mas, como autor da propriedade de dependência, você pode optar por fazer com que a propriedade use TwoWay o modo de vinculação por padrão. Um exemplo de uma propriedade de dependência existente é ; o cenário para essa propriedade é MenuItem.IsSubmenuOpenque a lógica de configuração e a IsSubmenuOpen composição de interagem com o estilo de MenuItem tema padrão. A IsSubmenuOpen lógica da propriedade 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 se 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 Inherits sinalizador. A herança de propriedade é útil para um cenário em que os elementos pai e filho têm uma propriedade em comum e faz sentido que os elementos filho tenham esse valor da propriedade específico definido com o mesmo valor definido pelo pai. Um exemplo de propriedade hereditária é , que é DataContextusada para operações de vinculação para habilitar o cenário mestre-detalhe importante para apresentação de dados. Ao tornar DataContext herdável, todos os elementos filho herdam esse contexto de dados também. Devido à herança do valor da propriedade, é possível especificar um contexto de dados na página ou na raiz do aplicativo e não é necessário especificá-lo novamente 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 sempre pode ser definida localmente em qualquer elemento filho específico; para obter detalhes, consulte Usar o padrão mestre-detalhe com dados hierárquicos. A herança do valor da propriedade tem um possível custo de desempenho e, portanto, deve ser usada com moderação; para obter detalhes, consulte Herança do valor da propriedade.

  • Defina o sinalizador para indicar se sua propriedade de dependência deve ser detectada Journal ou usada pelos serviços de registro no diário de navegação. Um exemplo é a SelectedIndex propriedade: qualquer item selecionado em um controle de seleção deve ser persistido quando o histórico de registro no diário é navegado.

Propriedades de dependência somente leitura

É possível definir uma propriedade de dependência somente leitura. No entanto, os cenários para os motivos pelos quais é possível definir a propriedade como somente leitura são um pouco diferentes, assim como o procedimento para registrá-la no sistema de propriedades e expôr o identificador. Para obter mais informações, consulte Propriedades de dependência somente leitura.

Propriedades de dependência do tipo de coleção

As propriedades de dependência do tipo de coleção apresentam algumas outras questões de implementação a serem consideradas. Para obter detalhes, consulte Propriedades de dependência do tipo de coleção.

Considerações sobre segurança das propriedades de dependência

As propriedades de dependência devem ser declaradas como propriedades públicas. Os campos do identificador das propriedades de dependência devem ser declarados como campos estáticos públicos. Mesmo se você tentar declarar outros níveis de acesso (como protegido), uma propriedade de dependência sempre poderá 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 Segurança das propriedades de dependência.

Propriedades de dependência e construtores de classe

Há um princípio geral na programação de código gerenciado (frequentemente imposto pelas ferramentas de análise de código como o FxCop) que estabelece que os construtores de classe não devem chamar métodos virtuais. Isso ocorre porque os construtores podem ser chamados como a inicialização base de um construtor de classe derivada e a entrada no método virtual por meio do construtor pode ocorrer em um estado de inicialização incompleto da instância do objeto que está sendo construída. Quando você deriva de qualquer classe que já deriva do , você deve estar ciente de que o próprio sistema de DependencyObjectpropriedades 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 da determinação do valor. Para evitar possíveis problemas com a inicialização em runtime, você não deverá definir valores da propriedade de dependência dentro de construtores de classes, a menos que um padrão de construtor muito específico seja seguido. Para obter detalhes, consulte Padrões de construtor seguros para DependencyObjects.

Confira também