XAML e classes personalizadas para WPF

O XAML conforme implementado em estruturas CLR (Common Language Runtime) oferece suporte à capacidade de definir uma classe ou estrutura personalizada em qualquer linguagem CLR (Common Language Runtime) e, em seguida, acessar essa classe usando a marcação XAML. Você pode usar uma mistura de tipos definidos pelo Windows Presentation Foundation (WPF) e seus tipos personalizados no mesmo arquivo de marcação, normalmente mapeando os tipos personalizados para um prefixo de namespace XAML. Este tópico aborda as exigências que uma classe personalizada deve cumprir para que possa ser usada como um elemento XAML.

Classes personalizadas em aplicativos ou assemblies

As classes personalizadas usadas em XAML podem ser definidas de duas maneiras distintas: dentro do code-behind ou outro código que produz o aplicativo WPF (Windows Presentation Foundation) primário ou como uma classe em um assembly separado, como um executável ou DLL usado como uma biblioteca de classes. Cada uma dessas abordagens apresenta vantagens e desvantagens.

  • A vantagem de criar uma biblioteca de classes é que qualquer uma dessas classes personalizadas pode ser compartilhada entre vários aplicativos possíveis diferentes. Uma biblioteca separada também facilita o controle de problemas de versão dos aplicativos e simplifica uma classe em que o uso pretendido da classe é como um elemento raiz em uma página XAML.

  • A vantagem de definir as classes personalizadas no aplicativo é que essa técnica é relativamente simples e minimiza os problemas de implantação e testes encontrados quando assemblies separados são introduzidos além do executável principal do aplicativo.

  • Se definidas no mesmo assembly ou em um assembly diferente, as classes personalizadas precisam ser mapeadas entre o namespace de CLR e o namespace de XML para que possam ser usadas no XAML como elementos. Consulte Namespaces de XAML e mapeamento de namespace para XAML do WPF.

Exigências para uma classe personalizada como um elemento XAML

Para que possa ser instanciada como um elemento de objeto, sua classe deve cumprir as exigências abaixo:

  • A classe personalizada deve ser pública e dar suporte a um construtor público padrão (sem parâmetros). (Consulte a seção a seguir para ver as observações sobre estruturas.)

  • A classe personalizada não deve ser uma classe aninhada. As classes aninhadas e o "ponto" em sua sintaxe geral de uso do CLR interferem em outros recursos do WPF e/ou XAML, como propriedades anexadas.

Além de habilitar a sintaxe de elemento de objeto, sua definição de objeto também habilita a sintaxe de elemento de propriedade para outras propriedades públicas que assumem o objeto como o tipo de valor. Isso ocorre porque, agora, o objeto pode ser instanciado como um elemento de objeto e pode preencher o valor do elemento da propriedade de tal propriedade.

Estruturas

As estruturas que você define como tipos personalizados sempre podem ser construídas em XAML no WPF. Isso ocorre porque os compiladores CLR criam implicitamente um construtor sem parâmetros para uma estrutura que inicializa todos os valores de propriedade para seus padrões. Em alguns casos, o comportamento de construção padrão e/ou o uso do elemento de objeto para uma estrutura não é desejável. Isso pode ocorrer porque a estrutura se destina a preencher valores e a funcionar conceitualmente como uma união, em que os valores contidos poderão ter interpretações mutuamente exclusivas e, consequentemente, nenhuma das propriedades será configurável. Um exemplo de WPF de tal estrutura é GridLength. Em geral, tais estruturas devem implementar um conversor de tipo de modo que os valores possam ser expressos na forma de atributo, usando as convenções de cadeia de caracteres que criam a interpretações ou modos diferentes de valores da estrutura. A estrutura também deve expor comportamento semelhante para a construção de código por meio de um construtor sem parâmetros.

Exigências para as propriedades de uma classe personalizada como atributos XAML

As propriedades devem fazer referência a um tipo por valor (como uma primitiva) ou usar uma classe para tipo que tenha um construtor sem parâmetros ou um conversor de tipo dedicado que um processador XAML possa acessar. Na implementação XAML do CLR, os processadores XAML encontram esses conversores por meio do suporte nativo para primitivas de linguagem ou por meio da aplicação de a um tipo ou membro em definições de tipo de TypeConverterAttribute backup

Como alternativa, a propriedade poderá fazer referência a um tipo de classe abstrata ou a uma interface. Para classes abstratas ou interfaces, a expectativa de análise de XAML é que o valor da propriedade deve ser preenchido com instâncias de classe práticas que implementam a interface ou instâncias dos tipos que derivam da classe abstrata.

As propriedades podem ser declaradas em uma classe abstrata, mas podem ser definidas somente em classes práticas que derivam da classe abstrata. Isso ocorre porque a criação do elemento object para a classe requer um construtor public parameterless na classe.

Sintaxe de atributo habilitada para TypeConverter

Se você fornecer um conversor de tipos atribuído e dedicado no nível de classe, a conversão de tipos aplicada habilita a sintaxe de atributo para qualquer propriedade que precise instanciar esse tipo. Um conversor de tipo não habilita o uso do elemento objeto do tipo; Somente a presença de um construtor sem parâmetros para esse tipo habilita o uso do elemento Object. Portanto, propriedades que são habilitadas por conversor de tipos geralmente não são utilizáveis em sintaxe de propriedade, a menos que o próprio tipo também dê suporte à sintaxe de elemento de objeto. A exceção é que é possível especificar uma sintaxe de elemento de propriedade, mas o elemento de propriedade contêm uma cadeia de caracteres. Esse uso é essencialmente equivalente a um uso de sintaxe de atributo, e tal uso não é comum, a menos que haja a necessidade de uma manipulação de espaço em branco mais robusta do valor do atributo. O exemplo a seguir é um uso de elemento de propriedade que usa uma cadeia de caracteres e o equivalente do uso de atributo:

<Button>Hallo!
  <Button.Language>
    de-DE
  </Button.Language>
</Button>
<Button Language="de-DE">Hallo!</Button>

Exemplos de propriedades em que a sintaxe de atributo é permitida, mas a sintaxe de elemento de propriedade que contém um elemento de objeto não é permitida por meio de XAML, são várias propriedades que usam o Cursor tipo. A Cursor classe tem um conversor CursorConverterde tipo dedicado, mas não expõe um construtor sem parâmetro, portanto, a Cursor propriedade só pode ser definida por meio da sintaxe de atributo, mesmo que o tipo real Cursor seja um tipo de referência.

Conversores de tipo por propriedade

Como alternativa, a própria propriedade poderá declarar um conversor de tipos no nível de propriedade. Isso habilita uma "mini linguagem" que instancia objetos do tipo da propriedade embutida, processando valores de cadeia de caracteres de entrada do atributo como entrada para uma ConvertFrom operação com base no tipo apropriado. Normalmente, isso é feito para fornecer um acessador de conveniência, mas não como meio exclusivo para configurar uma propriedade em XAML. No entanto, também é possível usar conversores de tipo para atributos onde você deseja usar tipos CLR existentes que não fornecem um construtor sem parâmetros ou um conversor de tipo atribuído. Exemplos da API do WPF são determinadas propriedades que usam o CultureInfo tipo. Nesse caso, o WPF usou o tipo existente do Microsoft .NET Framework CultureInfo para abordar melhor cenários de compatibilidade e migração que eram usados em versões anteriores de estruturas, mas o tipo não dava CultureInfo suporte aos construtores necessários ou à conversão de tipo em nível de tipo para ser utilizável como um valor de propriedade XAML diretamente.

Sempre que você expõe uma propriedade que tem uma utilização de XAML, especialmente se for um autor de controle, considere a possibilidade de dar suporte a essa propriedade com uma propriedade de dependência. Isso é particularmente verdadeiro se você usar a implementação existente do Windows Presentation Foundation (WPF) do processador XAML, porque você pode melhorar o desempenho usando DependencyProperty o backup. Uma propriedade de dependência vai expor recursos do sistema de propriedades para a propriedade que os usuários virão a esperar de uma propriedade acessível do XAML. Isso inclui recursos como animação, vinculação de dados e suporte de estilo. Para obter mais informações, consulte Propriedades de dependência personalizada e Carregamento de XAML e propriedades de dependência.

Gravando e atribuindo um conversor de tipos

Ocasionalmente, você precisará escrever uma classe derivada personalizada TypeConverter para fornecer conversão de tipo para seu tipo de propriedade. Para obter instruções sobre como derivar e criar um conversor de tipos que pode dar suporte a usos de XAML e como aplicar o TypeConverterAttribute, consulte TypeConverters e XAML.

Exigências de sintaxe de atributo do manipulador de eventos XAML em eventos de uma classe personalizada

Para ser utilizável como um evento CLR, o evento deve ser exposto como um evento público em uma classe que ofereça suporte a um construtor sem parâmetros ou em uma classe abstrata em que o evento possa ser acessado em classes derivadas. Para ser usado convenientemente como um evento roteado, seu evento CLR deve implementar métodos e explícitos, que adicionam e removem manipuladores para a assinatura de evento CLR e encaminham esses manipuladores para os métodos e addremoveRemoveHandler .AddHandler Esses métodos adicionam ou removem os manipuladores para o armazenamento do manipulador de eventos roteados na instância à qual o evento está anexado.

Observação

É possível registrar manipuladores diretamente para eventos roteados usando AddHandlero , e deliberadamente não definir um evento CLR que exponha o evento roteado. Em geral, isso não é recomendado, porque o evento não permitirá sintaxe de atributo XAML para anexar manipuladores; a classe resultante oferecerá um modo de exibição de XAML menos transparente dos recursos do tipo.

Gravando propriedades de coleção

As propriedades que usam um tipo de coleção têm uma sintaxe XAML que permite especificar os objetos que são adicionados à coleção. Essa sintaxe possui dois recursos notáveis.

  • O objeto que é o objeto de coleção não precisa ser especificado na sintaxe de elemento de objeto. A presença desse tipo de coleção é implícita sempre que você especifica uma propriedade em XAML que utiliza um tipo de coleção.

  • Os elementos filho da propriedade de coleção na marcação são processados para se tornarem membros da coleção. Normalmente, o acesso ao código para os membros de uma coleção é realizado por meio de métodos de lista/dicionário, como Add, ou por meio de um indexador. Entretanto, a sintaxe de XAML não dá suporte a métodos ou indexadores (exceção: o XAML 2009 pode dar suporte a métodos, mas seu uso restringe os possíveis usos do WPF; consulte Recursos da linguagem XAML 2009). As coleções obviamente são uma exigência muito comum para a criação de uma árvore de elementos; você precisa de alguma forma para preencher essas coleções em XAML declarativo. Portanto, os elementos filho de uma propriedade de coleção são processados ao serem adicionados à coleção que é o valor de tipo de propriedade da coleção.

A implementação de serviços de XAML do .NET Framework e, consequentemente, o processador XAML do WPF usam a seguinte definição para o que constitui uma propriedade de coleção. O tipo de propriedade da propriedade deve implementar um dos seguintes:

Cada um desses tipos de CLR tem um método do Add, que é usado pelo processador de XAML para adicionar itens à coleção subjacente ao criar o grafo do objeto.

Observação

As interfaces genéricas List e (IList<T> e IDictionary<TKey,TValue>Dictionary ) não têm suporte para detecção de coleção pelo processador XAML do WPF. No entanto, você pode usar a List<T> classe como uma classe base, porque ela implementa diretamente, ou Dictionary<TKey,TValue> como uma classe base, porque ela implementa IListIDictionary diretamente.

Quando declarar uma propriedade que utiliza uma coleção, tenha cuidado com a maneira como o valor da propriedade é inicializado em novas instâncias do tipo. Se você não estiver implementando a propriedade como uma propriedade de dependência, é adequado que ela use um campo de suporte que chame o construtor do tipo de coleção. Se a propriedade for uma propriedade de dependência, talvez seja necessário inicializar a propriedade de coleção como parte do construtor de tipo padrão. Isso ocorre porque uma propriedade de dependência extrai o valor padrão de metadados e, normalmente, você não quer que o valor inicial de uma propriedade de coleção seja uma coleção estática e compartilhada. Deve haver uma instância de coleção por cada instância de tipo. Para obter mais informações, consulte Propriedades de dependência personalizadas.

É possível implementar um tipo de coleção personalizada para sua propriedade da coleção. Devido ao tratamento implícito da propriedade de coleção, o tipo de coleção personalizada não precisa fornecer um construtor sem parâmetros para ser usado em XAML implicitamente. No entanto, você pode opcionalmente fornecer um construtor sem parâmetros para o tipo de coleção. Pode ser uma prática que vale a pena. A menos que você forneça um construtor sem parâmetros, você não pode declarar explicitamente a coleção como um elemento de objeto. Alguns autores de marcação podem preferir ver a coleção explícita como uma questão de estilo de marcação. Além disso, um construtor sem parâmetros pode simplificar os requisitos de inicialização quando você cria novos objetos que usam seu tipo de coleção como um valor de propriedade.

Declarando propriedades de conteúdo XAML

A linguagem XAML define o conceito de uma propriedade de conteúdo XAML. Cada classe que é útil na sintaxe de objetos pode ter exatamente uma propriedade de conteúdo XAML. Para declarar uma propriedade como sendo a propriedade de conteúdo XAML para sua classe, aplique o ContentPropertyAttribute como parte da definição de classe. Especifique o nome da propriedade de conteúdo XAML pretendida como o Name no atributo. A propriedade é especificada como uma cadeia de caracteres pelo nome, não como uma construção de reflexo como PropertyInfo.

Você pode especificar uma propriedade de coleção para ser a propriedade de conteúdo XAML. Isso resulta em um uso para a propriedade em que o elemento de objeto pode ter um ou mais elementos filho, sem nenhum elemento de objeto de coleção interveniente ou marcas de elemento de propriedade. Em seguida, esses elementos são tratados como o valor da propriedade de conteúdo XAML e adicionados à instância de coleção de suporte.

Algumas propriedades de conteúdo XAML existentes usam o tipo de propriedade de Object. Isso habilita uma propriedade de conteúdo XAML que pode usar valores primitivos, como um único valor de objeto de String referência. Se você seguir esse modelo, seu tipo será responsável pela determinação de tipo, bem como pela manipulação de tipos possíveis. A razão típica para um tipo de conteúdo é oferecer suporte a um meio simples de adicionar conteúdo de objeto como uma cadeia de caracteres (que recebe um tratamento de apresentação padrão) ou um Object meio avançado de adicionar conteúdo de objeto que especifica uma apresentação não padrão ou dados adicionais.

Serialização de XAML

Para alguns cenários, como se você fosse um autor de controle, você poderia querer garantir que qualquer representação de objeto que possa ser instanciada em XAML também possa ser serializada para marcação XAML equivalente. As exigências de serialização não são descritas neste tópico. Consulte Visão geral da criação de controle e Árvore de elementos e serialização.

Confira também