Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Aqui, explicamos como definir e implementar suas próprias propriedades de dependência para um aplicativo do Windows Runtime usando C++, C#ou Visual Basic. Listamos os motivos pelos quais 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
Presumimos que você tenha lido a visão geral das propriedades de dependência e que entenda as propriedades de dependência da perspectiva de um consumidor de propriedades de dependência existentes. Para seguir os exemplos neste tópico, você também deve entender o XAML e saber como escrever um aplicativo básico do Windows Runtime usando C++, C#ou Visual Basic.
O que é uma propriedade de dependência?
Para dar suporte a estilos, associação de dados, animações e valores padrão para uma propriedade, ela deve ser implementada como uma propriedade de dependência. Os valores da propriedade de dependência não são armazenados como campos na classe, são armazenados pela estrutura xaml e são referenciados usando uma chave, que é recuperada quando a propriedade é registrada com o sistema de propriedades do Windows Runtime chamando o método DependencyProperty.Register . As propriedades de dependência só podem ser usadas 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 dar suporte a propriedades de dependência. Para obter mais informações sobre as propriedades de dependência e algumas das terminologias e convenções usadas para descrevê-las nesta documentação, consulte a visão geral das propriedades de dependência.
Exemplos de propriedades de dependência no Windows Runtime são: Control.Background, FrameworkElement.Width e TextBox.Text, entre muitos outros.
A convenção é que cada propriedade de dependência exposta por uma classe tem uma propriedade pública estática readonly correspondente do tipo DependencyProperty que é exposta na mesma classe, fornecendo 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 "Propriedade" 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, em seguida, pode ser usado para outras operações envolvendo a propriedade de dependência, como chamar SetValue.
Wrappers de propriedade
Propriedades de dependência normalmente têm uma implementação de capa. Sem o wrapper, a única maneira de obter ou definir as propriedades seria usar os métodos utilitários de propriedade de dependência GetValue e SetValue e passar o identificador como parâmetro para eles. Este é um uso não natural para algo que é ostensivamente uma propriedade. Mas com o wrapper, seu código e qualquer outro código que referencie a propriedade de dependência podem usar uma sintaxe de propriedade de objeto simples que é natural para o idioma que você está usando.
Se você implementar uma propriedade de dependência personalizada por conta própria e quiser que ela seja pública e fácil de chamar, defina os wrappers de propriedade também. Os wrappers de propriedade também são úteis para relatar informações básicas sobre a propriedade de dependência para reflexão ou processos de 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ê implementa uma propriedade pública de leitura/gravação em uma classe, desde que sua classe seja derivada 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 dependerá dos cenários que você pretende que sua propriedade suporte.
Você pode considerar implementar sua propriedade como uma propriedade de dependência quando quiser que ela dê suporte a um ou mais desses recursos do Windows Runtime ou de aplicativos do Windows Runtime:
- Definindo a propriedade por meio de um Estilo
- Agindo como propriedade de destino válida para vinculação de dados com {Binding}
- Suporte a valores animados por meio de um Storyboard
- Relatando quando o valor da propriedade foi alterado por:
- Ações executadas pelo próprio sistema de propriedades
- O ambiente
- Ações do usuário
- Estilos de leitura e escrita
Lista de verificação para definir uma propriedade de dependência
Definir uma propriedade de dependência pode ser considerada como um conjunto de conceitos. Esses conceitos não são necessariamente etapas processuais, pois vários conceitos podem ser abordados em uma única linha de código na implementação. Essa lista fornece apenas uma visão geral rápida. Explicaremos cada conceito com mais detalhes mais adiante neste tópico e mostraremos o código de exemplo em vários idiomas.
- Registre o nome da propriedade no sistema de propriedade (chamando 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 nulo para isso ou se você quiser um comportamento alterado pela 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ública estática de somente leitura no tipo de proprietário.
- Defina uma propriedade wrapper, seguindo o modelo do acessador de propriedades usado no idioma que você está implementando. O nome da propriedade wrapper deve corresponder à string de nome que você usou no 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 de sua própria propriedade como um parâmetro.
- (Opcional) Coloque atributos como ContentPropertyAttribute no invólucro.
Observação
Se você estiver definindo uma propriedade anexada personalizada, você geralmente omite o wrapper. Em vez disso, você escreve um estilo diferente de acessador que um processador XAML pode usar. Consulte propriedades personalizadas anexadas.
Registrando a propriedade
Para que sua propriedade seja uma propriedade de dependência, você deve registrar a propriedade em um repositório de propriedades mantido pelo sistema de propriedades do Windows Runtime. Para registrar a propriedade, você chama o método Register .
Para idiomas do Microsoft .NET (C# e Microsoft Visual Basic), você chama o Registro dentro do corpo da classe (dentro da classe, mas fora de qualquer definição de membro). O identificador é fornecido pela chamada do método Register , como o valor retornado. A chamada Registrar normalmente é feita como um construtor estático ou como parte da inicialização de uma propriedade estática pública somente leitura do tipo DependencyProperty em sua classe. Essa propriedade expõe o identificador da sua propriedade de dependência. Aqui estão exemplos da chamada Registro.
Observação
Registrar a propriedade de dependência como parte da definição de propriedade do 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 poderá 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 de como dividir a implementação entre o cabeçalho e o arquivo de código. A divisão típica é declarar o próprio identificador como propriedade pública estática na declaração, com uma implementação get, mas sem set. A implementação get refere-se a um campo privado, que é uma instância de DependencyProperty não inicializada. Você também pode declarar os wrappers e as implementações get e set do wrapper. Nesse caso, o cabeçalho inclui alguma implementação mínima. Se o wrapper precisar de atribuição do Windows Runtime, o atributo também será atribuído no cabeçalho. Coloque a chamada Registrar no arquivo de código, dentro de uma função auxiliar que só é executada quando o aplicativo inicializa na primeira vez. Use o valor retornado do Registro para preencher os identificadores estáticos, mas não inicializados que você declarou no cabeçalho, que você inicialmente definiu 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 você tem um campo privado e uma propriedade pública de leitura acessível que apresenta o DependencyProperty é para que outros chamadores que usam o seu DependencyProperty também possam usar APIs do utilitário 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ário. Exemplos dessa API e cenários incluem GetValue ou SetValue por escolha, ClearValue, GetAnimationBaseValue, SetBinding e Setter.Property. Você não pode usar um campo público para isso, pois as regras de metadados do Windows Runtime não permitem campos públicos.
Convenções de nome de propriedade de dependência
Há convenções de nomenclatura para propriedades de dependência; siga-as em todas as circunstâncias, salvo exceções. A propriedade de dependência em si tem um nome básico ("Label" no exemplo anterior) que é fornecido como o primeiro parâmetro do Register. O nome deve ser exclusivo em cada tipo de registro e o requisito de exclusividade também se aplica a todos os membros herdados. As propriedades de dependência herdadas por meio de tipos base já são consideradas parte do tipo de registro; os nomes das propriedades herdadas não podem ser registrados novamente.
Aviso
Embora o nome fornecido aqui possa ser qualquer identificador de cadeia de caracteres que seja válido na programação para sua linguagem de escolha, você geralmente deseja ser capaz de definir sua propriedade de dependência em XAML também. Para ser definido em XAML, o nome da propriedade escolhido deve ser um nome XAML válido. Para obter mais informações, consulte a visão geral do XAML.
Ao criar a propriedade do identificador, combine o nome com que você registrou essa propriedade com o sufixo "Property" ("LabelProperty", por exemplo). Essa propriedade é seu identificador para a propriedade de dependência e é usada como entrada para as chamadas SetValue e GetValue que você faz em seus próprios envoltórios de propriedade. Ele também é usado pelo sistema de propriedades e outros processadores XAML, como {x:Bind}
Implementando o wrapper
O wrapper de propriedade deve chamar GetValue na implementação get e SetValue na implementação set.
Aviso
Em todas as circunstâncias menos excepcionais, suas implementações de wrapper devem executar apenas as operações GetValue e SetValue . Caso contrário, você obterá um comportamento diferente quando sua propriedade for definida via XAML versus quando ela for definida por meio do código. Para obter eficiência, o analisador XAML ignora wrappers ao definir propriedades de dependência; e conversa com o repositório de backup por meio do 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 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 retorno de chamada estático que é automaticamente invocado no sistema de propriedades sempre que uma alteração no valor da propriedade é detectada.
Chamando Registro com metadados de propriedade
Nos exemplos anteriores de chamar DependencyProperty.Register, passamos um valor nulo para o parâmetro propertyMetadata . Para habilitar uma propriedade de dependência para fornecer um valor padrão ou usar um retorno de chamada alterado pela propriedade, você deve definir uma instância propertyMetadata que fornece um ou ambos os recursos.
Normalmente, você fornece um PropertyMetadata como uma instância criada em linha, dentro dos parâmetros de DependencyProperty.Register.
Observação
Se você estiver definindo uma implementação CreateDefaultValueCallback , deverá usar o método de utilitário PropertyMetadata.Create em vez de chamar um construtor PropertyMetadata para definir a instância PropertyMetadata .
Este próximo exemplo modifica os exemplos de DependencyProperty.Register mostrados anteriormente referenciando uma instância PropertyMetadata com um valor PropertyChangedCallback . A implementação do callback "OnLabelChanged" será apresentada 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 padrão
Você pode especificar um valor padrão para uma propriedade de dependência de modo que a propriedade sempre retorne um valor padrão específico quando ela não estiver configurada. 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 de 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 primitivo de linguagem (por exemplo, 0 para um inteiro ou uma cadeia de caracteres vazia para uma cadeia de caracteres). O principal motivo 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, especialmente para tipos de valor. No entanto, quando se trata de tipos de referência, certifique-se de que definir um valor padrão não resulte inadvertidamente em um padrão singleton. Para obter mais informações, consulte As 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 registre com um valor padrão de UnsetValue. Se você fizer isso, confundirá os consumidores de propriedades e terá consequências não intencionais dentro do sistema de propriedades.
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 threads de interface do usuário diferentes 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 para valores padrão. O valor retornado por CreateDefaultValueCallback é sempre associado ao thread CreateDefaultValueCallback da interface do usuário atual 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 de um CreateDefaultValueCallback é criar uma nova classe DependencyObject , definir o valor da propriedade específica de cada propriedade do DependencyObject como o padrão pretendido e retornar a nova classe como uma referência de objeto por meio do valor retornado 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 alterado pela propriedade para definir as interações de sua propriedade com outras propriedades de dependência ou atualizar uma propriedade interna ou estado do objeto sempre que a propriedade for alterada. Se o retorno de chamada for invocado, o sistema de propriedades determinará que há uma alteração efetiva do 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 forma, geralmente realizando alguma outra alteração no objeto passado como d. 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 ao NewValue.
Este próximo exemplo mostra uma implementação PropertyChangedCallback . Ele implementa o método que você viu referenciado nos exemplos de Registro anteriores, como parte dos argumentos de construção para o PropertyMetadata. O cenário abordado por esse callback é que a classe possui também uma propriedade somente leitura calculada chamada "HasLabelValue" (implementação não mostrada). Sempre que a propriedade "Label" é reavaliada, este método de callback é invocado e garante que o valor dependente calculado permaneça sincronizado com as alterações da 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;
}
}
Comportamento de propriedade alterado para estruturas e enumerações
Se o tipo de dependencyProperty for uma enumeração ou uma estrutura, o retorno de chamada poderá ser invocado mesmo se os valores internos da estrutura ou o valor de enumeração não forem alterados. Isso é diferente de um primitivo do sistema, como uma cadeia de caracteres, em que ele só será invocado se o valor for alterado. Esse é um efeito colateral das operações de box e unbox nesses valores, realizadas internamente. Se você tiver um método PropertyChangedCallback para uma propriedade em que seu valor seja uma enumeração ou estrutura, será necessário comparar o OldValue e o NewValue convertendo 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 desse tipo estiver disponível (o que pode ser o caso de uma estrutura personalizada), talvez seja necessário comparar os valores individuais. Normalmente, você não escolheria fazer nada se o resultado fosse 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
}
}
Práticas recomendadas
Tenha as seguintes considerações em mente como práticas recomendadas ao definir sua propriedade de dependência personalizada.
DependencyObject e multithreading
Todas as instâncias dependencyObject devem ser criadas no thread de interface do usuário associado à janela atual mostrada por um aplicativo 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 aspectos de threading do DependencyObject são relevantes porque geralmente significa que somente o código executado no thread de 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 em código de interface do usuário típico que faz uso correto de padrões assíncronos e threads de trabalho em segundo plano. Normalmente, você só encontrará problemas de threading relacionados a DependencyObject se estiver definindo seus próprios tipos DependencyObject e tentar usá-los para fontes de dados ou outros cenários em que um DependencyObject não é necessariamente apropriado.
Evitando singletons não intencionais
Um singleton não intencional pode ocorrer se você estiver declarando uma propriedade de dependência que utiliza um tipo de referência e chamar o construtor desse 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 tipo de referência único que você construiu. Todas as subpropriedades desse tipo de valor definidas por meio de sua propriedade de dependência são propagadas para outros objetos de maneiras que talvez você não tenha desejado.
Você pode usar construtores de classe para definir valores iniciais para uma propriedade de dependência de tipo de referência se quiser um valor não nulo, mas lembre-se 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 der 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 coleção
As propriedades de dependência do tipo coleção têm alguns problemas de implementação adicionais a serem considerados.
As propriedades de dependência do tipo coleção são relativamente raras na API do Windows Runtime. 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, você não pré-preenche os itens em uma coleção com estilos ou um template.
- 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 origem de associaçã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 obter mais informações sobre associação de e para coleções, consulte Associação de dados detalhadamente.
- 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 para propriedades de dependência do tipo coleção. As próximas três seções fornecem algumas diretrizes 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 como uma coleção única (instância) como parte da lógica do construtor de classe para a classe proprietária da propriedade da 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>();
}
Uma DependencyProperty e o valor padrão de 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 uma notificação de alteração para os itens na coleção, mesmo quando o sistema de propriedades invoca o método de retorno de chamada "PropertyChanged". Se você quiser notificações para coleções ou itens de coleção, por exemplo, para um cenário de associação de dados, implemente a interface INotifyPropertyChanged ou INotifyCollectionChanged . Para obter mais informações, consulte Vinculação de dados em detalhes.
Considerações de segurança para propriedades de dependência
Declare propriedades de dependência como propriedades públicas. Declare identificadores de propriedade de dependência como membros públicos estáticos somente leitura. Mesmo se você tentar declarar outros níveis de acesso permitidos por um idioma (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. Declarar o identificador de propriedade de dependência como interno ou privado não funcionará, pois o sistema de propriedades não pode operar corretamente.
As propriedades do wrapper são realmente apenas para conveniência, os mecanismos de segurança aplicados aos wrappers podem ser ignorados chamando GetValue ou SetValue . Portanto, mantenha as propriedades do wrapper públicas; caso contrário, você só torna sua propriedade mais difícil para os chamadores legítimos usarem sem fornecer nenhum benefício real de segurança.
O Windows Runtime não fornece um modo de registrar uma propriedade dependente 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 base de um construtor de classe derivada e a inserção do método virtual por meio 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á herda de DependencyObject, lembre-se de que o sistema de propriedades, em si, invoca e expõe métodos virtuais internamente como parte de suas funcionalidades. 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 que C#, tanto devido à separação em cabeçalho quanto ao arquivo de implementação e também 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 de inicializador estático do escopo raiz diretamente no DllMain, enquanto os compiladores C# atribuem os inicializadores estáticos às classes e, portanto, evitam problemas de bloqueio de carga DllMain .). A melhor prática aqui é declarar uma função auxiliar que realize todo o registro das propriedades de dependência para uma classe, sendo uma função por classe. Em seguida, para cada classe personalizada que seu aplicativo consome, você terá que referenciar a função de registro auxiliar exposta por cada classe personalizada que você deseja usar. Chame cada função de registro auxiliar uma vez como parte do construtor de aplicativo (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 retomado, por exemplo. Além disso, como visto no exemplo de registro C++ anterior, a verificação de nullptr em torno de cada chamada Register é importante: é uma garantia de que nenhum invocador da função possa registrar a propriedade duas vezes. Uma segunda chamada de registro provavelmente travaria seu aplicativo sem essa verificação porque o nome da propriedade seria duplicado. Você poderá ver esse padrão de implementação no usuário XAML e no exemplo de controles personalizados se examinar o código da versão C++/CX do exemplo.
Tópicos relacionados
Windows developer