SlideShare une entreprise Scribd logo
1  sur  29
Télécharger pour lire hors ligne
Parte II: Qt Intermediário
6. Gerenciamento do Layout
Planejando os Widgets em um Form
Layouts Empilhados
Splitters
Áreas de Rolagem
Encurtando Janelas e Barra de Ferramentas
Interface Múltipla de Documento
Todo widget que é colocado em um form deve receber um tamanho e posição
apropriados. O Qt fornece diversas classes que ajustam widgets em um Form:
QHBoxLayout, QVBoxLayout, QGridLayout, e QSTackLayout. Essas classes são tão convenientes e
fáceis de se usar que quase todo desenvolvedor Qt as usa, seja diretamente no
código fonte , ou através do Qt Designer.
Outra razão para se usar classes de layout do Qt é que elas garantem que as
formas se adaptem automaticamente para diferentes formas, linguagens e
plataformas. Caso o usuário mude as configurações de fonte do sistema, as Forms
da aplicação irão se adaptar automaticamente, redimensionando caso seja
necessário. Se você traduzir a interface da aplicação para outro idioma, as clases
de layout levam em consideração os conteúdos traduzidos das widgets para evitar
entroncamento do texto.
Outras classes que realizam manutenção de layout incluem QSplitter, QScrollArea,
QMainWindow, e QMdiArea. Todas essas classes fornecem um layout flexível que o
usuário pode manipular. Por exemplo, QSplitter fornece uma barra separador a que o
usuário pode arrastar para redimensionar widgets, e QMdiArea oferece suporte à MDI
( Multiple Document Interface), uma maneira de mostrar diversos documentos
simultaneamente dentro da janela principal de uma aplicação. Devido ao fato de
serem recentemente usadas como alternativas para as classes de layout
adequadas, vamos abordar essa técnica neste capítulo.
Planejando Widgets em um Form
Existem três formas básicas de gerenciar o layout em widgets menores num Form:
posicionamento absoluto, layout manual, e gerenciadores de layout. Vamos analisar
cada abordagem de uma vez, usando a caixa de diálogo de Busca por Arquivos,
como mostra a Figura 6.1 abaixo.
Figura 6.1. A Caixa de diálogo Find File
Posicionamento absoluto é a forma mais crua de projetar widgets. E alcançável
através da atribuição de tamanhos e posições para as widgets-filhas do Form, além
de um tamanho fixo para o Form. O construtor de FindFileDialog usando
posicionamento absoluto fica assim:
FindFileDialog::FindFileDialog(QWidget *parent)
QDialog(parent)
{
..
namedLabel->setGeometry(9, 9, 50, 25);
namedLineEdit->setGeometry(65, 9, 200, 25);
lookInLabel->setGeometry(9, 40, 50, 25);
lookInLineEdit->setGeometry(65, 40, 200, 25);
subfoldersCheckBox->setGeometry(9, 71, 256, 23);
tableWidget->setGeometry(9, 100, 256, 100);
messageLabel->setGeometry(9, 206, 256, 25);
findButton->setGeometry(271, 9, 85, 32);
stopButton->setGeometry(271, 47, 85, 32);
closeButton->setGeometry(271, 84, 85, 32);
helpButton->setGeometry(271, 199, 85, 32);
setWindowTitle(tr("Find Files or Folders"));
setFixedSize(365, 240);
}
Posicionamento Absoluto possui diversas desvantagens:
 O usuário nunca poderá redimensionar a janela;
 Partes do texto podem ser truncadas se o usuário escolher um tipo de fonte
muito largo ou se a aplicação for traduzida para outro idioma.
 Os Widgets podem ter tamanhos inapropriados para alguns estilos.
 As posições e tamanhos devem ser calculados manualmente. Isso é
entediante e sujeito a erros, além, de tornar manutenção um desafio.
Um alternativa para Posicionamento Absoluto é layout manual. Com Layout Manual,
ainda são dadas posições absolutas para os widgets, mas seus tamanhos são
definidos proporcionais ao tamanho da janela, sendo desnecessária codificação
bruta.
Código:
FindFileDialog::FindFileDialog(QWidget *parent)
: QDialog(parent)
{
...
setMinimumSize(265, 190);
resize(365, 240);
}
void FindFileDialog::resizeEvent(QResizeEvent * /* event */)
{
int extraWidth = width() - minimumWidth();
int extraHeight = height() - minimumHeight();
namedLabel->setGeometry(9, 9, 50, 25);
namedLineEdit->setGeometry(65, 9, 100 + extraWidth, 25);
lookInLabel->setGeometry(9, 40, 50, 25);
lookInLineEdit->setGeometry(65, 40, 100 + extraWidth, 25);
subfoldersCheckBox->setGeometry(9, 71, 156 + extraWidth, 23);
tableWidget->setGeometry(9, 100, 156 + extraWidth,
50 + extraHeight);
messageLabel->setGeometry(9, 156 + extraHeight, 156 +
extraWidth, 25);
findButton->setGeometry(171 + extraWidth, 9, 85, 32);
stopButton->setGeometry(171 + extraWidth, 47, 85, 32);
closeButton->setGeometry(171 + extraWidth, 84, 85, 32);
helpButton->setGeometry(171 + extraWidth, 149 + extraHeight, 85,
32);
}
No construtor FindFileDialog , Ajustamos o tamanho mínimo do form para 265 X 190 e
o tamanho inicial para 365 X 240. No controlador resizeEvent(), damos o tamanho
extra à widgets conforme o quanto queremos expandi-la. Isso garante que o Form
aumente gradualmente quando o usuário a redimensiona.
Assim como posicionamento absoluto, layout manual requer bastante codificação
de constantes para serem calculadas pelo programador. Codificação escrita dessa
forma é canstiva, especialmente com mudanças de aspecto visual. Layout manual
também corre risco de sofrer entruncamento do texto. Podemois evitar isto
levando em consideração as sugestões de tamanho das widgets-filhas, mas isto
tornaria o código ainda mais complicado.
A forma mais conveniente de se solucionar problemas de layout de widgets em Qt é
o uso dos gerenciadores de layout. Os gerenciadores de layout fornecem padrões
sensíveis para cada tipo de widget e levam em conta as sugestões de tamanho de
cada widget, o que depende do conteúdo, estilo e tamanho da fonte do widget.
Gerenciadores de layout também respeitam tamanhos mínimo e máximo, e
automaticamente ajustam o layout em resposta às mudanças de fonte, e de
conteúdo, além de redimensionamento de janela. Uma versão redimensionável do
Find File dialog é mostrado na Figura 6.2.
Figura 6.2. Redimensionando uma janela redimensionável
[Janela aumentada]
Os três gerenciadores de layout mais importantes são : QHBoxLayout, QVBoxLayout e
QGridLayout. Essas classes são derivadas de QLayout, que fornece o framework básico
para layouts. Todas as três classes são totalmente suportadas pelo Qt Designer e
podem ser usadas também diretamente em código.
Aqui está o código de FindFIleDialog usando Gerenciadores de Layout:
Código:
FindFileDialog::FindFileDialog(QWidget *parent)
: QDialog(parent)
{
...
QGridLayout *leftLayout = new QGridLayout;
leftLayout->addWidget(namedLabel, 0, 0);
leftLayout->addWidget(namedLineEdit, 0, 1);
leftLayout->addWidget(lookInLabel, 1, 0);
leftLayout->addWidget(lookInLineEdit, 1, 1);
leftLayout->addWidget(subfoldersCheckBox, 2, 0, 1, 2);
leftLayout->addWidget(tableWidget, 3, 0, 1, 2);
leftLayout->addWidget(messageLabel, 4, 0, 1, 2);
QVBoxLayout *rightLayout = new QVBoxLayout;
rightLayout->addWidget(findButton);
rightLayout->addWidget(stopButton);
rightLayout->addWidget(closeButton);
rightLayout->addStretch();
rightLayout->addWidget(helpButton);
QHBoxLayout *mainLayout = new QHBoxLayout;
mainLayout->addLayout(leftLayout);
mainLayout->addLayout(rightLayout);
setLayout(mainLayout);
setWindowTitle(tr("Find Files or Folders"));
}
O layout é controlado por um QHBoxLayout, um QGridLayout, e um QVBoxLayout. O QGridLayout
na esquerda e o QVBoxLayout na direita são colocados lado a lado por outro QHBoxLayout.
A margem em volta da caixa e o espaço entre as widgets crianças são ajustados
com valores default baseados no estilo atual do widget; podem ser trocados usando
QLayout::setContentsMargins() e QLayout::setSpacing().
Figura 6.3. O layout da caixa de Pesquisa de Arquivo
A mesma caixa poderia ter sido criada visualmente utilizando o Qt Designer através
da inclusão dos widgets filhos nas posições aproximadas; selecionando aqueles que
devem ser ajustados junto; e clicando em Form|Lay Out Horizontally, Form|Lay Out
Vertically, ou Form|Lay Out in a Grid. Usamos esta abordagem no Capítulo 2 para
criar as dialogs Go to Cell e Sort da aplicação Spreadsheet.
Usar QHBoxLayout e QVBoxLayout é um método direto, mas usar QGridLayout requer um
pouco mais de trabalho. QGridLayout trabalha em uma grade bidimensional de células.
O QLabel o canto superior esquerdo do layout está na posição (0,0), e o QLineEdit
correspondente está na posição (0,1). O QCheckBox abrangem duas colunas; ocupa
as células das posições (2,0) e (2,1). O QTreeWidget e o QLabel abaixo dele também
expande duas colunas. As chamadas a QGridLayout::addWidget() têm a seguinte sintaxe:
layout->addWidget(widget, row, column, rowSpan, columnSpan);
Aqui, widget é o widget-filho para inserir dentro do layout, (row, column) é a célula no
canto superior esquerdo ocupado pelo widget, rowSpan é o número de linhas
ocupadas pelo widget, e columnSpan é o número de colunas ocupadas pelo widget,
Caso sejam omitidos, os argumentos rowSpan e columnSpan assumem valor 1.
A chamada addStretch() diz ao gerenciador vertical do layout para consumir espaço
naquele ponto do layout. Ao adicionar um trecho de um item, mandamos um aviso
ao gerenciador para que coloque qualquer espaço em excesso entre os botões
Close e Help. No Qt Designer, podemos atingir o mesmo efeito inserindo um
espaçador. Espaçadores aparecem no Qt Designer como “springs” azuis.
O uso de gerenciadores de layout garante benefícios adicionais àqueles que
discutimos até então. Se adicionarmos um widget em um layout ou removermos
um widget de um layout, o layout irá se adaptar automaticamente à nova situação.
O mesmo se aplica se chamarmos hide() ou show() em um widget filho. Caso o
tamanho sugerido do widget filho mude, o layout será refeito automaticamente,
levando em conta a nova sugestão de tamanho. Além disso, gerenciadores de
layout automaticamente ajustam um valor mínimo para o form como um todo,
baseado nos tamanhos mínimos e sugestões de tamanho dos widgets filhos do
form.
Nos exemplos apresentados até agora, nós simplesmente colocamos widgets em
layouts e usamos separadores para consumir qualquer excesso de espaço. Em
alguns casos, isto não é suficiente para deixar o layout exatamente do jeito que
desejamos. Nessas situações, podemos ajustar o layout mudando as políticas e
sugestões de tamanho dos widgets que estão sendo usados.
Uma política de tamanho do widget diz ao sistema de layout como deve ser
estendido ou encolhido. Qt fornece políticas de padrões de tamanho sensíveis para
todos os widgets projetados, porém já que nenhum padrão único pode constar em
cada possível layout, ainda é muito comum entre os desenvolvedores a prática de
mudar as regras de tamanhos para um ou outro widget em um form. QSizePolicy
possui componentes vertical e horizontal. Eis os valores mais importantes:
 Fixed significa que o widget nunca poderá ser expandido ou encolhido.
Permanecerá sempre do tamanho designado pela sugestão.
 Minimum significa que a sugestão de tamanho do widget é o seu tamanho
mínimo. O widget nunca poderá encolher para um valor menor do que a
sugestão, mas pode aumentar de tamanho.
 Maximum significa que a sugestão de tamanho do widget é seu tamanho
máximo, podendo-se diminuir o widget para a menor sugestão de tamanho.
 Preferred significa que o tamanho sugerido é o tamanho mais indicado, mas é
livre para aumentar ou diminuir tamanho.
 Expanding significa que o widget pode aumentar ou diminuir, mas que sua
tendência é crescer.
A Figura 6.4 resume os significados das diferentes políticas de tamanho, usando um
QLabel.
Figura 6.4 O significado de diferentes políticas de tamanho
Na figura, Preferred e Expanding são descritos da mesma forma. Então qual é a
diferença? Quando um form que contém widgets do tipo Preferred e Expanding é
redimensionado, um espaço extra é dado aos widgets marcados com Expanding,
enquanto que os widgets Preferred mantém-se em sua sugestão de tamanho.
Existem duas outras políticas de tamanho: MinimumExpanding e Ignored. O primeiro foi
necessário em pouquíssimos casos em versões mais antigas de Qt, mas não é mais
útil; a estratégia mais indicada é usar Expanding e reimplementar minimumSizeHint()
apropriadamente. O último é similar a Expanding, Exceto pelo fato de que ignora a
sugestão de tamanho do widget e a sugestão de tamanho mínimo.
Adicionalmente aos componentes horizontais e verticais das políticas de tamanho, a
classe QSizePolicy armazena um fator horizontal e vertical para alargamentos de
fatores. Esses fatores de alargamentos podem ser usados para indicar que
diferentes widgets filhos devem crescer em taxas diferentes quando o form
expande. Por exemplo, se tivermos um QTreeWidget sobre um QTextEdit e queremos que
QTextEdit seja o dobro do tamanho de QTreeWidget, podemos ajustar o fator de
alargamento vertical de QTextEdit para 2 e o fator de alargamento vertical de
QTreeWidget para 1.
Layouts Empilhados
A classe QStackedLayout projeta um conjunto de widgets filhos, ou “páginas”, e
mostre um de cada vez, escondendo os demais do usuário. QStackedLayout em si é
invisível e não fornece nenhuma maneira do usuário mudar a págna. As pequenas
flehas e o frame cinza-escuro na Figura 6.5 são fornecidos por Qt Designer para
tornar o layout mais fásil de se modelar. Por conveniência, Qt também inclui
QStackedWidget, que possui um QWidget com um QStackedLayout pré-produzido.
Figura 6.5. QStackedLayout
As páginas são numeradas a partir de 0. Para tornar um widget filho específico
visível, podemos chamar setCurrentIndex() com um número de página. Para obter
o número de página para um widget filho, utilize indexOf().
A caixa de Preferências mostrada na Figura 6.6 é um exemplo de uso de
QStackedLayout. A caixa consiste de um QListWidget na esquerda, e um
QStackedLayout na direita. Cada item em QListWidget corresponde a uma página
diferente no QStackedLayout. Eis o código relevante do construtor da caixa:
PreferenceDialog::PreferenceDialog(QWidget *parent)
: QDialog(parent)
{
...
listWidget = new QListWidget;
listWidget->addItem(tr("Appearance"));
listWidget->addItem(tr("Web Browser"));
listWidget->addItem(tr("Mail & News"));
listWidget->addItem(tr("Advanced"));
stackedLayout = new QStackedLayout;
stackedLayout->addWidget(appearancePage);
stackedLayout->addWidget(webBrowserPage);
stackedLayout->addWidget(mailAndNewsPage);
stackedLayout->addWidget(advancedPage);
connect(listWidget, SIGNAL(currentRowChanged(int)),
stackedLayout, SLOT(setCurrentIndex(int)));
...
listWidget->setCurrentRow(0);
}
Figura 6.6 Duas páginas da caixa Preferências
Criamos um QListWidget e o populamos com os nomes de páginas. Depois, criamos
um QSTackedLayout e chamamos addWidget() para cada página. Conectamos o
sinal de currentRowChanged(int) de cada widget da lista com setCurrentIndex(int)
do layout empilhado para implementar a troca de páginas e chamamos
setCurrentRow() na lista de widget no final do construtor para começar na página
0.
Formas como esta são muito fáceis de se criar usando o Qt Designer:
1. Crie um novo form beaseado em um dos templates “Dialog”, ou no
template “Widget”.
2. Adicione um QListWidget e um QStackedWidget no form.
3. Preencha cada página com widgets filhos e layout.
(Para criar uma nova página, clique com o botão direito e selecione
Insert Page; para trocar páginas, clique na pequena seta “esquerda ou direita” ,
localizadas no canto superior direito de QStackedWidget.)
4. Modele os widgets lado a lado usando um layout horizontal.
5. Conecte o sinal de currentRowChanged(int) do widget da lista com o
slot de setCurrentIndex(int) do widget empilhado.
6. Ajuste o valor de currentRow do widget da lista para 0.
Já que implementamos a troca de páginas usando sinais e slots pré-definidos, a
caixa irá exibir o comportamento correto quando pré-visualizada no Qt Designer.
Para casos onde o número de páginas é pequeno e propenso a permanescer
pequeno, uma alternativa mais simples para o uso de QStackedWidget e
QListWidget é o uso de um QTabWdget.
Separadores
Um QSplitter é um widget que contém outros widgets. Os widgets em um
separador (splitter) são separados por alças separadoras. Usuários podem mudar
os tamanhos dos widgets filhos de um separador , arrastando as alças.
Separadores podem ser usados como uma alternativa para gerenciadores de
layout, para dar mais controle ao usuário.
Os widgets filhos de um QSplitter são automaticamente posicionados lado a lado
(ou um abaixo do outro) na ordem em que foram criados, como barras separadoras
entre widgets adjacentes. Aqui está o código para criação da janela da Figura 6.7:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QTextEdit *editor1 = new QTextEdit;
QTextEdit *editor2 = new QTextEdit;
QTextEdit *editor3 = new QTextEdit;
QSplitter splitter(Qt::Horizontal);
splitter.addWidget(editor1);
splitter.addWidget(editor2);
splitter.addWidget(editor3);
...
splitter.show();
return app.exec();
}
Figura 6.7. Aplicação Splitter
O exemplo consiste de três campos QTextEdit, modelados horizontalmente por um
widget QSplitter – isto é mostrado esquematicamente na Figura 6.8. Diferente dos
gerenciadores de layout, que simplesmente modelam os widgets filhos de um form
e não possuem representação visual, QSplitter é derivado de QWidget e pode ser
usado como qualquer outro widget.
Figura 6.8. Os widgets da aplicação Splitter
Áreas de Rolagem
A classe QScrollArea fornece uma interface de rolagem e duas barras de rolagem.
Se quisermos adicionar barras de rolagem em um widget, é mais simples usar
QScrollArea do que instanciar todos os QSCrollBar e implementar as
funcionalidades de rolagem.
A maneira correta de se usar QScrollArea é através da chamda a setWidget() com o
widget no qual adicionaremos barras de rolagem. QScrollArea automaticamente
ajusta o widget para que este se torne um filho da janela principal (acessível
através de QScrollArea::viewport()), caso este não o seja. Por exemplo, se
quisermos barras de rolagem em volta do widget IconEditor feito no capítulo 5 (
mostrado na Figura 6.11), podemos escrever o seguinte:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
IconEditor *iconEditor = new IconEditor;
iconEditor->setIconImage(QImage(":/images/mouse.png"));
QScrollArea scrollArea;
scrollArea.setWidget(iconEditor);
scrollArea.viewport()->setBackgroundRole(QPalette::Dark);
scrollArea.viewport()->setAutoFillBackground(true);
scrollArea.setWindowTitle(QObject::tr("Icon Editor"));
scrollArea.show();
return app.exec();
}
Figura 6.11. Redimensionando um QScrollArea
O QScrollArea ( mostrado esquematicamente na Figura 6.12) mostra o widget em
seu tamanho atual ou usa a sugestão de tamanho caso o widget não tenha sido
redimensionado ainda. Através da chamada a setWidgetResizable(true), podemos
dizer a QScrollArea para automaticamente redimensionar o widget para tomar
vantagem de qualquer espaço extra além do seu tamanho sugerido.
Figura 6.12. Widgets que constituem QScrollArea
Por padrão, as barras de rolagem são exibidas somente quando a janela de
visualização é menor do que o widget filho. Podemos forçar as barras de rolagem a
sempre serem mostradas, através do ajuste a alguns controles de rolagem:
scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
scrollArea.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
QScrollArea herda muito de suas funcionalidades de QAbstractScrollArea. Classes
como QTextEdit e QAbstractItemView ( a base das classes de visualização do Qt)
derivam de QAbstractScrollArea, assim não precisamos envolvê-las em um
QScrollArea para adquirir barras de rolagem.
Janelas e Barras de Ferramentas Anexáveis
Janelas anexadas são janelas que podem ser inseridas dentro de um QMainWindow
ou soltas como janelas independentes. QMainWindow fornece quatro áreas para
janelas anexadas: uma acima, uma á esquerda, uma abaixo, e uma à direita do
widget central. Aplicações como Microsoft Visual Studio e Qt Linguist fazem uso de
janelas anexadas para criar uma interface de usuário mais flexível. Em Qt, janelas
anexadas são instâncias de QDockWidget. A Figure 6.13 mostra uma aplicação Qt
com barras de ferramentas e uma janela anexada.
Figura 6.13. Uma QMainWindow com uma janela anexada
Cada janela anexada tem sua própria barra de título, mesmo quando está anexada.
Usuários podem mover janelas anexadas de um ponto de anexação para outro,
arrastando a barra de título. Podem inclusive despregar uma janela anexada de sua
área, e deixar a janela flutuar como uma janela independente, arrastando a janela
para fora de qualquer área de anexação. Janelas livres estão sempre “on top” sobre
a janela principal. Usuários podem fechar uma QDockWidget clicando no botão
fechar na barra de título da janela. Qualquer combinação dessas funcionalidades
pode ser desabilitada através de uma chamada a QDockWidget::setFeatures().
Em versões anteriores do Qt, barras de ferramentas eram tratadas como janelas
anexadas compartilhavam as mesmasáreas de anexação. A partir de Qt 4, barras
de ferramentas passaram a ocupar suas próprias áreas em volta do widget
central(como mostra a Figura 6.14) e não podem ser desanexadas. Se é necessária
uma barra flutuantes, podemos simplesmente a colocar dentro de um
QDockWidget.
Figura 6.14. Áreas da barra de ferramentas e anexação de QMainWindow
As curvas indicadas com linhas pontilhadas podem pertencer às qualquer uma das
duas áreas de anexação contíguas. Por exemplo, poderíamos fazer a curva no canto
esquerdo superior pertencer à área de anexação esquerda, chamando
QMainWindow::setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea).
O código que segue mostra como envolver um widget existente (neste caso, um
QTreeWidget) em um QDockWidget e inseri-lo na região de anexação direita:
QDockWidget *shapesDockWidget = new QDockWidget(tr("Shapes"));
shapesDockWidget->setObjectName("shapesDockWidget");
shapesDockWidget->setWidget(treeWidget);
shapesDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea
| Qt::RightDockWidgetArea);
addDockWidget(Qt::RightDockWidgetArea, shapesDockWidget);
A chamada a setAllowedAreas() especifica restrições nas quais área de anexação
podem aceitar a janela anexada. Aqui, apenas permitimos o usuário a arrastar a
janela de anexação dentro das áreas de anexação esquerda e direita, onde existe
espaço vertical suficiente para que seja exibido corretamente. Se nenhuma área
permitida for setada explicitamente, o usuário pode arrastar a janela para qualquer
uma das quatro áreas.
Todo QObject pode receber um “nome de objeto”. Este nome pode ser útil na hora
do debug e é usado por algumas ferramentas de teste. Normalmente não nos
importamos em dar nomes de objeto aos widgets, mas quando criamos janelas e
barras anexadas, devemos nomear os objetos caso queiramos usar
QMainWindow::saveState() e QMainWindow::restoreState() para salvar e restaurar
os aspectos geométricos e estados da janela anexada e da barra de ferramentas
anexada.
Aqui está o código de criação de barra de ferramentas contendo um QComboBox,
um QSpinBox, e alguns QToolButton’s de um construtor da subclasse de
QMainWindow:
QToolBar *fontToolBar = new QToolBar(tr("Font"));
fontToolBar->setObjectName("fontToolBar");
fontToolBar->addWidget(familyComboBox);
fontToolBar->addWidget(sizeSpinBox);
fontToolBar->addAction(boldAction);
fontToolBar->addAction(italicAction);
fontToolBar->addAction(underlineAction);
fontToolBar->setAllowedAreas(Qt::TopToolBarArea
| Qt::BottomToolBarArea);
addToolBar(fontToolBar);
Se quisermos salvar a posição de todas as janelas e barras de ferramentas
anexáveis, a fim de ser possível restaurá-las na próxima vez que a aplicação for
executada, podemos usitlizar um código que é similar ao código usado para salvar
o estado de um QSplitter, usando as funções saveState() e restoreState() de
QMainWindow:
void MainWindow::writeSettings()
{
QSettings settings("Software Inc.", "Icon Editor");
settings.beginGroup("mainWindow");
settings.setValue("geometry", saveGeometry());
settings.setValue("state", saveState());
settings.endGroup();
}
void MainWindow::readSettings()
{
QSettings settings("Software Inc.", "Icon Editor");
settings.beginGroup("mainWindow");
restoreGeometry(settings.value("geometry").toByteArray());
restoreState(settings.value("state").toByteArray());
settings.endGroup();
}
Finalmente, QMainWindow fornece um menu de contexto que lista todas as janelas
e baras de ferramentas anexáveis. Este menu é mostrado na Fiura 6.15. O usuário
pode fechar e restaurar janelas anexáveis e esconder e restaurar barras de
ferramentas através do menu.
Figura 6.15. O menu de contexto de QMainWindow
Interface de Documento Múltiplo
Aplicações que fornecem documentos múltiplos dentro da área central da janela são
as chamadas aplicações de interface de documentos, ou aplicações MDI. Em Qt,
uma aplicação MDI é criada usando a classe QMidArea como o widget central e
fazendo cada janela de documento uma sub-janela QMdiArea.
È convencional para aplicações MDI fornecer um menu Window que inclua alguns
comandos para manusear ambas janelas e a ista de janelas. A janela ativa é
identificada com uma marca. O usuário pode tornar qualquer janela ativa, clicando
em sua entrada no menu WIndow.
Nesta seção, vamos desenvolver a aplicação MDI Editor mostrada na Figura 6.16
para demonstrar como criar uma aplicação MDI e como implementar seu menu
Window. Todos os menus da aplicação são mostrados na Figura 6.17.
Figura 6.16. A Aplicação MDI Editor
Figura 6.17. Os menus da aplicação MDI Editor
A aplicação consiste de duas classes: MainWindow e Editor. O código é
incrementado com os exemplos do livro, e já que a maioria do que veremos é
similar ou a mesma da aplicação Spreadsheet, vista na Parte 1, apresentaremos
apenas o código MDI-relevante.
Iniciemos com a classe MainWindow.
MainWindow::MainWindow()
{
mdiArea = new QMdiArea;
setCentralWidget(mdiArea);
connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)),
this, SLOT(updateActions()));
createActions();
createMenus();
createToolBars();
createStatusBar();
setWindowIcon(QPixmap(":/images/icon.png"));
setWindowTitle(tr("MDI Editor"));
QTimer::singleShot(0, this, SLOT(loadFiles()));
}
No construtor de MainWindow, criamos um widget QMidArea e o tornamos o widget
central. Conectamos o sinal subWindowActivated() de QMidArea para o slot em
que usaremos para manter o menu da janela atualizado, e no qual nos
asseguramos que as ações estão habilitadas ou desabilitadas dependendo do
estado da aplicação.
No fim do construtor, ajustamos um timer com intervalo de 0 milissegundos para
chamar a função loadFiles(). Esses temporizadores disparam assim que o ciclo
de eventos fica ocioso. Em prática, isto significa que o construtor encerrará, e
depois que a janela principal for mostrada, loadFiles() será chamado. Se não
fizermos isto existirem muitos arquivos grandes a serem carregados, o construtor
não encerrará enquanto todos os arquivos não forem carregados, e enquanto isto,
o usuário não verá nada na tela e pode pensar que a aplicação falhou ao iniciar.
void MainWindow::loadFiles()
{
QStringList args = QApplication::arguments();
args.removeFirst();
if (!args.isEmpty()) {
foreach (QString arg, args)
openFile(arg);
mdiArea->cascadeSubWindows(); } else {
newFile();
}
mdiArea->activateNextSubWindow();
}
Caso o usuário tenha iniciado a aplicação com um ou mais nomes de arquivos na
linha de comando, esta função tenta carregar cada arquivo e no final cascateia as
sub-janelas de forma que o usuário pode vê-las facilmente. Opções específicas de
linhas de comando do Qt, como –style e –font, são automaticamente removidas
da lista de argumentos pelo construtor QAplication. Assim, se escrevermos
mdieditor -style motif readme.txt
na linha de comando, QAplication::arguments() retorna um QStringList
contendo dois itens (“mdieditor” e “readme.txt”), e a aplicação MDI Editor inicia
com o documento readme.txt.
Se nenhum arquivo for especificado na linha de comando, uma nova sub-janela de
editor vazia é criada para que o usuário possa começar a digitar. A chamada para
activateNextSubWindow() significa que uma janela de edição recebe foco, e
assegura que a função updateActions() é chamada para atualizar o menu Window
e habilitar e desabilitar ações, de acordo com o estado da aplicação.
void MainWindow::newFile()
{
Editor *editor = new Editor;
editor->newFile();
addEditor(editor);
}
O slot newFile() corresponde à opção File|New do menu. Ela cria um widget
Editor e passa ele para a função privada addEditor().
void MainWindow::open()
{
Editor *editor = Editor::open(this);
if (editor)
addEditor(editor);
}
A função open() corresponde à File|Open. Ela faz uma chamada para a função
estática Editor::open(), que abre uma janela para selecionar arquivo para abrir.
Se o usuário escolhe um arquivo, um novo Editor é criado, o texto do arquivo é
lido, e caso a leitura seja feita com sucesso, o ponteiro para o Editor é retornado.
Caso o usuário cancele a janela de abertura de arquivo, ou se a leitura falhar, um
ponteiro null é retornado e o usuário é notificado do erro. Faz mais sentido
implementar as operações sob arquivos na classe Editor do que na classe
MainWindow, já que cada Editor precisa manter seu próprio estado independente.
void MainWindow::addEditor(Editor *editor)
{
connect(editor, SIGNAL(copyAvailable(bool)),
cutAction, SLOT(setEnabled(bool)));
connect(editor, SIGNAL(copyAvailable(bool)),
copyAction, SLOT(setEnabled(bool)));
QMdiSubWindow *subWindow = mdiArea->addSubWindow(editor);
windowMenu->addAction(editor->windowMenuAction());
windowActionGroup->addAction(editor->windowMenuAction());
subWindow->show();
}
A função privada addEditor() é chamada em newFile() e open() para completar
a inicialização de um novo widget Editor. Ela começa estabelecendo duas conexões
sinal-slot. Essas conexões garantem que Edit|Cut e Edit|Copy sejam habilitadas ou
desabilitadas, dependendo se há texto selecionado.
Como estamos usando MDI, é possível que mútiplos widgets Editor estejam em
uso. Isto é algo para se tomar cuidado, pois estamos interessados apenas em
responder ao sinal copyAvailable(bool) da janela Editor ativa, e não das demais.
Mas estes sinais podem apenas ser emitidos pela janela ativa, então isto deixa de
ser um problema na prática.
A função QMdiArea::addSubWindow() cria um novo QMdiSubWindow, insere o
widget passado como parâmetro dentro da sub-janela, e retorna a sub-janela.
Depois, criamos um QAction que representa a janela para o menu Window. A ação
é fornecida pela classe Editor, que será vista em breve. Também adicionamos a
ação para um objeto QActionGroup. Este garante que apenas um item Window do
menu é checado por vez. Finalmente, chamamos show() na nova QMdiSubWindow
para a tornar visível.
void MainWindow::save()
{
if (activeEditor())
activeEditor()->save();
}
O slot save() faz uma chamada a Editor::save() no editor ativo, caso haja um.
Novamente, o código que realiza o verdadeiro trabalho é localizado na classe
Editor.
Editor *MainWindow::activeEditor()
{
QMdiSubWindow *subWindow = mdiArea->activeSubWindow();
if (subWindow)
return qobject_cast<Editor *>(subWindow->widget());
return 0;
}
A função privada activeEditor() retorna o widget abrigado dentro da subjanela
ativa como um ponteiro do tipo Editor, ou um ponteiro nulo caso não haja uma
subjanela ativa.
void MainWindow::cut()
{
if (activeEditor())
activeEditor()->cut();
}
O slot cut() chama Editor::cut() no editor ativo. Não mostramos os slots
copy() e paste() pois estes seguem o mesmo padrão.
void MainWindow::updateActions()
{
bool hasEditor = (activeEditor() != 0);
bool hasSelection = activeEditor()
&& activeEditor()>textCursor().hasSelection();
saveAction->setEnabled(hasEditor);
saveAsAction->setEnabled(hasEditor);
cutAction->setEnabled(hasSelection);
copyAction->setEnabled(hasSelection);
pasteAction->setEnabled(hasEditor);
closeAction->setEnabled(hasEditor);
closeAllAction->setEnabled(hasEditor);
tileAction->setEnabled(hasEditor);
cascadeAction->setEnabled(hasEditor);
nextAction->setEnabled(hasEditor);
previousAction->setEnabled(hasEditor);
separatorAction->setVisible(hasEditor);
if (activeEditor())
activeEditor()->windowMenuAction()->setChecked(true);
}
O sinal subWindowActivated() é emitido toda vez que uma nova subjanela se
torna ativa, e quando a última subjanela é fechada (no caso, seu parâmetro é um
ponteiro para null). O sinal é conectado ao slot updateActions().
A maioria das opções dos menus faz sentido apenas se há uma janela ativa, sendo
assim as desabilitamos caso não haja uma janela. No fim, chamamos
setChecked() na QAction que representa a janela ativa. Graças a QActionGroup,
não precisamos desmarcar explicitamente a janela ativa anterior.
void MainWindow::createMenus()
{
...
windowMenu = menuBar()->addMenu(tr("&Window"));
windowMenu->addAction(closeAction);
windowMenu->addAction(closeAllAction);
windowMenu->addSeparator();
windowMenu->addAction(tileAction);
windowMenu->addAction(cascadeAction);
windowMenu->addSeparator();
windowMenu->addAction(nextAction);
windowMenu->addAction(previousAction);
windowMenu->addAction(separatorAction);
...
}
A função privada createMenus() preenche o menu Window com ações. Todas as
ações são típicas de menus e são facilmente implementadas usando os slots de
QMdiArea,closeActiveSubWindow(), closeAllSubWindows(), tileSubWindows(),
e cascadeSubWIndows(). Toda vez que o usuário abre uma nova janela, ela é
adicionada à lista de ações do menu Window. (Isto é feito na função addEditor()
que vimos na página 160). Quando o usuário fecha uma janela de edição, sua ação
no meno Window é deletada (já que a ação respectiva é propriedade da janela de
edição), assim a ação é automaticamente removida do menu Window.
void MainWindow::closeEvent(QCloseEvent *event)
{
mdiArea->closeAllSubWindows();
if (!mdiArea->subWindowList().isEmpty()) {
event->ignore();
} else {
event->accept();
}
}
A função closeEvent() é reimplementada para fechar todas as subjanelas, fazendo
com que cada subjanela receba um evento de fecho. Se uma subjanela “ignorar”
seu evento de fecho ( caso o usuário cancele uma caixa de mensagens “ unsaved
changes”), ignoramos o evento de fecho para MainWindow; de outra forma,
aceitamos este, fazendo com que o Qt encerre a aplicação por completo. Se não
tivéssemos reimplementado closeEvent() em MainWindow, não seria dada ao
usuário oportunidade de salvar mudanças não salvas.
Terminamos agora nossa revisão de MainWindow, assim podemos prosseguir na
implementação do Editor. A classe Editor representa uma subjanela. É derivada de
QTextEdit, que fornece a funcionalidade de edição de texto. Em uma aplicação do
mundo real, se um componente de edição de código é exigido, podemos considerar
usar o Scintilla, disponível para Qt como QScintilla em
HTTP://www.riverbankcomputing.uk/qscintilla/.
Assim como qualquer widget Qt pode ser usado como uma janela independente,
qualquer widget Qt pode ser inserido dentro de um QMdiSubWindow e usado como
uma subjanela em uma área MDI.
Aqui está a definição da classe:
class Editor : public QTextEdit
{
Q_OBJECT
public:
Editor(QWidget *parent = 0);
void newFile();
bool save();
bool saveAs();
QSize sizeHint() const;
QAction *windowMenuAction() const { return action; }
static Editor *open(QWidget *parent = 0);
static Editor *openFile(const QString &fileName,
QWidget *parent = 0);
protected:
void closeEvent(QCloseEvent *event);
private slots:
void documentWasModified();
private:
bool okToContinue();
bool saveFile(const QString &fileName);
void setCurrentFile(const QString &fileName);
bool readFile(const QString &fileName);
bool writeFile(const QString &fileName);
QString strippedName(const QString &fullFileName);
QString curFile;
bool isUntitled;
QAction *action;
};
Quatro das funções privadas que estavam na classe MainWindow da aplicação
Spreadsheet estão também presentes na classe Editor: okToContinue(),
saveFile(), setCurrentFile(), e strippedName().
Editor::Editor(QWidget *parent)
: QTextEdit(parent)
{
action = new QAction(this);
action->setCheckable(true);
connect(action, SIGNAL(triggered()), this, SLOT(show()));
connect(action, SIGNAL(triggered()), this, SLOT(setFocus()));
isUntitled = true;
connect(document(), SIGNAL(contentsChanged()),
this, SLOT(documentWasModified()));
setWindowIcon(QPixmap(":/images/document.png"));
setWindowTitle("[*]");
setAttribute(Qt::WA_DeleteOnClose);
}
Primeiro, criamos uma QAction que representa o editor no menu Window da
aplicação, e conectamos essa ação aos slots show() e setFocus().
Já que permitimos usuários a criar qualquer número de janelas de edição, devemos
fazer algumas disposições para nomeá-las para que elas possam ser distinguidas
antes de serem salvas pela primeira vez. Uma forma comum de controlar isto é
alocando nomes que incluam números (ex: document1.txt). Usamos a variável
isUntitled para distinguir entre nomes fornecidos pelo usuário e nomes que o
programa gera automaticamente.
Conectamos o sinal contentsChanged do documento de texto para o slot privado
documentWasModified(). Este slot faz uma chamada para
setWindowModified(true).
Finalmente, setamos o atributo Qt::WA_DeleteOnClose para evitar vazamentos de
memória quando o usuário fechar uma janela Editor.
void Editor::newFile()
{
static int documentNumber = 1;
curFile = tr("document%1.txt").arg(documentNumber);
setWindowTitle(curFile + "[*]");
action->setText(curFile);
isUntitled = true;
++documentNumber;
}
A função newFile() gera um nome como document1.txt para o novo documento.
O código pertence a newFile(), e não ao construtor, pois não queremos consumir
números quando chamamos open() para abrir um documento existente em uma
nova janela Editor criada. Já que documentNumber é declarado estático, ele é
dividido através de todas as instancias de Editor.
O marcador “[*]” no título da janela é um marcador de local para quando
quisermos que o asterisco apareça quando o arquivo possuir mudanças não-salvas
em plataformas que não sejam Mac OS X. Para Mac, documentos não salvos
possuem um ponto no botão de fechar da janela. Cobrimos este marcador de local
no Capítulo 3 (p.61).
Além de criar novos arquivos, usuários freqüentemente querem abrir arquivos
existentes, carregados ou de uma caixa de diálogo de arquivo, ou uma lista de
arquivos recentemente abertos. Duas funções estáticas São fornecidas para
suportar esses usos: open() para escolher um nome de arquivo do sistema de
arquivos, e openFile() para criar um Editor e ler o conteúdo de um arquivo
específico.
Editor *Editor::open(QWidget *parent)
{
QString fileName =
QFileDialog::getOpenFileName(parent, tr("Open"), ".");
if (fileName.isEmpty())
return 0;
return openFile(fileName, parent);
}
A função estática open() faz surgir uma caixa de diálogo de arquivo, pela qual o
usuário pode escolher um arquivo. Se um arquivo é escolhido, openFile() é
chamado para criar um Editor e lê o conteúdo do arquivo.
Editor *Editor::openFile(const QString &fileName, QWidget *parent)
{
Editor *editor = new Editor(parent);
if (editor->readFile(fileName)) {
editor->setCurrentFile(fileName);
return editor;
} else {
delete editor;
return 0; }
}
A função estática começa criando um novo widget Editor, e depois tenta ler no
arquivo específico. Caso a leitura seja feita com sucesso, o Editor é retornado;
caso contrário, o usuário é informado do problema (em readFile()), o editor é
deletado, e um ponteiro para null é retornado.
bool Editor::save()
{
if (isUntitled) {
return saveAs();
} else {
return saveFile(curFile);
}
}
A função save() usa a variável isUntitled para determinar se ela deve chamar
saveFile() ou saveAs().
void Editor::closeEvent(QCloseEvent *event)
{
if (okToContinue()) {
event->accept();
} else {
event->ignore();
}
}
A função closeEvent() é reimplementada para permitir o usuário a salvar
mudanças não-salvas. A lógica é codificada na função okToContinue(), que faz
gera um pop up de uma caixa de mensagem que pergunta, “Do you want to save
your changes?” se okToContinue() retornar true, aceitamos o evento de fecho; de
outra forma, “ignoramos” a mensagem e deixamos a janela inalterada.
void Editor::setCurrentFile(const QString &fileName)
{
curFile = fileName;
isUntitled = false;
action->setText(strippedName(curFile));
document()->setModified(false);
setWindowTitle(strippedName(curFile) + "[*]");
setWindowModified(false);
}
A função setCurrentFile() é chamada de openFile() e saveFile() para
atualizar as variáveis curFile e isUntitled, para habilitar o título da janela e
texto de ação, e para habilitar o flag “modified” do documento para false. A
Qualquer momento em que o usuário modifica o texto no editor, o subjacente
QTextDocument emite o sinal contentsChanged() e habilita sua flag interna
“modified” para true.
QSize Editor::sizeHint() const
{
return QSize(72 * fontMetrics().width('x'),
25 * fontMetrics().lineSpacing());
}
Finalmente, a função sizeHint() retorna um tamanho baseado no tamanho da
letra “x” e na altura da letra de uma linha de texto. QMdiArea usa a sugestão de
tamanho para dar um tamanho inicial à janela.
MDI é uma forma de controlas múltiplos documentos simultaneamente. Em Max OS
X, a abordagem preferida é usar janelas top-level mútliplas. Cobrimos esta
abordagem na seção “Documentos Múltiplos” do Capítulo 3.

Contenu connexe

En vedette

Aula 04 planejamento de mkt digital
Aula 04   planejamento de mkt digitalAula 04   planejamento de mkt digital
Aula 04 planejamento de mkt digitalThais Godinho
 
Pós Graduação em Marketing Digital :: Disciplina de redes sociais (2)
Pós Graduação em Marketing Digital :: Disciplina de redes sociais (2)Pós Graduação em Marketing Digital :: Disciplina de redes sociais (2)
Pós Graduação em Marketing Digital :: Disciplina de redes sociais (2)Raquel Camargo
 
Contaminación de los Parques
Contaminación de los ParquesContaminación de los Parques
Contaminación de los ParquesIdalyss
 
Aula 05 planejamento de mkt digital
Aula 05   planejamento de mkt digitalAula 05   planejamento de mkt digital
Aula 05 planejamento de mkt digitalThais Godinho
 
Presentació de les novetats i llicències Vmware esxi 5
Presentació de les novetats i llicències Vmware esxi 5Presentació de les novetats i llicències Vmware esxi 5
Presentació de les novetats i llicències Vmware esxi 5Roger Casadejús Pérez
 

En vedette (6)

Aula 04 planejamento de mkt digital
Aula 04   planejamento de mkt digitalAula 04   planejamento de mkt digital
Aula 04 planejamento de mkt digital
 
Pós Graduação em Marketing Digital :: Disciplina de redes sociais (2)
Pós Graduação em Marketing Digital :: Disciplina de redes sociais (2)Pós Graduação em Marketing Digital :: Disciplina de redes sociais (2)
Pós Graduação em Marketing Digital :: Disciplina de redes sociais (2)
 
Contaminación de los Parques
Contaminación de los ParquesContaminación de los Parques
Contaminación de los Parques
 
Aula 05 planejamento de mkt digital
Aula 05   planejamento de mkt digitalAula 05   planejamento de mkt digital
Aula 05 planejamento de mkt digital
 
Seo: Com triomfar en els buscadors
Seo: Com triomfar en els buscadorsSeo: Com triomfar en els buscadors
Seo: Com triomfar en els buscadors
 
Presentació de les novetats i llicències Vmware esxi 5
Presentació de les novetats i llicències Vmware esxi 5Presentació de les novetats i llicències Vmware esxi 5
Presentació de les novetats i llicències Vmware esxi 5
 

Similaire à Qt Layouts

Java interface gráfica layouts
Java   interface gráfica layoutsJava   interface gráfica layouts
Java interface gráfica layoutsArmando Daniel
 
Dojo de programação - Dia de Java - UFSCar
Dojo de programação - Dia de Java - UFSCarDojo de programação - Dia de Java - UFSCar
Dojo de programação - Dia de Java - UFSCarLuiz Ribeiro
 
Fazendo Injeção de dependência com Unity 1.2
Fazendo Injeção de dependência com Unity 1.2Fazendo Injeção de dependência com Unity 1.2
Fazendo Injeção de dependência com Unity 1.2Giovanni Bassi
 
Construindo Sistemas Com Django
Construindo Sistemas Com DjangoConstruindo Sistemas Com Django
Construindo Sistemas Com DjangoMarinho Brandão
 
Mantendo a Sanidade com o Glade
Mantendo a Sanidade com o GladeMantendo a Sanidade com o Glade
Mantendo a Sanidade com o GladeMarcelo Lira
 
Aula03 android layouts_views
Aula03 android layouts_viewsAula03 android layouts_views
Aula03 android layouts_viewsRoberson Alves
 
Curso de Ruby on Rails - Aula 02
Curso de Ruby on Rails - Aula 02Curso de Ruby on Rails - Aula 02
Curso de Ruby on Rails - Aula 02Maurício Linhares
 
Apostila_JavaScript_pela_axademia_ardkgfv
Apostila_JavaScript_pela_axademia_ardkgfvApostila_JavaScript_pela_axademia_ardkgfv
Apostila_JavaScript_pela_axademia_ardkgfverickrodrigo23
 
Data Binding Para Vinculo de Dados na UI Android
Data Binding Para Vinculo de Dados na UI AndroidData Binding Para Vinculo de Dados na UI Android
Data Binding Para Vinculo de Dados na UI AndroidVinícius Thiengo
 
QGIS 2.4: Recorte de Raster via Shapefile (Batch Mode)
QGIS 2.4: Recorte de Raster via Shapefile (Batch Mode)QGIS 2.4: Recorte de Raster via Shapefile (Batch Mode)
QGIS 2.4: Recorte de Raster via Shapefile (Batch Mode)Jorge Santos
 
[CLPE] Novidades do Asp.net mvc 2
[CLPE] Novidades do Asp.net mvc 2[CLPE] Novidades do Asp.net mvc 2
[CLPE] Novidades do Asp.net mvc 2Felipe Pimentel
 
Minicurso GIT 2022 - SENAC
Minicurso GIT 2022 - SENACMinicurso GIT 2022 - SENAC
Minicurso GIT 2022 - SENACDanilo Pinotti
 
Aubr 24 autodesk-revit-architecture-para-arquitetura-de-interiores
Aubr 24 autodesk-revit-architecture-para-arquitetura-de-interioresAubr 24 autodesk-revit-architecture-para-arquitetura-de-interiores
Aubr 24 autodesk-revit-architecture-para-arquitetura-de-interioresVivaldo Chagas
 
Mini Curso PHP Twig - PHP Conference 2017
Mini Curso PHP Twig - PHP Conference 2017 Mini Curso PHP Twig - PHP Conference 2017
Mini Curso PHP Twig - PHP Conference 2017 Luis Gustavo Almeida
 

Similaire à Qt Layouts (20)

Cap4
Cap4Cap4
Cap4
 
CURSO JAVA 02
CURSO JAVA 02CURSO JAVA 02
CURSO JAVA 02
 
Gerenciadores de Layout
Gerenciadores de LayoutGerenciadores de Layout
Gerenciadores de Layout
 
Java interface gráfica layouts
Java   interface gráfica layoutsJava   interface gráfica layouts
Java interface gráfica layouts
 
Dojo de programação - Dia de Java - UFSCar
Dojo de programação - Dia de Java - UFSCarDojo de programação - Dia de Java - UFSCar
Dojo de programação - Dia de Java - UFSCar
 
Fazendo Injeção de dependência com Unity 1.2
Fazendo Injeção de dependência com Unity 1.2Fazendo Injeção de dependência com Unity 1.2
Fazendo Injeção de dependência com Unity 1.2
 
Construindo Sistemas Com Django
Construindo Sistemas Com DjangoConstruindo Sistemas Com Django
Construindo Sistemas Com Django
 
Apostila Android
Apostila AndroidApostila Android
Apostila Android
 
Mantendo a Sanidade com o Glade
Mantendo a Sanidade com o GladeMantendo a Sanidade com o Glade
Mantendo a Sanidade com o Glade
 
Aula03 android layouts_views
Aula03 android layouts_viewsAula03 android layouts_views
Aula03 android layouts_views
 
Tw Course Ajax 2007 Ap05
Tw Course Ajax 2007 Ap05Tw Course Ajax 2007 Ap05
Tw Course Ajax 2007 Ap05
 
Curso de Ruby on Rails - Aula 02
Curso de Ruby on Rails - Aula 02Curso de Ruby on Rails - Aula 02
Curso de Ruby on Rails - Aula 02
 
Apostila_JavaScript_pela_axademia_ardkgfv
Apostila_JavaScript_pela_axademia_ardkgfvApostila_JavaScript_pela_axademia_ardkgfv
Apostila_JavaScript_pela_axademia_ardkgfv
 
Data Binding Para Vinculo de Dados na UI Android
Data Binding Para Vinculo de Dados na UI AndroidData Binding Para Vinculo de Dados na UI Android
Data Binding Para Vinculo de Dados na UI Android
 
QGIS 2.4: Recorte de Raster via Shapefile (Batch Mode)
QGIS 2.4: Recorte de Raster via Shapefile (Batch Mode)QGIS 2.4: Recorte de Raster via Shapefile (Batch Mode)
QGIS 2.4: Recorte de Raster via Shapefile (Batch Mode)
 
[CLPE] Novidades do Asp.net mvc 2
[CLPE] Novidades do Asp.net mvc 2[CLPE] Novidades do Asp.net mvc 2
[CLPE] Novidades do Asp.net mvc 2
 
Minicurso GIT 2022 - SENAC
Minicurso GIT 2022 - SENACMinicurso GIT 2022 - SENAC
Minicurso GIT 2022 - SENAC
 
Aubr 24 autodesk-revit-architecture-para-arquitetura-de-interiores
Aubr 24 autodesk-revit-architecture-para-arquitetura-de-interioresAubr 24 autodesk-revit-architecture-para-arquitetura-de-interiores
Aubr 24 autodesk-revit-architecture-para-arquitetura-de-interiores
 
Mini Curso PHP Twig - PHP Conference 2017
Mini Curso PHP Twig - PHP Conference 2017 Mini Curso PHP Twig - PHP Conference 2017
Mini Curso PHP Twig - PHP Conference 2017
 
Código Limpo
Código LimpoCódigo Limpo
Código Limpo
 

Plus de Cedemir Pereira

Plus de Cedemir Pereira (8)

cdr-intro.pdf
cdr-intro.pdfcdr-intro.pdf
cdr-intro.pdf
 
Livro Cidades Inteligentes(2)-31-66.pdf
Livro Cidades Inteligentes(2)-31-66.pdfLivro Cidades Inteligentes(2)-31-66.pdf
Livro Cidades Inteligentes(2)-31-66.pdf
 
slidesWtisc(1).pptx
slidesWtisc(1).pptxslidesWtisc(1).pptx
slidesWtisc(1).pptx
 
Cap11
Cap11Cap11
Cap11
 
Cap9
Cap9Cap9
Cap9
 
Cap7
Cap7Cap7
Cap7
 
Cap12
Cap12Cap12
Cap12
 
c-gui-programming-with-qt-4-2ndedition -Cap1e2
c-gui-programming-with-qt-4-2ndedition -Cap1e2c-gui-programming-with-qt-4-2ndedition -Cap1e2
c-gui-programming-with-qt-4-2ndedition -Cap1e2
 

Qt Layouts

  • 1. Parte II: Qt Intermediário 6. Gerenciamento do Layout Planejando os Widgets em um Form Layouts Empilhados Splitters Áreas de Rolagem Encurtando Janelas e Barra de Ferramentas Interface Múltipla de Documento Todo widget que é colocado em um form deve receber um tamanho e posição apropriados. O Qt fornece diversas classes que ajustam widgets em um Form: QHBoxLayout, QVBoxLayout, QGridLayout, e QSTackLayout. Essas classes são tão convenientes e fáceis de se usar que quase todo desenvolvedor Qt as usa, seja diretamente no código fonte , ou através do Qt Designer. Outra razão para se usar classes de layout do Qt é que elas garantem que as formas se adaptem automaticamente para diferentes formas, linguagens e plataformas. Caso o usuário mude as configurações de fonte do sistema, as Forms da aplicação irão se adaptar automaticamente, redimensionando caso seja necessário. Se você traduzir a interface da aplicação para outro idioma, as clases de layout levam em consideração os conteúdos traduzidos das widgets para evitar entroncamento do texto. Outras classes que realizam manutenção de layout incluem QSplitter, QScrollArea, QMainWindow, e QMdiArea. Todas essas classes fornecem um layout flexível que o usuário pode manipular. Por exemplo, QSplitter fornece uma barra separador a que o usuário pode arrastar para redimensionar widgets, e QMdiArea oferece suporte à MDI ( Multiple Document Interface), uma maneira de mostrar diversos documentos
  • 2. simultaneamente dentro da janela principal de uma aplicação. Devido ao fato de serem recentemente usadas como alternativas para as classes de layout adequadas, vamos abordar essa técnica neste capítulo. Planejando Widgets em um Form Existem três formas básicas de gerenciar o layout em widgets menores num Form: posicionamento absoluto, layout manual, e gerenciadores de layout. Vamos analisar cada abordagem de uma vez, usando a caixa de diálogo de Busca por Arquivos, como mostra a Figura 6.1 abaixo. Figura 6.1. A Caixa de diálogo Find File Posicionamento absoluto é a forma mais crua de projetar widgets. E alcançável através da atribuição de tamanhos e posições para as widgets-filhas do Form, além de um tamanho fixo para o Form. O construtor de FindFileDialog usando posicionamento absoluto fica assim: FindFileDialog::FindFileDialog(QWidget *parent) QDialog(parent) { ..
  • 3. namedLabel->setGeometry(9, 9, 50, 25); namedLineEdit->setGeometry(65, 9, 200, 25); lookInLabel->setGeometry(9, 40, 50, 25); lookInLineEdit->setGeometry(65, 40, 200, 25); subfoldersCheckBox->setGeometry(9, 71, 256, 23); tableWidget->setGeometry(9, 100, 256, 100); messageLabel->setGeometry(9, 206, 256, 25); findButton->setGeometry(271, 9, 85, 32); stopButton->setGeometry(271, 47, 85, 32); closeButton->setGeometry(271, 84, 85, 32); helpButton->setGeometry(271, 199, 85, 32); setWindowTitle(tr("Find Files or Folders")); setFixedSize(365, 240); } Posicionamento Absoluto possui diversas desvantagens:  O usuário nunca poderá redimensionar a janela;  Partes do texto podem ser truncadas se o usuário escolher um tipo de fonte muito largo ou se a aplicação for traduzida para outro idioma.  Os Widgets podem ter tamanhos inapropriados para alguns estilos.  As posições e tamanhos devem ser calculados manualmente. Isso é entediante e sujeito a erros, além, de tornar manutenção um desafio. Um alternativa para Posicionamento Absoluto é layout manual. Com Layout Manual, ainda são dadas posições absolutas para os widgets, mas seus tamanhos são definidos proporcionais ao tamanho da janela, sendo desnecessária codificação bruta. Código:
  • 4. FindFileDialog::FindFileDialog(QWidget *parent) : QDialog(parent) { ... setMinimumSize(265, 190); resize(365, 240); } void FindFileDialog::resizeEvent(QResizeEvent * /* event */) { int extraWidth = width() - minimumWidth(); int extraHeight = height() - minimumHeight(); namedLabel->setGeometry(9, 9, 50, 25); namedLineEdit->setGeometry(65, 9, 100 + extraWidth, 25); lookInLabel->setGeometry(9, 40, 50, 25); lookInLineEdit->setGeometry(65, 40, 100 + extraWidth, 25); subfoldersCheckBox->setGeometry(9, 71, 156 + extraWidth, 23); tableWidget->setGeometry(9, 100, 156 + extraWidth, 50 + extraHeight); messageLabel->setGeometry(9, 156 + extraHeight, 156 + extraWidth, 25); findButton->setGeometry(171 + extraWidth, 9, 85, 32); stopButton->setGeometry(171 + extraWidth, 47, 85, 32); closeButton->setGeometry(171 + extraWidth, 84, 85, 32); helpButton->setGeometry(171 + extraWidth, 149 + extraHeight, 85, 32); }
  • 5. No construtor FindFileDialog , Ajustamos o tamanho mínimo do form para 265 X 190 e o tamanho inicial para 365 X 240. No controlador resizeEvent(), damos o tamanho extra à widgets conforme o quanto queremos expandi-la. Isso garante que o Form aumente gradualmente quando o usuário a redimensiona. Assim como posicionamento absoluto, layout manual requer bastante codificação de constantes para serem calculadas pelo programador. Codificação escrita dessa forma é canstiva, especialmente com mudanças de aspecto visual. Layout manual também corre risco de sofrer entruncamento do texto. Podemois evitar isto levando em consideração as sugestões de tamanho das widgets-filhas, mas isto tornaria o código ainda mais complicado. A forma mais conveniente de se solucionar problemas de layout de widgets em Qt é o uso dos gerenciadores de layout. Os gerenciadores de layout fornecem padrões sensíveis para cada tipo de widget e levam em conta as sugestões de tamanho de cada widget, o que depende do conteúdo, estilo e tamanho da fonte do widget. Gerenciadores de layout também respeitam tamanhos mínimo e máximo, e automaticamente ajustam o layout em resposta às mudanças de fonte, e de conteúdo, além de redimensionamento de janela. Uma versão redimensionável do Find File dialog é mostrado na Figura 6.2. Figura 6.2. Redimensionando uma janela redimensionável [Janela aumentada] Os três gerenciadores de layout mais importantes são : QHBoxLayout, QVBoxLayout e QGridLayout. Essas classes são derivadas de QLayout, que fornece o framework básico para layouts. Todas as três classes são totalmente suportadas pelo Qt Designer e podem ser usadas também diretamente em código. Aqui está o código de FindFIleDialog usando Gerenciadores de Layout: Código: FindFileDialog::FindFileDialog(QWidget *parent) : QDialog(parent) { ... QGridLayout *leftLayout = new QGridLayout; leftLayout->addWidget(namedLabel, 0, 0); leftLayout->addWidget(namedLineEdit, 0, 1); leftLayout->addWidget(lookInLabel, 1, 0); leftLayout->addWidget(lookInLineEdit, 1, 1); leftLayout->addWidget(subfoldersCheckBox, 2, 0, 1, 2);
  • 6. leftLayout->addWidget(tableWidget, 3, 0, 1, 2); leftLayout->addWidget(messageLabel, 4, 0, 1, 2); QVBoxLayout *rightLayout = new QVBoxLayout; rightLayout->addWidget(findButton); rightLayout->addWidget(stopButton); rightLayout->addWidget(closeButton); rightLayout->addStretch(); rightLayout->addWidget(helpButton); QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->addLayout(leftLayout); mainLayout->addLayout(rightLayout); setLayout(mainLayout); setWindowTitle(tr("Find Files or Folders")); } O layout é controlado por um QHBoxLayout, um QGridLayout, e um QVBoxLayout. O QGridLayout na esquerda e o QVBoxLayout na direita são colocados lado a lado por outro QHBoxLayout. A margem em volta da caixa e o espaço entre as widgets crianças são ajustados com valores default baseados no estilo atual do widget; podem ser trocados usando QLayout::setContentsMargins() e QLayout::setSpacing(). Figura 6.3. O layout da caixa de Pesquisa de Arquivo A mesma caixa poderia ter sido criada visualmente utilizando o Qt Designer através da inclusão dos widgets filhos nas posições aproximadas; selecionando aqueles que devem ser ajustados junto; e clicando em Form|Lay Out Horizontally, Form|Lay Out Vertically, ou Form|Lay Out in a Grid. Usamos esta abordagem no Capítulo 2 para criar as dialogs Go to Cell e Sort da aplicação Spreadsheet. Usar QHBoxLayout e QVBoxLayout é um método direto, mas usar QGridLayout requer um pouco mais de trabalho. QGridLayout trabalha em uma grade bidimensional de células. O QLabel o canto superior esquerdo do layout está na posição (0,0), e o QLineEdit
  • 7. correspondente está na posição (0,1). O QCheckBox abrangem duas colunas; ocupa as células das posições (2,0) e (2,1). O QTreeWidget e o QLabel abaixo dele também expande duas colunas. As chamadas a QGridLayout::addWidget() têm a seguinte sintaxe: layout->addWidget(widget, row, column, rowSpan, columnSpan); Aqui, widget é o widget-filho para inserir dentro do layout, (row, column) é a célula no canto superior esquerdo ocupado pelo widget, rowSpan é o número de linhas ocupadas pelo widget, e columnSpan é o número de colunas ocupadas pelo widget, Caso sejam omitidos, os argumentos rowSpan e columnSpan assumem valor 1. A chamada addStretch() diz ao gerenciador vertical do layout para consumir espaço naquele ponto do layout. Ao adicionar um trecho de um item, mandamos um aviso ao gerenciador para que coloque qualquer espaço em excesso entre os botões Close e Help. No Qt Designer, podemos atingir o mesmo efeito inserindo um espaçador. Espaçadores aparecem no Qt Designer como “springs” azuis. O uso de gerenciadores de layout garante benefícios adicionais àqueles que discutimos até então. Se adicionarmos um widget em um layout ou removermos um widget de um layout, o layout irá se adaptar automaticamente à nova situação. O mesmo se aplica se chamarmos hide() ou show() em um widget filho. Caso o tamanho sugerido do widget filho mude, o layout será refeito automaticamente, levando em conta a nova sugestão de tamanho. Além disso, gerenciadores de layout automaticamente ajustam um valor mínimo para o form como um todo, baseado nos tamanhos mínimos e sugestões de tamanho dos widgets filhos do form. Nos exemplos apresentados até agora, nós simplesmente colocamos widgets em layouts e usamos separadores para consumir qualquer excesso de espaço. Em alguns casos, isto não é suficiente para deixar o layout exatamente do jeito que desejamos. Nessas situações, podemos ajustar o layout mudando as políticas e sugestões de tamanho dos widgets que estão sendo usados. Uma política de tamanho do widget diz ao sistema de layout como deve ser estendido ou encolhido. Qt fornece políticas de padrões de tamanho sensíveis para todos os widgets projetados, porém já que nenhum padrão único pode constar em cada possível layout, ainda é muito comum entre os desenvolvedores a prática de mudar as regras de tamanhos para um ou outro widget em um form. QSizePolicy possui componentes vertical e horizontal. Eis os valores mais importantes:  Fixed significa que o widget nunca poderá ser expandido ou encolhido. Permanecerá sempre do tamanho designado pela sugestão.  Minimum significa que a sugestão de tamanho do widget é o seu tamanho mínimo. O widget nunca poderá encolher para um valor menor do que a sugestão, mas pode aumentar de tamanho.  Maximum significa que a sugestão de tamanho do widget é seu tamanho máximo, podendo-se diminuir o widget para a menor sugestão de tamanho.  Preferred significa que o tamanho sugerido é o tamanho mais indicado, mas é livre para aumentar ou diminuir tamanho.
  • 8.  Expanding significa que o widget pode aumentar ou diminuir, mas que sua tendência é crescer. A Figura 6.4 resume os significados das diferentes políticas de tamanho, usando um QLabel. Figura 6.4 O significado de diferentes políticas de tamanho Na figura, Preferred e Expanding são descritos da mesma forma. Então qual é a diferença? Quando um form que contém widgets do tipo Preferred e Expanding é redimensionado, um espaço extra é dado aos widgets marcados com Expanding, enquanto que os widgets Preferred mantém-se em sua sugestão de tamanho. Existem duas outras políticas de tamanho: MinimumExpanding e Ignored. O primeiro foi necessário em pouquíssimos casos em versões mais antigas de Qt, mas não é mais útil; a estratégia mais indicada é usar Expanding e reimplementar minimumSizeHint() apropriadamente. O último é similar a Expanding, Exceto pelo fato de que ignora a sugestão de tamanho do widget e a sugestão de tamanho mínimo. Adicionalmente aos componentes horizontais e verticais das políticas de tamanho, a classe QSizePolicy armazena um fator horizontal e vertical para alargamentos de fatores. Esses fatores de alargamentos podem ser usados para indicar que diferentes widgets filhos devem crescer em taxas diferentes quando o form expande. Por exemplo, se tivermos um QTreeWidget sobre um QTextEdit e queremos que QTextEdit seja o dobro do tamanho de QTreeWidget, podemos ajustar o fator de alargamento vertical de QTextEdit para 2 e o fator de alargamento vertical de QTreeWidget para 1.
  • 9. Layouts Empilhados A classe QStackedLayout projeta um conjunto de widgets filhos, ou “páginas”, e mostre um de cada vez, escondendo os demais do usuário. QStackedLayout em si é invisível e não fornece nenhuma maneira do usuário mudar a págna. As pequenas flehas e o frame cinza-escuro na Figura 6.5 são fornecidos por Qt Designer para tornar o layout mais fásil de se modelar. Por conveniência, Qt também inclui QStackedWidget, que possui um QWidget com um QStackedLayout pré-produzido. Figura 6.5. QStackedLayout As páginas são numeradas a partir de 0. Para tornar um widget filho específico visível, podemos chamar setCurrentIndex() com um número de página. Para obter o número de página para um widget filho, utilize indexOf(). A caixa de Preferências mostrada na Figura 6.6 é um exemplo de uso de QStackedLayout. A caixa consiste de um QListWidget na esquerda, e um QStackedLayout na direita. Cada item em QListWidget corresponde a uma página diferente no QStackedLayout. Eis o código relevante do construtor da caixa: PreferenceDialog::PreferenceDialog(QWidget *parent) : QDialog(parent) { ... listWidget = new QListWidget; listWidget->addItem(tr("Appearance")); listWidget->addItem(tr("Web Browser")); listWidget->addItem(tr("Mail & News")); listWidget->addItem(tr("Advanced")); stackedLayout = new QStackedLayout; stackedLayout->addWidget(appearancePage);
  • 10. stackedLayout->addWidget(webBrowserPage); stackedLayout->addWidget(mailAndNewsPage); stackedLayout->addWidget(advancedPage); connect(listWidget, SIGNAL(currentRowChanged(int)), stackedLayout, SLOT(setCurrentIndex(int))); ... listWidget->setCurrentRow(0); } Figura 6.6 Duas páginas da caixa Preferências Criamos um QListWidget e o populamos com os nomes de páginas. Depois, criamos um QSTackedLayout e chamamos addWidget() para cada página. Conectamos o sinal de currentRowChanged(int) de cada widget da lista com setCurrentIndex(int) do layout empilhado para implementar a troca de páginas e chamamos setCurrentRow() na lista de widget no final do construtor para começar na página 0. Formas como esta são muito fáceis de se criar usando o Qt Designer: 1. Crie um novo form beaseado em um dos templates “Dialog”, ou no template “Widget”. 2. Adicione um QListWidget e um QStackedWidget no form. 3. Preencha cada página com widgets filhos e layout. (Para criar uma nova página, clique com o botão direito e selecione Insert Page; para trocar páginas, clique na pequena seta “esquerda ou direita” , localizadas no canto superior direito de QStackedWidget.) 4. Modele os widgets lado a lado usando um layout horizontal. 5. Conecte o sinal de currentRowChanged(int) do widget da lista com o slot de setCurrentIndex(int) do widget empilhado.
  • 11. 6. Ajuste o valor de currentRow do widget da lista para 0. Já que implementamos a troca de páginas usando sinais e slots pré-definidos, a caixa irá exibir o comportamento correto quando pré-visualizada no Qt Designer. Para casos onde o número de páginas é pequeno e propenso a permanescer pequeno, uma alternativa mais simples para o uso de QStackedWidget e QListWidget é o uso de um QTabWdget.
  • 12. Separadores Um QSplitter é um widget que contém outros widgets. Os widgets em um separador (splitter) são separados por alças separadoras. Usuários podem mudar os tamanhos dos widgets filhos de um separador , arrastando as alças. Separadores podem ser usados como uma alternativa para gerenciadores de layout, para dar mais controle ao usuário. Os widgets filhos de um QSplitter são automaticamente posicionados lado a lado (ou um abaixo do outro) na ordem em que foram criados, como barras separadoras entre widgets adjacentes. Aqui está o código para criação da janela da Figura 6.7: int main(int argc, char *argv[]) { QApplication app(argc, argv); QTextEdit *editor1 = new QTextEdit; QTextEdit *editor2 = new QTextEdit; QTextEdit *editor3 = new QTextEdit; QSplitter splitter(Qt::Horizontal); splitter.addWidget(editor1); splitter.addWidget(editor2); splitter.addWidget(editor3); ... splitter.show(); return app.exec(); } Figura 6.7. Aplicação Splitter O exemplo consiste de três campos QTextEdit, modelados horizontalmente por um widget QSplitter – isto é mostrado esquematicamente na Figura 6.8. Diferente dos gerenciadores de layout, que simplesmente modelam os widgets filhos de um form e não possuem representação visual, QSplitter é derivado de QWidget e pode ser usado como qualquer outro widget.
  • 13. Figura 6.8. Os widgets da aplicação Splitter Áreas de Rolagem A classe QScrollArea fornece uma interface de rolagem e duas barras de rolagem. Se quisermos adicionar barras de rolagem em um widget, é mais simples usar QScrollArea do que instanciar todos os QSCrollBar e implementar as funcionalidades de rolagem. A maneira correta de se usar QScrollArea é através da chamda a setWidget() com o widget no qual adicionaremos barras de rolagem. QScrollArea automaticamente ajusta o widget para que este se torne um filho da janela principal (acessível através de QScrollArea::viewport()), caso este não o seja. Por exemplo, se quisermos barras de rolagem em volta do widget IconEditor feito no capítulo 5 ( mostrado na Figura 6.11), podemos escrever o seguinte: int main(int argc, char *argv[]) { QApplication app(argc, argv); IconEditor *iconEditor = new IconEditor; iconEditor->setIconImage(QImage(":/images/mouse.png")); QScrollArea scrollArea; scrollArea.setWidget(iconEditor); scrollArea.viewport()->setBackgroundRole(QPalette::Dark); scrollArea.viewport()->setAutoFillBackground(true); scrollArea.setWindowTitle(QObject::tr("Icon Editor")); scrollArea.show(); return app.exec(); } Figura 6.11. Redimensionando um QScrollArea
  • 14. O QScrollArea ( mostrado esquematicamente na Figura 6.12) mostra o widget em seu tamanho atual ou usa a sugestão de tamanho caso o widget não tenha sido redimensionado ainda. Através da chamada a setWidgetResizable(true), podemos dizer a QScrollArea para automaticamente redimensionar o widget para tomar vantagem de qualquer espaço extra além do seu tamanho sugerido. Figura 6.12. Widgets que constituem QScrollArea Por padrão, as barras de rolagem são exibidas somente quando a janela de visualização é menor do que o widget filho. Podemos forçar as barras de rolagem a sempre serem mostradas, através do ajuste a alguns controles de rolagem: scrollArea.setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); scrollArea.setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); QScrollArea herda muito de suas funcionalidades de QAbstractScrollArea. Classes como QTextEdit e QAbstractItemView ( a base das classes de visualização do Qt) derivam de QAbstractScrollArea, assim não precisamos envolvê-las em um QScrollArea para adquirir barras de rolagem.
  • 15. Janelas e Barras de Ferramentas Anexáveis Janelas anexadas são janelas que podem ser inseridas dentro de um QMainWindow ou soltas como janelas independentes. QMainWindow fornece quatro áreas para janelas anexadas: uma acima, uma á esquerda, uma abaixo, e uma à direita do widget central. Aplicações como Microsoft Visual Studio e Qt Linguist fazem uso de janelas anexadas para criar uma interface de usuário mais flexível. Em Qt, janelas anexadas são instâncias de QDockWidget. A Figure 6.13 mostra uma aplicação Qt com barras de ferramentas e uma janela anexada. Figura 6.13. Uma QMainWindow com uma janela anexada Cada janela anexada tem sua própria barra de título, mesmo quando está anexada. Usuários podem mover janelas anexadas de um ponto de anexação para outro, arrastando a barra de título. Podem inclusive despregar uma janela anexada de sua área, e deixar a janela flutuar como uma janela independente, arrastando a janela para fora de qualquer área de anexação. Janelas livres estão sempre “on top” sobre a janela principal. Usuários podem fechar uma QDockWidget clicando no botão fechar na barra de título da janela. Qualquer combinação dessas funcionalidades pode ser desabilitada através de uma chamada a QDockWidget::setFeatures(). Em versões anteriores do Qt, barras de ferramentas eram tratadas como janelas anexadas compartilhavam as mesmasáreas de anexação. A partir de Qt 4, barras de ferramentas passaram a ocupar suas próprias áreas em volta do widget central(como mostra a Figura 6.14) e não podem ser desanexadas. Se é necessária
  • 16. uma barra flutuantes, podemos simplesmente a colocar dentro de um QDockWidget. Figura 6.14. Áreas da barra de ferramentas e anexação de QMainWindow As curvas indicadas com linhas pontilhadas podem pertencer às qualquer uma das duas áreas de anexação contíguas. Por exemplo, poderíamos fazer a curva no canto esquerdo superior pertencer à área de anexação esquerda, chamando QMainWindow::setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea). O código que segue mostra como envolver um widget existente (neste caso, um QTreeWidget) em um QDockWidget e inseri-lo na região de anexação direita: QDockWidget *shapesDockWidget = new QDockWidget(tr("Shapes")); shapesDockWidget->setObjectName("shapesDockWidget"); shapesDockWidget->setWidget(treeWidget); shapesDockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::RightDockWidgetArea, shapesDockWidget);
  • 17. A chamada a setAllowedAreas() especifica restrições nas quais área de anexação podem aceitar a janela anexada. Aqui, apenas permitimos o usuário a arrastar a janela de anexação dentro das áreas de anexação esquerda e direita, onde existe espaço vertical suficiente para que seja exibido corretamente. Se nenhuma área permitida for setada explicitamente, o usuário pode arrastar a janela para qualquer uma das quatro áreas. Todo QObject pode receber um “nome de objeto”. Este nome pode ser útil na hora do debug e é usado por algumas ferramentas de teste. Normalmente não nos importamos em dar nomes de objeto aos widgets, mas quando criamos janelas e barras anexadas, devemos nomear os objetos caso queiramos usar QMainWindow::saveState() e QMainWindow::restoreState() para salvar e restaurar os aspectos geométricos e estados da janela anexada e da barra de ferramentas anexada. Aqui está o código de criação de barra de ferramentas contendo um QComboBox, um QSpinBox, e alguns QToolButton’s de um construtor da subclasse de QMainWindow: QToolBar *fontToolBar = new QToolBar(tr("Font")); fontToolBar->setObjectName("fontToolBar"); fontToolBar->addWidget(familyComboBox); fontToolBar->addWidget(sizeSpinBox); fontToolBar->addAction(boldAction); fontToolBar->addAction(italicAction); fontToolBar->addAction(underlineAction); fontToolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); addToolBar(fontToolBar); Se quisermos salvar a posição de todas as janelas e barras de ferramentas anexáveis, a fim de ser possível restaurá-las na próxima vez que a aplicação for executada, podemos usitlizar um código que é similar ao código usado para salvar o estado de um QSplitter, usando as funções saveState() e restoreState() de QMainWindow: void MainWindow::writeSettings() { QSettings settings("Software Inc.", "Icon Editor"); settings.beginGroup("mainWindow"); settings.setValue("geometry", saveGeometry()); settings.setValue("state", saveState()); settings.endGroup(); } void MainWindow::readSettings() {
  • 18. QSettings settings("Software Inc.", "Icon Editor"); settings.beginGroup("mainWindow"); restoreGeometry(settings.value("geometry").toByteArray()); restoreState(settings.value("state").toByteArray()); settings.endGroup(); } Finalmente, QMainWindow fornece um menu de contexto que lista todas as janelas e baras de ferramentas anexáveis. Este menu é mostrado na Fiura 6.15. O usuário pode fechar e restaurar janelas anexáveis e esconder e restaurar barras de ferramentas através do menu. Figura 6.15. O menu de contexto de QMainWindow
  • 19. Interface de Documento Múltiplo Aplicações que fornecem documentos múltiplos dentro da área central da janela são as chamadas aplicações de interface de documentos, ou aplicações MDI. Em Qt, uma aplicação MDI é criada usando a classe QMidArea como o widget central e fazendo cada janela de documento uma sub-janela QMdiArea. È convencional para aplicações MDI fornecer um menu Window que inclua alguns comandos para manusear ambas janelas e a ista de janelas. A janela ativa é identificada com uma marca. O usuário pode tornar qualquer janela ativa, clicando em sua entrada no menu WIndow. Nesta seção, vamos desenvolver a aplicação MDI Editor mostrada na Figura 6.16 para demonstrar como criar uma aplicação MDI e como implementar seu menu Window. Todos os menus da aplicação são mostrados na Figura 6.17. Figura 6.16. A Aplicação MDI Editor
  • 20. Figura 6.17. Os menus da aplicação MDI Editor A aplicação consiste de duas classes: MainWindow e Editor. O código é incrementado com os exemplos do livro, e já que a maioria do que veremos é similar ou a mesma da aplicação Spreadsheet, vista na Parte 1, apresentaremos apenas o código MDI-relevante. Iniciemos com a classe MainWindow. MainWindow::MainWindow() { mdiArea = new QMdiArea; setCentralWidget(mdiArea); connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(updateActions())); createActions(); createMenus(); createToolBars(); createStatusBar(); setWindowIcon(QPixmap(":/images/icon.png")); setWindowTitle(tr("MDI Editor")); QTimer::singleShot(0, this, SLOT(loadFiles())); } No construtor de MainWindow, criamos um widget QMidArea e o tornamos o widget central. Conectamos o sinal subWindowActivated() de QMidArea para o slot em que usaremos para manter o menu da janela atualizado, e no qual nos asseguramos que as ações estão habilitadas ou desabilitadas dependendo do estado da aplicação. No fim do construtor, ajustamos um timer com intervalo de 0 milissegundos para chamar a função loadFiles(). Esses temporizadores disparam assim que o ciclo de eventos fica ocioso. Em prática, isto significa que o construtor encerrará, e depois que a janela principal for mostrada, loadFiles() será chamado. Se não
  • 21. fizermos isto existirem muitos arquivos grandes a serem carregados, o construtor não encerrará enquanto todos os arquivos não forem carregados, e enquanto isto, o usuário não verá nada na tela e pode pensar que a aplicação falhou ao iniciar. void MainWindow::loadFiles() { QStringList args = QApplication::arguments(); args.removeFirst(); if (!args.isEmpty()) { foreach (QString arg, args) openFile(arg); mdiArea->cascadeSubWindows(); } else { newFile(); } mdiArea->activateNextSubWindow(); } Caso o usuário tenha iniciado a aplicação com um ou mais nomes de arquivos na linha de comando, esta função tenta carregar cada arquivo e no final cascateia as sub-janelas de forma que o usuário pode vê-las facilmente. Opções específicas de linhas de comando do Qt, como –style e –font, são automaticamente removidas da lista de argumentos pelo construtor QAplication. Assim, se escrevermos mdieditor -style motif readme.txt na linha de comando, QAplication::arguments() retorna um QStringList contendo dois itens (“mdieditor” e “readme.txt”), e a aplicação MDI Editor inicia com o documento readme.txt. Se nenhum arquivo for especificado na linha de comando, uma nova sub-janela de editor vazia é criada para que o usuário possa começar a digitar. A chamada para activateNextSubWindow() significa que uma janela de edição recebe foco, e assegura que a função updateActions() é chamada para atualizar o menu Window e habilitar e desabilitar ações, de acordo com o estado da aplicação. void MainWindow::newFile() { Editor *editor = new Editor; editor->newFile(); addEditor(editor); } O slot newFile() corresponde à opção File|New do menu. Ela cria um widget Editor e passa ele para a função privada addEditor().
  • 22. void MainWindow::open() { Editor *editor = Editor::open(this); if (editor) addEditor(editor); } A função open() corresponde à File|Open. Ela faz uma chamada para a função estática Editor::open(), que abre uma janela para selecionar arquivo para abrir. Se o usuário escolhe um arquivo, um novo Editor é criado, o texto do arquivo é lido, e caso a leitura seja feita com sucesso, o ponteiro para o Editor é retornado. Caso o usuário cancele a janela de abertura de arquivo, ou se a leitura falhar, um ponteiro null é retornado e o usuário é notificado do erro. Faz mais sentido implementar as operações sob arquivos na classe Editor do que na classe MainWindow, já que cada Editor precisa manter seu próprio estado independente. void MainWindow::addEditor(Editor *editor) { connect(editor, SIGNAL(copyAvailable(bool)), cutAction, SLOT(setEnabled(bool))); connect(editor, SIGNAL(copyAvailable(bool)), copyAction, SLOT(setEnabled(bool))); QMdiSubWindow *subWindow = mdiArea->addSubWindow(editor); windowMenu->addAction(editor->windowMenuAction()); windowActionGroup->addAction(editor->windowMenuAction()); subWindow->show(); } A função privada addEditor() é chamada em newFile() e open() para completar a inicialização de um novo widget Editor. Ela começa estabelecendo duas conexões sinal-slot. Essas conexões garantem que Edit|Cut e Edit|Copy sejam habilitadas ou desabilitadas, dependendo se há texto selecionado. Como estamos usando MDI, é possível que mútiplos widgets Editor estejam em uso. Isto é algo para se tomar cuidado, pois estamos interessados apenas em responder ao sinal copyAvailable(bool) da janela Editor ativa, e não das demais. Mas estes sinais podem apenas ser emitidos pela janela ativa, então isto deixa de ser um problema na prática. A função QMdiArea::addSubWindow() cria um novo QMdiSubWindow, insere o widget passado como parâmetro dentro da sub-janela, e retorna a sub-janela. Depois, criamos um QAction que representa a janela para o menu Window. A ação é fornecida pela classe Editor, que será vista em breve. Também adicionamos a ação para um objeto QActionGroup. Este garante que apenas um item Window do menu é checado por vez. Finalmente, chamamos show() na nova QMdiSubWindow para a tornar visível.
  • 23. void MainWindow::save() { if (activeEditor()) activeEditor()->save(); } O slot save() faz uma chamada a Editor::save() no editor ativo, caso haja um. Novamente, o código que realiza o verdadeiro trabalho é localizado na classe Editor. Editor *MainWindow::activeEditor() { QMdiSubWindow *subWindow = mdiArea->activeSubWindow(); if (subWindow) return qobject_cast<Editor *>(subWindow->widget()); return 0; } A função privada activeEditor() retorna o widget abrigado dentro da subjanela ativa como um ponteiro do tipo Editor, ou um ponteiro nulo caso não haja uma subjanela ativa. void MainWindow::cut() { if (activeEditor()) activeEditor()->cut(); } O slot cut() chama Editor::cut() no editor ativo. Não mostramos os slots copy() e paste() pois estes seguem o mesmo padrão. void MainWindow::updateActions() { bool hasEditor = (activeEditor() != 0); bool hasSelection = activeEditor() && activeEditor()>textCursor().hasSelection(); saveAction->setEnabled(hasEditor); saveAsAction->setEnabled(hasEditor); cutAction->setEnabled(hasSelection); copyAction->setEnabled(hasSelection); pasteAction->setEnabled(hasEditor); closeAction->setEnabled(hasEditor); closeAllAction->setEnabled(hasEditor); tileAction->setEnabled(hasEditor);
  • 24. cascadeAction->setEnabled(hasEditor); nextAction->setEnabled(hasEditor); previousAction->setEnabled(hasEditor); separatorAction->setVisible(hasEditor); if (activeEditor()) activeEditor()->windowMenuAction()->setChecked(true); } O sinal subWindowActivated() é emitido toda vez que uma nova subjanela se torna ativa, e quando a última subjanela é fechada (no caso, seu parâmetro é um ponteiro para null). O sinal é conectado ao slot updateActions(). A maioria das opções dos menus faz sentido apenas se há uma janela ativa, sendo assim as desabilitamos caso não haja uma janela. No fim, chamamos setChecked() na QAction que representa a janela ativa. Graças a QActionGroup, não precisamos desmarcar explicitamente a janela ativa anterior. void MainWindow::createMenus() { ... windowMenu = menuBar()->addMenu(tr("&Window")); windowMenu->addAction(closeAction); windowMenu->addAction(closeAllAction); windowMenu->addSeparator(); windowMenu->addAction(tileAction); windowMenu->addAction(cascadeAction); windowMenu->addSeparator(); windowMenu->addAction(nextAction); windowMenu->addAction(previousAction); windowMenu->addAction(separatorAction); ... } A função privada createMenus() preenche o menu Window com ações. Todas as ações são típicas de menus e são facilmente implementadas usando os slots de QMdiArea,closeActiveSubWindow(), closeAllSubWindows(), tileSubWindows(), e cascadeSubWIndows(). Toda vez que o usuário abre uma nova janela, ela é adicionada à lista de ações do menu Window. (Isto é feito na função addEditor() que vimos na página 160). Quando o usuário fecha uma janela de edição, sua ação no meno Window é deletada (já que a ação respectiva é propriedade da janela de edição), assim a ação é automaticamente removida do menu Window. void MainWindow::closeEvent(QCloseEvent *event) { mdiArea->closeAllSubWindows(); if (!mdiArea->subWindowList().isEmpty()) {
  • 25. event->ignore(); } else { event->accept(); } } A função closeEvent() é reimplementada para fechar todas as subjanelas, fazendo com que cada subjanela receba um evento de fecho. Se uma subjanela “ignorar” seu evento de fecho ( caso o usuário cancele uma caixa de mensagens “ unsaved changes”), ignoramos o evento de fecho para MainWindow; de outra forma, aceitamos este, fazendo com que o Qt encerre a aplicação por completo. Se não tivéssemos reimplementado closeEvent() em MainWindow, não seria dada ao usuário oportunidade de salvar mudanças não salvas. Terminamos agora nossa revisão de MainWindow, assim podemos prosseguir na implementação do Editor. A classe Editor representa uma subjanela. É derivada de QTextEdit, que fornece a funcionalidade de edição de texto. Em uma aplicação do mundo real, se um componente de edição de código é exigido, podemos considerar usar o Scintilla, disponível para Qt como QScintilla em HTTP://www.riverbankcomputing.uk/qscintilla/. Assim como qualquer widget Qt pode ser usado como uma janela independente, qualquer widget Qt pode ser inserido dentro de um QMdiSubWindow e usado como uma subjanela em uma área MDI. Aqui está a definição da classe: class Editor : public QTextEdit { Q_OBJECT public: Editor(QWidget *parent = 0); void newFile(); bool save(); bool saveAs(); QSize sizeHint() const; QAction *windowMenuAction() const { return action; } static Editor *open(QWidget *parent = 0); static Editor *openFile(const QString &fileName, QWidget *parent = 0); protected: void closeEvent(QCloseEvent *event); private slots: void documentWasModified(); private: bool okToContinue();
  • 26. bool saveFile(const QString &fileName); void setCurrentFile(const QString &fileName); bool readFile(const QString &fileName); bool writeFile(const QString &fileName); QString strippedName(const QString &fullFileName); QString curFile; bool isUntitled; QAction *action; }; Quatro das funções privadas que estavam na classe MainWindow da aplicação Spreadsheet estão também presentes na classe Editor: okToContinue(), saveFile(), setCurrentFile(), e strippedName(). Editor::Editor(QWidget *parent) : QTextEdit(parent) { action = new QAction(this); action->setCheckable(true); connect(action, SIGNAL(triggered()), this, SLOT(show())); connect(action, SIGNAL(triggered()), this, SLOT(setFocus())); isUntitled = true; connect(document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified())); setWindowIcon(QPixmap(":/images/document.png")); setWindowTitle("[*]"); setAttribute(Qt::WA_DeleteOnClose); } Primeiro, criamos uma QAction que representa o editor no menu Window da aplicação, e conectamos essa ação aos slots show() e setFocus(). Já que permitimos usuários a criar qualquer número de janelas de edição, devemos fazer algumas disposições para nomeá-las para que elas possam ser distinguidas antes de serem salvas pela primeira vez. Uma forma comum de controlar isto é alocando nomes que incluam números (ex: document1.txt). Usamos a variável isUntitled para distinguir entre nomes fornecidos pelo usuário e nomes que o programa gera automaticamente. Conectamos o sinal contentsChanged do documento de texto para o slot privado documentWasModified(). Este slot faz uma chamada para setWindowModified(true). Finalmente, setamos o atributo Qt::WA_DeleteOnClose para evitar vazamentos de memória quando o usuário fechar uma janela Editor.
  • 27. void Editor::newFile() { static int documentNumber = 1; curFile = tr("document%1.txt").arg(documentNumber); setWindowTitle(curFile + "[*]"); action->setText(curFile); isUntitled = true; ++documentNumber; } A função newFile() gera um nome como document1.txt para o novo documento. O código pertence a newFile(), e não ao construtor, pois não queremos consumir números quando chamamos open() para abrir um documento existente em uma nova janela Editor criada. Já que documentNumber é declarado estático, ele é dividido através de todas as instancias de Editor. O marcador “[*]” no título da janela é um marcador de local para quando quisermos que o asterisco apareça quando o arquivo possuir mudanças não-salvas em plataformas que não sejam Mac OS X. Para Mac, documentos não salvos possuem um ponto no botão de fechar da janela. Cobrimos este marcador de local no Capítulo 3 (p.61). Além de criar novos arquivos, usuários freqüentemente querem abrir arquivos existentes, carregados ou de uma caixa de diálogo de arquivo, ou uma lista de arquivos recentemente abertos. Duas funções estáticas São fornecidas para suportar esses usos: open() para escolher um nome de arquivo do sistema de arquivos, e openFile() para criar um Editor e ler o conteúdo de um arquivo específico. Editor *Editor::open(QWidget *parent) { QString fileName = QFileDialog::getOpenFileName(parent, tr("Open"), "."); if (fileName.isEmpty()) return 0; return openFile(fileName, parent); } A função estática open() faz surgir uma caixa de diálogo de arquivo, pela qual o usuário pode escolher um arquivo. Se um arquivo é escolhido, openFile() é chamado para criar um Editor e lê o conteúdo do arquivo. Editor *Editor::openFile(const QString &fileName, QWidget *parent) { Editor *editor = new Editor(parent); if (editor->readFile(fileName)) { editor->setCurrentFile(fileName);
  • 28. return editor; } else { delete editor; return 0; } } A função estática começa criando um novo widget Editor, e depois tenta ler no arquivo específico. Caso a leitura seja feita com sucesso, o Editor é retornado; caso contrário, o usuário é informado do problema (em readFile()), o editor é deletado, e um ponteiro para null é retornado. bool Editor::save() { if (isUntitled) { return saveAs(); } else { return saveFile(curFile); } } A função save() usa a variável isUntitled para determinar se ela deve chamar saveFile() ou saveAs(). void Editor::closeEvent(QCloseEvent *event) { if (okToContinue()) { event->accept(); } else { event->ignore(); } } A função closeEvent() é reimplementada para permitir o usuário a salvar mudanças não-salvas. A lógica é codificada na função okToContinue(), que faz gera um pop up de uma caixa de mensagem que pergunta, “Do you want to save your changes?” se okToContinue() retornar true, aceitamos o evento de fecho; de outra forma, “ignoramos” a mensagem e deixamos a janela inalterada. void Editor::setCurrentFile(const QString &fileName) { curFile = fileName; isUntitled = false; action->setText(strippedName(curFile)); document()->setModified(false); setWindowTitle(strippedName(curFile) + "[*]"); setWindowModified(false); }
  • 29. A função setCurrentFile() é chamada de openFile() e saveFile() para atualizar as variáveis curFile e isUntitled, para habilitar o título da janela e texto de ação, e para habilitar o flag “modified” do documento para false. A Qualquer momento em que o usuário modifica o texto no editor, o subjacente QTextDocument emite o sinal contentsChanged() e habilita sua flag interna “modified” para true. QSize Editor::sizeHint() const { return QSize(72 * fontMetrics().width('x'), 25 * fontMetrics().lineSpacing()); } Finalmente, a função sizeHint() retorna um tamanho baseado no tamanho da letra “x” e na altura da letra de uma linha de texto. QMdiArea usa a sugestão de tamanho para dar um tamanho inicial à janela. MDI é uma forma de controlas múltiplos documentos simultaneamente. Em Max OS X, a abordagem preferida é usar janelas top-level mútliplas. Cobrimos esta abordagem na seção “Documentos Múltiplos” do Capítulo 3.