Desenvolvimento - ASP. NET

WPF ComboBox: Explorando o componente

Veja neste artigo como utilizar o componente ComboBox do WPF de diversas formas, conhecendo os principais recursos que ele nos oferece.

por Joel Rodrigues



Em WPF, a maioria dos componentes visuais pode ser customizada para exibir informações em um formato específico desejado, nos permitindo fugir da formatação padrão e desenvolver aplicações com interfaces mais bem elaboradas. Isso permite que esses controles sejam ligados a praticamente qualquer tipo de dado e mesmo assim consiga exibir corretamente a informação desejada.

Além da forma de visualização padrão de cada controle, podemos utilizar templates para definir uma formatação complexa que inclua outros controles, estruturas condicionais e outros recursos interessantes. Neste artigo estudaremos o componente ComboBox, dando especial atenção à exibição de dados dinâmicos (funcionamento conhecido como lookup). Veremos a seguir como listar informações corretamente neste componente e como recuperar essa informação posteriormente, utilizando todos os recursos que estão à nossa disposição.

Para seguir os exemplos que serão mostrados aqui, crie uma aplicação WPF e adicione um combobox à janela principal, nomeando-a da forma como preferir (aqui será utilizado o nome “comboBoxDados”).

Propriedades e eventos do componente

Para o contexto deste artigo, vale a pena destacar algumas propriedades do componente ComboBox:

  • ItemsSource: quando desejarmos exibir informações no combobox, devemos atribuir a esta propriedade a nossa coleção de dados. É interessante notar que praticamente qualquer coleção (desde que implemente a interface IEnumerable) pode ser utilizada como fonte de dados.
  • SelectedIndex: índice do item selecionado no combobox.
  • SelectedItem: refere-se ao item selecionado. Esta propriedade é do tipo Object, o que indica que o item selecionado pode ser de qualquer tipo, portanto, na hora de acessá-lo, devemos efetuar o cast para o tipo desejado. Essa propriedade será bastante abordada mais adiante.
  • SelectedValue: se desejarmos, é possível exibir uma informação no combobox e internamente armazenar outra. Por exemplo, poderíamos listar a descrição de um objeto e no momento de verificar o item selecionado, obter não a descrição, mas sim um valor chave, neste caso podemos utilizar essa propriedade, que também será utilizada nos exemplos a seguir.
  • SelectionChanged: este evento ocorre quando selecionamos um novo item no combobox.

Obviamente existem várias outras propriedades úteis, porém neste momento essas serão o bastante para atender a todas as nossas principais necessidades. Além disso, é importante que fique claro para o leitor as diferenças entre essas três últimas e quando utilizar cada uma.

Listando dados simples

Por dados simples, neste contexto, podemos entender aqueles que não possuem múltiplas propriedades relevantes quanto à sua função principal. Por exemplo, valores numéricos e textuais (int e string, por exemplo). A função desses tipos de dados é armazenar um valor simples e único, diferentemente de uma classe Pessoa, por exemplo, que armazena valores em várias propriedades como Nome e Telefone.

Para exibir um item, por padrão o componente utiliza o método ToString do seu tipo de dados, por este motivo, tipos de dado como int e string são exibidos com facilidade, pois esse método gera apenas uma representação textual do seu valor.

Na Listagem 1 temos um pequeno exemplo em que listamos um array de strings no combobox. O resultado, como esperado, é a exibição bastante simples de cada item do array, como vemos na Figura 1.

Listagem 1: Listando dados simples

string[] categorias =  {"Eletrodomésticos", "Limpeza", "Frios"};
comboBoxDados.ItemsSource = categorias;
Dados simples listados em exibição padrão

Figura 1: Dados simples listados em exibição padrão

Para este tipo de informação, essa formatação padrão em geral é bastante para atender à necessidade do desenvolvedor, pois cada item representa apenas um texto simples. Porém, quando temos mais de uma propriedade em cada item precisamos que mais de uma delas esteja visível ao mesmo tempo, precisamos customizar o template de dados do componente, que é o assunto do próximo tópico.

Listando tipos de dados complexos

Aqui o termo “tipo de dado complexo” refere-se simplesmente a classes de negócio, as quais contêm geralmente mais de uma propriedade que fazem sentido em conjunto. Tomemos como exemplo uma aplicação de vendas em que temos a classe Vendedor, que possui propriedades como Nome e Código (ou Matricula do funcionário). Como podemos ter mais de um vendedor com o mesmo nome, precisamos que na tela de vendas tenhamos um combobox em que o operador possa visualizar além do nome, também o código daquele vendedor, a fim de que não haja dúvida sobre qual funcionário está sendo selecionado.

A Listagem 2 mostra o trecho de código em que atribuímos ao combobox uma lista de vendedores a serem exibidos. Porém, se executarmos a aplicação, teremos um resultado que não é satisfatório (Figura 2), com os itens sendo exibidos de forma ilegível para o usuário final.

Listagem 2: Listando tipos complexos

List<Vendedor> vendedores = new List<Vendedor>();
vendedores.Add(new Vendedor() { Codigo = 101, Nome = "Carlos" }); 
vendedores.Add(new Vendedor() { Codigo = 250, Nome = "João" });
vendedores.Add(new Vendedor() { Codigo = 335, Nome = "Maria" });
vendedores.Add(new Vendedor() { Codigo = 458, Nome = "Carlos" });
vendedores.Add(new Vendedor() { Codigo = 976, Nome = "Antonia" });
comboBoxDados.ItemsSource = vendedores;
Exibição padrão para dados com múltiplas propriedades

Figura 2: Exibição padrão para dados com múltiplas propriedades

Como já foi dito, o comportamento padrão deste componente é utilizar o retorno do método ToString para exibir as informações. Assim, o método ToString da classe vendedor retorna apenas o nome do tipo, incluindo seu namespace, como vemos na Figura 2. Esse resultado claramente não serve para nós, mas felizmente temos algumas formas de corrigir isso. A primeira delas é sobrescrever o método ToString da classe e fazer com que ele retorne um texto que represente melhor o objeto. Por exemplo, na Listagem 3 sobrescrevemos esse método da classe Vendedor para retornar agora o código e o nome do mesmo. Se executarmos a aplicação, teremos um resultado mais adequado (Figura 3).

Listagem 3: Sobrescrevendo o método ToString

public override string ToString()
{
    return this.Codigo.ToString() + " - " + this.Nome;
}
Exibição customizada com o método ToString

Figura 3: Exibição customizada com o método ToString

Para o que precisamos neste momento, dentro do nosso exemplo, essa formatação já é adequada, porém haverá situações em que precisaremos customizar ainda mais a exibição dos dados. Nestes casos, a solução mais adequada e que mais permite customizações é utilizar o template de dados dos itens do combobox. Isso é feito diretamente no código XAML e não requer que sobrescrevamos o método ToString.

Na Listagem 4 temos um exemplo dessa funcionalidade, em que exibimos cada item do combobox como sendo um StackPanel contendo dois TextBlocks, cada um para uma propriedade da classe Vendedor. Note que o código do vendedor agora aparecerá em negrito, simulando algum tipo de customização mais avançada.

Listagem 4: Customizando o template de dados

<ComboBox Margin="164,124,136,164" Name="comboBoxDados">
    <ComboBox.ItemTemplate>
        <DataTemplate>
            <StackPanel Orientation="Horizontal">
                <TextBlock FontWeight="Bold" Text="{Binding Codigo}" Width="50"/>
                <TextBlock Text="{Binding Nome}"/>
            </StackPanel>
        </DataTemplate>
    </ComboBox.ItemTemplate>
</ComboBox>

Dentro da tag DataTemplate podemos inserir qualquer tipo de controle e liga-lo às propriedades da classe que será exibida no componente. Para isso utilizamos o recurso de Binding, muito presente no WPF. Aqui cabe uma observação importante: a tag DataTemplate deve conter apenas uma tag filha diretamente, ou seja, se desejarmos adicionar mais de um controle no template de dados, eles devem estar contidos em um container, como é o caso dos TextBlocks, que estão contidos em um StackPanel.

Agora, quando executamos a aplicação temos como resultado algo semelhante ao que mostra a Figura 4.

Exibição customizada através do DataTemplate

Figura 4: Exibição customizada através do DataTemplate

DisplayMemberPath e SelectedValuePath

As propriedades DisplayMemberPath e SelectedValuePath permitem definir uma propriedade do item a ser exibida e uma a ser armazenada como chave equivalente, respectivamente. Por exemplo, se desejássemos exibir apenas o nome do vendedor e ao selecionar um item, poder recuperar o código daquele vendedor, essas propriedades nos permitiriam fazer isso de forma bastante simples. Na Listagem 5 está o código XAML do ComboBox utilizando essas duas propriedades. Note que como vamos exibir apenas uma propriedade de classe Vendedor, não precisamos mais do DataTemplate.

Listagem 5: Utilizando as propriedades DisplayMemberPath e SelectedValuePath

<ComboBox Margin="164,124,136,164" Name="comboBoxDados" SelectedValuePath="Codigo" DisplayMemberPath="Nome"/>

Na Figura 5 temos o combobox listando apenas o nome de cada vendedor, como era esperado.

Dados exibidos utilizando DisplayMemberPath

Figura 5: Dados exibidos utilizando DisplayMemberPath

A definição da propriedade SelectedValuePath nos permite então utilizar a propriedade SelectedValue para recuperar o código do vendedor selecionado.

SelectedValue

Como definimos qual propriedade da nossa classe está sendo armazenada internamente quando selecionamos um item, podemos recuperar esse valor em tempo de execução utilizando a propriedade SelectedValue. É importante notar que esta propriedade também é do tipo Object, o que remete à possibilidade de termos nela qualquer tipo de dado. Portanto, na hora de ler esse valor, é necessário fazer a conversão para o tipo correto. A seguir, na Listagem 6 estamos tratando o evento SelectionChanged e exibindo o valor contido em seu SelectedValue, que neste caso é o código do vendedor selecionado.

Listagem 6: Lendo o valor selecionado

private void comboBoxDados_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    int codigoSelecionado = Convert.ToInt32(comboBoxDados.SelectedValue);
    MessageBox.Show(string.Format("Você selecionou o vendedor de código {0}", codigoSelecionado));
}

Note que o código não aparece para o usuário final neste caso, mas caso desejemos exibi-lo, poderíamos utilizar a propriedade SelectedValuePath em conjunto com as outras abordagens apresentadas anteriormente. Porém, seria necessário omitir a propriedade DisplayMemberPath, uma vez que não teríamos uma única propriedade sendo exibida.

SelectedItem

Essa propriedade é, de certa forma, mais completa que a SelectedValue, pois ela refere-se ao item selecionado por completo, não apenas uma propriedade deste. Esta propriedade, como foi dito, é do tipo Object e precisa ser convertida no momento da leitura. Seu tipo real dependerá do tipo de dados que estejamos listando no componente. No nosso exemplo, sabemos que esta propriedade representa um objeto da classe Vendedor, portanto precisamos fazer um cast quando desejarmos ler seu valor em tempo de execução. Essa propriedade é útil especialmente quando estamos listando objetos com muitos atributos e que precisarão ser lidos posteriormente. Por exemplo, a partir dela poderíamos ler o código, o nome e qualquer outra propriedade do objeto Vendedor selecionado. A Listagem 7 mostra um exemplo disso, ainda tratando o evento SelectionChanged do combobox.

Listagem 7: Obtendo o item selecionado

private void comboBoxDados_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Vendedor vendedorSelecionado = comboBoxDados.SelectedItem as Vendedor;
    MessageBox.Show(string.Format("Você selecionou o vendedor {0} de código {1}", vendedorSelecionado.Nome, vendedorSelecionado.Codigo));
}

Como vimos, existem várias formas de obter o mesmo resultado, algumas mais limitadas, enquanto outras nos dão mais possibilidades. Cabe ao desenvolvedor decidir qual utilizar, mantendo sempre a maior simplicidade possível para economizar tempo e recursos.

A propriedade SelectedIndex não foi abordada diretamente em nenhum tópico devido à sua simplicidade, pois ela representa apenas o índice do item que está selecionado no combobox e seu valor começa a contar a partir do zero.

Erros comuns

A seguir veremos alguns erros comuns cometidos principalmente por aqueles que estão iniciando o desenvolvimento em WPF e ainda não conhecem bem quais propriedades utilizar em cada momento.

O primeiro erro que veremos aqui ocorre quando não definimos a propriedade SelectedValuePath e tentamos obter o valor contido em SelectedValue, convertendo-o para um tipo específico. Por exemplo, se não tivéssemos definido o SelectedValuePath, como vimos na Listagem 5, e tentássemos converter o SelectedValue para inteiro, como foi feito na Listagem 6, teríamos uma exceção do tipo InvalidCastException, dizendo que não pode converter o tipo Vendedor (Figura 6). Isso ocorre porque neste caso, o conteúdo de SelectedValue é equivalente ao de SelectedItem, ou seja, um objeto completo contendo todas as propriedades.

Erro por não definir o SelectedValuePath

Figura 6: Erro por não definir o SelectedValuePath

Outro erro muito comum ocorre quando tentamos ler o item selecionado sem que haja um, seja por o combobox estar vazio, ou por realmente o usuário não ter selecionado nenhuma opção. Nesse caso, ocorre uma exceção do tipo NullReferenceException, como vemos na a Figura 7. Para evitar isso, poderíamos fazer uma simples verificação e só tratar o item selecionado caso ele seja diferente de nulo, assim como mostra a Listagem 8.

Exceção de referência nula por não haver item selecionado

Figura 7: Exceção de referência nula por não haver item selecionado

Listagem 8: Evitando exceção de referência nula

if (comboBoxDados.SelectedItem != null)
{
    Vendedor vendedorSelecionado = comboBoxDados.SelectedItem as Vendedor;
    MessageBox.Show(string.Format("Você selecionou o vendedor {0} de código {1}", vendedorSelecionado.Nome, vendedorSelecionado.Codigo));
}

Com essas informações o leitor deve conseguir lidar de forma eficiente com o componente ComboBox. Além disso, boa parte do que foi mostrado aqui também se aplica a outros componentes, como o ListBox, portanto o leitor pode seguir o mesmo raciocínio e certamente obterá resultados satisfatórios.

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).