Partilhar via


Propriedades de dependência personalizadas

Aqui explicamos como definir e implementar suas próprias propriedades de dependência para um aplicativo do Tempo de Execução do Windows em C++, C# ou Visual Basic. Listamos os motivos pelos quais os desenvolvedores de aplicativos e autores de componentes podem querer criar propriedades de dependência personalizadas. Descrevemos as etapas de implementação para uma propriedade de dependência personalizada, bem como algumas práticas recomendadas que podem melhorar o desempenho, a usabilidade ou a versatilidade da propriedade de dependência.

Pré-requisitos

Supomos que você tenha lido a Visão geral das propriedades de dependência e que compreenda as propriedades de dependência da perspetiva de um consumidor de propriedades de dependência existentes. Para seguir os exemplos neste tópico, você também deve entender XAML e saber como escrever um aplicativo básico do Tempo de Execução do Windows em C++, C# ou Visual Basic.

O que é uma propriedade de dependência?

Para oferecer suporte a estilo, vinculação de dados, animações e valores padrão para uma propriedade, ela deve ser implementada como uma propriedade de dependência. Os valores de propriedade de dependência não são armazenados como campos na classe, eles são armazenados pela estrutura xaml e são referenciados usando uma chave, que é recuperada quando a propriedade é registrada no sistema de propriedades do Tempo de Execução do Windows chamando o método DependencyProperty.Register . As propriedades de dependência podem ser usadas somente por tipos derivados de DependencyObject. Mas DependencyObject é bastante alto na hierarquia de classes, portanto, a maioria das classes destinadas ao suporte à interface do usuário e à apresentação 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 nesta documentação, consulte Visão geral das propriedades de dependência.

Exemplos de propriedades de dependência no Tempo de Execução do Windows são: Control.Background, FrameworkElement.Width e TextBox.Text, entre muitas outras.

A convenção é que cada propriedade de dependência exposta por uma classe tem uma propriedade somente leitura estática pública correspondente do tipo DependencyProperty que é exposta nessa mesma classe que fornece o identificador para a propriedade de dependência. O nome do identificador segue esta convenção: o nome da propriedade de dependência, com a cadeia de caracteres "Property" adicionada ao final do nome. Por exemplo, o identificador DependencyProperty correspondente para a propriedade Control.Background é Control.BackgroundProperty. O identificador armazena as informações sobre a propriedade de dependência como ela foi registrada e pode ser usado para outras operações envolvendo a propriedade de dependência, como chamar SetValue.

Invólucros de propriedade

As propriedades de dependência normalmente têm uma implementação de wrapper. Sem o wrapper, a única maneira de obter ou definir as propriedades seria usar os métodos de utilidade de propriedade de dependência GetValue e SetValue e passando o identificador a eles como parâmetro. Este é um uso bastante antinatural para algo que é ostensivamente uma propriedade. Mas com o wrapper, o seu código e qualquer outro código que faça referência à propriedade de dependência pode usar uma sintaxe simples de propriedade de objeto que é natural para a linguagem que está a utilizar.

Se você mesmo implementar uma propriedade de dependência personalizada e quiser que ela seja pública e fácil de chamar, defina também os wrappers de propriedade. Os envoltórios de propriedades também são úteis para fornecer informações básicas sobre a propriedade de dependência para processos de reflexão ou para a análise estática. Especificamente, o wrapper é onde você coloca atributos como ContentPropertyAttribute.

Quando implementar uma propriedade como uma propriedade de dependência

Sempre que você implementar uma propriedade pública de leitura/gravação em uma classe, desde que sua classe derive de DependencyObject, você terá a opção de fazer sua propriedade funcionar como uma propriedade de dependência. Às vezes, a técnica típica de apoiar sua propriedade com um campo privado é adequada. Definir sua propriedade personalizada como uma propriedade de dependência nem sempre é necessário ou apropriado. A escolha vai depender dos cenários que pretende que o seu imóvel suporte.

Você pode considerar implementar sua propriedade como uma propriedade de dependência quando quiser que ela ofereça suporte a um ou mais destes recursos do Tempo de Execução do Windows ou de aplicativos do Tempo de Execução do Windows:

  • Definindo a propriedade através de um estilo
  • Atuando como propriedade de destino válida para vinculação de dados com {Binding}
  • Apoiar valores animados através de um Storyboard
  • Informar quando o valor do imóvel foi alterado por:
    • Ações tomadas pelo próprio sistema imobiliário
    • O ambiente
    • Ações do usuário
    • Estilos de leitura e escrita

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

A definição de uma propriedade de dependência pode ser pensada como um conjunto de conceitos. Esses conceitos não são necessariamente etapas processuais, porque vários conceitos podem ser abordados em uma única linha de código na implementação. Esta lista fornece apenas uma visão geral rápida. Explicaremos cada conceito com mais detalhes mais adiante neste tópico e mostraremos exemplos de código em vários idiomas.

  • Registe o nome da propriedade no sistema de propriedade (chame Register), especificando um tipo de proprietário e o tipo do valor da propriedade.
    • Há um parâmetro necessário para Register que espera metadados de propriedade. Especifique null para isso ou, se desejar um comportamento alterado de propriedade ou um valor padrão baseado em metadados que possa ser restaurado chamando ClearValue, especifique uma instância de PropertyMetadata.
  • Defina um identificador DependencyProperty como um membro de propriedade público estático somente leitura no tipo de proprietário.
  • Defina uma propriedade wrapper, seguindo o modelo de acessador de propriedade usado no idioma que você está implementando. O nome da propriedade contenedora deve corresponder à cadeia de caracteres name que usou em Register. Implemente os acessadores get e set para conectar o wrapper com a propriedade de dependência que ele encapsula, chamando GetValue e SetValue e passando o identificador da sua própria propriedade como um parâmetro.
  • (Opcional) Coloque atributos como ContentPropertyAttribute no wrapper.

Observação

Se estiver a definir uma propriedade anexada personalizada, geralmente omite o wrapper. Em vez disso, você escreve um estilo diferente de acessador que um processador XAML pode usar. Consulte Propriedades anexadas personalizadas.

Registar o imóvel

Para que sua propriedade seja uma propriedade de dependência, você deve registrá-la em um repositório de propriedades mantido pelo sistema de propriedades do Tempo de Execução do Windows. Para registrar a propriedade, você chama o método Register .

Para linguagens Microsoft .NET (C# e Microsoft Visual Basic), você chama Register dentro do corpo da sua classe (dentro da classe, mas fora de quaisquer definições de membro). O identificador é fornecido pela chamada do método Register , como o valor de retorno. A chamada Register normalmente é feita como um construtor estático ou como parte da inicialização de uma propriedade somente leitura estática pública do tipo DependencyProperty como parte de sua classe. Esta propriedade expõe o identificador da sua propriedade de dependência. Aqui estão exemplos da chamada Register .

Observação

Registrar a propriedade de dependência como parte da definição de propriedade de identificador é a implementação típica, mas você também pode registrar 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.

Para C++/CX, você tem opções para dividir a implementação entre o cabeçalho e o arquivo de código. O procedimento típico é declarar o próprio identificador como propriedade pública estática no cabeçalho, com get mas sem set. A implementação get refere-se a um campo privado, que é uma instância DependencyProperty não inicializada. Você também pode declarar os wrappers e as implementações get e set do wrapper. Neste caso, o cabeçalho inclui alguma implementação mínima. Se o wrapper precisar de atribuição do Tempo de Execução do Windows, atribua também no cabeçalho. Coloque a chamada Registrar no arquivo de código, dentro de uma função auxiliar que só é executada quando o aplicativo é inicializado pela primeira vez. Use o valor de retorno de Register para preencher os identificadores estáticos, mas não inicializados, que você declarou no cabeçalho, que você definiu inicialmente como nullptr no escopo raiz do arquivo de implementação.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(Nothing))
// ImageWithLabelControl.idl
namespace ImageWithLabelControlApp
{
    runtimeclass ImageWithLabelControl : Windows.UI.Xaml.Controls.Control
    {
        ImageWithLabelControl();
        static Windows.UI.Xaml.DependencyProperty LabelProperty{ get; };
        String Label;
    }
}

// ImageWithLabelControl.h
...
struct ImageWithLabelControl : ImageWithLabelControlT<ImageWithLabelControl>
{
...
public:
    static Windows::UI::Xaml::DependencyProperty LabelProperty()
    {
        return m_labelProperty;
    }

private:
    static Windows::UI::Xaml::DependencyProperty m_labelProperty;
...
};

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr }
);
...
//.h file
//using namespace Windows::UI::Xaml::Controls;
//using namespace Windows::UI::Xaml::Interop;
//using namespace Windows::UI::Xaml;
//using namespace Platform;

public ref class ImageWithLabelControl sealed : public Control
{
private:
    static DependencyProperty^ _LabelProperty;
...
public:
    static void RegisterDependencyProperties();
    static property DependencyProperty^ LabelProperty
    {
        DependencyProperty^ get() {return _LabelProperty;}
    }
...
};

//.cpp file
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml.Interop;

DependencyProperty^ ImageWithLabelControl::_LabelProperty = nullptr;

// This function is called from the App constructor in App.xaml.cpp
// to register the properties
void ImageWithLabelControl::RegisterDependencyProperties()
{
    if (_LabelProperty == nullptr)
    {
        _LabelProperty = DependencyProperty::Register(
          "Label", Platform::String::typeid, ImageWithLabelControl::typeid, nullptr);
    }
}

Observação

Para o código C++/CX, o motivo pelo qual tem um campo privado e uma propriedade pública somente leitura que expõe a DependencyProperty é para que outros utilizadores que usam sua propriedade de dependência também possam usar APIs utilitárias do sistema de propriedades que exigem que o identificador seja público. Se você mantiver o identificador privado, as pessoas não poderão usar essas APIs de utilitários. Exemplos de tais API e cenários incluem GetValue ou SetValue por escolha, ClearValue, GetAnimationBaseValue, SetBinding, e Setter.Property. Não é possível usar um campo público para isso, porque as regras de metadados do Tempo de Execução do Windows não permitem campos públicos.

Convenções de nome de propriedade de dependência

Existem convenções de nomenclatura para propriedades de dependência; siga-as em todas as circunstâncias, exceto em circunstâncias excecionais. A propriedade dependency em si tem um nome básico ("Label" no exemplo anterior) que é dado como o primeiro parâmetro de Register. O nome deve ser exclusivo dentro de cada tipo de registro, e o requisito de exclusividade também se aplica a todos os membros herdados. As propriedades de dependência herdadas através de tipos base já são consideradas parte do tipo de registo; Os nomes das propriedades herdadas não podem ser registrados novamente.

Advertência

Embora o nome fornecido aqui possa ser qualquer identificador de cadeia de caracteres válido na programação para a linguagem de sua escolha, você geralmente deseja poder definir sua propriedade de dependência em XAML também. Para ser definido em XAML, o nome da propriedade escolhida deve ser um nome XAML válido. Para saber mais, veja Visão geral de XAML.

Ao criar a propriedade identifier, combine o nome da propriedade como você a registrou com o sufixo "Property" ("LabelProperty", por exemplo). Essa propriedade é seu identificador para a propriedade de dependência e é usada como uma entrada para as chamadas SetValue e GetValue que você faz em seus próprios wrappers de propriedade. Ele também é usado pelo sistema de propriedades e outros processadores XAML, como {x:Bind}

Implementando o wrapper

O seu wrapper de propriedade deve chamar GetValue na implementação get e SetValue na implementação set.

Advertência

Em todas as circunstâncias, exceto em circunstâncias excecionais, suas implementações de wrapper devem executar apenas as operações GetValue e SetValue . Caso contrário, você terá um comportamento diferente quando sua propriedade for definida via XAML versus quando for definida por meio de código. Para eficiência, o analisador XAML ignora os wrappers ao definir propriedades de dependência e comunica-se com a estrutura subjacente via SetValue.

public String Label
{
    get { return (String)GetValue(LabelProperty); }
    set { SetValue(LabelProperty, value); }
}
Public Property Label() As String
    Get
        Return DirectCast(GetValue(LabelProperty), String)
    End Get
    Set(ByVal value As String)
        SetValue(LabelProperty, value)
    End Set
End Property
// ImageWithLabelControl.h
...
winrt::hstring Label()
{
    return winrt::unbox_value<winrt::hstring>(GetValue(m_labelProperty));
}

void Label(winrt::hstring const& value)
{
    SetValue(m_labelProperty, winrt::box_value(value));
}
...
//using namespace Platform;
public:
...
  property String^ Label
  {
    String^ get() {
      return (String^)GetValue(LabelProperty);
    }
    void set(String^ value) {
      SetValue(LabelProperty, value);
    }
  }

Metadados de propriedade para uma propriedade de dependência personalizada

Quando os metadados de propriedade são atribuídos a uma propriedade de dependência, os mesmos metadados são aplicados a essa propriedade para cada instância do tipo de proprietário da propriedade ou suas subclasses. Em metadados de propriedade, você pode especificar dois comportamentos:

  • Um valor padrão que o sistema de propriedades atribui a todos os casos da propriedade.
  • Um método de callback estático que é invocado automaticamente no sistema de propriedades sempre que é detetada uma alteração no valor da propriedade.

Invocando o Registo com metadados de propriedade

Nos exemplos anteriores de chamada de DependencyProperty.Register, passamos um valor nulo para o parâmetro propertyMetadata . Para permitir que uma propriedade de dependência forneça um valor padrão ou utilize um retorno de chamada para alteração de propriedade, é necessário definir uma instância de PropertyMetadata que ofereça uma ou ambas essas funcionalidades.

Normalmente, você fornece um PropertyMetadata como uma instância criada embutida, dentro dos parâmetros para DependencyProperty.Register.

Observação

Se você estiver definindo uma implementação CreateDefaultValueCallback , deverá usar o método utilitário PropertyMetadata.Create em vez de chamar um construtor PropertyMetadata para definir a instância PropertyMetadata .

Este próximo exemplo modifica os exemplos DependencyProperty.Register mostrados anteriormente fazendo referência a uma instância PropertyMetadata com um valor PropertyChangedCallback . A implementação do retorno de chamada "OnLabelChanged" será mostrada posteriormente nesta seção.

public static readonly DependencyProperty LabelProperty = DependencyProperty.Register(
  nameof(Label),
  typeof(String),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null,new PropertyChangedCallback(OnLabelChanged))
);
Public Shared ReadOnly LabelProperty As DependencyProperty =
    DependencyProperty.Register("Label",
      GetType(String),
      GetType(ImageWithLabelControl),
      New PropertyMetadata(
        Nothing, new PropertyChangedCallback(AddressOf OnLabelChanged)))
// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ nullptr, Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...
DependencyProperty^ ImageWithLabelControl::_LabelProperty =
    DependencyProperty::Register("Label",
    Platform::String::typeid,
    ImageWithLabelControl::typeid,
    ref new PropertyMetadata(nullptr,
      ref new PropertyChangedCallback(&ImageWithLabelControl::OnLabelChanged))
    );

Valor predefinido

Você pode especificar um valor padrão para uma propriedade de dependência de modo que a propriedade sempre retorne um determinado valor padrão quando ela é desdefinida. Esse valor pode ser diferente do valor padrão inerente para o tipo dessa propriedade.

Se um valor padrão não for especificado, o valor padrão para uma propriedade de dependência será nulo para um tipo de referência ou o padrão do tipo para um tipo de valor ou primitiva de idioma (por exemplo, 0 para um inteiro ou uma cadeia de caracteres vazia para uma cadeia de caracteres). A principal razão para estabelecer um valor padrão é que esse valor é restaurado quando você chama ClearValue na propriedade. Estabelecer um valor padrão por propriedade pode ser mais conveniente do que estabelecer valores padrão em construtores, particularmente para tipos de valor. No entanto, para tipos de referência, certifique-se de que o estabelecimento de um valor padrão não crie um padrão singleton não intencional. Para obter mais informações, consulte Práticas recomendadas mais adiante neste tópico

// ImageWithLabelControl.cpp
...
Windows::UI::Xaml::DependencyProperty ImageWithLabelControl::m_labelProperty =
    Windows::UI::Xaml::DependencyProperty::Register(
        L"Label",
        winrt::xaml_typename<winrt::hstring>(),
        winrt::xaml_typename<ImageWithLabelControlApp::ImageWithLabelControl>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(L"default label"), Windows::UI::Xaml::PropertyChangedCallback{ &ImageWithLabelControl::OnLabelChanged } }
);
...

Observação

Não se registre com um valor padrão de UnsetValue. Se o fizer, confundirá os consumidores imobiliários e terá consequências não intencionais dentro do sistema imobiliário.

CreateDefaultValueCallback

Em alguns cenários, você está definindo propriedades de dependência para objetos que são usados em mais de um thread de interface do usuário. Esse pode ser o caso se você estiver definindo um objeto de dados que é usado por vários aplicativos ou um controle que você usa em mais de um aplicativo. Você pode habilitar a troca do objeto entre diferentes threads da interface do usuário fornecendo uma implementação CreateDefaultValueCallback em vez de uma instância de valor padrão, que está vinculada ao thread que registrou a propriedade. Basicamente, um CreateDefaultValueCallback define uma fábrica de valores padrão. O valor retornado por CreateDefaultValueCallback é sempre associado ao thread atual da interface do usuário CreateDefaultValueCallback que está usando o objeto.

Para definir metadados que especificam um CreateDefaultValueCallback, você deve chamar PropertyMetadata.Create para retornar uma instância de metadados; os construtores PropertyMetadata não têm uma assinatura que inclua um parâmetro CreateDefaultValueCallback .

O padrão de implementação típico para um CreateDefaultValueCallback é criar uma nova classe DependencyObject , definir o valor da propriedade específica de cada propriedade do DependencyObject para o padrão pretendido e, em seguida, retornar a nova classe como uma referência Object por meio do valor de retorno do método CreateDefaultValueCallback .

Método de retorno de chamada para alteração de propriedade

Você pode definir um método de retorno de chamada para alterações de propriedade, a fim de determinar as interações da sua propriedade com outras propriedades dependentes ou para atualizar uma propriedade interna ou o estado interno de um objeto sempre que a propriedade for alterada. Se o retorno de chamada for invocado, o sistema de propriedades determinou que há uma alteração efetiva no valor da propriedade. Como o método de retorno de chamada é estático, o parâmetro d do retorno de chamada é importante porque informa qual instância da classe relatou uma alteração. Uma implementação típica usa a propriedade NewValue dos dados de evento e processa esse valor de alguma maneira, geralmente executando alguma outra alteração no objeto passado como d. As respostas adicionais a uma alteração de propriedade são rejeitar o valor relatado por NewValue, restaurar OldValue ou definir o valor como uma restrição programática aplicada a NewValue.

Este próximo exemplo mostra uma implementação PropertyChangedCallback . Ele implementa o método que você viu referenciado nos exemplos Register anteriores, como parte dos argumentos de construção para o PropertyMetadata. O cenário abordado por este retorno de chamada é que a classe também tem uma propriedade somente de leitura calculada chamada "HasLabelValue" (implementação não mostrada). Sempre que a propriedade "Label" for reavaliada, este método de callback é invocado, o que permite que o valor calculado dependente permaneça sincronizado com as alterações na propriedade de dependência.

private static void OnLabelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    ImageWithLabelControl iwlc = d as ImageWithLabelControl; //null checks omitted
    String s = e.NewValue as String; //null checks omitted
    if (s == String.Empty)
    {
        iwlc.HasLabelValue = false;
    } else {
        iwlc.HasLabelValue = true;
    }
}
    Private Shared Sub OnLabelChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
        Dim iwlc As ImageWithLabelControl = CType(d, ImageWithLabelControl) ' null checks omitted
        Dim s As String = CType(e.NewValue,String) ' null checks omitted
        If s Is String.Empty Then
            iwlc.HasLabelValue = False
        Else
            iwlc.HasLabelValue = True
        End If
    End Sub
void ImageWithLabelControl::OnLabelChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto iwlc{ d.as<ImageWithLabelControlApp::ImageWithLabelControl>() };
    auto s{ winrt::unbox_value<winrt::hstring>(e.NewValue()) };
    iwlc.HasLabelValue(s.size() != 0);
}
static void OnLabelChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    ImageWithLabelControl^ iwlc = (ImageWithLabelControl^)d;
    Platform::String^ s = (Platform::String^)(e->NewValue);
    if (s->IsEmpty()) {
        iwlc->HasLabelValue=false;
    }
}

Mudança de comportamento da propriedade para estruturas e enumerações

Se o tipo de um DependencyProperty for uma enumeração ou uma estrutura, o retorno de chamada pode ser invocado mesmo se os valores internos da estrutura ou o valor de enumeração não foram alterados. Isso é diferente de uma primitiva de sistema, como uma cadeia de caracteres, onde ela só é invocada se o valor for alterado. Este é um efeito colateral das operações de caixa e descaixa nesses valores que é feito internamente. Se você tiver um método PropertyChangedCallback para uma propriedade em que seu valor é uma enumeração ou estrutura, precisará comparar OldValue e NewValue lançando os valores por conta própria e usando os operadores de comparação sobrecarregados que estão disponíveis para os valores agora convertidos. Ou, se nenhum operador estiver disponível (o que pode ser o caso de uma estrutura personalizada), talvez seja necessário comparar os valores individuais. Normalmente, você optaria por não fazer nada se o resultado for que os valores não foram alterados.

private static void OnVisibilityValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
    if ((Visibility)e.NewValue != (Visibility)e.OldValue)
    {
        //value really changed, invoke your changed logic here
    } // else this was invoked because of boxing, do nothing
}
Private Shared Sub OnVisibilityValueChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
    If CType(e.NewValue,Visibility) != CType(e.OldValue,Visibility) Then
        '  value really changed, invoke your changed logic here
    End If
    '  else this was invoked because of boxing, do nothing
End Sub
static void OnVisibilityValueChanged(Windows::UI::Xaml::DependencyObject const& d, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const& e)
{
    auto oldVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.OldValue()) };
    auto newVisibility{ winrt::unbox_value<Windows::UI::Xaml::Visibility>(e.NewValue()) };

    if (newVisibility != oldVisibility)
    {
        // The value really changed; invoke your property-changed logic here.
    }
    // Otherwise, OnVisibilityValueChanged was invoked because of boxing; do nothing.
}
static void OnVisibilityValueChanged(DependencyObject^ d, DependencyPropertyChangedEventArgs^ e)
{
    if ((Visibility)e->NewValue != (Visibility)e->OldValue)
    {
        //value really changed, invoke your changed logic here
    }
    // else this was invoked because of boxing, do nothing
    }
}

Melhores práticas

Tenha em mente as seguintes considerações como práticas recomendadas ao definir sua propriedade de dependência personalizada.

DependencyObject e encadeamento

Todas as instâncias de DependencyObject devem ser criadas na thread da interface de utilizador associada à Janela atual mostrada por uma aplicação do Windows Runtime. Embora cada DependencyObject deva ser criado no thread principal da interface do usuário, os objetos podem ser acessados usando uma referência de dispatcher de outros threads, chamando Dispatcher.

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.

Evitar a criação não intencional de singletons

Um singleton acidental pode acontecer se estiveres a declarar uma propriedade de dependência que utiliza um tipo de referência e chamares um construtor para esse tipo de referência como parte do código que estabelece o seu PropertyMetadata. O que acontece é que todos os usos da propriedade de dependência compartilham apenas uma instância de PropertyMetadata e, portanto, tentam compartilhar o único tipo de referência que você construiu. Todas as subpropriedades desse tipo de valor que você definir por meio de sua propriedade de dependência serão propagadas para outros objetos de maneiras que você pode não ter pretendido.

Você pode usar construtores de classe para definir valores iniciais para uma propriedade de dependência de tipo de referência se desejar um valor não nulo, mas esteja ciente de que isso seria considerado um valor local para fins de visão geral das propriedades de dependência. Pode ser mais apropriado usar um modelo para essa finalidade, se sua classe oferecer suporte a modelos. Outra maneira de evitar um padrão singleton, mas ainda fornecer um padrão útil, é expor uma propriedade estática no tipo de referência que fornece um padrão adequado para os valores dessa classe.

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

As propriedades de dependência do tipo de coleção têm alguns problemas de implementação adicionais a serem considerados.

As propriedades de dependência do tipo de coleção são relativamente raras na API do Tempo de Execução do Windows. Na maioria dos casos, você pode usar coleções em que os itens são uma subclasse DependencyObject , mas a própria propriedade de coleção é implementada como uma propriedade CLR ou C++ convencional. Isso ocorre porque as coleções não se adequam necessariamente a alguns cenários típicos em que as propriedades de dependência estão envolvidas. Por exemplo:

  • Normalmente, você não anima uma coleção.
  • Normalmente, não pré-preenches os itens de uma coleção com estilos ou um modelo.
  • Embora a associação a coleções seja um cenário importante, uma coleção não precisa ser uma propriedade de dependência para ser uma fonte de vinculação. Para destinos de associação, é mais comum usar subclasses de ItemsControl ou DataTemplate para dar suporte a itens de coleção ou usar padrões de modelo de exibição. Para saber mais sobre a vinculação de e para coleções, veja Vinculação de dados em profundidade.
  • As notificações para alterações de coleção são melhor tratadas por meio de interfaces como INotifyPropertyChanged ou INotifyCollectionChanged, ou derivando o tipo de coleção de ObservableCollection<T>.

No entanto, existem cenários onde propriedades de dependência do tipo coleção são aplicáveis. As próximas três seções fornecem algumas orientações sobre como implementar uma propriedade de dependência do tipo coleção.

Inicializando a coleção

Ao criar uma propriedade de dependência, você pode estabelecer um valor padrão por meio de metadados de propriedade de dependência. Mas tenha cuidado para não usar uma coleção estática singleton como o valor padrão. Em vez disso, você deve definir deliberadamente o valor da coleção para uma coleção exclusiva (instância) como parte da lógica do construtor de classe para a classe owner da propriedade de coleção.

// WARNING - DO NOT DO THIS
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(new List<object>())
);

// DO THIS Instead
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register(
  nameof(Items),
  typeof(IList<object>),
  typeof(ImageWithLabelControl),
  new PropertyMetadata(null)
);

public ImageWithLabelControl()
{
    // Need to initialize in constructor instead
    Items = new List<object>();
}

Um DependencyProperty e o valor padrão do seu PropertyMetadata fazem parte da definição estática do DependencyProperty. Ao fornecer um valor de coleção padrão (ou outra instância) como o valor padrão, ele será compartilhado em todas as instâncias de sua classe, em vez de cada classe ter sua própria coleção, como normalmente seria desejado.

Notificações de alteração

Definir a coleção como uma propriedade de dependência não fornece automaticamente notificação de alteração para os itens na coleção em virtude do sistema de propriedades invocar o método de retorno de chamada "PropertyChanged". Se desejar notificações para coleções ou itens de coleção, por exemplo, para um cenário de vinculação de dados, implemente a interface INotifyPropertyChanged ou INotifyCollectionChanged . Para obter mais informações, consulte Vinculação de dados em profundidade.

Considerações sobre segurança de propriedades dependentes

Declare propriedades de dependência como propriedades públicas. Declare identificadores de propriedade de dependência como membros somente leitura estáticos públicos. Mesmo que você tente declarar outros níveis de acesso permitidos por um idioma (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. Declarar o identificador de propriedade de dependência como interno ou privado não funcionará, porque o sistema de propriedades não pode funcionar corretamente.

As propriedades do wrapper são realmente apenas por conveniência, os mecanismos de segurança aplicados aos wrappers podem ser ignorados chamando GetValue ou SetValue em vez disso. Portanto, mantenha as propriedades do envoltório públicas; caso contrário, você apenas torna a sua propriedade mais difícil para chamadores legítimos usarem, sem fornecer qualquer benefício real de segurança.

O Tempo de Execução do Windows não fornece uma maneira de registrar uma propriedade de dependência personalizada como somente leitura.

Propriedades de dependência e construtores de classe

Há um princípio geral de que os construtores de classe não devem chamar métodos virtuais. Isso ocorre porque os construtores podem ser chamados para realizar a inicialização de base de um construtor de classe derivada, e inserir o método virtual através do construtor pode ocorrer quando a instância de objeto que está sendo construída ainda não está completamente inicializada. Quando você deriva de qualquer classe que já deriva de DependencyObject, lembre-se de que o próprio sistema de propriedades chama e expõe métodos virtuais internamente como parte de seus serviços. Para evitar possíveis problemas com a inicialização em tempo de execução, não defina valores de propriedade de dependência dentro de construtores de classes.

Registrando as propriedades de dependência para aplicativos C++/CX

A implementação para registrar uma propriedade em C++/CX é mais complicada do que em C#, tanto por causa da separação em cabeçalho e arquivo de implementação quanto porque a inicialização no escopo raiz do arquivo de implementação é uma prática incorreta. (As extensões de componente do Visual C++ (C++/CX) colocam o código do inicializador estático do escopo raiz diretamente no DllMain, enquanto os compiladores C# atribuem os inicializadores estáticos às classes e, assim, evitam problemas de bloqueio de carga do DllMain .). A prática recomendada aqui é declarar uma função auxiliar que faz todo o registro de propriedade de dependência para uma classe, uma função por classe. Em seguida, para cada classe personalizada que seu aplicativo consome, você terá que fazer referência à função de registro auxiliar exposta por cada classe personalizada que você deseja usar. Chame cada função de registo de suporte uma vez como parte do construtor Application (App::App()), antes de InitializeComponent. Esse construtor só é executado quando o aplicativo é realmente referenciado pela primeira vez; ele não será executado novamente se um aplicativo suspenso for reativado, por exemplo. Além disso, como visto no exemplo de registo C++ anterior, a verificação nullptr em torno de cada chamada Register é importante: assegura que nenhum invocador da função possa registar a propriedade duas vezes. Uma segunda chamada de registro provavelmente travaria seu aplicativo sem essa verificação, porque o nome da propriedade seria uma duplicata. Você pode ver esse padrão de implementação no exemplo de usuário e controles personalizados XAML se examinar o código da versão C++/CX do exemplo.