Partilhar via


Propriedades de dependência personalizadas (WPF .NET)

Os desenvolvedores de aplicativos e autores de componentes do Windows Presentation Foundation (WPF) 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.

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 registradas com o sistema de propriedades do WPF por meio de Register chamadas ou RegisterReadOnly . O Register método retorna uma DependencyProperty instância que contém o nome registrado e as características de uma propriedade de dependência. Você atribuirá a DependencyProperty instância a um campo estático somente leitura, conhecido como identificador de propriedade de dependência, que, por convenção, é denominado <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 suporte de uma propriedade com um campo privado. Não apenas o sistema de propriedades usa o identificador, os processadores XAML podem usá-lo e seu código (e possivelmente código externo) pode acessar 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 dá suporte a propriedades de dependência, pois DependencyObject está próxima da raiz da hierarquia de classes do WPF. Para obter mais informações sobre propriedades de dependência e a terminologia e as convenções usadas para descrevê-las, consulte Visão geral das propriedades de dependência.

Wrappers 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 get e set acessadores. 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 acessadores and set interagem com o sistema de propriedades subjacente por meio DependencyObject.GetValue de chamadas and DependencyObject.SetValue , passando o identificador de propriedade de dependência como um parâmetro. Os consumidores de propriedades de dependência normalmente não chamam 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 de DependencyObject, você a torna uma propriedade de dependência apoiando sua propriedade com um DependencyProperty identificador. Se é benéfico criar uma propriedade de dependência depende do seu cenário. Embora o backup 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 dê suporte a um ou mais dos seguintes recursos do WPF:

  • Propriedades que podem ser definidas em um estilo. Para obter mais informações, confira Estilos e modelos.

  • Propriedades que dão suporte à associação de dados. Para obter mais informações sobre propriedades de dependência de associação de dados, consulte Associar 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 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 do 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 em metadados de propriedade que será invocado 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 propriedade de dependência.

  • Acesso a metadados de propriedade de dependência, que são lidos por 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 visuais para um elemento.

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

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

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 de quão próximo esse cenário se assemelha à implementação de propriedades e classes de dependência existentes do WPF. 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) Crie metadados de propriedade de dependência.

  2. Registre a propriedade de dependência no 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 DependencyProperty identificador como um public static readonly campo no tipo de proprietário. O nome do campo identificador é o nome da propriedade com o sufixo Property anexado.

  4. Defina uma propriedade wrapper CLR com o mesmo nome que o nome da propriedade de dependência. No wrapper CLR, implemente get e set acessadores que se conectam com a propriedade de dependência que dá suporte ao wrapper.

Cadastrando a propriedade

Para que sua propriedade seja uma propriedade de dependência, você deve registrá-la no sistema de propriedades. 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 Register chamada é feita fora das definições de membro é porque você atribui o valor de retorno a 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 AquariumGraphicde dependência .

// 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 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.

Nomenclatura 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ê cria 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, até 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 de 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 get acessador e SetValue o set acessador (para propriedades de leitura/gravação). O exemplo a seguir mostra um wrapper, seguindo a chamada de registro e a declaração do campo do 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 um 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á se deparar com estes problemas:

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

  • A maioria das ferramentas e designers depende das convenções de nomenclatura para serializar corretamente o XAML e fornecer assistência ao ambiente do 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 Carregamento XAML e propriedades de dependência.

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 da propriedade de dependência.

Se você estiver criando uma propriedade de dependência em uma classe derivada de , poderá usar a classe FrameworkPropertyMetadata de metadados mais especializada em vez de FrameworkElementsua classe PropertyMetadatabase . 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 que você especifique 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 do WPF, como o mecanismo de layout.

Configurando sinalizadores de metadados

Considere o seguinte ao definir sinalizadores de metadados:

  • Se o valor da propriedade (ou for alterado nele) afetar a forma 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 é AffectsArrange definida em 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 para sinalizar ações principais 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 associação de dados quando não houver um cenário realista para ela ou quando o desempenho da associaçã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 vinculação específica para TwoWay. Para obter mais informações, consulte Direção da associação. Como autor de propriedade de dependência, você pode até optar por tornar a associação bidirecional o modo padrão. Um exemplo de uma propriedade de dependência existente que usa associação de dados bidirecional é MenuItem.IsSubmenuOpen, que tem um estado baseado em outras propriedades e chamadas de método. O cenário é IsSubmenuOpen que sua lógica de configuração e a composição de , interagem com o estilo de MenuItemtema padrão. TextBox.Text é outra propriedade de dependência do WPF que usa associaçã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 da propriedade comum. Um exemplo de uma propriedade hereditária é DataContext, que dá suporte a operações de associaçã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 elemento 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 Journal sinalizador para indicar que sua propriedade de dependência deve ser detectada ou usada pelos serviços de registro no diário de navegação. Por exemplo, a SelectedIndex propriedade define o Journal sinalizador para recomendar que os 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 é 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 de tipo de coleção

As propriedades de dependência de tipo de coleção têm problemas extras de implementação a serem considerados, como a configuração de um valor padrão para tipos de referência e suporte à associaçã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 identificador protegido é potencialmente acessível por meio de relatórios de metadados do WPF ou APIs de determinação de valor, como LocalValueEnumerator. Para obter mais informações, consulte Segurança de propriedade de dependência.

Para propriedades de dependência somente leitura, o valor retornado é RegisterReadOnly DependencyPropertyKey, e normalmente você não fará DependencyPropertyKey um public membro de sua classe. Como o sistema de propriedades do WPF não se propaga para fora DependencyPropertyKey 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, geralmente imposto por ferramentas de análise de código, de 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 de DependencyObject, 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 do 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 do runtime, você não deve definir valores de propriedade de dependência em construtores de classes, a menos que siga um padrão de construtor específico. Para obter mais informações, consulte Padrões de construtor seguro para DependencyObjects.

Confira também