Tutorial Qt – Capitulo 12 – Listas, árvores e tabelas

Muitas aplicações tendem a exibir dados na forma de listas, tabelas e árvores. Essa necessidade é satisfeita pelo Qt através de uma grande faixa de classes desde simples listas de textos até tabelas complexas. Começaremos do mais simples, que é lidar com QListBox.

QListBox

Figure 13-1 The QListBox example application
Figure 13-1 The QListBox example application

A QListBox é a forma mais simples de uma lista. È normalmente usada para exibir uma coluna única de itens de texto mas pode ser usada em contextos mais avançados. Quando estiver exibindo uma lista de itens de texto os métodos insertItem ou insertStringList são usados para criar a lista. Para limpar a lista, use o método clear para remover todos os itens da lista. Isso é demonstrado no código abaixo:


ListBoxExample::ListBoxExample( QWidget *parent, char *name ) : QVBox( parent, name )
{
m_listBox = new QListBox( this );
QHBox *hb = new QHBox( this );
m_lineEdit = new QLineEdit( hb );
QPushButton *pbAdd = new QPushButton( "Add", hb );
QPushButton *pbClear = new QPushButton( "Clear", hb );
connect( pbAdd, SIGNAL(clicked()), this, SLOT(addItem()) );
connect( pbClear, SIGNAL(clicked()), m_listBox, SLOT(clear()) );
}
void ListBoxExample::addItem()
{
m_listBox->insertItem( m_lineEdit->text() );
m_lineEdit->setText( "" );
}

O código mostra a implementação do widget que demonstra o QListBox. Os ponteiros m_listBox e m_lineEdit são membros privados da clases. Observe quão fácil podem ser conectados diretamente a um signal.

Agora sabemos como preencher e limpar a lista. Como sabemos quais itens estão selecionados e como reagimos a eles? Um widget QListBOx pode ter três modos de seleção diferentes: único, multi e estendido.

  • Único: o usuário pode selecionar um único item por vez.
  • Multi: o usuário pode selecionar qualquer quan tidade de itens. Clicar em um item alterna seus status de seleção.
  • Estendido: o usuário pode selecionar qualquer quantidade de itens. A seleção é gerenciada da mesma forma que as listbox são gerenciadas no Windows. A tecla shift é usada para selecionar faixas enquanto a chave ctrl é usada para selecionar itens um por um.

A fourth mode is also available that simply prevents the user from selecting anything. The default selection mode is single. So, how do we get to know when an item is selected, and which item that is? To test that we add a label at the bottom of our widget and a slot that fills it with the current selection. In the constructor we connect the signal selectionChanged to our slot. The code is shown in example 13-2.


ListBoxExample::ListBoxExample( QWidget *parent, char *name ) : QVBox( parent, name )
{
...
connect( m_listBox, SIGNAL(selectionChanged()), this, SLOT(newSelection()) );
}
void ListBoxExample::newSelection()
{
if( !m_listBox->selectedItem() )
m_label->setText( "nothing" );
else
m_label->setText( m_listBox->selectedItem()->text() );
}

Experimente o código do exemplo e brinque com os botões para gerar signals. Por exemplo, observe que selectionChanged é melhor usado com uma lista de consteúdo estático já que o signal não é emitido quando a lista é limpa.
É possivel criar uma sub-classe de QListBoxItem para fornecer listas mais avançadas. Exemplos disso são QListBoxPixmap e QjListBoxPixmap que fornecem uma listbox com texto e figuras. O código do exemplo abaixo mostra como criar uma listbox com esse tipo de item ao invés de itens de texto.


QListBox *lb = new QListBox();
QPixmap pm( 12, 12 );
pm.fill( Qt::red );
new QListBoxPixmap( lb, pm, "Red" );
pm.fill( Qt::yellow );
new QListBoxPixmap( lb, pm, "Yellow" );
pm.fill( Qt::green );
new QListBoxPixmap( lb, pm, "Green" );
pm.fill( Qt::cyan );
new QListBoxPixmap( lb, pm, "Cyan" );
pm.fill( Qt::blue );
new QListBoxPixmap( lb, pm, "Blue" );
pm.fill( Qt::magenta );
new QListBoxPixmap( lb, pm, "Magenta" );

A aplicação resultante é mostrada abaixo. No código, observe que os itens da listbox são instânciados como uma referência para listbox. Isso faz com que eles se adicionem a lista automaticamente. Compare isso a como os itens de texto apenas são adicionados a lista. È possível usar o método inseryItem com itens personalizados também.

Figure 13-2 A QListBox with QListBoxPixmaps
Figure 13-2 A QListBox with QListBoxPixmaps

QListView

Quando estiver lidando com dados mais complexos e multi-colunas, o widget QListView é mais adequado que o QListBox. O QListView pode ser exibir dados multi-colunas armazenado como uma lista ou uma estrutura em árvore. Cada coluna pode ter um cabeçalho que permite ordenar a lista pelo clique sobre ela. A figura abaixo mostra um exemplo da aplicação.

Figure 13-3 The list tab
Figure 13-3 The list tab

O widget listview é bastante complexo, por isso o exemplo é dividido em quatro partes, cada uma mostra uma aba diferente da aplicação exemplo. Nós começamos com o caso mais básico – uma lista simples com várias colunas. Isso é mostrado na aba com o título “List” mostrada na figura 13-3. O código que cria a listview é mostrado no exemplo abaixo:


QWidget *ListViewExample::setupListTab()
{
m_listView = new QListView();
m_listView->addColumn( "Foo" );
m_listView->addColumn( "Bar" );
m_listView->addColumn( "Baz" );
m_listView->setAllColumnsShowFocus( true );
new QListViewItem( m_listView, "(1, 1)", "(1, 2)", "(1, 3)" );
new QListViewItem( m_listView, "(2, 1)", "(2, 2)", "(2, 3)" );
new QListViewItem( m_listView, "(3, 1)", "(3, 2)", "(3, 3)" );
new QListViewItem( m_listView, "(4, 1)", "(4, 2)", "(4, 3)" );
return m_listView;
}

O código mostra os passos básicos necessários para usar a listview. Primeiro, algumas colunas são criadas. Depois, qualquer configuração especial é feita. Nesse caso, nos certificamos que a seleção é mostrada sobre toda a linha, e não sobre a primeira coluna somente. Finalmente, preenchemos a lista pela criação de um conjunto de QListBiewItems que referencem-se a listview como parent.

Figure 13-4 The tree tab
Figure 13-4 The tree tab

O segundo caso de uso é exibir os itens da listview em uma hierarquia de árvore. Isso é mostrado na aba com o titulo “Tree”. O código que cria a listview é mostrado no exemplo abaixo. Quando executar a aplicação exemplo, experimente clicar nos cabeçalhos da coluna para ordenar os itens. Observe que cada seção (A, B ou C) é ordenada separadamente.


QWidget *ListViewExample::setupTreeTab()
{
m_treeView = new QListView();
m_treeView->addColumn( "Tree" );
m_treeView->addColumn( "First" );
m_treeView->addColumn( "Second" );
m_treeView->addColumn( "Third" );
m_treeView->setRootIsDecorated( true );
QListViewItem *root = new QListViewItem( m_treeView, "root" );
QListViewItem *a = new QListViewItem( root, "A" );
QListViewItem *b = new QListViewItem( root, "B" );
QListViewItem *c = new QListViewItem( root, "C" );
new QListViewItem( a, "foo", "1", "2", "3" );
new QListViewItem( a, "bar", "i", "ii", "iii" );
new QListViewItem( a, "baz", "a", "b", "c" );
new QListViewItem( b, "foo", "1", "2", "3" );
new QListViewItem( b, "bar", "i", "ii", "iii" );
new QListViewItem( b, "baz", "a", "b", "c" );
new QListViewItem( c, "foo", "1", "2", "3" );
new QListViewItem( c, "bar", "i", "ii", "iii" );
new QListViewItem( c, "baz", "a", "b", "c" );
return m_treeView;
}

O código é estruturada da mesma forma que a lista básica. A diferente é que ao invés de usar um widget listview como parent para os itens, alguns itens possuem  outros itens como parent. As razões para usar esse tipo de organização é fornecer um feedback para o usuário. Somente ver o item raiz não diz ao usuário que existem mais itens. O sinal de mais a direita do texto diz isso ao usuário.

Figure 13-5 Sorting per column
Figure 13-5 Sorting per column

O terceira case de uso demonstra uma problema comum que muitos usuário tem ciência dele. Se você tentar ordenar numeros que são tratados como strings de texto, os números acima de 10 terminará entre os números 1 e 2. A solução mais fácil, mas não a melhor, é acomodar os núemros com zeroes a esquerda ou tratar as strings como valores ao invés de pedaços de texto.


class SortItem : public QListViewItem
{
public:
SortItem( QListView *parent, QString c1, QString c2, QString c3, QString c4 ) : QListViewItem( parent, c1, c2, c3, c4 )
{
}
int compare( QListViewItem *i, int col, bool asc ) const
{
if( col == 2 )
return text( col ).toInt() - i->text( col ).toInt();
else
return QListViewItem::compare( i, col, asc );
}
};

O exemplos de ordenação usa uma classe de item de listview personalizada mostrada no exemplo acima. A classe de item personalizada re-implementa o método compare e trata as terceira coluna como número ao invés de uma string. As outras colunas usam o método compare padrão de QListViewItem.
Experimente rodar o exemplo. Ordene as diferentes colunas e observe como funciona. Observe também  como a ordenação pela quarta coluna leva em conta a caixa do caractere. O código para esse caso é mostrado abaixo:


QWidget *ListViewExample::setupSortTab()
{
m_sortView = new QListView();
m_sortView->addColumn( "Number" );
m_sortView->addColumn( "Padded" );
m_sortView->addColumn( "Corrected" );
m_sortView->addColumn( "Alphabetical" );
m_sortView->setShowSortIndicator( true );
new SortItem( m_sortView, "1", "02", "1", "foo" );
new SortItem( m_sortView, "3", "04", "1", "bar" );
new SortItem( m_sortView, "5", "06", "2", "foo" );
new SortItem( m_sortView, "7", "08", "3", "bar" );
new SortItem( m_sortView, "9", "10", "5", "Foo" );
new SortItem( m_sortView, "11", "12", "8", "bar" );
new SortItem( m_sortView, "13", "14", "13", "foo" );
new SortItem( m_sortView, "15", "00", "21", "Bar" );
return m_sortView;
}

Figure 13-6 Icons in a QListView
Figure 13-6 Icons in a QListView

A tabela “Icons” mostra quão fácil é colocar icones numa listview. Eles podem ser itens diferentes de diferentes colunas ou multiplos icones em cada linha. O código fonte é mostrado abaixo. Como você pode ver, colocar um icone em uma coluna é tão fácil quanto chamar setPixmap.


QWidget *ListViewExample::setupIconTab()
{
m_iconView = new QListView();
m_iconView->addColumn( "Foo" );
m_iconView->addColumn( "Bar" );
m_iconView->addColumn( "Baz" );
m_iconView->setAllColumnsShowFocus( true );
QPixmap pm( 10, 10 );
QListViewItem *lvi = new QListViewItem( m_iconView, "(1, 1)", "(1, 2)", "(1, 3)" );
pm.fill( Qt::red );
lvi->setPixmap( 0, pm );
lvi = new QListViewItem( m_iconView, "(2, 1)", "(2, 2)", "(2, 3)" );
pm.fill( Qt::green );
lvi->setPixmap( 1, pm );
lvi = new QListViewItem( m_iconView, "(3, 1)", "(3, 2)", "(3, 3)" );
pm.fill( Qt::blue );
lvi->setPixmap( 2, pm );
lvi = new QListViewItem( m_iconView, "(4, 1)", "(4, 2)", "(4, 3)" );
pm.fill( Qt::yellow );
lvi->setPixmap( 0, pm );
pm.fill( Qt::magenta );
lvi->setPixmap( 2, pm );
return m_iconView;
}

Trabalhar com a listview é parecido com trabalhar com listbox. O signal selectionChanged aidna está disponível junto com os métodos isSelected e setSelected. Em aplicações modernas é preferivel normalmente a listview sobre a listbox por que a listview fornece cabeçalhos para as colunas que dão ao usuário mais informações.

Um problema ao trabalhar com listas é coordenar os conteúdo da lista com os dados da aplicação. Por exemplo, vamos dizer que você tem uma aplicação de livro de telefone. Cada item da lista tem uma foto e algum detalhe associado junto como o nome, numero do telefone, e-mail, endereço, etc. A listview que exibe a lista de contatos  não de destina a mostrar toda a informação, provavelmente somento os nomes dos contatos devem interessar até que um item seja selecionado. Existem várias abordagens para cuidar desse problema:

  1. Os contatos estão armazenados em um banco de dados. Cada contato possui um id único para separa-los (para permtiir múltiplos contatos com o mesmo nome). Esse id nunca é exibido para o usuário e só existe realmente nos bastidores. Colocando esse id em uma coluna oculta e ela pode ser usada para localizar o contato a partir de um QValueList ou QMap.
  2. Os contatos são lidos da memória e armazenados em uma lista. Um item de listview especial  pega um contato da entrada e re-implementa o método de extração de texto da informação requisitada. Quando alterar os dados, apenas o contato é alterado. O item da listview apenas precisa ser re-desenhado para refletir as alterações.
  3. A classe QListViewItem é uma sub-classe e o método de texto é re-implementado. A nova classe, ContactItem, contém todas as informações necessárias e podem ser usadas na listview. O problema surge quando um segundo view de mesmo item é requisitado.
  4. A sincronização da lista de constatos e lista de itens pode ser embutida dentro das ações de edição. Cada ação precisa cuidar das alterações da listview e da lista de contatos da mesma forma. Isso pode facilmente fugir do controle – e poluir o código das ações.

As três primeira soluções são as preferíveis. A número três apenas se uma view é necessária, a número dois se todos os dados estiverem na memória e a número um se os dados não couberem na memória. Para implementar a primeira solução uma coluna oculta é necessária. Qualquer coluna que é ocultada precisa ter  o modo de definição da largura configurado para manual. O código para esconder a coluna três da listview, lv, incluindo alteração do modo de  definir a largura, está no exemplos abaixo:


lv->setColumnWidthMode( 3, QListView::Manual );
lv->hideColumn( 3 );

QTable

Com os dados ficando mais e mais estruturados e complexos tanto a listview quanto a listbox precisam ficar simples. Ao invés delas, uma tabela é necessária. Usando o widget QTable, cada celula da tabela pode armazenar um widget diferente. Por exemplo, checkbox, listas dropdown e texto podem ser mantidos em um celula.

É fácil criar uma tabela e preenche-la com itens usando os métodos setText, set Pixmap e setItem.

O truque real, quando estiver usando tabelas, é ser capaz de criar itens personalizados. Isso é feito para fornecer uma experiência de uso melhor para os usuários. Por exemplo, ao invés de selecionar o nome de uma cor em uma lista dropdown nós forneceremos um item que mostra uma lista das cores e deixa o usuário escolher uma delas. Uma tela da aplicação pode ser vista abaixo:

Figure 13-7 A QTable with ColorTableItems
Figure 13-7 A QTable with ColorTableItems

O que fizemos foi criar uma sub-classe de QTableItem e criar a nova classe: ColorTableItem. A declaração da classe é mostrada a seguir:


class ColorTableItem : public QTableItem
{
public:
ColorTableItem( QTable *table, const QString &color );
QWidget *createEditor() const;
void setContentFromEditor( QWidget *w );
void paint( QPainter *p, const QColorGroup &cg, const QRect &cr, bool selected );
};

Da observação do código acima podemos ver que os métodos interessantes são paint, createEditor e setContentFroMEditor. Os doid últimos funcionam como um par, e o primeiro é implementado independentemente dos demais.

O método paint pega o texto da celula e assume que é o nome de uma cor. Depois preenche a celula com esse cor. O exemplo abaixo mostra a implementação desse método:


void ColorTableItem::paint( QPainter *p, const QColorGroup &cg, const QRect &cr, bool selected )
{
if( text() == "White" )
p->setBrush( Qt::white );
else if( text() == "Gray" )
p->setBrush( Qt::gray );
else if( text() == "Black" )
p->setBrush( Qt::black );
else if( text() == "Red" )
p->setBrush( Qt::red );
else if( text() == "Yellow" )
p->setBrush( Qt::yellow );
else if( text() == "Green" )
p->setBrush( Qt::green );
else if( text() == "Cyan" )
p->setBrush( Qt::cyan );
else if( text() == "Blue" )
p->setBrush( Qt::blue );
else if( text() == "Magenta" )
p->setBrush( Qt::magenta );
p->drawRect( table()->cellRect(row(), col()) );
}

Os métodos createEditor e setContentFromEditor trabalham juntos. Primeiro, o createEditor cria o widget que é usado para editar a celula, e depois o resultado colhido do widget do editor usando setContentFromEditor. Assim, no exemplo abaixo, o código para createEditor é mostrado. Ele cria uma combobox, que é uma lista dropdown, com itens de cores. Também certifica que o item correto é selecionada da lista.


QWidget *ColorTableItem::createEditor() const
{
QComboBox *cb = new QComboBox( table()->viewport() );
QObject::connect( cb, SIGNAL( activated( int ) ), table(), SLOT( doValueChanged() ) );
QPixmap pm( 100, 20 );
pm.fill( Qt::white );
cb->insertItem( pm );
pm.fill( Qt::gray );
cb->insertItem( pm );
pm.fill( Qt::black );
cb->insertItem( pm );
pm.fill( Qt::red );
cb->insertItem( pm );
pm.fill( Qt::yellow );
cb->insertItem( pm );
pm.fill( Qt::green );
cb->insertItem( pm );
pm.fill( Qt::cyan );
cb->insertItem( pm );
pm.fill( Qt::blue );
cb->insertItem( pm );
pm.fill( Qt::magenta  );
cb->insertItem( pm );
if( text() == "White" )
cb->setCurrentItem( 0 );
else if( text() == "Gray" )
cb->setCurrentItem( 1 );
else if( text() == "Black" )
cb->setCurrentItem( 2 );
else if( text() == "Red" )
cb->setCurrentItem( 3 );
else if( text() == "Yellow" )
cb->setCurrentItem( 4 );
else if( text() == "Green" )
cb->setCurrentItem( 5 );
else if( text() == "Cyan" )
cb->setCurrentItem( 6 );
else if( text() == "Blue" )
cb->setCurrentItem( 7 );
else if( text() == "Magenta" )
cb->setCurrentItem( 8 );
return cb;
}

Depois, como mostrado no próximo exemplo, o método setContentFromEditor simplesmente pega o editor do widget, checa o que foi selecionado do combobox e coloca o reaultado de volta na celula.


void ColorTableItem::setContentFromEditor( QWidget *w )
{
if( w->inherits( "QComboBox" ) )
{
switch( ((QComboBox*)w)->currentItem() )
{
case 0:
setText( "White" );
break;
case 1:
setText( "Gray" );
break;
case 2:
setText( "Black" );
break;
case 3:
setText( "Red" );
break;
case 4:
setText( "Yellow" );
break;
case 5:
setText( "Green" );
break;
case 6:
setText( "Cyan" );
break;
case 7:
setText( "Blue" );
break;
case 8:
setText( "Magenta" );
break;
}
}
else
QTableItem::setContentFromEditor( w );
}

O código que configura a tabela com os itens personalizados é mostrado abaixo. Observe que os nomes das cores tem que serem valores válidos para que o programa funcione, mas isso pode ser melhorado usando uma tipo enumerado, ou simplesmente usando QColor.


QTable t( 3, 3 );
t.setItem( 0, 0, new ColorTableItem( &t, "Red" ) );
t.setItem( 0, 1, new ColorTableItem( &t, "Green" ) );
t.setItem( 0, 2, new ColorTableItem( &t, "Black" ) );
t.setItem( 1, 0, new ColorTableItem( &t, "Yellow" ) );
t.setItem( 1, 1, new ColorTableItem( &t, "Magenta" ) );
t.setItem( 1, 2, new ColorTableItem( &t, "Blue" ) );
t.setItem( 2, 0, new ColorTableItem( &t, "Gray" ) );
t.setItem( 2, 1, new ColorTableItem( &t, "Cyan" ) );
t.setItem( 2, 2, new ColorTableItem( &t, "White" ) );

Qt 4 – Interview

No Qt4, as classes QListView w QTabel forma parcialmente mescladas como parte do framework Interview. A arquitetura será baseada em torno dos views e de um modelo de aramazenamento de dados. Isso reduz a necessidade de armazenar informações em vários lugares na memória (isto é, como um item de uma listview w ema estrutura de dados especifica da aplicação).

Sumário

O código dos exemplos desse capitulo podem ser baixados aqui ex13.tar
Traduzido de http://www.digitalfanatics.org/projects/qt_tutorial/chapter13.html