Partilhar via


Propriedades personalizadas anexadas

Uma propriedade anexada é um conceito XAML. As propriedades anexadas são normalmente 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 de acessador necessária para que sua propriedade anexada seja utilizável em XAML.

Pré-requisitos

Supomos que você entenda as propriedades de dependência da perspetiva de um consumidor de propriedades de dependência existentes e que tenha lido a Visão geral das propriedades de dependência. Você deve também ter lido a Visão geral das propriedades anexadas. 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.

Cenários para propriedades anexadas

Você pode criar uma propriedade anexada quando houver um motivo para ter um mecanismo de configuração de propriedade disponível para classes diferentes da classe definidora. Os cenários mais comuns para isso são o suporte a layout e serviços. Exemplos de propriedades de layout existentes são Canvas.ZIndex e Canvas.Top. Em um cenário de layout, os elementos que existem como elementos descendentes para elementos controladores de layout podem expressar requisitos de layout para seus elementos pai individualmente, cada um definindo um valor de propriedade que o pai define como uma propriedade anexada. Um exemplo do cenário de suporte a serviços na API do Tempo de Execução do Windows é o conjunto de propriedades anexadas do ScrollViewer, como ScrollViewer.IsZoomChainingEnabled.

Advertência

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

Registrando uma propriedade anexada personalizada

Se você estiver definindo a propriedade anexada estritamente para uso em outros tipos, a classe onde a propriedade está registrada não precisa derivar de DependencyObject. No entanto, é necessário ter o parâmetro de destino para que os acessores usem DependencyObject ao seguir o modelo típico onde a sua propriedade anexada também é uma propriedade de dependência, para que possa utilizar o armazenamento de propriedades subjacente.

Defina a sua propriedade anexada como uma propriedade de dependência declarando uma propriedade pública estática somente leitura 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 especificado como o parâmetro RegisterAttachedname , com a cadeia de caracteres "Property" adicionada ao final. Esta é a convenção estabelecida para nomear os identificadores de propriedades de dependência em relação às propriedades que elas representam.

A principal área em que a definição de uma propriedade anexada personalizada difere de uma propriedade de dependência personalizada é como se definem os acessores ou contornos. Em vez de utilizar a técnica de wrapper descrita em Propriedades de dependência personalizadas, deve-se também fornecer métodos estáticos GetPropertyName e SetPropertyName como métodos de acesso 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á-lo provavelmente receberá um erro de analisador XAML. Além disso, as ferramentas de design e programação geralmente dependem das convenções "*Property" para nomear identificadores quando encontram uma propriedade de dependência customizada num conjunto referenciado.

Accessors

A assinatura para o acessador GetPropertyName deve ser esta.

public static valueTypeObterPropertyName(DependencyObject target)

Para o Microsoft Visual Basic, é isso.

Public Shared Function Get Nome da(ByVal target As DependencyObject) As propriedadevalueType)

O objeto de destino pode ser de um tipo mais específico em sua implementação, mas deve derivar de DependencyObject. O valor de retorno ValueType também pode ser de um tipo mais específico em sua implementação. O tipo de objeto básico é aceitável, mas muitas vezes você desejará que sua propriedade anexada imponha a segurança do tipo. O uso de tipagem nas assinaturas getter e setter é uma técnica de segurança de tipo recomendada.

A assinatura para o acessador setPropertyName deve ser esta.

public static void Set Nome da Propriedade(DependencyObject target ,tipoDeValor value)

Para Visual Basic, é isso.

Public Shared Sub Set Nome da Propriedade(ByVal target As DependencyObject, ByVal value AstipoDeValor)

O objeto de destino pode ser de um tipo mais específico em sua implementação, mas deve derivar de DependencyObject. O objeto value e seu valueType podem ser de um tipo mais específico em 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, para 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 de objeto básico é aceitável, mas muitas vezes você vai querer mais segurança de tipo. Para conseguir isso, coloque a restrição de tipos nos métodos de acesso.

Observação

Também é possível definir uma propriedade anexada onde o uso pretendido é através da sintaxe do elemento de propriedade. Nesse caso, você não precisa de conversão de tipo 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 suporta apenas o uso de elementos de propriedade.

Exemplo de código

Este exemplo mostra o registo da propriedade de dependência (usando o método RegisterAttached), bem como os acessores Get e Set, para uma propriedade anexada personalizada. No exemplo, o nome da propriedade anexada é IsMovable. Portanto, os acessadores devem ser nomeados GetIsMovable e SetIsMovable. O proprietário da propriedade anexada é uma classe de serviço nomeada GameService que não tem uma IU própria. A 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 pouco mais complexo. Você tem que 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ência personalizadas. Em C++/CX, deve definir essa relação campo-propriedade de forma explícita, em vez de confiar nas palavras-chave somente leitura 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 só é executada uma vez, quando o aplicativo é iniciado pela primeira vez, mas antes que as páginas XAML que precisem da propriedade anexada sejam carregadas. O local típico para chamar as funções auxiliares de registo de propriedade para quaisquer dependências ou propriedades anexadas é no construtor App / Application no código do ficheiro 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));
}

Definindo 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 de XAML. Para fazer 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, você deve incluir essa biblioteca como parte do pacote do aplicativo para o aplicativo.

Um mapeamento de namespace XML para XAML normalmente é colocado no elemento raiz de uma página XAML. Por exemplo, para a classe nomeada GameService no namespace UserAndCustomControls que contém as definições de propriedade 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 GameService.IsMovable propriedade anexada em qualquer elemento que corresponda à sua definição de destino, incluindo um tipo existente que o Tempo de Execução do Windows define.

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

Se você estiver definindo a propriedade em um elemento que também esteja dentro do mesmo namespace 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 considerado como estando dentro do mesmo namespace XML que o elemento onde o atributo está incluído, mesmo que, por regras XML normais, os atributos possam herdar namespace de elementos. Por exemplo, se estiveres a definir GameService.IsMovable num tipo personalizado de ImageWithLabelControl (definição não mostrada), e mesmo que ambos sejam definidos no mesmo namespace de código mapeado para o mesmo prefixo, o XAML será ainda assim.

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

Observação

Se você estiver escrevendo uma interface do usuário XAML com C++/CX, deverá incluir o cabeçalho para o 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 para a definição do tipo de proprietário da propriedade anexada.

Definindo a sua propriedade anexada personalizada de forma imperativa

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

<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 utilizado como valor de uma propriedade personalizada anexada 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 acesso Get e Set, e também como parâmetro propertyType na chamada RegisterAttached.

O tipo de valor mais comum para propriedades anexadas (personalizadas ou não) é uma cadeia de caracteres simples. Isso ocorre porque as propriedades anexadas geralmente se destinam 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 para métodos de cadeia de caracteres, como inteiro, duplo 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, aqueles que não oferecem suporte à conversão de cadeia de caracteres nativa, como o valor da propriedade anexada. No entanto, isso implica fazer uma escolha sobre o uso ou a implementação:

  • Você pode deixar a propriedade anexada como está, mas a propriedade anexada pode suportar o uso somente quando a propriedade anexada é um elemento de propriedade e o valor é declarado como um elemento de objeto. Nesse caso, o tipo de propriedade precisa dar suporte ao uso de XAML como um elemento de objeto. Para classes de referência existentes do Tempo de Execução do Windows, verifique a sintaxe XAML para certificar-se de que o tipo suporta o uso de elementos de objeto XAML.
  • Você pode deixar a propriedade anexada como está, mas usá-la somente em um uso de atributo por meio de uma técnica de referência XAML, como Binding ou StaticResource que pode ser expressa como uma cadeia de caracteres.

Mais sobre o exemplo Canvas.Left

Em exemplos anteriores de usos de propriedade anexada, 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 este exemplo em particular para compreender melhor, pois ao implementar uma propriedade anexada, é interessante observar o que mais uma classe proprietária típica pretende fazer com os seus valores de propriedade anexados se os encontrar noutros objetos.

A principal função de um Canvas é ser um contêiner de layout posicionado de forma absoluta na interface do usuário. Os filhos de um Canvas são armazenados em uma propriedade definida de classe base Children. De todos os painéis, o Canvas é o único que utiliza posicionamento absoluto. Teria inflacionado o modelo de objeto do tipo comum UIElement ao adicionar propriedades que talvez apenas interessem ao Canvas e àqueles casos específicos de UIElement em que eles são elementos filhos de um UIElement. Definir as propriedades de controlo de layout de um Canvas como propriedades anexadas que qualquer UIElement pode utilizar ajuda a manter o modelo de objeto mais limpo.

Para ser um painel prático, o Canvas tem um comportamento que substitui os métodos Measure e Arrange no nível da estrutura. É aqui que o Canvas realmente verifica os valores de propriedade anexados em seus filhos. Parte dos padrões Measure e Arrange é um loop que itera sobre qualquer conteúdo, e um painel tem a propriedade Children que torna explícito o que deve ser considerado filho de um painel. Assim, o comportamento de layout do Canvas itera através desses filhos e faz com que Canvas.GetLeft e Canvas.GetTop estáticos chamem 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 posicionar absolutamente cada filho no espaço de layout disponível do Canvas de acordo com os valores específicos fornecidos por cada filho e confirmados usando Arrange.

O código se parece com esse 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 os painéis funcionam, veja Visão geral dos painéis personalizados XAML.