Cap6

552 visualizações

Publicada em

0 comentários
0 gostaram
Estatísticas
Notas
  • Seja o primeiro a comentar

  • Seja a primeira pessoa a gostar disto

Sem downloads
Visualizações
Visualizações totais
552
No SlideShare
0
A partir de incorporações
0
Número de incorporações
1
Ações
Compartilhamentos
0
Downloads
14
Comentários
0
Gostaram
0
Incorporações 0
Nenhuma incorporação

Nenhuma nota no slide

Cap6

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

×