Propriedades de dependência personalizadas (WPF .NET)

Os desenvolvedores de aplicativos do Windows Presentation Foundation (WPF) e os autores de componentes podem criar propriedades de dependência personalizadas para estender a funcionalidade de suas propriedades. Ao contrário de uma propriedade CLR (Common Language Runtime), uma propriedade de dependência adiciona suporte para estilo, associação de dados, herança, animações e valores padrão. Background, Widthe Text são exemplos de propriedades de dependência existentes em classes WPF. Este artigo descreve como implementar propriedades de dependência personalizadas e apresenta opções para melhorar o desempenho, a usabilidade e a versatilidade.

Importante

A documentação do Guia da Área de Trabalho para .NET 7 e .NET 6 está em construção.

Pré-requisitos

O artigo pressupõe um conhecimento básico das propriedades de dependência e que você leu Visão geral das propriedades de dependência. Para seguir os exemplos neste artigo, é útil se você estiver familiarizado com XAML (Extensible Application Markup Language) e souber como escrever aplicativos WPF.

Identificador de propriedade de dependência

Propriedades de dependência são propriedades que são registradas com o sistema de propriedades WPF através de RegisterRegisterReadOnly ou chamadas. O Register método retorna uma instância que contém o nome registrado e as características de uma DependencyProperty propriedade de dependência. Você atribuirá a instância a DependencyProperty um campo somente leitura estático, conhecido como identificador de propriedade de dependência, que, por convenção, é chamado <property name>Property. Por exemplo, o campo identificador da Background propriedade é sempre BackgroundProperty.

O identificador de propriedade de dependência é usado como um campo de suporte para obter ou definir valores de propriedade, em vez do padrão padrão de backup de uma propriedade com um campo privado. O sistema de propriedades não usa apenas o identificador, os processadores XAML podem usá-lo e seu código (e possivelmente o código externo) pode acessar as propriedades de dependência por meio de seus identificadores.

As propriedades de dependência só podem ser aplicadas a classes derivadas de DependencyObject tipos. A maioria das classes WPF oferece suporte a propriedades de dependência, porque DependencyObject está próxima à raiz da hierarquia de classes WPF. Para obter mais informações sobre propriedades de dependência e a terminologia e convenções usadas para descrevê-las, consulte Visão geral das propriedades de dependência.

Invólucros de propriedade de dependência

As propriedades de dependência do WPF que não são propriedades anexadas são expostas por um wrapper CLR que implementa e set acessaget. Usando um wrapper de propriedade, os consumidores de propriedades de dependência podem obter ou definir valores de propriedade de dependência, assim como fariam com qualquer outra propriedade CLR. Os get e acessadores interagem com o sistema de propriedades subjacente por meio de DependencyObject.GetValue e setDependencyObject.SetValue chamadas, passando o identificador de propriedade de dependência como um parâmetro. Os consumidores de propriedades de dependência normalmente não ligam GetValue ou SetValue diretamente, mas se você estiver implementando uma propriedade de dependência personalizada, usará esses métodos no wrapper.

Quando implementar uma propriedade de dependência

Ao implementar uma propriedade em uma classe derivada do , você a torna uma propriedade de dependência fazendo backup de DependencyObjectsua propriedade com um DependencyProperty identificador. Se é benéfico criar uma propriedade de dependência, depende do seu cenário. Embora o suporte de sua propriedade com um campo privado seja adequado para alguns cenários, considere implementar uma propriedade de dependência se quiser que sua propriedade ofereça suporte a um ou mais dos seguintes recursos do WPF:

  • Propriedades que são configuráveis dentro de um estilo. Para obter mais informações, confira Estilos e modelos.

  • Propriedades que oferecem 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.

  • Propriedades que são configuráveis por meio de referências de recursos dinâmicos. Para obter mais informações, consulte Recursos XAML.

  • Propriedades que herdam automaticamente seu valor de um elemento pai na árvore de elementos. Para isso, você precisará se registrar usando RegisterAttached, mesmo que você também crie um wrapper de propriedade para acesso CLR. Para obter mais informações, consulte Herança de valor de propriedade.

  • Propriedades que são animáveis. Para obter mais informações, confira Visão geral de animação.

  • Notificação pelo sistema de propriedades WPF quando um valor de propriedade é alterado. As alterações podem ser devidas a ações do sistema de propriedades, ambiente, usuário ou estilos. Sua propriedade pode especificar um método de retorno de chamada nos metadados da propriedade que serão invocados sempre que o sistema de propriedades determinar que o valor da propriedade foi alterado. Um conceito relacionado é a coerção do valor da propriedade. Para obter mais informações, consulte Retornos de chamada e validação de propriedades de dependência.

  • Acesso aos metadados da propriedade de dependência, que são lidos pelos processos do WPF. Por exemplo, você pode usar metadados de propriedade para:

    • Especifique se um valor de propriedade de dependência alterado deve fazer com que o sistema de layout recomponha elementos visuais para um elemento.

    • Defina o valor padrão de uma propriedade de dependência, substituindo metadados em classes derivadas.

  • Suporte ao designer WPF do Visual Studio, como editar as propriedades de um controle personalizado na janela Propriedades . Para obter mais informações, consulte Visão geral sobre a criação de controles.

Para alguns cenários, substituir os metadados de uma propriedade de dependência existente é uma opção melhor do que implementar uma nova propriedade de dependência. 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 de propriedades e classes de dependência WPF existentes. Para obter mais informações sobre como substituir metadados em propriedades de dependência existentes, consulte Metadados de propriedade de dependência.

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

Siga estas etapas para criar uma propriedade de dependência. Algumas das etapas podem ser combinadas e implementadas em uma única linha de código.

  1. (Opcional) Criar metadados de propriedade de dependência.

  2. Registre a propriedade de dependência com o sistema de propriedades, especificando um nome de propriedade, um tipo de proprietário, o tipo de valor de propriedade e, opcionalmente, metadados de propriedade.

  3. Defina um identificador como um DependencyPropertypublic static readonly campo no tipo de proprietário. O nome do campo identificador é o nome da propriedade com o sufixo Property acrescentado.

  4. Defina uma propriedade wrapper CLR com o mesmo nome que o nome da propriedade de dependência. No wrapper CLR, implemente get e acesse set que se conectam com a propriedade de dependência que faz backup do wrapper.

Cadastro do imóvel

Para que seu imóvel seja um imóvel dependente, você deve registrá-lo no sistema de imóveis. Para registrar sua propriedade, chame o Register método de dentro do corpo de sua classe, mas fora de qualquer definição de membro. O Register método retorna um identificador de propriedade de dependência exclusivo que você usará ao chamar a API do sistema de propriedades. O motivo pelo qual a chamada é feita fora das definições de membro é porque você atribui o valor de retorno a Register um public static readonly campo do tipo DependencyProperty. Esse campo, que você criará em sua classe, é o identificador de sua propriedade de dependência. No exemplo a seguir, o primeiro argumento de Register nomeia a propriedade AquariumGraphicdependency .

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

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 poderá fazer sentido se você precisar de mais de uma linha de código para inicializar a propriedade de dependência.

Nomeação de propriedade de dependência

A convenção de nomenclatura estabelecida para propriedades de dependência é obrigatória para o comportamento normal do sistema de propriedades. O nome do campo identificador que você criar deve ser o nome registrado da propriedade com o sufixo Property.

Um nome de propriedade de dependência deve ser exclusivo dentro da classe de registro. As propriedades de dependência herdadas por meio de um tipo base já foram registradas e não podem ser registradas por um tipo derivado. No entanto, você pode usar uma propriedade de dependência que foi registrada por um tipo diferente, mesmo um tipo do qual sua classe não herda, adicionando sua classe como proprietária da propriedade de dependência. Para obter mais informações sobre como adicionar uma classe como proprietário, consulte Metadados da propriedade de dependência.

Implementando um wrapper de propriedade

Por convenção, o nome da propriedade wrapper deve ser o mesmo que o primeiro parâmetro da chamada, que é o nome da propriedade de Register dependência. Sua implementação de wrapper chamará GetValue o acessador e SetValue o getset acessador (para propriedades de leitura-gravação). O exemplo a seguir mostra um wrapper — após a chamada de registro e a declaração de campo de identificador. Todas as propriedades de dependência pública em classes WPF usam um modelo de wrapper semelhante.

// Register a dependency property with the specified property name,
// property type, owner type, and property metadata. Store the dependency
// property identifier as a public static readonly member of the class.
public static readonly DependencyProperty AquariumGraphicProperty =
    DependencyProperty.Register(
      name: "AquariumGraphic",
      propertyType: typeof(Uri),
      ownerType: typeof(Aquarium),
      typeMetadata: new FrameworkPropertyMetadata(
          defaultValue: new Uri("http://www.contoso.com/aquarium-graphic.jpg"),
          flags: FrameworkPropertyMetadataOptions.AffectsRender,
          propertyChangedCallback: new PropertyChangedCallback(OnUriChanged))
    );

// Declare a read-write property wrapper.
public Uri AquariumGraphic
{
    get => (Uri)GetValue(AquariumGraphicProperty);
    set => SetValue(AquariumGraphicProperty, value);
}
' Register a dependency property with the specified property name,
' property type, owner type, and property metadata. Store the dependency
' property identifier as a public static readonly member of the class.
Public Shared ReadOnly AquariumGraphicProperty As DependencyProperty =
    DependencyProperty.Register(
        name:="AquariumGraphic",
        propertyType:=GetType(Uri),
        ownerType:=GetType(Aquarium),
        typeMetadata:=New FrameworkPropertyMetadata(
            defaultValue:=New Uri("http://www.contoso.com/aquarium-graphic.jpg"),
            flags:=FrameworkPropertyMetadataOptions.AffectsRender,
            propertyChangedCallback:=New PropertyChangedCallback(AddressOf OnUriChanged)))

' Declare a read-write property wrapper.
Public Property AquariumGraphic As Uri
    Get
        Return CType(GetValue(AquariumGraphicProperty), Uri)
    End Get
    Set
        SetValue(AquariumGraphicProperty, Value)
    End Set
End Property

Exceto em casos raros, sua implementação de wrapper deve conter GetValue apenas e SetValue código. Para obter os motivos por trás disso, consulte Implicações para propriedades de dependência personalizadas.

Se sua propriedade não seguir as convenções de nomenclatura estabelecidas, você poderá ter estes problemas:

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

  • A maioria das ferramentas e designers dependem das convenções de nomenclatura para serializar corretamente o XAML e 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 para processar valores de atributo. Para obter mais informações, consulte Propriedades de carregamento e dependência XAML.

Metadados de propriedade de dependência

Quando você registra uma propriedade de dependência, o sistema de propriedades cria um objeto de metadados para armazenar características de propriedade. As sobrecargas do método permitem especificar metadados de propriedade durante o Register registro, por exemplo Register(String, Type, Type, PropertyMetadata). Um uso comum de metadados de propriedade é aplicar um valor padrão personalizado para novas instâncias que usam uma propriedade de dependência. Se você não fornecer metadados de propriedade, o sistema de propriedades atribuirá valores padrão a muitas das características de propriedade de dependência.

Se você estiver criando uma propriedade de dependência em uma classe derivada do , poderá usar a classe de metadados mais especializada em vez de FrameworkElementsua classe FrameworkPropertyMetadataPropertyMetadatabase. Várias FrameworkPropertyMetadata assinaturas de construtor permitem especificar diferentes combinações de características de metadados. Se você quiser apenas especificar um valor padrão, use FrameworkPropertyMetadata(Object) e passe o valor padrão para o Object parâmetro. Verifique se o tipo de valor corresponde ao propertyType especificado na Register chamada.

Algumas FrameworkPropertyMetadata sobrecargas permitem especificar sinalizadores de opção de metadados para sua propriedade. O sistema de propriedades converte esses sinalizadores em propriedades discretas e os valores de sinalizador são usados por processos WPF, como o mecanismo de layout.

Definindo sinalizadores de metadados

Considere o seguinte ao definir sinalizadores de metadados:

  • Se o valor da propriedade (ou as alterações nela) afetar como o sistema de layout renderiza um elemento da interface do usuário, defina um ou mais dos seguintes sinalizadores:

    • AffectsMeasure, que indica que uma alteração no valor da propriedade requer uma alteração na renderização da interface do usuário, especificamente o espaço ocupado por um objeto em seu pai. Por exemplo, defina esse sinalizador de metadados para uma Width propriedade.

    • AffectsArrange, que indica que uma alteração no valor da propriedade requer uma alteração na renderização da interface do usuário, especificamente a posição de um objeto em seu pai. Normalmente, o objeto também não muda de tamanho. Por exemplo, defina esse sinalizador de metadados para uma Alignment propriedade.

    • AffectsRender, que indica que ocorreu uma alteração que não afeta o layout e a medida, mas ainda requer outra renderização. Por exemplo, defina esse sinalizador para uma Background propriedade ou qualquer outra propriedade que afete a cor de um elemento.

    Você também pode usar esses sinalizadores como entradas para suas implementações de substituição dos retornos de chamada do sistema de propriedades (ou layout). Por exemplo, você pode usar um OnPropertyChanged retorno de chamada para chamar InvalidateArrange quando uma propriedade da instância relata uma alteração de valor e definiu AffectsArrange metadados.

  • Algumas propriedades afetam as características de renderização de seu elemento pai de outras maneiras. Por exemplo, as alterações na MinOrphanLines propriedade podem alterar a renderização geral de um documento de fluxo. Use AffectsParentArrange ou AffectsParentMeasure sinalize ações pai em suas próprias propriedades.

  • Por padrão, as propriedades de dependência dão suporte à vinculação de dados. No entanto, você pode usar IsDataBindingAllowed para desabilitar a vinculação de dados quando não houver um cenário realista para ela ou quando o desempenho da vinculação de dados for problemático, como em objetos grandes.

  • Embora o modo de vinculação de dados padrão para propriedades de dependência seja OneWay, você pode alterar o modo de vinculação de uma associação específica para TwoWay. Para obter mais informações, consulte Direção de vinculação. Como um autor de propriedade de dependência, você pode até optar por tornar a vinculação bidirecional o modo padrão. Um exemplo de uma propriedade de dependência existente que usa vinculação de dados bidirecional é MenuItem.IsSubmenuOpen, que tem um estado baseado em outras propriedades e chamadas de método. O cenário para IsSubmenuOpen é que sua lógica de configuração e a composição do , interagem com o estilo de MenuItemtema padrão. TextBox.Text é outra propriedade de dependência do WPF que usa vinculação bidirecional por padrão.

  • Você pode habilitar a herança de propriedade para sua propriedade de dependência definindo o Inherits sinalizador. A herança de propriedade é útil para cenários em que os elementos pai e filho têm uma propriedade em comum e faz sentido que o elemento filho herde o valor pai para a propriedade comum. Um exemplo de uma propriedade hereditária é DataContext, que oferece suporte a operações de vinculação que usam o cenário mestre-detalhe para apresentação de dados. A herança de valor de propriedade permite especificar um contexto de dados na raiz da página ou do aplicativo, o que evita a necessidade de especificá-lo para associações de elementos filho. Embora um valor de propriedade herdado substitua o valor padrão, os valores de propriedade podem ser definidos localmente em qualquer elemento filho. Use a herança de valor de propriedade com moderação porque ela tem um custo de desempenho. Para obter mais informações, consulte Herança de valor de propriedade.

  • Defina o sinalizador para indicar que sua propriedade de dependência deve ser detectada Journal ou usada pelos serviços de registro no diário de navegação. Por exemplo, a propriedade define o Journal sinalizador para recomendar que os SelectedIndex aplicativos mantenham um histórico de registro no diário dos itens selecionados.

Propriedades de dependência somente leitura

Você pode definir uma propriedade de dependência que seja somente leitura. Um cenário típico é uma propriedade de dependência que armazena o estado interno. Por exemplo, IsMouseOver é somente leitura porque seu estado só deve ser determinado pela entrada do mouse. 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 de tipo de coleção têm problemas de implementação adicionais a serem considerados, como a definição de um valor padrão para tipos de referência e suporte à vinculação de dados para elementos de coleção. Para obter mais informações, consulte Propriedades de dependência de tipo de coleção.

Segurança de propriedade de dependência

Normalmente, você declarará propriedades de dependência como propriedades públicas e DependencyProperty campos de identificador como public static readonly campos. Se você especificar um nível de acesso mais restritivo, como protected, uma propriedade de dependência ainda poderá ser acessada por meio de seu identificador em combinação com APIs do sistema de propriedades. Até mesmo um campo de identificador protegido é potencialmente acessível por meio de relatórios de metadados do WPF ou APIs de determinação de valor, como LocalValueEnumeratoro . Para obter mais informações, consulte Segurança de propriedade de dependência.

Para propriedades de dependência somente leitura, o valor retornado de é DependencyPropertyKey, e normalmente você não se tornará DependencyPropertyKey um public membro de RegisterReadOnly sua classe. Como o sistema de propriedades WPF não propaga o DependencyPropertyKey exterior do código, uma propriedade de dependência somente leitura tem melhor set segurança do que uma propriedade de dependência de leitura-gravação.

Propriedades de dependência e construtores de classe

Há um princípio geral na programação de código gerenciado, muitas vezes imposto por ferramentas de análise de código, que os construtores de classe não devem chamar métodos virtuais. Isso ocorre porque os construtores base podem ser chamados durante a inicialização de um construtor de classe derivada, e um método virtual chamado por um construtor base pode ser executado antes da inicialização completa da classe derivada. Quando você deriva de uma classe que já deriva do , 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 tempo de execução, você não deve definir valores de propriedade de dependência dentro de construtores de classes, a menos que você siga um padrão de construtor específico. Para obter mais informações, consulte Padrões de construtor seguros para DependencyObjects.

Confira também