Qual é a diferença entre uma propriedade de dependência e uma propriedade anexada no WPF?

Qual é a diferença entre uma propriedade de dependência (personalizada) e uma propriedade anexada no WPF? Quais são os usos para cada um? Como as implementações normalmente diferem?

As propriedades anexadas são um tipo de propriedade de dependência. A diferença está em como eles são usados.

Com uma propriedade anexada, a propriedade é definida em uma class que não é da mesma class para a qual está sendo usada. Isso geralmente é usado para layout. Bons exemplos são Panel.ZIndex ou Grid.Row – você aplica isso a um controle (ex .: Button), mas na verdade é definido em Panel ou Grid. A propriedade é “anexada” à instância do botão.

Isso permite que um contêiner, por exemplo, crie propriedades que possam ser usadas em qualquer UIelement.

Quanto às diferenças de implementação – é basicamente uma questão de usar Register vs. RegisterAttached quando você define a propriedade.

As propriedades anexadas são basicamente destinadas aos elementos de contêiner. Como se você tivesse uma grade e você tivesse grid.row agora, isso é considerado uma propriedade anexada de um elemento de grade. Também é possível usar essa propriedade no texbox, botão etc. lugar na grade.

Propriedade de dependência é como a propriedade basicamente pertence a alguma outra class e é usada em outra class. Por exemplo: como se você tivesse um retângulo, aqui height e width são propriedades regulares de retângulo, mas left e top são a propriedade de dependência, já que pertencem à class Canvass.

Abstrato

Desde que eu encontrei pouca ou nenhuma documentação sobre o assunto, demorou algum picar em torno do código-fonte , mas aqui está uma resposta.

Há uma diferença entre registrar uma propriedade de dependência como uma propriedade regular e como anexada, além de uma propriedade “filosófica” ( propriedades regulares destinam-se a ser usadas pelo tipo declarante e seus tipos derivados, propriedades anexadas devem ser usadas como extensões em instâncias arbitrárias de DependencyObject ). “Filosófico”, porque, como @MarqueIV notou em seu comentário à resposta do @ ReedCopsey, propriedades regulares também podem ser usadas com instâncias arbitrárias de DependencyObject .

Além disso, tenho que discordar de outras respostas afirmando que a propriedade anexada é “tipo de propriedade de dependência”, porque é enganosa – não há “tipos” de propriedades de dependência. O framework não se importa se a propriedade foi registrada como anexada ou não – nem é possível determinar (no sentido de que esta informação não é registrada, porque é irrelevante). Na verdade, todas as propriedades são registradas como se fossem propriedades anexadas, mas, no caso de propriedades regulares, algumas coisas adicionais são feitas, modificando um pouco seu comportamento.

Trecho de código

Para evitar o problema de passar pelo código-fonte, aqui está uma versão resumida do que acontece.

Ao registrar uma propriedade sem metadados especificados, chamar

 DependencyProperty.Register( name: "MyProperty", propertyType: typeof(object), ownerType: typeof(MyClass)) 

produz exatamente o mesmo resultado que chamar

 DependencyProperty.RegisterAttached( name: "MyProperty", propertyType: typeof(object), ownerType: typeof(MyClass)) 

No entanto, ao especificar metadados, chamar

 DependencyProperty.Register( name: "MyProperty", propertyType: typeof(object), ownerType: typeof(MyClass), typeMetadata: new FrameworkPropertyMetadata { CoerceValueCallback = CoerceCallback, DefaultValue = "default value", PropertyChangedCallback = ChangedCallback }); 

é equivalente a chamar

 var property = DependencyProperty.RegisterAttached( name: "MyProperty", propertyType: typeof(object), ownerType: typeof(MyClass), defaultMetadata: new PropertyMetadata { DefaultValue = "default value", }); property.OverrideMetadata( forType: typeof(MyClass), typeMetadata: new FrameworkPropertyMetadata { CoerceValueCallback = CoerceCallback, DefaultValue = "default value", PropertyChangedCallback = ChangedCallback }); 

Conclusões

A principal (e única) diferença entre as propriedades de dependência regular e anexada são os metadados padrão disponíveis por meio da propriedade DependencyProperty.DefaultMetadata . Isso é mencionado na seção Comentários :

Para propriedades não anexadas, o tipo de metadados retornado por essa propriedade não pode ser convertido em tipos derivados do tipo PropertyMetadata , mesmo que a propriedade tenha sido registrada originalmente com um tipo de metadados derivado. Se você deseja que os metadados originalmente registrados incluam seu tipo de metadados possivelmente derivado original, chame GetMetadata (Type) , passando o tipo de registro original como um parâmetro.

Para propriedades anexadas, o tipo de metadados retornado por essa propriedade corresponderá ao tipo fornecido no método de registro RegisterAttached original.

Isso é claramente visível no código fornecido. Pequenas dicas também estão ocultas nos methods de registro, isto é, para RegisterAttached o parâmetro de metadados é denominado defaultMetadata , enquanto que para Register ele é denominado typeMetadata . Para propriedades anexadas, os metadados fornecidos se tornam os metadados padrão. No entanto, no caso de propriedades regulares, os metadados padrão são sempre uma nova instância de PropertyMetadata com apenas o conjunto DefaultValue (seja dos metadados fornecidos ou automaticamente). Somente a chamada subsequente para OverrideMetadata realmente usa os metadados fornecidos.

Consequências

A principal diferença prática é que, no caso de propriedades regulares, CoerceValueCallback e PropertyChangedCallback são aplicáveis apenas a tipos derivados do tipo declarado como o tipo de proprietário e, para propriedades anexadas, são aplicáveis ​​a todos os tipos. Por exemplo, neste cenário:

 var d = new DependencyObject(); d.SetValue(SomeClass.SomeProperty, "some value"); 

o PropertyChangedCallback registrado será chamado se a propriedade foi registrada como uma propriedade anexada, mas não será chamada se estiver registrada como uma propriedade regular. O mesmo vale para CoerceValueCallback .

Uma diferença secundária deriva do fato de que OverrideMetadata requer que o tipo fornecido derive de DependencyObject . Na prática, isso significa que o tipo de proprietário para propriedades regulares deve derivar de DependencyObject , enquanto que para propriedades anexadas em pode ser qualquer tipo (incluindo classs estáticas, estruturas, enums, delegates, etc.).

Suplemento

Além da sugestão do @ MarqueIV, em várias ocasiões eu me deparei com opiniões de que as propriedades regulares e anexadas diferem na maneira como elas podem ser usadas no XAML . Ou seja, as propriedades regulares exigem uma syntax de nome implícita, em oposição à syntax de nome explícita exigida pelas propriedades anexadas. Isso tecnicamente não é verdade , embora na prática geralmente seja o caso. Para maior clareza:

     

No XAML puro , as únicas regras que governam o uso dessas syntaxs são as seguintes:

  • A syntax do nome implícito pode ser usada em um elemento se, e somente se, a class que este elemento representa tiver uma propriedade CLR desse nome
  • A syntax do nome explícito pode ser usada em um elemento se, e somente se, a class especificada pela primeira parte do nome completo expuser methods estáticos get / set apropriados (chamados de assessores ) com nomes correspondentes à segunda parte do nome completo

A satisfação dessas condições permite usar a syntax correspondente, independentemente de a propriedade de dependência de backup estar registrada como regular ou anexada.

Agora, o equívoco mencionado é causado pelo fato de que a grande maioria dos tutoriais (junto com os snippets de código do Visual Studio ) instrui você a usar a propriedade CLR para propriedades de dependência regulares e obter / definir acessadores para os anexos. Mas não há nada que impeça você de usar os dois ao mesmo tempo, permitindo que você use a syntax que preferir.

Eu acho que você pode definir propriedade anexada na própria class ou você pode defini-lo em outra class. Nós sempre poderíamos usar propriedade anexada para estender os controles padrão da Microsoft. Mas propriedade de dependência, você define em seu próprio controle personalizado. Por exemplo, você pode herdar seu controle de um controle padrão e definir uma propriedade de dependência em seu próprio controle e usá-la. Isso é equivalente a definir uma propriedade anexada e usar essa propriedade anexada no controle padrão.

Propriedades anexadas são um tipo especial de DependencyProperties. Eles permitem que você anexe um valor a um object que não sabe nada sobre esse valor. Um bom exemplo para este conceito são os painéis de layout. Cada painel de layout precisa de dados diferentes para alinhar seus elementos filhos. O Canvas precisa de Top e Left, o DockPanel precisa de Dock, etc. Como você pode escrever seu próprio painel de layout, a lista é infinita. Então, você vê, não é possível ter todas essas propriedades em todos os controles do WPF. A solução são propriedades anexadas. Eles são definidos pelo controle que precisa dos dados de outro controle em um contexto específico. Por exemplo, um elemento alinhado por um painel de layout pai.