Desenvolvimento - WPF

Criando User Controls em WPF

Neste artigo veremos como trabalhar com os User Controls em WPF, criando controles customizados a partir da união de outros controles nativos.

por Joel Rodrigues



A plataforma de desenvolvimento Windows já dispõe, nativamente, de uma ampla gama de componentes que atendem praticamente a todas as necessidades dos sistemas mais comuns. Porém, há situações em que é necessário customizar esses controles para atender uma demanda específica, unindo vários componentes nativos em um só, de forma que eles funcionem em conjunto, mas aproveitando as características de cada um.

Em WPF temos a possibilidade de criar User Controls, que são controles criados a partir da união de vários outros componentes nativos, como TextBoxes, ListBoxes, etc. Quando criados, esses controles podem ser inseridos na interface do projeto como se fossem componentes nativos, bastando apenas referenciar seu namespace através da diretiva xmlns, no XAML, ou using, no C#.

Diferentemente dos Custom Controls, nos User Controls não se pode alterar o funcionamento normal de um controle nativo, assim o objetivo é uni-los para realizar uma função específica.

Criando um User Control

Vamos criar um User Control em uma aplicação WPF no Visual Studio para entender seu processo de construção e utilização. Com um projeto WPF Application criado, podemos clicar com o botão direito sobre o Solution Explorer e depois use as opções New Item ou diretamente User Control, como mostra a Figura 1. Em seguida, basta colocar um nome para o novo controle e clicar em OK.

Adicionando um User Control

Figura 1. Adicionando um User Control

Para melhor organizar a estrutura do projeto, pode ser interessante colocar os controles novos em pastas separadas. Aqui, por exemplo, criaremos uma pasta UserControls e colocaremos os novos controles dentro dela, para que possamos separá-los em um namespace específico.

Feito isso, o próximo passo é definir a estrutura do User Control propriamente dito, alterando seu código XAML de acordo com a necessidade. Aqui criaremos um controle que servirá para listar arquivos de um diretório e o chamaremos de ControleListagemDiretorio. Para isso, ele contará com um TextBox, onde o caminho do diretório deve ser inserido, um ListBox para listar os arquivos e um botão para executar a ação de listagem.

Utilizaremos também os recursos de Data Binding, juntamente com a interface INotifyPropertyChanged, que nos auxilia na implementação do padrão Observer para manter as propriedades do controle sempre atualizadas de acordo com a interação do usuário com a interface gráfica.

Na Listagem 1 temos o código XAML do controle: observe que o texto do TextBox, bem como os itens e o item selecionado do ListBox, estão ligados a propriedades que serão definidas posteriormente via C#.

Listagem 1. Código XAML do User Control

  <UserControl x:Class="ExemploUserControl.UserControls.ControleListagemDiretorio"
               xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
               xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
               xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
               xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
               xmlns:local="clr-namespace:ExemploUserControl.UserControls"
               mc:Ignorable="d" 
               d:DesignHeight="300" d:DesignWidth="300">
      <Grid>
          <Grid.ColumnDefinitions>
              <ColumnDefinition Width="*"/>
              <ColumnDefinition Width="Auto"/>
          </Grid.ColumnDefinitions>
          
          <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="*"/>
          </Grid.RowDefinitions>
   
          <TextBlock Text="Diretório" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"/>
   
          <TextBox Text="{Binding Diretorio, Mode=TwoWay}" Grid.Row="1" Grid.Column="0"/>
          
          <Button Content="Listar" Grid.Row="1" Grid.Column="1" Click="btnListar_Click"/>
   
          <ListBox ItemsSource="{Binding ItensDiretorio}" SelectedItem="{Binding ItemSelecionado, Mode=TwoWay}" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2"/>
      </Grid>
  </UserControl>

Com isso, a interface do controle deve ficar como mostra a Figura 2.

Interface do controle em tempo de design

Figura 2. Interface do controle em tempo de design

Na sequência, precisamos definir as propriedades Diretorio, ItensDiretorio e ItemSelecionado, que são usadas nos controles visuais. Quando as definirmos como públicas, essas propriedades serão visíveis em qualquer lugar onde o User Control for utilizado posteriormente. Sendo assim, um utilizador externo poderá obter o valor do TextBox, por exemplo, da seguinte forma: userControl.Diretorio.

Na Listagem 2 temos o código C# do User Control, em que usamos conceitos como encapsulamento nas propriedades e o operador nameof do C# 6.0.

Listagem 2. Código C# do User Control

  public partial class ControleListagemDiretorio : UserControl, INotifyPropertyChanged
  {
      private string diretorio;
   
      public string Diretorio
      {
          get { return diretorio; }
          set
          {
              if (diretorio != value)
              {
                  diretorio = value;
                  NotificarPropriedadeAlterada(nameof(Diretorio));
              }
          }
      }
   
      private string[] itensDiretorio;
   
      public string[] ItensDiretorio
      {
          get { return itensDiretorio; }
          set
          {
              if (itensDiretorio != value)
              {
                  itensDiretorio = value;
                  NotificarPropriedadeAlterada(nameof(ItensDiretorio));
              }
          }
      }
   
      private string itemSelecionado;
   
      public string ItemSelecionado
      {
          get { return itemSelecionado; }
          set
          {
              if (itemSelecionado != value)
              {
                  itemSelecionado = value;
                  NotificarPropriedadeAlterada(nameof(itemSelecionado));
              }
          }
      }
   
   
      public event PropertyChangedEventHandler PropertyChanged;
   
      private void NotificarPropriedadeAlterada(string propriedade)
      {
          PropertyChanged(this, new PropertyChangedEventArgs(propriedade));
      }
   
      public ControleListagemDiretorio()
      {
          InitializeComponent();
          DataContext = this;
      }       
   
      private void btnListar_Click(object sender, RoutedEventArgs e)
      {
          if(!String.IsNullOrWhiteSpace(diretorio))
          {
              ItensDiretorio = Directory.GetFiles(diretorio);
          }
      }
  }

Sempre que clicarmos no botão btnListar, os arquivos contidos no diretório cujo caminho foi inserido no TextBox serão listados no ListBox. Note que em nenhum momento precisamos atribuir a lista de strings à propriedade ItemsSource do ListBox, nem precisamos pegar o texto diretamente do TextBox. Isso foi possível porque estamos utilizando os recursos de Data Binding no modo TwoWay, em que uma alteração na interface automaticamente é refletida nas variáveis e vice-versa. Assim, quando alteramos a propriedade ItensDiretorio, o ListBox é automaticamente atualizado.

Utilizando o User Control

Com o User Control criado, podemos agora inseri-lo em outras partes do projeto. Aqui vamos adicionar um controle novo na MainWindow, para isso é necessário primeiramente referenciar seu namespace dentro do elemento Window, utilizando a seguinte sintaxe:

xmlns:controls="clr-namespace:ExemploUserControl.UserControls"

Neste caso, o projeto se chama ExemploUserControl e a pasta onde o User Control foi criado é a UserControls, formando assim seu namespace. O identificador controls será usado a partir de então para adicionar à janela quaisquer controles que estejam nesse namespace.

Para adicionar o controle, basta usar a seguinte sintaxe, assim ele já aparecerá na interface:

<controls:ControleListagemDiretorio/>

Se desejarmos obter as propriedades do controle em tempo de execução, precisamos definir um nome para ele utilizando o atributo x:Name. Na Listagem 3 podemos ver um exemplo da MainWindow com um ControleListagemDiretorio adicionado, bem como um botão que será usado para listar suas propriedades.

Listagem 3. Testando o User Control

  <Window x:Class="ExemploUserControl.MainWindow"
          xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
          xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
          xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
          xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
          xmlns:local="clr-namespace:ExemploUserControl"
          xmlns:controls="clr-namespace:ExemploUserControl.UserControls"
          mc:Ignorable="d"
          Title="MainWindow" Height="350" Width="525">
      <Grid >
          <Grid.ColumnDefinitions>
              <ColumnDefinition Width="*"/>
              <ColumnDefinition Width="*"/>
          </Grid.ColumnDefinitions>
   
          <Grid.RowDefinitions>
              <RowDefinition Height="Auto"/>
              <RowDefinition Height="*"/>
          </Grid.RowDefinitions>
          
          <controls:ControleListagemDiretorio Grid.Column="0" Margin="10" x:Name="listaDiretorio" Grid.Row="0" Grid.RowSpan="2"/>
          
          <Button Content="Ver propriedades do controle" Grid.Column="1" Height="80" Grid.Row="0" Click="Button_Click" Margin="0,10,10,0"/>
   
          <TextBox x:Name="txtPropriedades" Grid.Column="1" Grid.Row="1" Margin="0,0,10,10"/>
      </Grid>
  </Window>

Por fim, temos na Listagem 4 o método que trata o evento Click do botão, no qual também usamos o recurso de String Interpolation da C# 6.0.

Listagem 4. Acessando as propriedades do botão

  private void Button_Click(object sender, RoutedEventArgs e)
  {
      StringBuilder strPropriedades = new StringBuilder();
      strPropriedades.AppendLine($"Diretório: {listaDiretorio.Diretorio}");
      strPropriedades.AppendLine($"Itens: {listaDiretorio.ItensDiretorio.Count()}");
      strPropriedades.AppendLine($"Item selecionado: {listaDiretorio.ItemSelecionado}");
      txtPropriedades.Text = strPropriedades.ToString();
  }

Agora já podemos testar nossa aplicação. Executando-a e temos como resultado o que mostra a Figura 3, com o novo User Control à esquerda.

Utilizando o User Control

Figura 3. Utilizando o User Control

E na Figura 4 vemos o controle em funcionamento, com suas propriedades lidas ao lado.

Acessando as propriedades do User Control

Figura 4. Acessando as propriedades do User Control

São diversas as possibilidades de utilização dos User Controls, e de acordo com a necessidade é possível utilizar vários controles em sua composição, estilos, data binding, como foi visto e outros recursos da plataforma.

Joel Rodrigues

Joel Rodrigues - Técnico em Informática - IFRN Cursando Bacharelado em Ciências e Tecnologia - UFRN Programador .NET/C# e Delphi há quase 3 anos, já tendo trabalhado com Webservices, WPF, Windows Phone 7 e ASP.NET, possui ainda conhecimentos em HTML, CSS e Javascript (JQuery).