O objetivo deste artigo é apresentar o WPF, tecnologia que veio para enriquecer interfaces e interações com usuários. Ao final deste artigo você estará apto a criar, customizar e escolher entre os melhores objetos e controles do WPF, e começar a aplicar uma melhor usabilidade utilizando WPF em sua aplicação.

As aplicações crescem cada dia mais, e uma interface usável, interativa e agradável pode fazer a diferença. Com o WPF podemos criar interfaces ricas e atrativas onde o usuário tem um grande poder de customização e liberdade para trabalhar de forma rápida e simples.


Guia do artigo:

Este artigo se aplica a todos que desejam criar interfaces ricas, altamente interativas, e de altíssima qualidade gráfica. Essa é uma crescente demanda de mercado e um diferencial importante para as aplcações de hoje.

O WPF possúi um conteúdo muito extenso, porém neste artigo veremos alguns dos controles que abordam boa parte de seu conteúdo. O intuito final é a compreensão da criação e customização destes controles, facilitando assim a criação de controles mais complexos posteriormente.

O .NET Framework 3.0 é a nova plataforma de desenvolvimento da Microsoft. Ele foi baseado no .NET Framework 2.0, e incorpora alguma novidades como o WPF (Windows Presentation Foundation, antes chamado de Avalon). O WPF utiliza recursos de hardware avançados para criar gráficos jamais vistos, e ainda contém um conjunto de classes incorporadas no próprio Framework para manipulação destas funções. Junto ao WPF, temos o XAML (eXtensible Application Markup Language), que utiliza tags XML para criação de objetos.

XAML é o novo conceitode linguagem, utilizado para representação de controles em interfaces Windows e Web. A utilização do XAML propõe a melhor divisão das camadas de apresentação e de regras de negócio. O XAML pode ser facilmente manipulado através de um editor de textos (XamlPad, Notepad++) e visualizado sem necessidade de nenhuma compilação, através do Internet Explorer por exemplo (Requer .NET Framework 3.0 ou superior instalado).

As sintaxes XAML são relativamente simples, e podem ser comparadas as tags HTML, onde pelo arquivo .XAML pode-se construir e inicializar qualquer objeto do .NET Framework. O próprio .NET Framework 3.x trás um compilador e um run-time parser (Utilizado para casts), assim como um plug-in para visualização de aplicações WPF Standalones dentro do Internet Explorer.

A especificação XAML define as regras utilizadas para mapear tipos, propriedades, eventos e namespaces do .NET Framework dentro de XAML Namespaces, atributos e elementos. Por exemplo, podemos criar um botão através de uma tag XAML ou através de um código C#, como mostrado na Listagem 1.

Listagem 1. Criando um bootão via XAML e via C#.Botão declarado em XAML

        <Controls:Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        Content="OK"/>

        Botão declarado em C#
        System.Windows.Controls.Button b = new System.Windows.Controls.Button();
        b.Content = "OK";
        

Aparentemente os dois códigos fazem a mesma coisa, porém a diferença é que neste caso, o código XAML poderia ser instantaneamente visualizado no Internet Explorer, enquanto o código C# teria de ser compilado.

Declarar um objeto via XAML é equivalente a instanciar um objeto via código, através do seu método construtor. Desta mesma forma, quando atribuímos algum valor a uma propriedade em um arquivo XAML, isto equivale a atribuição deste mesmo valor via código, e assim acontece com os eventos também. Veja o exemplo da Listagem 2.

Listagem 2. Criação de um objeto e atribuição de valores a suas propriedades e eventos.XAML

        <Controls:Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        Content="OK" Click="button_Click"/>

        C#
        System.Windows.Controls.Button b = new System.Windows.Controls.Button();
        b.Click += new System.Windows.RoutedEventHandler(button_Click);
        b.Content = "OK";
        

Ambos códigos acima fazem chamada a um evento chamado button_Click, que deve ser criado com sua devida assinatura para manipular o evento ocorrido ao clicar do mouse sobre o botão criado. Este código utiliza-se da mistura de código gerenciado e XAML, e sendo assim não pode ser instantaneamente executado, ele necessita ser compilado primeiro. Resumidamente, podemos tanto criar objetos através de códigos XAML quanto códigos C#.

Trabalhando com Namespaces

Assim como quando trabalhamos com códigos como C# e VB.NET, nos arquivos XAML, temos a possibilidade de incluir Namespaces em nossas aplicações.

O objeto raiz de um arquivo XAML deve importar no mínimo um Namespace que identifique ele mesmo e seus filhos. Podemos adicionar Namespaces em qualquer objeto de um documento XAML, desde que um prefixo distinto tenha sido fornecido aos mesmos. Por exemplo, tipicamente um arquivo XAML usa o prefixo “x” antes do Namespace.


        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        

Note que utilizamos xmlns:x ao invés de utilizar apenas xmlns. Este é o Namespace padrão da linguagem XAML, que incorpora os recursos do Namespace System.Windows.Markup e define algumas diretivas especiais para o XAML Compiler e XAML Parser. Utilizar este namespace é o mesmo que utilziar o namespace System em um arquivo C#.

Nota
O Namespace x já mapeia os seguintes Namespaces

        System.Windows
        System.Windows.Automation
        System.Windows.Controls
        System.Windows.Controls.Primitives
        System.Windows.Data
        System.Windows.Documents
        System.Windows.Forms.Integration
        System.Windows.Ink
        System.Windows.Input
        System.Windows.Media
        System.Windows.Media.Animation
        System.Windows.Media.Effects
        System.Windows.Media.Imaging
        System.Windows.Media.Media3D
        System.Windows.Media.TextFormatting
        System.Windows.Navigation
        System.Windows.Shapes
        

Principais Propriedades

Uma das grandes “mágicas” do WPF, é o poder que temos para manipular as propriedades de um objeto. Praticamente todos os objetos tem uma flexibilidade enorme em sua disposição, tamanho e principalmente conteúdo. Vamos tomar como base o seguinte exemplo da Listagem 3.

Listagem 3. Criando um botão via C# e inserindo um retângulo em seu conteúdo.

        public MainPage()
        {
        InitializeComponent();

        //Instancia um Retângulo
        Rectangle myRectangle = new Rectangle();

        //Instancia um botão
        Button myButton = new Button();

        //Define altura e largura do retangulo
        myRectangle.Height = 100;
        myRectangle.Width = 200;

        //Define altura e largura do botão
        myButton.Width = 400;
        myButton.Height = 200;

        // Cria um SolidBrush para pintar o retângulo de azul
        SolidColorBrush blue = new SolidColorBrush();
        blue.Color = Colors.Blue;

        //Preenche o retangulo com o SolidBrush criado
        myRectangle.Fill = blue;

        //Atribui o retângulo como conteúdo do botão
        myButton.Content = myRectangle;

        // Adiciona os objetos na tela
        LayoutRoot.Children.Add(myButton);
        LayoutRoot.Children.Add(myRectangle);
        }
        

Isto é possível por quê a propriedade Content do objeto Button é to tipo Object, permitindo que qualquer objeto seja atribuída a ela. Este mesmo resultado pode ser alcançado somente utilizando XAML, como mostrado na Listagem 4.

Listagem 4. Criando um botão via XAML e inserindo um retângulo em seu conteúdo.

        <Button Width="400" Height="200">
        <Button.Content>
        <Rectangle Fill="Blue" Width="200" Height="100" />
        </Button.Content>
        </Button>
        

Note que no exemplo acima, tratamos a propriedade Content em um outro atributo XML, necessário neste caso. Sendo assim, temos duas maneiras de representar as propriedades de um objeto com XAML, setando a propriedade na tag inicial do objeto ou criando uma tag XML para a propriedade. Veja melhor esta diferença no código da Listagem 5.

Listagem 5. Atribuição de valores nas propriedades dos objetos via XAML.

        <!-- Conteúdo atribuido já na criação do objeto -->
        <Button Width="100" Height="50" Content="OK" Background="White" />

        <!-- Conteúdo atribuido através de uma nova tag XML -->
        <Button Width="100" Height="50">
        <Button.Content>
        <TextBlock Text="OK" />
        </Button.Content>
        <Button.Background>
        AntiqueWhite
        </Button.Background>
        </Button>
        

Veja que os botões são identicos, sendo apenas escritos de forma diferente. Neste momento você deve estar se perguntando: “Se o código gerado em C# é o mesmo gerado em XAML, por que no C# atribuimos a cor através do namespace System.Windows.Brushs e no XAML podemos fazer isso através de uma String?”. Este fato se deve ao XAML parser, que identifica e transforma o valor atribuído para o tipo necessário, neste caso transforma a String “White” para o tipo System.Windows.Brushs.

Trabalhando com Coleções de Itens

No nosso dia-a-dia, dificilmente não teremos uma aplicação que trabalhe conectada a uma fonte de dados, e ainda mais, dificilmente não teremos uma aplicação que não precise trabalhar com coleções de objetos. O XAML, é claro, nos dá este suporte.

No XAML as listas são derivadas da interface System.Collections.IList, da classe System.Collection.ArrayList, ou de outras classes que implementam uma dessas duas. A Listagem 6 demonstra a implementação de um ListBox em XAML e C#.

Listagem 6. Criação de um ListBox em XAML e C#. XAML

        <ListBox Width="100" Height="50">
        <ListBoxItem Content="Item 1" />
        <ListBoxItem Content="Item 2" />
        </ListBox>

        C#
        ListBox listbox = new ListBox();
        listbox.Width = 100;
        listbox.Height = 50;
        ListBoxItem item1 =new ListBoxItem();
        ListBoxItem item2 = new ListBoxItem();
        item1.Content = "Item 1";
        item2.Content = "Item 2";
        listbox.Items.Add(item1);
        listbox.Items.Add(item2);
        LayoutRoot.Children.Add(listbox);
        

Novamente, o conteúdo do ListBoxItem é do tipo Object, aceitando assim um objeto como conteúdo. O exemplo abaixo (Listagem 7 e Figura 1) demonstra o uso de botões dentro de um ListBoxItem.

Listagem 7. Criação de um ListBox com botões em seu Content.

        <ListBox Width="200" Height="100">
        <ListBoxItem>
        <ListBoxItem.Content>
        <Button Content="OK" Width="50" Height="20" />
        </ListBoxItem.Content>
        </ListBoxItem>
        <ListBoxItem>
        <ListBoxItem.Content>
        <Button Content="Cancel" Width="50" Height="20" />
        </ListBoxItem.Content>
        </ListBoxItem>
        </ListBox>
        
ListBox com botões em seu Content
Figura 1. ListBox com botões em seu Content.

Outro tipo de coleção muito utilizada no WPF são os Dicionários. Podemos tomar como exemplo um dicionário de recursos, onde temos todos os recursos de imagens da nossa aplicação. Os dicionários utilizam-se do Namespace System.Windows.ResourceDictionary, que implementa o Namespace System.Collections.Idictionary, que por sua vez suporta adição, remoção e enumeração dos seus Keys/Values (Chaves/Valores). O exemplo da Listagem 8 demonstra a criação de um dicionário de cores.

Listagem 8. Criação de um dicionário de Cores em XAML e C#.

        XAML
        <ResourceDictionary>
        <Color x:Key="r" A="255" R="255" G="0" B="0" />
        <Color x:Key="b" A="255" R="0" G="0" B="255" />
        </ResourceDictionary>

        C#
        ResourceDictionary d = new ResourceDictionary();
        Color color1 = new Color();
        Color color2 = new Color();
        color1.A = 255; color1.R = 255; color1.G = 255; color1.B = 255;
        color2.A = 0; color2.R = 0; color2.G = 0; color2.B = 0;
        d.Add("1", color1);
        d.Add("2", color2);
        

Ambos códigos tem o memo resultado, sendo que no código C# no método Add temos os atributos Chave e Valor e no XAML definimos a Chave no parâmetro x:Key do objeto que é seu próprio valor. O x:Key no XAML também é uma string, que posteriormente será convertida pelo XAML Parser e representará uma Chave.

É fácil imaginar que o WPF nos oferece um grande número de controles, porém algumas modficicações para facilitar nossas vidas foram feitas. Veremos agora quais são os principais controles que temos no WPF. E para melhor organização, dividiremos estes controles entre os tópico a seguir: Content Controls, Items Controls, Range Controls e Text.

Content Controls

Os Content Controls foram construídos para armazenar apenas um item. Todos os Content Controls derivam do System.Windows.Controls.ContentControl, o qual tem a propriedade Content, do tipo Object, onde podemos armazenar um objeto.

Apesar dos Content Controls terem a possibilidade de armazenar apenas um item, podemos ter uma série de itens dentro dele, se apenas um item destes for tratado como filho (Child) do controle. Podemos tomar como exemplo a Figura 2, onde temos um Button com um ListBox dentro, que por sua veztem como itens outros dois Buttons (OK e Cancel). A codificação deste botão pode ser encontrado na Listagem 9.

Atribuindo um ListBox como
objeto filho do Button
Figura 2. Atribuindo um ListBox como objeto filho do Button.
Listagem 9. Código do exemplo da Figura 2.

        <Button Width="300" Height="200">
        <ContentControl>
        <ListBox Width="200" Height="100">
        <ListBoxItem>
        <ListBoxItem.Content>
        <Button Content="OK" Width="50" Height="20" />
        </ListBoxItem.Content>
        </ListBoxItem>
        <ListBoxItem>
        <ListBoxItem.Content>
        <Button Content="Cancel" Width="50" Height="20" />
        </ListBoxItem.Content>
        </ListBoxItem>
        </ListBox>
        </ContentControl>
        </Button>
        

Outra observação muito interessante sobre os ContentControls é a propriedade booleana HasContent, que retorna falso caso o controle esteja vazio (Content Nulo) ou verdadeiro caso contrário. Os Built-In ContentControls também estão dividos em três partes, sendo elas: Buttons, Containers Simples e Containers de Cabeçalho.

Nota

O termo Built-in significa algo como “de fábrica”, e sempre que o utilizarmos estamos nos referindo aos controles nativos do Framework.

Button

O botão (Button) provavelmente é o controle mais familiar e essencial nas aplicações. Porém, mesmo com toda a familiaridade que temos com este controle, temos agora no WPF algumas novidades, além de alguns tipos de botões diferentes.

Você já deve ter notado que um botão pode ser clicado, mas nunca duplo-clicado. No WPF este comportamento é capturado por uma classe abstrata chamada ButtonBase na qual outros controles também derivam.

A classe ButtonBase contém o evento Click e toda lógica que define o que é “um botão estar sendo clicado”. Ainda na classe ButtonBase, temos a propriedade booleana IsPressed que define o estado atual do botão.

No WPF, talvez a propriedade mais interessante do Button seja a ClickMode. Esta propriedade pode receber um valor de um Enumerador exatamente quando um evento Click for disparado. Seus valores podem ser: Release (Valor padrão), Press e Hover.Vejamos a seguir os controles que derivam da classe ButtonBase.

A classe Button do WPF adiciona dois conceitos a mais do que os fornecidos pela classe ButtonBase: CancelButton ou DefaultButton. Se o Button.IsCancel for definido como true por exemplo, sua janela atual é automaticamente fechada. Se o Button.IsDefault é atribuido como true, seu evento Clicked é acionado. Veja na Listagem 10 um exemplo de como criar um simples botão em WPF.

Listagem 10. Criando um botão simples via XAML e C#.XAML

        <Button Width="300" Height="200" Content="My Button">
        </Button>

        C#
        Button b = new Button();
        b.Width = 200;
        b.Height = 100;
        b.Content = "My Button";
        LayoutRoot.Children.Add(b);
        

RepeatButton

Outro tipo de botão são os RepeatButtons. Os RepeatButtons são iguais aos Buttons, exceto que neste controle, enquanto pressionado, seu evento Click fica sendo disparado, até que o botão do mouse seja solto. Veja o exemplo da Listagem 11. Confira na Figura 3, que ao mantermos o RepeatButton pressionado, o conteúdo do TextBlock é constantemente incrementado.

Listagem 11. Criação e Código do botão RepeatButton no XAML.

        <TextBlock x:Name="txtCounter" Text="0" />
        <RepeatButton
        Width="200"
        Height="100"
        Content="Repeat It"
        Click="RepeatButton_Click" />

        C#
        private void RepeatButton_Click(object sender, RoutedEventArgs e)
        {
        int counter = int.Parse(txtCounter.Text);
        counter++;
        txtCounter.Text = counter.ToString();
        }
        
Botão sendo clicado e atualizando o contador
Figura 3. Botão sendo clicado e atualizando o contador.

ToggleButton

Este controle age de forma similar a um CheckBox, onde podemos ter dois estados: true ou false através de sua propriedade IsChecked. Clicando pela primeira vez, sua propriedade IsChecked será atribuida a true e clicando novamente será atribuida para false. A aparência padrão do ToggleButton é identica a de um botão normal.

Os ToggleButtons também tem um propriedade chamada IsThreeState, que se atribuido true, adiciona ao controle a possíbilidade de atribuir um terceiro valor, nulo a sua propriedade IsChecked. Neste caso, no primeiro click sua propriedade IsChecked é atribuida como true, na segunda como nulo e na terceira como false, e assim por diante. Para cada estado do ToggleButton, um evento diferente é definido, veja a Tabela 1.

Tabela 1. Valores e eventos da propriedade IsChecked
Estado Evento
Checked True
Unchecked False
Indeterminate Null

Assim como os RepeatButtons, este controle também está no Namespace System.Windows.Controls.Primitives. Note que a propriedade IsChecked é do tipo Nullable<Boolean>, também conhecida em sua declaração como “bool?”. Isso é que nos permite ter um valor booleano com três estados possíveis: true, false, e o estado nulo. Podemos ver na Listagem 12 um exemplo de ToggleButton.

Listagem 12. Criação e codificação de um ToggleButton.XAML>

        <TextBlock x:Name="txtStatus" Text="Indeterminate" />
        <ToggleButton
        Width="150"
        Height="50"
        Content="My Toggle Button"
        IsThreeState="True"
        Checked="ToggleButton_Checked"
        Unchecked="ToggleButton_Unchecked"
        Indeterminate="ToggleButton_Indeterminate" />

        C#
        private void ToggleButton_Checked(object sender, RoutedEventArgs e)
        {
        txtStatus.Text = "Checked";
        }

        private void ToggleButton_Unchecked(object sender, RoutedEventArgs e)
        {
        txtStatus.Text = "Unchecked";
        }

        private void ToggleButton_Indeterminate(object sender, RoutedEventArgs e)
        {
        txtStatus.Text = "Indeterminate";
        }
        

CheckBox

Pode parecer estranho, mas o controle CheckBox no WPF também é considerado um botão, principalmente por ter algumas características diferenciadas, como por exemplo: Ele mantém o estado Checked ou Unchecked quando clicado; E também suporta three-state mode. Veja na Listagem 13, um exemplo do CheckBox.

Listagem 13. Criando um CheckBox via XAML e C#.XAML


            <CheckBox x:Name="chkCheckMe" Canvas.Top="100" Canvas.Left="50"
            Content="Chek me if you want" />

            C#
            public Window1()
            {
            InitializeComponent();

            CheckBox chkCheckMe = new CheckBox();
            chkCheckMe.Content = "Criado via C#";
            rootCanvas.Children.Add(chkCheckMe);
            }
            

RadioButton

RadioButton é outro controle que deriva do ToggleButton. Quando um grupo de RadioButtons é criado, somente um deles pode ser selecionado, tendo todos os outros marcados como UnChecked e apenas o RadioButton selecionado é marcado como Checked. Vejamos na Listagem 14 como podemos criar um controle RadioButton.

Listagem 14. Criando RadioButtons via XAML e C#.XAML

        <StackPanel>
        <RadioButton>Opção 1</RadioButton>
        <RadioButton>Opção 2</RadioButton>
        <RadioButton>Opção 3</RadioButton>
        <RadioButton>Todas acima</RadioButton>
        </StackPanel>

        C#
        RadioButton rdoOpcao1 = new RadioButton();
        rdoOpcao1.Content = "Opção 1";
        rootCanvas.Children.Add(rdoOpcao1);
        

Veja que podemos utilizar o StackPanel para automaticamente organizar os RadioButtons, assim como qualquer outro tipo de controle. Veja na Figura 4 como fica a interface desse grupo de RadionButtons.

RadioButtons criados na Listagem 4
Figura 4. RadioButtons criados na Listagem 14.

Note que um grupo foi automaticamente atribuido a eles, e não conseguimos marcar mais de um RadioButton por vez. No caso, para atribuirmos grupos diferentes para cada RadioButton, basta utilizar a propriedade GroupName, como mostrado na Listagem 15.

Listagem 15. Atribuindo grupos aos RadioButtons.

        <RadioButton Canvas.Top="10" GroupName="Grupo1">Opção 1</RadioButton>
        <RadioButton Canvas.Top="25" GroupName="Grupo2">Opção 2</RadioButton>
        <RadioButton Canvas.Top="40" GroupName="Grupo1">Opção 3</RadioButton>
        <RadioButton Canvas.Top="55" GroupName="Grupo2">Todas acima</RadioButton>
        

Label

O WPF contém vários built-in content controls que não possuem funções de Click, mas que mesmo assim, tem diferenciais que justificam sua existência.

Este é o mais clássico dos controles, que vem de outras tecnologias e tem uma antiga história. O Label nada mais é do que um controle que armazena um texto simples, e no caso do WPF não suporta tags HTML para formatação interna do texto. Confira como é a criação de um Label em WPF na Listagem 16.

Listagem 16. Criando um Label via XAML e C#.XAML

        <Label x:Name="lblLabel" FontFamily="Arial" FontSize="16" Foreground="Blue"
        FontWeight="Bold">
        <Label.Content>
        DevMedia
        </Label.Content>
        </Label>

        • C#
        public Window1()
        {
        InitializeComponent();

        Label lblLabel = new Label();
        lblLabel.Content = "DevMedia";
        rootCanvas.Children.Add(lblLabel);
        }
        

ToolTip

ToolTip texts são os famosos comentários que aparecem quando deixamos o mouse sobre uma imagem ou algum outro elemento na tela. O que mais chama a atenção na criação de ToolTips no WPF é que seu conteúdo é do tipo Object, aceitando qualquer tipo de objeto que desejar, tornando assim um recurso explicativo muito poderoso.

Devemos tomar cuidado ao criarmos o conteúdo de um ToolTip, lembrando sempre que qualquer controle dentro de um ToolTip não pode ser clicado. A Listagem 17 mostra a criação de um ToolTip personalizado. E na Figura 5 você confere o resultado.

>Listagem 17. Criando um Label ToolTip personalizado via XAML.

        <CheckBox x:Name="chkTestandoToolTip" Content="Deixe o mouse sobre mim">
        <ToolTipService.ToolTip>
        <Canvas Width="140" Height="150">
        <TextBlock FontFamily="Arial" FontWeight="Bold" Canvas.Top="3">
        Controle CheckBox
        </TextBlock>
        <Rectangle Height="2" Width="140" Fill="Black" Canvas.Top="20" />
        <TextBlock TextWrapping="Wrap" Width="140" Canvas.Top="25">
        Lorem ipsum dolor sit amet, consectetur adipiscing elit.
        Ut dignissim feugiat nisl, ut mattis risus laoreet ac. Pellentesque in auctor orci.
        </TextBlock>
        </Canvas>
        </ToolTipService.ToolTip>
        </CheckBox>
        
Exemplo de ToolTip
Figura 5. Exemplo de ToolTip.

Expander

Exapander é um controle que funciona similar ao AccordionPane do ASP.NET Ajax, onde temos um painel que se abre e fecha ao clicar do usuário. Veja na Listagem 18 como criar um Expander em XAML. Na Figura 6 você confere o comportamento desse controle.

Listagem 18. Criando um Exapander via XAML

        <Expander Header="Eu abro e fecho!">
        <StackPanel>
        <CheckBox>Opção 1</CheckBox>
        <CheckBox>Opção 2</CheckBox>
        <CheckBox>Opção 3</CheckBox>
        </StackPanel>
        </Expander>
        
Exemplo de Expander
Figura 6. Exemplo de Expander

Tamanho e Posicionamento de Objetos

Trabalhar com tamanhos e posicionamento de objetos é sempre complicado. No WPF temos recursos de alinhamento e posicionamento para cada objeto, mas devemos nos atentar não a tela em sí, mas sim ao objeto pai. A Figura 7 ilustra bem esta característica.

Representação de posicionamento de objetos no WPF
Figura 7. Representação de posicionamento de objetos no WPF

Note que temos um alinhamento referente ao topo e a esquerda da página, que neste caso atua como elemento principal (root) do documento WPF. Veremos a seguir as principais propriedades de posicionamento no WPF.

Width e Height definem respectivamente a largura e altura de um objeto, e no WPF essas propriedades funcionam exatamente como no Windows Forms, sendo definidas em Pixels. Já as propriedades Top e Left definem onde um objeto será posicionado em relação ao seu objeto pai. Podemos tomar como exemplo o nosso form principal, onde podemos posicionar um objeto referindo-se a quantos pixels este objeto está distânte do topo ou da esquerda da página.

Veja na Listagem 19 como definir as dimensões e posicionamento de um Button, em XAML. E confira o resultado na Figura 8.

Listagem 19. Botão com largura e altura definidos com 100 pixels.

        <Button x:Name="btnBotao" Width="100" Height="100" Content="Botão" />

        <Window x:Class="DevMedia.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window1" Height="300" Width="300">
        <Canvas x:Name="rootCanvas">
        <Button x:Name="btnBotao" Canvas.Top="20" Canvas.Left="50"
        Width="100" Height="100" Content="Botão" />
        </Canvas>
        </Window>
        
Botão posicionado na tela
Figura 8. Botão posicionado na tela.

Note que este posicionamento se refere ao objeto pai que é uma canvas, sendo assim podem haver mudanças neste posicionamento caso o objeto pai seja uma Grid por exemplo. Para melhor entendimento, vejamos a seguinte situação demonstrada aqui na Listagem 20 e Figura 9.

Listagem 20. Canvas aninhadas.

        <Canvas x:Name="CanvasPai">
        <Canvas
        x:Name="Canvas1"
        Background="Red"
        Width="150"
        Height="150"
        Canvas.Top="10"
        Canvas.Left="10">
        <Canvas
        x:Name="Canvas2"
        Background="Green"
        Width="135"
        Height="135"
        Canvas.Top="10"
        Canvas.Left="10">
        <Button x:Name="btnBotao"
        Canvas.Top="10"
        Canvas.Left="10"
        Width="100"
        Height="100" Content="Botão" />
        </Canvas>
        </Canvas>
        </Canvas>
        
Canvas aninhadas
Figura 9. Canvas aninhadas

Primeiro temos o objeto CanvasPai, que é o elemento principal (root). Em seguida, temos a Canvas1, que está posicionada a 10 pixels de ditância do topo e da esqueda da página. Seguindo esta mesma linha de raciocínio, temos a Canvas2 que da mesma forma está a 10 pixels de distância da Canvas1, que é seu objeto pai.

Margin e Padding

Margin e Padding são propriedades que definem o espaçamento externo e interno de um objeto respectivamente. Veja como definir essas propriedades na Listagem 21. E na Figura 10 você vê como fica o resultado desse botão na tela.

Listagem 21. Botão com espaçamento interno de 0 pixels.

        <Button x:Name="btnTester" Width="150" Height="80" Padding="0">
        <Button.Content>
        <Rectangle Fill="Red" Width="50" Height="50" />
        </Button.Content>
        </Button>
        
Botão com espaçamento interno
de 0 pixels
Figura 10. Botão com espaçamento interno de 0 pixels.

Até este ponto não temos nada de anormal, mas vejamos na Figura 11 o que acontece quando atribuimos o espaçamento interno do botão para 20 pixels. Podemos notar que o retângulo vermelho se deslocou para direita e para baixo, pois o espaçamento interno está forçando-o a fazer isto. A Figura 12 ilustra como isso acontece.

Botão com espaçamento interno
de 20 pixels
Figura 11. Botão com espaçamento interno de 20 pixels.
Ilustração de como a
propriedade Padding funciona
Figura 12. Ilustração de como a propriedade Padding funciona.

Seguindo esta mesma linha de raciocínio temos a propriedade Margin, que por sua vez define o espaçamento externo do objeto. Veja na Listagem 22 e Figura 13, como fica um botão declarado com 20 pixels de margem externa.

Listagem 22. Botão com margem externa de 20 pixels.

        <Button x:Name="btnTester" Width="150" Height="80" Margin="20">
        <Button.Content>
        <Rectangle Fill="Red" Width="50" Height="50" />
        </Button.Content>
        </Button>
        
Botão com espaçamento externo de 20 pixels
Figura 13. Botão com espaçamento externo de 20 pixels.

Podemos notar que o efeito foi parecido com o posicionamento feito através do Canvas.Top e Canvas.Left, e na verdade, este objeto agora possúi uma margem externa de 20 pixels, sendo assim ele sempre irá ficar 20 pixels da esquerda, direita, topo e base de seu objeto pai.

Tanto o Pading quanto o Margin aceitam atribuições específicas para cada lado do objeto (Esquerda, Topo, Direita e Base), bastando apenas separar estes valores por uma vírgula. A sintaxe para este tipo de declaração fica assim:


        <Button Margin="Left, Top, Right, Bottom">
        

Em alguns casos isto se torna necessário, pois nem sempre iremos alinhar o objeto verticalmente e horizontalmente com os mesmos valores. O exemplo da Listagem 23 mostra como podemos posicionar um objeto mais ao centro e menos ao topo. Confira o resultado na Figura 14.

Listagem 23. Espaçamento externo de 60 pixels para esquerda e 20 pixels para o topo.

        <Button x:Name="btnTester" Width="150" Height="80" Margin="60, 20, 0, 0">
        <Button.Content>
        <Rectangle Fill="Red" Width="50" Height="50" />
        </Button.Content>
        </Button>
        
Espaçamento externo de 60 pixels para esquerda e 20 pixels para o topo
Figura 14. Espaçamento externo de 60 pixels para esquerda e 20 pixels para o topo.

Visibilidade dos Objetos

O WPF incorporou em basicamente todos os objetos a propriedade Visibility e Opacity para trabalhar com a visibilidade dos objetos na tela. A propriedade Visibility define se o objeto está ou não invisível através das propriedades Collapsed, Visible ou Hidden. A Listagem 24 demostra o uso desta propriedade.

Listagem 24. Propriedade Visibility.

        <Rectangle Fill="Red" Width="30" Height="30" Canvas.Top="5" Canvas.Left="5"
        Visibility="Collapsed" />


        <Rectangle Fill="Green" Width="30" Height="30" Canvas.Top="45" Canvas.Left="5"
        Visibility="Hidden" />


        <Rectangle Fill="Blue" Width="30" Height="30" Canvas.Top="85" Canvas.Left="5"
        Visibility="Visible" />
        

Se você executar esse código, irá notar que os retângulos com a propriedade Visibility definida como Collapsed e Hidden, não serão mostrados. Collapsed significa que o objeto está recolhido, ou seja, ele foi diminuído ao zero. Hidden significa que o objeto está escondido, invisível.

A necessidade de se ter estas duas propriedades que podem até parecer ter a mesma função é que quando utilizamos o Collaspsed, o espaço do objeto também é modificado e outros objetos podem ocupar seu lugar, e já no caso do Hidden isto não ocorre. Para ilustrar esta situação, veja o código da Listagem 25, que cria 3 CheckBoxes na tela.

Listagem 25. CheckBoxes sem Visibility atribuidos.

        <StackPanel Canvas.Top="20" Canvas.Left="40">
        <CheckBox Content="ChekBox1" />
        <CheckBox Content="ChekBox2" />
        <CheckBox Content="ChekBox3" />
        </StackPanel>
        

Se você executar esse código, todos os CheckBoxes irão aparecer na janela, já que todos omitem a propriedade Visibility, que tem seu valor default igual a Visible. Vejamos agora o que acontece quando mudamos esta propriedade do CheckBox1 para Hidden. Confira o resultado na Figura 15.


        <CheckBox Content="ChekBox1" Visibility="Hidden" />
        
CheckBox1 com a visibilidade Hidden
Figura 15. CheckBox1 com a visibilidade Hidden.

Note que o CheckBox1 desapareceu, mas nenhum outro objeto preencheu seu lugar na tela.

Vamos ver agora o exemplo onde sua visibilidade está atribuída como Collapsed (Figura 16).


        <CheckBox Content="ChekBox1" Visibility="Collapsed" />
        
CheckBox1 com a visibilidade Collapsed
Figura 16. CheckBox1 com a visibilidade Collapsed.

Podemos notar que agora além de invisível, outro objeto tomou seu lugar na tela.A visibilidade pode ser atribuída via código também, como mostrado na Listagem 26.

Listagem 26. Atribuindo a visibilidade via C#

        Button btn = new Button();
        btn.Visibility = Visibility.Hidden;
        

Opacidade

A Opacidade define o quão visível seu objeto está na tela, baseando em um valor de 0 à 1 onde 0 é invisível e 1 é completamente visível.O comportamento da opacidade funciona da mesma forma que o Hidden da propriedade Visibility, onde o objeto fica invisível mas nenhum outro objeto ocupa seu lugar. A Listagem 27 e a Figura 17 ilustram esta propriedade.

Listagem 27. Atribuindo opacidade a objetos.

        <StackPanel Canvas.Top="20" Canvas.Left="40" Background="AliceBlue">
        <CheckBox Content="ChekBox1" Opacity="0" />
        <CheckBox Content="ChekBox2" Opacity="0.1" />
        <CheckBox Content="ChekBox3" Opacity="0.2" />
        <CheckBox Content="ChekBox4" Opacity="0.3" />
        <CheckBox Content="ChekBox5" Opacity="0.4" />
        <CheckBox Content="ChekBox6" Opacity="0.5" />
        <CheckBox Content="ChekBox7" Opacity="0.6" />
        <CheckBox Content="ChekBox8" Opacity="0.7" />
        <CheckBox Content="ChekBox9" Opacity="0.8" />
        <CheckBox Content="ChekBox10" Opacity="0.9" />
        <CheckBox Content="ChekBox11" Opacity="1" />
        </StackPanel>
        
Atribuindo opacidade a objetos
Figura 17. Atribuindo opacidade a objetos.

Tranformando Objetos

Para finalizar nosso trabalho sobre os componentes, trabalharemos agora com tranformações dos objetos no WPF. Basicamente, temos duas propriedades onde podemos trabalhar com tranformações no WPF, que são LayoutTransform e RenderTransform.

O LayoutTransform é aplicado antes do elemento ser definido, e o RenderTransform é aplicado após o elemento ser definido. A Listagem 28 e Figura 18 demonstram a aplicação do LayoutTransform.

Listagem 28. Aplicação do LayoutTransform

        <StackPanel>
        <CheckBox Content="CheckBox1" />
        <CheckBox Content="CheckBox2">
        <CheckBox.LayoutTransform>
        <RotateTransform Angle="30" />
        </CheckBox.LayoutTransform>
        </CheckBox>
        <CheckBox Content="CheckBox3" />
        </StackPanel>
        
Aplicação do LayoutTransform
Figura 18. Aplicação do LayoutTransform.

Note que foi aplicado uma transformação de rotação, que moveu o CheckBox2 em um ângulo de 30 graus. Agora veja na Listagem 29 como aplicar o RenderTransform. Na Figura 19 você confere o resultado.

Listagem 29. Aplicação do RenderTransform.

        <StackPanel Canvas.Top="30" Canvas.Left="60">
        <CheckBox Content="CheckBox1" />
        <CheckBox Content="CheckBox2">
        <CheckBox.RenderTransform>
        <RotateTransform Angle="30" />
        </CheckBox.RenderTransform>
        </CheckBox>
        <CheckBox Content="CheckBox3" />
        </StackPanel>
        
Aplicação do RenderTransform
Figura 19. Aplicação do RenderTransform.

Podemos ver que aplicando o LayoutTranform, um objeto não sobrepõe o outro, pois sua rotacão é definida ainda na sua criação, e um espaço referênte a esta já fica reservado na tela. Isso já não ocorre com o RenderTranform, onde primeiro os controles são renderizados e só depois a rotação é aplicada.

Nos últimos dois exemplos utilizamos o RotateTransform para ilustras as transformações. O RotateTransform rotaciona um elemento utilizando as propriedades Angle, CenterX e CenterY. A propriedade Angle se refere ao ângulo de rotação, medido em graus. As propriedades CenterX e CenterY definem respectivamente o centro horizontal e vertical de rotação (Ponto Pivot).

Levando em consideração o código da Listagem 30, onde aplicamos uma rotação de 45 graus, sem definirmos nenhuma das propriedades de Centro (CenterX e CenterY), a rotação do objeto pode ser entendida na Figura 20, que ilustra como é feito o movimento do objeto na janela.

Listagem 30. Aplicando rotação ao objeto.

        <Canvas Width="150" Height="80" Background="Coral" Canvas.Top="50" Canvas.Left="60">
        <Canvas.RenderTransform>
        <RotateTransform Angle="45" />
        </Canvas.RenderTransform>
        </Canvas>
        
Aplicação rotação ao objeto
Figura 20. Aplicação rotação ao objeto.

Note que como não atribuímos um ponto pivot (CenterX e CenterY), o valor padrão foi 0, sendo assim, o objeto está girando sobre o pixel 0 do topo superior esquerdo do objeto. Agora vamos ver como o objeto é rotacionado quando o ponto pivot muda. Mudando o ponto pivot para CenterX=”75” e CenterY=”40”, que é exatamente o centro do objeto. O resultado é mostrado na Figura 21, onde você pode ver claramente que agora o objeto gira em torno do seu centro.

Objeto rotacionando sobre o centro
Figura 21. Objeto rotacionando sobre o centro.

Na mesma linha do RotateTransform, temos o Scale Transform que redefine a escala do objeto, permitindo aumentá-lo e diminuí-lo através das propriedades ScaleX, ScaleY, CenterX e CenterY. O ScaleX e ScaleY aumenteam respectivamente a largura e altura do objeto, enquanto o CenterX e CenterY são respectivamente os centros Horizontal e Vertical de Crescimento do objeto (Ponto Pivot). Caso as propriedade CenterX e CenterY não sejam atribuídas, seu valor padrão é 0.

A Listagem 31 e a Figura 22 demonstram a diferenca entre um objeto sem Scale Transform e este mesmo objeto com Scale Transform.

Listagem 31. Objeto com e sem Scale Transform.

        <!-- Objeto sem Scale Transform -->
        <Canvas Width="100" Height="40" Background="Coral" Canvas.Top="50" Canvas.Left="60" Opacity="0.3" />

        <!-- Objeto com Scale Transform -->
        <Canvas Width="100" Height="40" Background="Coral" Canvas.Top="50" Canvas.Left="60" Opacity="0.3">
        <Canvas.RenderTransform>
        <ScaleTransform ScaleX="2" ScaleY="2" CenterX="0" CenterY="0" />
        </Canvas.RenderTransform>
        </Canvas>
        
Objeto com e sem Scale Transform
Figura 22 . Objeto com e sem Scale Transform

Note que o retângulo menor e mais escuro é o objeto sem o ScaleTransform, enquanto o maior e mais claro é o objeto com o ScaleTransform aplicado. Podemos notar a presença do ponto pivot no pixel 0 do topo esquerdo do objeto, fazendo com que o objeto cresça para esquerda e para baixo.

Vamos ver como fica este crescimento quando mudamos o ponto pivot para o centro do objeto (CenterX=”50” e CenterY=”20”). Confira o código na Listagem 32 e o resultado na Figura 23. Note que agora o objeto irá crescer para todos os lados, pois seu ponto de crescimento está centralizado.

Listagem 32. Objeto com e sem Scale Transform com ponto pivot centralizado.

        <!-- Objeto sem Scale Transform -->
        <Canvas Width="100" Height="40" Background="Coral" Canvas.Top="50" Canvas.Left="60" Opacity="0.3" />

        <!-- Objeto com Scale Transform -->
        <Canvas Width="100" Height="40" Background="Coral" Canvas.Top="50" Canvas.Left="60" Opacity="0.3">
        <Canvas.RenderTransform>
        <ScaleTransform ScaleX="2" ScaleY="2" CenterX="50" CenterY="20" />
        </Canvas.RenderTransform>
        </Canvas>
        
Objeto com e sem Scale Transform com ponto pivot centralizado
Figura 23. Objeto com e sem Scale Transform com ponto pivot centralizado.

O SkewTransform é utilizado para inclinarmos um elemento de acordo com as propriedades AngleX, AngleY, CenterX e CenterY. AnlgeX e AngleY são os graus horizontal e vertical usados para a “torção” do objeto. E as propriedades CenterX e CenterY são os pontos de origem horizontal e vertical dessa torção.

Caso as propriedade CenterX e CenterY não sejam atribuídas, seu valor padrão é 0. A Listagem 33 e a Figura 24 demonstram a diferenca entre um objeto sem Scale Transform e este mesmo objeto com Scale Transform.

Listagem 33. Objeto com e sem Skew Transform

        <!-- Objeto sem Skew -->
        <Canvas Width="100" Height="40" Background="Coral" Canvas.Top="50" Canvas.Left="60" Opacity="0.5" />

        <!-- Objeto com Skew -->
        <Canvas Width="100" Height="40" Background="Green" Canvas.Top="50" Canvas.Left="60" Opacity="0.5">
        <Canvas.RenderTransform>
        <SkewTransform AngleX="10" AngleY="20" />
        </Canvas.RenderTransform>
        </Canvas>
        
Objeto com Skew (Verde) e sem Skew (Vermelho)
Figura 24. Objeto com Skew (Verde) e sem Skew (Vermelho).

Conclusão

Esses foram apenas alguns conceitos do WPF, mas que já nos dão uma boa ideia da dinamica que temos que seguir ao construir interfaces nessa tecnologia. Podemos evoluir muito mais do que isso, como por exemplo, ainda na questão de transformações temos os recursos de TranslateTransform e MatrixTransform, além da possibilidade de combinar transformações.

O WPF é uma tecnologia que já evoluiu e vem evoluindo muito. A Microsoft investe muito no WPF e no Silverlight, nos mostrando que esse é certamente um caminho muito interessante a seguir para a construção de interfaces ricas.

Confira também