Partilhar via


Propriedades anexadas personalizadas

Uma propriedade anexada é um conceito XAML. Propriedades anexadas normalmente são definidas como uma forma especializada de propriedade de dependência. Este tópico explica como implementar uma propriedade anexada como uma propriedade de dependência e como definir a convenção do acessador necessária para que a propriedade anexada possa ser usada no XAML.

Pré-requisitos

Pressupõe-se que você entenda propriedades de dependência da perspectiva de um consumidor de propriedades de dependência existentes e que tenha lido a Visão geral sobre propriedades de dependência. Você também deve ter lido a Visão geral sobre propriedades anexadas. Para seguir os exemplos neste tópico, você também deve entender XAML e saber como escrever um aplicativo Windows Runtime básico em C++, C# ou Visual Basic.

Cenários para propriedades anexadas

É possível criar uma propriedade anexada quando há um motivo para ter um mecanismo de configuração de propriedade disponível para classes diferentes da classe de definição. Os cenários mais comuns para isso são layout e suporte a serviços. Exemplos de propriedades de layout existentes são Canvas.ZIndex e Canvas.Top. Em um cenário de layout, os elementos existentes como elementos filho para elementos de controle do layout podem expressar requisitos de layout para seus elementos pai individualmente, sendo que cada um configura um valor da propriedade que o pai definiu como propriedade anexada. Um exemplo do cenário de suporte a serviços na API do Windows Runtime é o conjunto das propriedades anexadas de ScrollViewer, como ScrollViewer.IsZoomChainingEnabled.

Aviso

Uma limitação existente da implementação XAML do Windows Runtime é que você não pode animar sua propriedade anexada personalizada.

Registrar uma propriedade anexada personalizada

Se você estiver definindo a propriedade anexada estritamente para uso em outros tipos, a classe na qual a propriedade está registrada não precisará derivar de DependencyObject. Mas você precisa fazer com que o parâmetro de destino para acessadores use DependencyObject se seguir o modelo típico de ter sua propriedade anexada também como uma propriedade de dependência, para que você possa usar o repositório de propriedades de apoio.

Defina sua propriedade anexada como uma propriedade de dependência declarando uma propriedade public static readonly do tipo DependencyProperty. Você define essa propriedade usando o valor de retorno do método RegisterAttached. O nome da propriedade deve corresponder ao nome da propriedade anexada que você especifica como o parâmetro de nome RegisterAttached , com a cadeia de caracteres "Property" adicionada ao final. Essa é a convenção estabelecida para nomear os identificadores de propriedades de dependência em relação às propriedades que elas representam.

A área principal em que a definição de uma propriedade anexada personalizada difere de uma propriedade de dependência personalizada está em como você define os acessadores ou wrappers. Em vez de usar a técnica de wrapper descrita em Propriedades de dependência personalizadas, você também deve fornecer os métodos estáticos GetPropertyName e SetPropertyName como acessadores para a propriedade anexada. Os acessadores são usados principalmente pelo analisador XAML, embora qualquer outro chamador também possa usá-los para definir valores em cenários não XAML.

Importante

Se você não definir os acessadores corretamente, o processador XAML não poderá acessar sua propriedade anexada, e qualquer pessoa que tentar usá-la provavelmente receberá um erro do analisador XAML. Além disso, as ferramentas de design e codificação geralmente dependem das convenções de "*Property" para nomear identificadores quando encontram uma propriedade de dependência personalizada em um assembly referenciado.

Acessadores

A assinatura do acessador GetPropertyName deve ser:

public statictipo de valorObterPropertyName (DependencyObject target)

Para o Microsoft Visual Basic, ela é:

Public Shared Function GetPropertyName(ByVal target As DependencyObject) As valueType)

O objeto de destino pode ser de um tipo mais específico na sua implementação, mas deve derivar de DependencyObject. O valor retornado valueType pode ser especificado como um tipo mais específico na sua implementação. O tipo básico Object é aceitável, mas em geral convém que a sua propriedade anexada imponha segurança de tipos. O uso de digitação nas assinaturas de getter e setter é uma técnica recomendada de segurança de tipos.

A assinatura do acessador SetPropertyName deve ser:

public static void SetPropertyName(DependencyObject target ,valueType value)

Para o Visual Basic, ela é:

Public Shared Sub SetPropertyName(ByVal target As DependencyObject, ByVal value AsvalueType)

O objeto de destino pode ser de um tipo mais específico na sua implementação, mas deve derivar de DependencyObject. O valor objeto value e seu valueType podem ser especificados como um tipo mais específico na sua implementação. Lembre-se de que o valor desse método é a entrada que vem do processador XAML quando ele encontra sua propriedade anexada na marcação. Deve haver conversão de tipo ou suporte de extensão de marcação existente para o tipo que você usa, de forma que o tipo apropriado possa ser criado a partir de um valor de atributo (que, em última análise, é apenas uma cadeia de caracteres). O tipo básico Object é aceitável, mas muitas vezes você vai querer garantir mais segurança de tipos. Para conseguir isso, aplique imposição de tipos aos acessadores.

Observação

Também é possível definir uma propriedade anexada em que o uso pretendido é por meio da sintaxe de elementos de propriedade. Nesse caso, você não precisa de conversão de tipos para os valores, mas precisa garantir que os valores pretendidos possam ser construídos em XAML. VisualStateManager.VisualStateGroups é um exemplo de uma propriedade anexada existente que oferece suporte apenas ao uso de elementos de propriedade.

Exemplo de código

Este exemplo mostra o registro da propriedade de dependência (usando o método RegisterAttached), bem como os acessadores Get e Set, para uma propriedade anexada personalizada. No exemplo, o nome da propriedade anexada é IsMovable. Portanto, os acessadores devem ser nomeados como GetIsMovable e SetIsMovable. O proprietário da propriedade anexada é uma classe de serviço GameService que não tem uma interface de usuário própria: sua finalidade é apenas fornecer os serviços de propriedade anexada quando a propriedade anexada GameService.IsMovable é usada.

Definir a propriedade anexada em C++/CX é um processo um pouco mais complexo. Você precisa decidir como fatorar entre o cabeçalho e o arquivo de código. Além disso, você deve expor o identificador como uma propriedade com apenas um acessador get, por motivos discutidos em Propriedades de dependências personalizadas. Em C++/CX, você deve definir essa relação entre propriedade e campo explicitamente, em vez de depender de palavras-chave readonly do .NET e no suporte implícito de propriedades simples. Você também precisa executar o registro da propriedade anexada em uma função auxiliar que é executada somente uma vez, quando o aplicativo é iniciado pela primeira vez, mas antes do carregamento de quaisquer páginas XAML que precisem da propriedade anexada. O local típico para chamar suas funções auxiliares de registro de propriedade para toda e qualquer propriedade de dependência ou anexada é de dentro do construtor App / Application no código do seu arquivo app.xaml.

public class GameService : DependencyObject
{
    public static readonly DependencyProperty IsMovableProperty = 
    DependencyProperty.RegisterAttached(
      "IsMovable",
      typeof(Boolean),
      typeof(GameService),
      new PropertyMetadata(false)
    );
    public static void SetIsMovable(UIElement element, Boolean value)
    {
        element.SetValue(IsMovableProperty, value);
    }
    public static Boolean GetIsMovable(UIElement element)
    {
        return (Boolean)element.GetValue(IsMovableProperty);
    }
}
Public Class GameService
    Inherits DependencyObject

    Public Shared ReadOnly IsMovableProperty As DependencyProperty = 
        DependencyProperty.RegisterAttached("IsMovable",  
        GetType(Boolean), 
        GetType(GameService), 
        New PropertyMetadata(False))

    Public Shared Sub SetIsMovable(ByRef element As UIElement, value As Boolean)
        element.SetValue(IsMovableProperty, value)
    End Sub

    Public Shared Function GetIsMovable(ByRef element As UIElement) As Boolean
        GetIsMovable = CBool(element.GetValue(IsMovableProperty))
    End Function
End Class
// GameService.idl
namespace UserAndCustomControls
{
    [default_interface]
    runtimeclass GameService : Windows.UI.Xaml.DependencyObject
    {
        GameService();
        static Windows.UI.Xaml.DependencyProperty IsMovableProperty{ get; };
        static Boolean GetIsMovable(Windows.UI.Xaml.DependencyObject target);
        static void SetIsMovable(Windows.UI.Xaml.DependencyObject target, Boolean value);
    }
}

// GameService.h
...
    static Windows::UI::Xaml::DependencyProperty IsMovableProperty() { return m_IsMovableProperty; }
    static bool GetIsMovable(Windows::UI::Xaml::DependencyObject const& target) { return winrt::unbox_value<bool>(target.GetValue(m_IsMovableProperty)); }
    static void SetIsMovable(Windows::UI::Xaml::DependencyObject const& target, bool value) { target.SetValue(m_IsMovableProperty, winrt::box_value(value)); }

private:
    static Windows::UI::Xaml::DependencyProperty m_IsMovableProperty;
...

// GameService.cpp
...
Windows::UI::Xaml::DependencyProperty GameService::m_IsMovableProperty =
    Windows::UI::Xaml::DependencyProperty::RegisterAttached(
        L"IsMovable",
        winrt::xaml_typename<bool>(),
        winrt::xaml_typename<UserAndCustomControls::GameService>(),
        Windows::UI::Xaml::PropertyMetadata{ winrt::box_value(false) }
);
...
// GameService.h
#pragma once

#include "pch.h"
//namespace WUX = Windows::UI::Xaml;

namespace UserAndCustomControls {
    public ref class GameService sealed : public WUX::DependencyObject {
    private:
        static WUX::DependencyProperty^ _IsMovableProperty;
    public:
        GameService::GameService();
        void GameService::RegisterDependencyProperties();
        static property WUX::DependencyProperty^ IsMovableProperty
        {
            WUX::DependencyProperty^ get() {
                return _IsMovableProperty;
            }
        };
        static bool GameService::GetIsMovable(WUX::UIElement^ element) {
            return (bool)element->GetValue(_IsMovableProperty);
        };
        static void GameService::SetIsMovable(WUX::UIElement^ element, bool value) {
            element->SetValue(_IsMovableProperty,value);
        }
    };
}

// GameService.cpp
#include "pch.h"
#include "GameService.h"

using namespace UserAndCustomControls;

using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Data;
using namespace Windows::UI::Xaml::Documents;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Interop;
using namespace Windows::UI::Xaml::Media;

GameService::GameService() {};

GameService::RegisterDependencyProperties() {
    DependencyProperty^ GameService::_IsMovableProperty = DependencyProperty::RegisterAttached(
         "IsMovable", Platform::Boolean::typeid, GameService::typeid, ref new PropertyMetadata(false));
}

Definir sua propriedade anexada personalizada a partir da marcação XAML

Depois de definir sua propriedade anexada e incluir seus membros de suporte como parte de um tipo personalizado, você deve disponibilizar as definições para uso em XAML. Para isso, você deve mapear um namespace XAML que fará referência ao namespace de código que contém a classe relevante. Nos casos em que você definiu a propriedade anexada como parte de uma biblioteca, deve incluir essa biblioteca como parte do pacote do aplicativo para o aplicativo.

Um mapeamento de namespace de XML para XAML normalmente é colocado no elemento raiz de uma página XAML. Por exemplo, para a classe GameService no namespace UserAndCustomControls que contém as definições de propriedades anexadas mostradas em trechos anteriores, o mapeamento pode ter esta aparência.

<UserControl
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:uc="using:UserAndCustomControls"
  ... >

Usando o mapeamento, você pode definir sua propriedade anexada GameService.IsMovable em qualquer elemento que corresponda à sua definição de destino, incluindo um tipo existente definido pelo Windows Runtime.

<Image uc:GameService.IsMovable="True" .../>

Se estiver definindo a propriedade em um elemento que também está dentro do mesmo namespace de XML mapeado, ainda deverá incluir o prefixo no nome da propriedade anexada. Isso ocorre porque o prefixo qualifica o tipo de proprietário. O atributo da propriedade anexada não pode ser assumido como estando dentro do mesmo namespace de XML que o elemento em que atributo está incluído, mesmo que, pelas regras XML normais, atributos possam herdar o namespace de elementos. Por exemplo, se você estiver definindo GameService.IsMovable em um tipo personalizado de ImageWithLabelControl (definição não mostrada), e mesmo se ambos fossem definidos no mesmo namespace de código mapeado para o mesmo prefixo, o XAML ainda seria esse.

<uc:ImageWithLabelControl uc:GameService.IsMovable="True" .../>

Observação

Se estiver escrevendo uma interface do usuário XAML com C++/CX, você deverá incluir o cabeçalho do tipo personalizado que define a propriedade anexada sempre que uma página XAML usar esse tipo. Cada página XAML tem um cabeçalho code-behind associado (.xaml.h). É aqui que você deve incluir (usando #include) o cabeçalho da definição do tipo de proprietário da propriedade anexada.

Definir sua propriedade anexada personalizada imperativamente

Você também pode acessar uma propriedade anexada personalizada a partir do código imperativo. O código abaixo mostra como fazer isso.

<Image x:Name="gameServiceImage"/>
// MainPage.h
...
#include "GameService.h"
...

// MainPage.cpp
...
MainPage::MainPage()
{
    InitializeComponent();

    GameService::SetIsMovable(gameServiceImage(), true);
}
...

Tipo de valor de uma propriedade anexada personalizada

O tipo usado como o tipo de valor de uma propriedade anexada personalizada afeta o uso e/ou a definição. O tipo de valor da propriedade anexada é declarado em vários locais: nas assinaturas dos métodos de acessador Get and Set e também como o parâmetro propertyType da chamada RegisterAttached.

O tipo de valor mais comum para propriedades anexadas (personalizadas ou não) é uma cadeia de caracteres simples. Isso ocorre porque propriedades anexadas geralmente são destinadas ao uso de atributos XAML, e o uso de uma cadeia de caracteres como o tipo de valor mantém as propriedades leves. Outras primitivas que têm conversão nativa em métodos de cadeia de caracteres, como interger, double ou um valor de enumeração, também são comuns como tipos de valor para propriedades anexadas. Você pode usar outros tipos de valor (que não oferecem suporte para conversão de cadeia de caracteres nativa) como o valor da propriedade anexada. Porém, isso implica fazer uma escolha sobre o uso ou a implementação:

  • Você pode deixar a propriedade anexada como está, mas ela pode oferecer suporte para uso somente quando é um elemento de propriedade, e o valor é declarado como um elemento de objeto. Nesse caso, o tipo de propriedade precisa oferecer suporte ao uso de XAML como um elemento de objeto. Para classes de referência existentes do Windows Runtime, verifique a sintaxe XAML para terc certeza de que o tipo oferece suporte ao uso de elementos de objeto XAML.
  • Você pode deixar a propriedade anexada como está, mas usá-la apenas em um atributo por meio de uma técnica de referência XAML, Binding ou StaticResource, que possa ser expressa como uma cadeia de caracteres.

Mais sobre o exemplo Canvas.Left

Em exemplos anteriores de usos de propriedades anexadas, mostramos diferentes maneiras de definir a propriedade anexada Canvas.Left . Mas o que isso muda sobre como um Canvas interage com seu objeto e quando isso acontece? Examinaremos esse exemplo específico mais adiante, porque, se você implementar uma propriedade anexada, é interessante ver o que mais uma classe típica de proprietário de propriedade anexada pretende fazer com seus valores de propriedades anexadas se os encontrar em outros objetos.

A principal função de um Canvas é ser um contêiner de layout com posicionamento absoluto na interface do usuário. Os filhos de Canvas são armazenados em uma propriedade Children definida pela classe base. De todos os painéis, Canvas é o único que usa posicionamento absoluto. Ele sobrecarregaria o modelo de objeto do tipo UIElement comum para adicionar propriedades que pudessem ser apenas de interesse para Canvas e aqueles casos específicos de UIElement em que são elementos filho de um UIElement. Definir as propriedades de controle de layout de um Canvas para serem propriedades anexadas que qualquer UIElement possa usar mantém o modelo de objeto mais limpo.

Para ser um painel prático, Canvas tem um comportamento que substitui os métodos Measure e Arrange no nível da estrutura. É aqui que Canvas realmente verifica se há valores de propriedades anexadas em seus filhos. Parte dos padrões de Measure e Arrange é um loop que itera sobre qualquer conteúdo, e um painel tem a a propriedade Children que torna explícito o que deve ser considerado o filho de um painel. Portanto, o comportamento de layout de Canvas itera por esses filhos e faz chamadas estáticas Canvas.GetLeft e Canvas.GetTop para cada filho para ver se essas propriedades anexadas contêm um valor não padrão (o padrão é 0). Esses valores são usados para o posicionamento absoluto de cada filho no espaço de layout disponível de Canvas, de acordo com os valores específicos fornecidos por cada filho e confirmados com o uso de Arrange.

O código fica mais ou menos parecido com este pseudocódigo:

protected override Size ArrangeOverride(Size finalSize)
{
    foreach (UIElement child in Children)
    {
        double x = (double) Canvas.GetLeft(child);
        double y = (double) Canvas.GetTop(child);
        child.Arrange(new Rect(new Point(x, y), child.DesiredSize));
    }
    return base.ArrangeOverride(finalSize); 
    // real Canvas has more sophisticated sizing
}

Observação

Para saber mais sobre como painéis funcionam, consulte Visão geral de painéis personalizados XAML.