Arquitetura WPF
Este tópico fornece um guia dirigido sobre a hierarquia de classes do Windows Presentation Foundation (WPF). Ele aborda a maioria dos subsistemas principais do WPF e descreve como eles interagem. Ele também detalha algumas das escolhas feitas pelos arquitetos do WPF.
Este tópico contém as seguintes seções.
- System.Object
- System.Threading.DispatcherObject
- System.Windows.DependencyObject
- System.Windows.Media.Visual
- System.Windows.UIElement
- System.Windows.FrameworkElement
- System.Windows.Controls.Control
- Resumo
- Tópicos relacionados
System.Object
O modelo de programação primário do WPF é exposto através de código gerenciado. No início da fase de projeto do WPF houve um número de debates sobre onde a linha deveria ser traçada entre os componentes gerenciados do sistema e os não gerenciados. O CLR fornece inúmeros recursos que tornam o desenvolvimento mais produtivo e robusto (incluindo o gerenciamento de memória, manipulação de erros, CTS (Common Type System), etc.), mas eles vêm com um custo.
Os principais componentes do WPF são ilustrados na figura abaixo. As seções em vermelho do diagrama (PresentationFramework, PresentationCore e milcore) são as partes principais de código do WPF. Desses, somente um é um componente não gerenciado – milcore. O milcore é escrito em código não gerenciado para permitir uma forte integração com o DirectX. Exibição de todos os em WPF é concluído por meio do DirectX mecanismo, permitindo hardware eficiente e renderização do software. WPF também necessário controle detalhado e execução de memória. O mecanismo de composição no milcore é extremamente sensível com relação a desempenho e foi necessário desistir de muitas vantagens do CLR para obter um melhor desempenho.
A comunicação entre as partes gerenciadas e não gerenciadas do WPF é abordada posteriormente neste tópico. O restante do modelo de programação gerenciado é descrito abaixo.
System.Threading.DispatcherObject
A maioria dos objetos em WPF derivar de DispatcherObject, que fornece as construções básicas para lidar com simultaneidade e threading. WPF baseia-se em um sistema de mensagens implementado pelo dispatcher. Isso funciona de modo muito semelhante a já familiar bomba de mensagens Win32; na verdade, o distribuidor do WPF usa mensagens User32 para realizar chamadas entre threads.
Existem, na verdade, dois conceitos principais a serem entendidos ao discutir concorrência no WPF – o distribuidor (dispatcher) e afinidade de threads.
Durante a fase de projeto do WPF, o objetivo era mudar para uma única thread de execução, mas um modelo não thread-"afinitizado". Afinidade de threads ocorre quando um componente usa a identidade da thread em execução para armazenar algum tipo de estado. A forma mais comum disso é usar o armazenamento local da thread (TLS) para armazenar o estado. Afinidade de threads requer que cada thread lógica de execução seja possuída apenas por uma thread física no sistema operacional, o que pode se tornar muito intensivo em memória. No final, modelo de threads do WPF foi mantido em sincronia com o modelo de threads User32 existente de execução em thread única com afinidade de threads. O principal motivo para isso foi interoperabilidade – sistemas como OLE 2.0, a área de transferência e o Internet Explorer, todos exigem execução em afinidade de thread única (STA).
Já que você tem objetos com threads STA, você precisa encontrar uma maneira para se comunicar entre threads e validar que você está na thread correta. Nesse ponto entra o papel do distribuidor (dispatcher). O distribuidor é um sistema básico de despacho de mensagens, com várias filas priorizadas. Exemplos de mensagens incluem notificações de entrada bruta (mouse foi movido), funções do framework (layout) ou comandos do usuário (execute esse método). Ao derivar de DispatcherObject você cria um objeto CLR que tem comportamento STA, e receberá um ponteiro para um distribuidor na hora da criação.
System.Windows.DependencyObject
Um das filosofias primárias da arquitetura usadas na criação do WPF foi a preferência por propriedades sobre métodos ou eventos. Propriedades são declarativas e permitem que você mais facilmente especifique a intenção em vez da ação. Isso também dava suporte a um sistema orientado a modelos, ou a dados, para exibição de conteúdo de interface do usuário. Essa filosofia tinha o efeito desejado de criar mais propriedades com as quais você pudesse fazer associação, para melhor controlar o comportamento de um aplicativo.
Para poder ter mais do sistema sendo orientado a propriedades, um sistema de propriedades mais sofisticado que o fornecido pelo CLR era necessário. Um exemplo simples desta sofisticação são as notificações de alteração. Para poder permitir associação em mão dupla, você precisa que ambos os lados da associação ofereçam suporte a notificação de alteração. Para poder ter comportamento ligado aos valores das propriedades, você precisa ser notificado quando o valor da propriedade é alterado. O Microsoft .NET Framework possui uma interface, INotifyPropertyChange , que permite que um objeto publique notificações de alteração, mas isso é opcional.
O WPF fornece um sistema de propriedades mais sofisticado, derivado do tipo DependencyObject. O sistema de propriedades é realmente um sistema de propriedade de "dependência" em que ele controla as dependências entre expressões de propriedade e automaticamente revalida os valores das propriedades quando as dependências são alteradas. Por exemplo, se você tiver uma propriedade que é herdada (como FontSize), o sistema é atualizado automaticamente se a propriedade for alterada em um pai de um elemento que herda o valor.
A base do sistema de propriedades do WPF é o conceito de uma expressão de propriedade. Nesta primeira versão do WPF, o sistema de expressão de propriedades é fechado, e as expressões são todas fornecidas como parte do framework. As expressões são o motivo pelo qual o sistema de propriedades não tem associação de dados, estilos, ou herança pré-definidas rigidamente em código, mas em vez disso, fornecidas por camadas posteriores dentro do framework.
O sistema de propriedades também fornece suporte a armazenamento esparso de valores de propriedade. Como os objetos podem ter dezenas (se não centenas) de propriedades, e a maioria dos valores está em seu estado padrão (herdado, definido por estilos, etc.), nem toda instância de um objeto precisará ter o peso total de cada propriedade definido nele.
Novo recurso do sistema de propriedade final é a noção de propriedades anexadas. WPF elementos são criados no princípio de composição e a reutilização do componente. Geralmente ocorre o caso de algum elemento contêiner (como um elemento Grid de layout) precisar de dados adicionais dos elementos filho para controlar seu comportamento (como informações de linha/coluna). Em vez de associar cada uma das propriedades a todo elemento, qualquer objeto tem permissão de fornecer definições de propriedades para qualquer outro objeto. Isso é semelhante aos recursos "expando" de JavaScript.
System.Windows.Media.Visual
Com um sistema definido, a próxima etapa é ter pixels desenhados na tela. The Visual classe oferece para criar uma árvore de objetos visuais, cada opcionalmente contendo instruções de desenho e os metadados sobre como processar essas instruções (corte, transformação, etc.). Visual foi projetado para ser extremamente leve e flexível, para maioria dos recursos que não público API exposição e dependem fortemente funções de retorno de chamada protegido.
Visual realmente é o ponto de entrada para o WPF sistema de composição. Visual é o ponto de conexão entre esses dois subsistemas, a gerenciado API e milcore não gerenciado.
O WPF exibe dados atravessando as estruturas de dados não gerenciadas que são gerenciadas pelo milcore. Essas estruturas, chamadas nós de composição, representam uma árvore hierárquica de exibição com instruções de renderização em cada nó. Essa árvore, ilustrada no lado direito da figura abaixo, somente está acessível através de um protocolo de mensagens.
Ao programar em WPF, você cria elementos Visual e tipos derivados, os quais internamente se comunicam com a árvore de composição através deste protocolo de mensagens. Cada Visual no WPF pode criar um, nenhum, ou vários nós de composição.
Há um detalhe arquitetural muito importante para se observar aqui – a árvore toda de elementos visuais e instruções de desenho é armazenada em cache. Em termos gráficos, o WPF usa um sistema de renderização retido (retained mode). Isso permite ao sistema redesenhar com altas taxas de atualização sem o sistema de composição bloquear em callbacks para código do usuário. Isso ajuda a evitar que um aplicativo pareça não responder.
Outro detalhe importante que não é realmente perceptível no diagrama é como o sistema realmente executa a composição.
No User32 e na GDI, o sistema funciona em um sistema de recorte em modo imediato. Quando um componente precisa ser renderizado, o sistema estabelece os limites de recorte fora dos quais o componente não tem permissão para tocar nos pixels, e em seguida, o componente é solicitado a pintar pixels dentro dessa caixa. Este sistema funciona muito bem em sistemas com memória restrita porque quando algo é alterado, você só tem que tocar o componente afetado – não há dois componentes contribuindo para a cor de um único pixel.
O WPF usa um modelo de pintura "algoritmo de pintor". Isso significa que, em vez de cortar cada componente, solicita-se que cada componente seja renderizado da parte do fundo para a frente da tela. Isso permite que cada componente pinte sobre a exibição do componente anterior. A vantagem desse modelo é que você pode ter formas complexas, parcialmente transparentes. Com o hardware gráfico moderno de hoje, esse modelo é relativamente rápido (o que não era o caso quando User32/GDI foram criados).
Como mencionado anteriormente, uma filosofia central do WPF é mudar para um modelo de programação mais declarativa, "centralizado em propriedades". No sistema visual, isso aparece em alguns interessantes.
Primeiro, se você pensar sobre o sistema gráfico em modo retido, isso está realmente se distanciando de um modelo imperativo tipo DrawLine/DrawLine, na direção de um modelo orientado a dados – new Line()/new Line(). Essa mudança para renderização orientada a dados permite que operações complexas nas instruções de desenho sejam expressas usando propriedades. Os tipos derivados de Drawing são efetivamente o modelo de objetos para renderização.
Em segundo lugar, se você avaliar o sistema de animação, você verá que ele é quase completamente declarativo. Em vez de exigir que um desenvolvedor calcule o próximo local, ou a próxima cor, você pode expressar animações como um conjunto de propriedades em um objeto de animação. Essas animações, em seguida, podem expressar a intenção do desenvolvedor ou designer (mover esse botão daqui para lá em 5 segundos), e o sistema pode determinar a maneira mais eficiente de realizar isso.
System.Windows.UIElement
UIElement define subsistemas centrais, que incluem layout, entrada e eventos.
Layout é um conceito central no WPF. Em muitos sistemas há qualquer um conjunto fixo de modelos de layout (HTML oferece suporte a três modelos para o layout; fluir, absoluto e tabelas) ou nenhum modelo de layout (User32 só oferece suporte ao posicionamento absoluto). WPF iniciado com a suposição de desenvolvedores e designers queriam um modelo de layout flexível e extensível, que pode ser orientado por valores de propriedade em vez de lógica imperativa. No nível de UIElement, o contrato básico para layout é apresentado – um modelo de duas fases com passos de Measure e Arrange.
Measure permite que um componente determine quanto tamanho ele deseja tomar. Esta é uma fase separada da de Arrange pois há muitas situações onde um elemento pai pedirá a um filho para se dimensionar várias vezes para determinar suas melhores posição e tamanho. O fato de que os elementos pai pedem a elementos filho para se dimensionarem demonstra outra filosofia chave do WPF – tamanho para conteúdo. Todos os controles no WPF oferecem suporte a capacidade de se redimensionar para o tamanho natural de seu conteúdo. Isso torna a localização muito mais fácil e permite layout dinâmico dos elementos a medida que as coisas são redimensionadas. A fase de Arrange permite que um pai posicione e determine o tamanho final de cada filho.
Muito tempo é geralmente gasto falando sobre o lado da saída do WPF – Visual e objetos relacionados. No entanto, há uma quantidade tremenda de inovação no lado de entrada também. Provavelmente, a alteração mais fundamental no modelo de entrada do WPF é o modelo consistente pelo qual os eventos de entrada são roteados através do sistema.
A entrada se origina como um sinal em um driver de dispositivo no kernel e é roteada para o processo e thread corretas por um processo intrincado envolvendo o kernel do Windows e User32. Depois que a mensagem que User32 corresponde à entrada é roteada para WPF, ele será convertido em um WPF brutos mensagem de entrada e enviadas para o distribuidor. WPFpermite que eventos de entrada brutos a ser convertido em vários eventos real, ativar recursos como "MouseEnter" a ser implementado em um nível baixo de um sistema com entrega garantida.
Cada evento de entrada é convertido em pelo menos dois eventos – um evento "preview" e o evento real. Todos os eventos no WPF ter uma noção de roteamento através da árvore de elementos. Se diz que os eventos se propagam como "bolhas" se atravessam de um alvo para cima na árvore em direção a raiz e se diz que os eventos se propagam por "tunelamento" se eles começam na raiz e percorrer a árvore para baixo até o alvo. Eventos preview de entrada são tunelados, permitindo que qualquer elemento na árvore tenha uma oportunidade de filtrar ou executar uma ação no evento. Os eventos normais (não preview) são propagados em seguida como bolha do alvo até a raiz.
Essa divisão entre a fase de tunelamento e a de "bolha" torna a implementação de recursos como aceleradores de teclado funcionar de uma maneira consistente em um mundo composto. No User32 você poderia implementar aceleradores de teclado ao ter uma única tabela global contendo todos os aceleradores que você quisesse oferecer (mapeamento de Ctrl+N para "novo"). No distribuidor para seu aplicativo você faria uma chamada a TranslateAccelerator que rastrearia as mensagens de entrada no User32 e determinaria se alguma corresponde a um acelerador registrado. No WPF isso não funcionaria pois o sistema é totalmente "passível de composição" – qualquer elemento pode tratar e usar qualquer acelerador de teclado. Este modelo de duas fases para a entrada permite que componentes implementem seu próprios "TranslateAccelerator".
Para levar isso um passo além, UIElement também introduz a noção de CommandBindings. O sistema de comandos do WPF permite que os desenvolvedores definam funcionalidade em termos de pontos de extremidade de comandos – algo que implementa ICommand. Associações de comando permitem a um elemento definir um mapeamento entre um gesto de entrada (Ctrl+N) e um comando (Novo). Tanto gestos de entrada quanto definições de comandos são extensíveis e podem ser ligados juntos em tempo de uso. Isso torna trivial, por exemplo, permitir que um usuário final personalize as associações de teclas que desejar usar dentro de um aplicativo.
Até esse ponto nesse tópico, recursos "centrais" do WPF – recursos implementados no conjunto de módulos (assembly) PresentationCore, têm sido o foco. Quando se estava criando o WPF, uma separação entre as partes fundamentais limpa (como o contrato de layout com Measure e Arrange) e partes do framework (como a implementação de um layout específico, como Grid) foi o resultado desejado. O objetivo era fornecer um ponto de extensibilidade baixo na pilha, o que permitiria que desenvolvedores externos criassem seus próprios frameworks, se necessário.
System.Windows.FrameworkElement
O FrameworkElement pode ser encarado de duas maneiras diferentes. Ele apresenta um conjunto de diretivas/políticas e personalizações dos subsistemas introduzidos nas camadas inferiores do WPF. Ele também introduz um conjunto de novos subsistemas.
A diretiva principal introduzida por FrameworkElement Trata o layout do aplicativo. FrameworkElement baseia o contrato de layout básico introduzido por UIElemente adiciona a noção de um layout "slot", que torna mais fácil para os autores de layout têm um conjunto consistente de propriedade orientado a semântica de layout. Propriedades como HorizontalAlignment, VerticalAlignment, MinWidth e Margin (para citar apenas algumas) fornecem a todos os componentes derivados de FrameworkElement um comportamento consistente dentro de contêineres de layout.
FrameworkElement também fornece uma exposição de API mais fácil a muitos recursos encontrados nas camadas principais do WPF. Por exemplo, FrameworkElement fornece acesso direto a animação através do método BeginStoryboard. Um Storyboard fornece uma maneira de definir um script para várias animações de um conjunto de propriedades.
As duas coisas mais importantes que FrameworkElement introduz são associação de dados e estilos.
O subsistema de associação de dados do WPF deve ser relativamente familiar para qualquer pessoa que usou Windows Forms ou ASP.NET para criar a interface do usuário (UI) de um aplicativo. Em cada um desses sistemas, há uma maneira simples para expressar a que você deseja que um ou mais propriedades de um determinado elemento deve ser limite a um item de dados. WPF tem suporte completo para ligação de propriedade, transformação e vinculação de lista.
Um dos recursos mais interessantes de associação de dados do WPF é a introdução de modelos de dados. Modelos de dados permitem que você especifique declarativamente como um conjunto de dados deve ser visualizado. Em vez de criar uma interface do usuário personalizada que pode ser associada aos dados, você pode inverter o problema e permitir que os dados determinem a modo de exibição que será criado.
Estilização é realmente uma forma leve de associação de dados. Usando estilos você pode associar um conjunto de propriedades de uma definição compartilhada a uma ou mais instâncias de um elemento. Estilos são aplicados a um elemento por referência explícita (definindo a propriedade Style) ou implicitamente ao associar um estilo com o tipo de elemento do CLR.
System.Windows.Controls.Control
Templating é o recurso mais significativo de um controle. Se você pensar sobre o sistema de composição do WPF como um sistema de renderização em modo retido, templating permite que um controle descreva sua renderização de maneira declarativa e parametrizada. Um ControlTemplate na verdade não é nada mais que um script para criar um conjunto de elementos filho, com associações a propriedades oferecidas pelo controle.
Control fornece um conjunto de propriedades pré-definidas, Foreground, Background, Padding, para citar apenas algumas, que os autores de modelo podem usar para personalizar a exibição de um controle. A implementação de um controle provê um modelo de dados e um modelo de interação. O modelo de interação define um conjunto de comandos (como Fechar para uma janela) e as associações para gestos de entrada (como clicar no X vermelho no canto superior da janela). O modelo de dados fornece um conjunto de propriedades para personalizar o modelo de interação ou personalizar a exibição (determinada pelo modelo).
Essa divisão entre o modelo de dados (propriedades), o modelo de interação (comandos e eventos) e o modelo de exibição (modelos) permite a completa personalização da aparência e do comportamento de um controle.
Um aspecto comum ao modelo de dados dos controles é o modelo de conteúdo. Se você observar um controle como Button, você verá que ele possui uma propriedade chamada "Content" do tipo Object. Em Windows Forms e ASP.NET, essa propriedade seria normalmente uma sequência de caracteres – no entanto, isso limita o tipo de conteúdo que você pode colocar em um botão. O conteúdo de um botão pode ser uma simples sequência de caracteres, um objeto de dados complexo ou uma árvore de elementos inteira. No caso de um objeto de dados, o modelo de dados é usado para construir sua exibição.
Resumo
O WPF foi projetado para permitir que você crie sistemas de apresentação dinâmicos e orientados a dados. Cada parte do sistema foi projetada para criar objetos através de conjuntos de propriedades que definem seu comportamento. Associação de dados é uma parte fundamental do sistema e está integrada em cada camada.
Aplicativos tradicionais criam uma exibição e, em seguida, fazem associação a alguns dados. No WPF, tudo sobre um controle, todos os aspectos de exibição, é gerado por algum tipo de associação de dados. O texto encontrado em um botão é exibido através da criação de um controle composto dentro do botão e a associação de sua exibição a propriedade de conteúdo do botão.
Quando você começa a desenvolver aplicativos com base no WPF, isso se torna bem familiar. Você pode definir propriedades, usar objetos, e associar dados praticamente da mesma maneira que você pode em Windows Forms ou ASP.NET. Com uma investigação mais profunda da arquitetura do WPF, você descobrirá que existe a possibilidade de criar aplicativos muito mais ricos que fundamentalmente tratem dados como o foco principal do aplicativo.
Consulte também
Conceitos
Revisão de Associação de Dados