Partilhar via


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 marcação XAML. Você pode usar uma mistura de tipos definidos pelo Windows Presentation Foundation (WPF) e seus tipos personalizados dentro do 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 satisfazer para ser utilizável 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 destas abordagens tem vantagens e desvantagens específicas.

  • A vantagem de criar uma biblioteca de classes é que essas classes personalizadas podem ser compartilhadas entre muitos aplicativos possíveis diferentes. Uma biblioteca separada também torna os problemas de controle de versão de aplicativos mais fáceis de controlar 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 quando você introduz 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 mapeamento de namespace para WPF XAML.

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 suportar um construtor público padrão (sem parâmetros). (Consulte a secção seguinte para obter notas sobre estruturas.)

  • A sua classe personalizada não deve ser uma classe aninhada. As classes aninhadas e o "ponto" na sua sintaxe geral de utilização no CLR interferem com funcionalidades WPF e/ou XAML, como as propriedades anexadas.

Além de habilitar a sintaxe do elemento objeto, sua definição de objeto também habilita a sintaxe do elemento de propriedade para quaisquer outras propriedades públicas que tomem esse objeto como o tipo de valor. Isso ocorre porque o objecto agora pode ser instanciado como um elemento de objeto e pode preencher o valor do elemento de propriedade dessa 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 de elementos de objeto para uma estrutura não são desejáveis. Isso pode ser porque a estrutura se destina a preencher valores e funcionar conceitualmente como uma união, onde os valores contidos podem ter interpretações mutuamente exclusivas e, portanto, nenhuma de suas propriedades é configurável. Um exemplo WPF de tal estrutura é GridLength. Geralmente, tais estruturas devem implementar um conversor de tipo tal que os valores possam ser expressos em forma 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 fazer referência a um tipo por valor (como uma primitiva) 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 CLR XAML, os processadores XAML encontram esses conversores através do suporte nativo para primitivos de linguagem, ou através da aplicação de TypeConverterAttribute a um tipo ou membro em definições de tipo de suporte.

Como alternativa, a propriedade pode fazer referência a um tipo de classe abstrata ou a 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 object para a classe requer um construtor sem parâmetros público na classe.

Sintaxe de atributo habilitado para TypeConverter

Se você fornecer um conversor de tipo dedicado e atribuído no nível da classe, a conversão de tipo aplicada habilitará a sintaxe de atributo para qualquer propriedade que precise instanciar esse tipo. Um conversor de tipo não permite o uso do elemento objeto do tipo; Somente a presença de um construtor sem parâmetros para esse tipo permite o uso do elemento Object. Portanto, as propriedades habilitadas para conversor de tipo geralmente não são utilizáveis na sintaxe da propriedade, a menos que o próprio tipo também ofereça suporte à sintaxe do elemento objeto. A exceção é 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 é realmente essencialmente equivalente a um uso de sintaxe de atributo, e tal uso não é comum, a menos que haja uma necessidade de manipulação de espaço em branco mais robusta do valor do atributo. Por exemplo, a seguir está um uso de elemento de propriedade que usa uma cadeia de caracteres e o equivalente de 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 do elemento de propriedade que contém um elemento de objeto não é permitida através do XAML, incluem várias propriedades que usam o tipo Cursor. A Cursor classe tem um conversor CursorConverterde tipo dedicado, mas não expõe um construtor sem parâmetros, portanto, a propriedade só pode ser definida por meio da Cursor sintaxe de atributo, mesmo que o tipo real Cursor seja um tipo de referência.

Os conversores de tipo Per-Property

Alternativamente, a própria propriedade pode declarar um conversor de tipo no nível da propriedade. Isso permite uma "mini linguagem" que instancia objetos do tipo da propriedade em linha, processando os valores de cadeia de caracteres recebidos do atributo como entrada para uma operação ConvertFrom baseada no tipo apropriado. Normalmente, isso é feito para fornecer um acessador de conveniência, e não como o único meio de habilitar a configuração de 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 utilizou o tipo existente do Microsoft .NET Framework CultureInfo para melhor abordar os cenários de compatibilidade e migração usados em versões anteriores de frameworks, mas o tipo CultureInfo não oferecia suporte aos construtores necessários ou à conversão de tipo a nível de tipo, tornando-se impróprio para ser utilizado diretamente como valor de propriedade XAML.

Sempre que você expõe uma propriedade que tem um uso de XAML, particularmente se você for um autor de controle, você deve considerar fortemente o suporte 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, porque pode melhorar o desempenho usando o suporte de DependencyProperty. Uma propriedade de dependência irá expor funcionalidades do sistema de propriedades para a sua propriedade que os utilizadores irão esperar para uma propriedade acessível em XAML. Isso inclui recursos como animação, vinculação de dados e suporte a estilo. Para obter mais informações, consulte Propriedades de dependência personalizadas e Propriedades de dependência e carregamento de XAML.

Escrevendo e atribuindo um conversor de tipo

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 tipo que possa suportar usos de XAML e como aplicar o TypeConverterAttribute, consulte TypeConverters e XAML.

Requisitos para a sintaxe de atributos do manipulador de eventos XAML em eventos de uma classe customizada

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

Observação

É possível registar manipuladores diretamente para eventos encaminhados usando AddHandler, e deliberadamente não definir um evento CLR que exponha o evento encaminhado. 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.

Redigindo Propriedades de Coleção

As propriedades que usam um tipo de coleção têm uma sintaxe XAML que permite especificar objetos que são adicionados à coleção. Esta sintaxe tem duas características 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 está implícita sempre que você especifica uma propriedade em XAML que usa 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 através de métodos de lista/dicionário, como Add, ou através de um indexador. Mas a sintaxe XAML não oferece suporte a métodos ou indexadores (exceção: XAML 2009 pode oferecer suporte a métodos, mas usar 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 criar uma árvore de elementos, e você precisa de alguma maneira de 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 corresponde ao tipo de valor da propriedade de coleção.

A implementação dos Serviços XAML do .NET Framework e, portanto, o processador WPF XAML usa 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 no CLR tem um Add método, que é usado pelo processador XAML para adicionar itens à coleção subjacente ao criar o gráfico de objeto.

Observação

Os interfaces genéricos List e Dictionary (IList<T> e IDictionary<TKey,TValue>) não são suportadas para deteção de coleção pelo processador WPF XAML. No entanto, você pode usar a List<T> classe como uma classe base, porque implementa IList diretamente, ou Dictionary<TKey,TValue> como uma classe base, porque implementa IDictionary diretamente.

Ao declarar uma propriedade que aceita uma coleção, tenha cuidado sobre 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, fazer com que a propriedade use um campo de suporte que chame 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 obtém 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 compartilhada. Deve haver uma instância de coleta por cada instância de tipo de contenção. 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 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. Esta 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 é 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 a 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 string pelo nome, não como uma construção de reflexão, como PropertyInfo.

Você pode especificar uma propriedade de coleção para ser a propriedade de conteúdo XAML. Isso resulta num uso para essa propriedade onde o elemento objeto pode ter um ou mais elementos filhos, sem que haja elementos de objeto de coleção ou marcas de elemento de propriedade a intervir. Esses elementos são então tratados como o valor da propriedade de conteúdo do XAML e adicionados à instância da coleção de apoio.

Algumas propriedades de conteúdo XAML existentes usam o tipo de propriedade de Object. Isso habilita uma propriedade de conteúdo XAML que pode aceitar valores primitivos, como String, bem como aceitar um único valor de objeto de referência. Se seguir este modelo, o seu tipo é responsável pela determinação do tipo, bem como pelo tratamento dos tipos possíveis. O motivo típico para um Object 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 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 se você for um autor de controle, você também pode querer garantir que qualquer representação de objeto que possa ser instanciada em XAML também possa ser serializada de volta para marcação XAML equivalente. Os requisitos de serialização não são descritos neste tópico. Consulte Visão geral da criação de controle e Árvore de elementos e serialização.

Ver também