Partilhar via


XAML e classes personalizadas para WPF

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

Classes personalizadas em aplicativos ou assemblies

Classes personalizadas usadas em XAML podem ser definidas de duas maneiras distintas: no code-behind ou em outro código que gera o aplicativo principal do Windows Presentation Foundation (WPF), ou então como uma classe em um assembly separado, como um executável ou DLL usada como biblioteca de classes. Cada uma dessas abordagens tem vantagens e desvantagens particulares.

  • A vantagem de criar uma biblioteca de classes é que essas classes personalizadas podem ser compartilhadas em vários aplicativos possíveis diferentes. Uma biblioteca separada também facilita o controle de problemas de controle de versão de aplicativos e simplifica a criação de uma classe em que o uso de classe pretendido é como um elemento raiz em uma página XAML.

  • A vantagem de definir as classes personalizadas no aplicativo é que essa técnica é relativamente leve e minimiza os problemas de implantação e teste encontrados ao introduzir assemblies separados além do executável do aplicativo principal.

  • Seja definido no mesmo assembly ou em um assembly diferente, as classes personalizadas precisam ser mapeadas entre o namespace CLR e o namespace XML para serem usadas em XAML como elementos. Consulte namespaces XAML e o mapeamento de namespaces paraXAML do WPF.

Requisitos para uma classe personalizada como um elemento XAML

Para poder ser instanciado como um elemento de objeto, sua classe deve atender aos seguintes requisitos:

  • Sua 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 obter notas sobre estruturas.)

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

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

Estruturas

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 de elemento de objeto para uma estrutura não são desejáveis. Isso pode ocorrer porque a estrutura destina-se a preencher valores e funcionar conceitualmente como uma união, em que os valores contidos podem ter interpretações mutuamente exclusivas e, portanto, nenhuma de suas propriedades é configurável. Um exemplo do WPF dessa estrutura é GridLength. Em geral, essas estruturas devem implementar um conversor de tipo de modo que os valores possam ser expressos no formulário de atributo, usando convenções de cadeia de caracteres que criam as diferentes interpretações ou modos dos valores da estrutura. A estrutura também deve expor um comportamento semelhante para a construção de código por meio de um construtor sem parâmetros.

Requisitos para propriedades de uma classe personalizada como atributos XAML

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

Como alternativa, a propriedade pode referenciar um tipo de classe abstrato ou uma interface. Para classes ou interfaces abstratas, a expectativa para a análise XAML é que o valor da propriedade deve ser preenchido com instâncias de classe concretas que implementam a interface ou instâncias de tipos que derivam da classe abstrata.

As propriedades podem ser declaradas em uma classe abstrata, mas só podem ser definidas em classes concretas que derivam da classe abstrata. Isso ocorre porque a criação do elemento de objeto para a classe exige um construtor público sem parâmetros na classe.

Sintaxe de atributo habilitado para TypeConverter

Se você fornecer um conversor de tipo atribuído dedicado no nível da classe, a conversão de tipo aplicado habilitará a sintaxe de atributo para qualquer propriedade que precise instanciar esse tipo. Um conversor de tipo não habilita o uso do elemento de objeto do tipo; apenas a presença de um construtor sem parâmetros para esse tipo habilita o uso do elemento de objeto. Portanto, propriedades que têm conversor de tipo habilitado geralmente não podem ser usadas na sintaxe de propriedade, a menos que o próprio tipo também ofereça suporte à sintaxe de elemento de objeto. A exceção a isso é que você pode especificar uma sintaxe de elemento de propriedade, mas fazer com que o elemento de propriedade contenha uma cadeia de caracteres. Esse uso é essencialmente equivalente a um uso de sintaxe de atributo e esse 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. Por exemplo, a seguir está o uso de um elemento de propriedade que aceita uma string, juntamente com o uso equivalente de um 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 inclui um elemento de objeto não é permitida através do XAML, são várias propriedades que aceitam o tipo Cursor. A classe Cursor tem um conversor de tipo dedicado CursorConverter, mas não expõe um construtor sem parâmetros, portanto, a propriedade Cursor só pode ser definida por meio da sintaxe de atributo, mesmo que o tipo de Cursor real seja um tipo de referência.

Conversores de tipo Per-Property

Como alternativa, a propriedade em si pode declarar um conversor de tipo no nível da propriedade. Isso habilita uma "mini linguagem" que cria objetos do tipo da propriedade internamente, processando os valores de string de entrada do atributo como entrada para uma operação de ConvertFrom com base no tipo apropriado. Normalmente, isso é feito para fornecer um acessador de conveniência e não como o único meio para habilitar a configuração de uma propriedade no XAML. No entanto, também é possível usar conversores de tipo para atributos em que você deseja usar tipos CLR existentes que não fornecem um construtor sem parâmetros ou um conversor de tipo atribuído. Exemplos de propriedades específicas que utilizam o tipo CultureInfo da API do WPF. Nesse caso, o WPF usou o tipo de CultureInfo existente do Microsoft .NET Framework para lidar melhor com cenários de compatibilidade e migração usados em versões anteriores de estruturas, mas o tipo CultureInfo não deu suporte aos construtores necessários ou à conversão de tipo no nível do tipo para serem utilizáveis como um valor de propriedade XAML diretamente.

Sempre que você expõe uma propriedade que tem um uso XAML, especialmente se você for um autor de controle, considere fortemente o backup dessa 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, pois é possível melhorar o desempenho usando suporte de base DependencyProperty. Uma propriedade de dependência irá expor os recursos do sistema de propriedades para a sua propriedade, que os usuários esperam de uma propriedade acessível pelo XAML. Isso inclui recursos como animação, associação de dados e suporte a estilo. Para obter mais informações, consulte Propriedades de Dependência Personalizadas e Carregamento XAML e Propriedades de Dependência.

Gravando e atribuindo um conversor de tipo

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

Requisitos para 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 dá suporte a um construtor sem parâmetros ou em uma classe abstrata em que o evento pode ser acessado em classes derivadas. Para ser usado convenientemente como um evento roteado, o evento CLR deve implementar métodos add e remove explícitos, que adicionam e removem manipuladores para a assinatura de evento CLR e encaminham esses manipuladores para os métodos AddHandler e RemoveHandler. Esses métodos adicionam ou removem os manipuladores ao repositório do manipulador de eventos roteado na instância à qual o evento está anexado.

Nota

É possível registrar manipuladores diretamente para eventos roteados usando AddHandlere não definir deliberadamente um evento CLR que expõe o evento roteado. Isso geralmente não é recomendado porque o evento não habilitará a sintaxe de atributo XAML para anexar manipuladores e sua classe resultante oferecerá uma exibição XAML menos transparente dos recursos desse tipo.

Gravando propriedades da coleção

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

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

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

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

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

Nota

As interfaces List e Dictionary genéricas (IList<T> e IDictionary<TKey,TValue>) não são compatíveis com a detecção de coleção pelo processador XAML do WPF. No entanto, você pode usar a classe List<T> como uma classe base, pois ela implementa IList diretamente ou Dictionary<TKey,TValue> como uma classe base, pois ela implementa IDictionary diretamente.

Ao declarar uma propriedade que recebe uma coleção, tenha cuidado com a maneira como o valor dessa propriedade é inicializado em novas instâncias do tipo. Se você não estiver implementando a propriedade como uma propriedade de dependência, fazer com que a propriedade use um campo de backup que chama o construtor de tipo de coleção é adequado. Se sua 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 usa seu valor padrão de metadados e você normalmente não deseja 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 que contenha. Para obter mais informações, consulte Propriedades de Dependência Personalizadas.

Você pode implementar um tipo de coleção personalizado para sua propriedade de coleção. Devido ao tratamento de propriedade de coleção implícita, o tipo de coleção personalizada não precisa fornecer um construtor sem parâmetros para ser usado implicitamente no XAML. No entanto, opcionalmente, você pode fornecer um construtor sem parâmetros para o tipo de coleção. Essa 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 ao criar 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 utilizável na sintaxe do objeto 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 por nome, não como um constructo de reflexão, como PropertyInfo.

Você pode especificar uma propriedade de coleção para ser a propriedade de conteúdo XAML. Isso resulta no uso dessa propriedade em que o elemento de objeto pode ter um ou mais elementos filho, sem quaisquer elementos de objeto de coleção ou tags de elementos de propriedade. Esses elementos são tratados como o valor da propriedade de conteúdo XAML e adicionados à instância da coleção de backup.

Algumas propriedades de conteúdo XAML existentes usam o tipo de propriedade de Object. Isso permite uma propriedade de conteúdo XAML que pode aceitar valores primitivos, como um String, como também aceitar um único valor de objeto de referência. Se você seguir esse modelo, seu tipo será responsável pela determinação do tipo, bem como pela manipulação de tipos possíveis. O motivo típico para um tipo de conteúdo Object é dar 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 a um meio avançado de adicionar conteúdo de objeto que especifica uma apresentação não padrão ou dados adicionais.

Serializando XAML

Para determinados cenários, como caso você seja um autor de controle, também pode ser importante garantir que qualquer representação de objeto que possa ser instanciada no XAML também possa ser serializada novamente na marcação XAML equivalente. Os requisitos de serialização não são descritos neste tópico. Consulte Visão Geral sobre Autoria de Controle e Árvore de Elementos e Serialização.

Consulte também