Pobierz prezentację
Pobieranie prezentacji. Proszę czekać
1
Omówienie: Qt4::Rozdział_3
Główne okno programu Sławomir Kubecki, III Informatyka dzienna 2007/08r
2
Spis treści: Wstęp Klasa MainWindow
Tworzenie menu i pasków narzędziowych Pasek statusu Implementacja menu File Używanie okien dialogowych Przechowywanie ustawień programu Obsługa wielu dokumentów jednocześnie Splash Screen A to wszystko przy pomocy czystego kodu…
3
Wstęp Program z graficznym interfejsem => obszerny kod, ale obsługa programu bardziej user-friendly W dzisiejszej prezentacji wykorzystamy kody poprzednio napisanych programów: Find - find.exe Go-to-Cell - gotocell.exe Sort – sort.exe Napiszemy w języku C++ cały kod realizujący główne okno programu, menu, menu podręczne, pasek narzędzi podręcznych, pasek tytułowy oraz ekran startowy. Zaczynamy…
4
Klasa MainWindow Program, który zamierzamy napisać, wykorzystywać będzie funkcjonalności poprzednich programów i będzie to dość prosty arkusz kalkulacyjny. Chcemy, aby główne okno naszego programu wyglądało następująco:
5
Klasa MainWindow Klasę głównego okna aplikacji tworzymy przez dziedziczenie po klasie QMainWindow. Wykorzystamy wiele technik z poprzedniego rozdziału do tworzenia głównego okna, gdyż QDialog i QMainWindow dziedziczą po klasie QWidget. Główne okno możemy również stworzyć przy pomocy programu Qt Designer, ale niestety nie jest to częścią prezentacji. Więcej informacji na ten temat znajduje się na stronie:
6
Klasa MainWindow mainwindow.h #ifndef MAINWINDOW_H
#define MAINWINDOW_H #include <QMainWindow> class QAction; class QLabel; class FindDialog; class Spreadsheet; class MainWindow : public QMainWindow // dziedziczymy po QMainWindow { Q_OBJECT // wymagane makro, aby klasa obsługiwała system sygnałów i slotów public: MainWindow(); // konstruktor protected: void closeEvent(QCloseEvent *event); /* closeEvent() to metoda wirtualna z klasy QWidget, która jest wywoływana, gdy zamykamy okno. Nadpiszemy ją, żeby przy zamykaniu okna, program zapytał użytkownika o chęć zapisania wprowadzonych zmian. */ … mainwindow.h
7
Klasa MainWindow … private slots: void newFile(); void open(); bool save(); // typ zwracany bool bool saveAs(); // typ zwracany bool void find(); void goToCell(); void sort(); void about(); void openRecentFile(); void updateStatusBar(); void spreadsheetModified(); /* wiekszość slotów zwraca typ void, ale save() i saveAs() zwracają wartość logiczną. Wartość zwracana przez slot jest ignorowana, gdy jest on wywoływany przez sygnał, ale gdy wywołujemy slot jak zwykłą metodę, wtedy wartość zwracana jest dla nas dostępna, jak w przypadku zwykłej metody. */ mainwindow.h
8
Klasa MainWindow … private: void createActions(); void createMenus(); void createContextMenu(); void createToolBars(); void createStatusBar(); void readSettings(); void writeSettings(); bool okToContinue(); bool loadFile(const QString &fileName); bool saveFile(const QString &fileName); void setCurrentFile(const QString &fileName); void updateRecentFileActions(); QString strippedName(const QString &fullFileName); /* Tutaj mamy garstkę metod prywatnych, które później umożliwią nam łatwą komunikację użytkownika z programem przy pomocy interfejsu graficznego */ mainwindow.h
9
Klasa MainWindow … // pola prywatne – ich wykorzystanie, będzie omówione w dalszej części. Spreadsheet *spreadsheet; FindDialog *findDialog; QLabel *locationLabel, *formulaLabel; QStringList recentFiles; QString curFile; enum { MaxRecentFiles = 5 }; QAction *recentFileActions[MaxRecentFiles], *separatorAction; QMenu *fileMenu, *editMenu; QToolBar *fileToolBar, *editToolBar; QAction *newAction, *openAction, *aboutQtAction; }; #endif Na tym zakończymy omawianie pliku nagłówkowego i przejdziemy do pliku implementacji… mainwindow.h
10
Klasa MainWindow mainwindow.cpp
#include <QtGui> /* plik nagłówkowy, który zawiera definicje wszystkich klas, z których korzystalismy tworząc podklasy */ // Dołączamy pliki nagłówkowe z poprzednich programów #include "finddialog.h" #include "gotocelldialog.h" #include "sortdialog.h " #include "mainwindow.h " // nasz plik nagłówkowy głównego okna #include "spreadsheet.h" // tego pliku jeszcze nie mamy, napiszemy go w rozdziale 4. … mainwindow.cpp
11
Klasa MainWindow … MainWindow::MainWindow() // konstruktor głównego okna programu { spreadsheet = new Spreadsheet; /* konstruujemy widgeta klasy Spreadsheet, dziedziczy on po QTableWidget… */ setCentralWidget(spreadsheet); // .. i ustawiamy go w centrum głównego okna // wywołujemy prywatne metody, aby wypełnić pozostałą część okna głównego createActions(); // tworzymy akcje createMenus(); // tworzymy menu createContextMenu(); // tworzymy menu podręczne createToolBars(); // tworzymy pasek narzędzi createStatusBar(); // tworzymy pasek statusu readSettings(); // metoda wczytuje zapisane opcje programu findDialog = 0; /* ustawiamy wskaźnik na null; gdy pierwszy raz zostanie wywołana funkcja MainWindow::find() utworzymy nowy obiekt klasy FindDialog */ setWindowIcon(QIcon(":/images/icon.png")); /* ustawiamy ikonę głównego okna – górny lewy róg paska tytułowego */ setCurrentFile(""); } mainwindow.cpp
12
Klasa MainWindow Obiekt klasy Spreadsheet (którego dziś nie będziemy omawiać) będzie naszym głównym widgetem – umieścimy go w centrum naszego okna. Pozostałe elementy okna głównego rozmieścimy wzorując się poniższym schematem: Window Title – pasek tytułowy Menu Bar – pasek Menu Toolbar Areas – przestrzeń pasków narzędziowych Dock Window Areas – pole do „dokowania” okienek (zwykle pasków narzędziowych) Status Bar – pasek statusu/informacyjny
13
Klasa MainWindow Qt obsługuje wiele formatów plików graficznych jak BMP, JPEG, PNG, PNM, XBM, XPM. GIFy standardowo nie były dostępne z powodów patentowych w niektórych państwach. Można je jednak "odblokować" korzystając z programu configure w pliku głównym Qt: "configure -qt-gif" lub zaznaczyć podczas instalacji odpowiednią opcję. Sposoby udostępniania grafiki dla programu: 1. Przechowywanie grafik w osobnych plikach i ładowanie ich z każdym uruchomieniem programu 2. Dołączanie plików XPM do kodu źródłowego 3. Użycie mechanizmu zasobów Qt Na potrzeby prezentacji skorzystamy z mechanizmu zasobów. Całą grafikę będziemy przechowywać w podkatalogu images
14
Klasa MainWindow Aby wykorzystać mechanizm zasobów tworzymy plik zasobów (np. spreadsheet.qrc) oraz dodajemy następującą linię do pliku projektu .pro: RESOURCES = spreadsheet.qrc Pliki .qrc mają strukturę prostego pliku XML. Fragment pliku: <!DOCTYPE RCC><RCC version="1.0"> <qresource> <file>images/icon.png</file> ... <file>images/gotocell.png</file> </qresource> </RCC> Pliki zasobów są "wkompilowywane" w plik wykonywalny. Do zasobów odwołujemy się wykorzystując prefix :/ np. :/images/icon.png Pliki zasobów mogą zawierać również inne rodzaje plików, nie tylko graficzne.
15
Menu i paski narzędziowe
Większość nowoczesnych aplikacji z graficznym interfejsem jest dostarczonych z różnymi menu, podręcznymi menu oraz paskami narzędziowymi, aby użytkownikowi ułatwić pracę z aplikacją. Technologia Qt upraszcza proces programowania menu i pasków narzędziowych poprzez system "akcji". Są to pewne własności które można dodać do dowolnej ilości menu i pasków narzędzi. Tworzenie menu i pasków narzędzi w Qt: 1. Utworzenie i ustawienie akcji 2. Utworzenie menu i połączenie ich z akcjami 3. Utworzenie pasków narzędzi i połączenie ich z akcjami W naszej aplikacji akcje są tworzone w metodze createActions():
16
Menu i paski narzędziowe
… void MainWindow::createActions() { newAction = new QAction(tr("&New"), this); // nadajemy akcji nazwę, oraz rodzica – główne okno newAction->setIcon(QIcon(":/images/new.png")); // nadajemy akcji ikonkę newAction->setShortcut(tr("Ctrl+N")); // nadajemy akcji skrót klawiszowy newAction->setStatusTip(tr("Create a new spreadsheet file")); /* nadajemy akcji adnotację, która pojawi się w pasku statusu */ connect(newAction, SIGNAL(triggered()), this, SLOT(newFile())); /* łączymy sygnał uruchomienia akcji triggered() ze slotem newFile() */ // Akcje Open, Save, Save As - analogicznie /* wypełniamy tablicę recentFileActions akcjami. Każda za nich jest ukryta i połączona ze slotem openRecentFile() */ for (int i = 0; i < MaxRecentFiles; ++i) recentFileActions[i] = new QAction(this); recentFileActions[i]->setVisible(false); // ukrywamy akcje, dopóki nie będą nam potrzebne connect(recentFileActions[i], SIGNAL(triggered()), this, SLOT(openRecentFile())); } mainwindow.cpp
17
Menu i paski narzędziowe
… // akcja Select All selectAllAction = new QAction(tr("&All"), this); selectAllAction->setShortcut(tr("Ctrl+A")); selectAllAction->setStatusTip(tr("Select all the cells in the spreadsheet")); connect(selectAllAction, SIGNAL(triggered()), spreadsheet, SLOT(selectAll())); /* slot selectAll jest udostępniony przez klasę QAbstractItemView */ /* Show Grid i Auto-Recalculate są indywidualnymi akcjami, które mogą być zaznaczone bądź odznaczone – dla skrócenia, pokażemy implementację tylko Show Grid*/ showGridAction = new QAction(tr("&Show Grid"), this); showGridAction->setCheckable(true); /* akcja jest wyświetlana z możliwością zaznaczenia lub jej odznaczenia */ showGridAction->setChecked(spreadsheet->showGrid()); showGridAction->setStatusTip(tr("Show or hide the spreadsheet's grid")); connect(showGridAction, SIGNAL(toggled(bool)), spreadsheet, SLOT(setShowGrid(bool))); /* łączymy sygnał zmiany stanu akcji ze slotem który odpowiada za wyświetlanie kratki w aplikacji */ /* Qt umożliwia również tworzenie akcji wykluczających się przy użyciu klasy QActionGroup */ mainwindow.cpp
18
Menu i paski narzędziowe
… /* Przy implementacji akcji "About Qt" wykorzystamy slot aboutQt() dostępny z poziomu zmiennej globalnej qApp klasy Qapplication */ aboutQtAction = new QAction(tr("About &Qt"), this); aboutQtAction->setStatusTip(tr("Show the Qt library's About box")); connect(aboutQtAction, SIGNAL(triggered()), qApp, SLOT(aboutQt())); } Tak prezentuje się okno informacyjne About Qt: mainwindow.cpp
19
Menu i paski narzędziowe
Teraz skoro mamy już akcje, przejdźmy do zbudowania systemu menu, który będzie je zawierał. W Qt, menu są instancjami QMenu. Metoda addMenu() tworzy nowy widget z określonym tekstem i umieszcza go na pasku menu. Metoda QMainWindow::menuBar() zwraca wskaźnik do obiektu klasy QMenuBar. Nasze menu jest tworzone gdy pierwszy raz wywołujemy menuBar(). … void MainWindow::createMenus() { // menu File fileMenu = menuBar()->addMenu(tr("&File")); fileMenu->addAction(newAction); // analogicznie openAction, saveAction, saveAsAction separatorAction = fileMenu->addSeparator(); /* zachowujemy wskaźnik do tego separatora - pozwoli nam to w ukryć go w przypadku, gdy nie ma odnotowanych ostatnio otwartych plików. */ for (int i = 0; i < MaxRecentFiles; ++i) fileMenu->addAction(recentFileActions[i]); // dodajemy kolejno akcje ostatnio otwartych plików fileMenu->addSeparator(); fileMenu->addAction(exitAction); mainwindow.cpp
20
Menu i paski narzędziowe
… // menu Edit editMenu = menuBar()->addMenu(tr("&Edit")); editMenu->addAction(cutAction); // analogicznie copyAction, pasteAction, deleteAction selectSubMenu = editMenu->addMenu(tr("&Select")); // dodajemy podmenu w menu Edit selectSubMenu->addAction(selectRowAction); selectSubMenu->addAction(selectColumnAction); selectSubMenu->addAction(selectAllAction); editMenu->addSeparator(); // dodajemy akcje związane z uprzednio napisanymi programami editMenu->addAction(findAction); editMenu->addAction(goToCellAction); mainwindow.cpp
21
Menu i paski narzędziowe
… toolsMenu = menuBar()->addMenu(tr("&Tools")); toolsMenu->addAction(recalculateAction); toolsMenu->addAction(sortAction); // akcja związana z napisanym wcześniej programem optionsMenu = menuBar()->addMenu(tr("&Options")); optionsMenu->addAction(showGridAction); optionsMenu->addAction(autoRecalcAction); menuBar()->addSeparator(); helpMenu = menuBar()->addMenu(tr("&Help")); helpMenu->addAction(aboutAction); helpMenu->addAction(aboutQtAction); } Nasze menu (w zależności od platformy systemowej) wygląda mniej więcej tak: mainwindow.cpp
22
Menu i paski narzędziowe
Dowolny widget w Qt może mieć przypisany zestaw akcji. By zbudować menu podręczne dla aplikacji, dodajemy akcje do wybranego widgeta i ustawiamy, by menu podręczne wyświetlało możliwe do wykonania akcje. Menu podręczne są wywoływane poprzez prawy klik myszą (bądź jego odpowiednik na innych platformach systemowych) na widgecie. Istnieje też inny bardziej skomplikowany sposób dostarczania menu podręcznych, polegający na tym by nadpisać metodę QWidget::contextMenuEvent(), stworzyć widgeta QMenu i wypełnić go pożądanymi akcjami i wywołać metodę exec() na nim. … void MainWindow::createContextMenu() { // dodajemy menu podręczne do głównego widgeta - spreadsheet spreadsheet->addAction(cutAction); spreadsheet->addAction(copyAction); spreadsheet->addAction(pasteAction); spreadsheet->setContextMenuPolicy(Qt::ActionsContextMenu); } Menu podręczne: mainwindow.cpp
23
Menu i paski narzędziowe
… void MainWindow::createToolBars() { fileToolBar = addToolBar(tr("&File")); fileToolBar->addAction(newAction); // analogicznie dla openAction, saveAction //… editToolBar = addToolBar(tr("&Edit")); editToolBar->addAction(cutAction); // analogicznie dla copyAction, pasteAction editToolBar->addSeparator(); editToolBar->addAction(findAction); editToolBar->addAction(goToCellAction); } Pasek narzędziowy: mainwindow.cpp
24
Różne stany paska statusu:
Pasek statusu Przejdziemy teraz do zaimplementowania paska statusu. Zwykle będzie on wyświetlał aktualną pozycję oraz formułę zaznaczonej komórki. Dodatkowo, czasami będzie wyświetlał tymczasowe informacje. Wszystkie informacje będą reprezentowane przez obiekty klasy QLabel, których rodzicem będzie pasek statusu. Jak widzimy na obrazku, dwa główne pola informacyjne wymagają różnej ilości miejsca na pasku. Decydujemy zatem, że w razie gdy będziemy zmieniać rozmiar okna podczas użytkowania, dodatkowe miejsce przeznaczymy dla pola z formułą po prawej stronie. Osiągamy to poprzez ustawienie współczynnika rozciągalności na 1. Różne stany paska statusu:
25
Pasek statusu … void MainWindow::createStatusBar() { locationLabel = new QLabel(" W999 "); /* zostawiamy trochę miejsca z obu stron dla lepszej widoczności */ locationLabel->setAlignment(Qt::AlignHCenter); locationLabel->setMinimumSize(locationLabel->sizeHint()); /* ustawiamy minimalną wielkość miejsca jaką powinna zająć informacja na pasku statusu */ formulaLabel = new QLabel; formulaLabel->setIndent(3); // dodajemy akapit przed tekstem statusBar()->addWidget(locationLabel); statusBar()->addWidget(formulaLabel, 1); /* ustawienie współczynnika rozciągalności na 1 (domyślnie ustawiany jest na 0) */ // łączymy dwa sygnały ze obiektu spreadsheet do slotów mainWindow connect(spreadsheet, SIGNAL(currentCellChanged(int, int, int, int)), this, SLOT(updateStatusBar())); connect(spreadsheet, SIGNAL(modified()), this, SLOT(spreadsheetModified())); updateStatusBar(); } mainwindow.cpp
26
Pasek statusu … /* slot updateStatusBar() uaktualnia wyświetlanie informacji na pasku statusu dotyczących aktualnie zaznaczonej komórki. Dodatkowo jest wywoływany jako zwykła metoda przy createStatusBar() by zainicjować wyświetlanie informacji, ponieważ obiekt spreadsheet nie wysyła sygnału currentCellChanged() przy uruchomieniu */ void MainWindow::updateStatusBar() { locationLabel->setText(spreadsheet->currentLocation()); formulaLabel->setText(spreadsheet->currentFormula()); } void MainWindow::spreadsheetModified() setWindowModified(true); // uaktualnienie paska tytułowego updateStatusBar(); /* uaktualenienie indykatorów informacyjnych paska statusu, by odpowiadały bieżącej sytuacji */ Na tym zakończymy implementację paska statusu i przejdziemy do implementacji menu File. mainwindow.cpp
27
Implementacja menu File
… /* metoda pytająca czy użytkownik chce zapisać ewentualne zmiany w akualnie edytowanym pliku w przypadku niezapisanych zmian */ bool MainWindow::okToContinue() { if (isWindowModified()) { // sprawdzamy czy były wprowadzone zmiany od ostatniego zapisu int r = QMessageBox::warning(this, tr("Spreadsheet"), tr("The document has been modified.\nDo you want to save your changes?"), QMessageBox::Yes | QMessageBox::Default, /* domyślna odpowiedź - naciśnięcie "Enter" jest równoznaczne z wyborem Yes */ QMessageBox::No, QMessageBox::Cancel | QMessageBox::Escape); /* naciśnięcie "ESC" na klawiaturze jest równoznaczne z wyborem Cancel */ if (r == QMessageBox::Yes) { return save(); // decydujemy się na zapis pliku } else if (r == QMessageBox::Cancel) { return false; } /* gdy rezygnujemy z decyzji która wywołala tę metodę, jak np. decyzja o utworzeniu nowego pliku, bądź zamknięcie programu */ } return true; // gdy nie chcemy zapisać zmian w pliku … mainwindow.cpp
28
Implementacja menu File
Składnia wywołania QMessageBox::warning() może wyglądać trochę zagmatwanie: QMessageBox::warning(parent, title, message, button0, button1, ...); QMessageBox udostępnia również information(), question() oraz critical() - każde ze swoją własną ikonką. … void MainWindow::newFile() { if (okToContinue()) spreadsheet->clear(); // czyścimy wszystko co jest w głównym widgecie setCurrentFile(""); // nadajemy nazwę nowemu dokumentu - pusty string } } … mainwindow.cpp
29
Implementacja menu File
… void MainWindow::open() { if (okToContinue()) QString fileName = QFileDialog::getOpenFileName(this, /* rodzic - warunkuje początkowe umiejscowienie okna na ekranie */ tr("Open Spreadsheet"), // tytuł okna ".", // rozpoczęcie wybierania pliku z katalog początkowego tr("Spreadsheet files (*.sp)\n" "Comma-separated values files (*.csv)\n" "Lotus files (*.wk1 *.wks)")) // filtry plików do wczytania (wymienione w kolejnych liniach) ); // statyczna metoda zwracająca ścieżkę do pliku, lub pusty string w przypadku naciśnięcia Cancel if (!fileName.isEmpty()) loadFile(fileName); } mainwindow.cpp
30
Implementacja menu File
… bool MainWindow::loadFile(const QString &fileName) { if (!spreadsheet->readFile(fileName)) /* wykorzystujemy Spreadsheet::readfile() by wczytać plik z dysku */ statusBar()->showMessage(tr("Loading canceled"), 2000); /* w przypadku problemów z otwarciem pliku pojawi się odpowiednia informacja na pasku statusu przez 2000ms, czyli 2s…*/ return false; // ...a metoda zwróci false } setCurrentFile(fileName); // uaktualniamy pasek tytułowy o nazwę aktualnego pliku statusBar()->showMessage(tr("File loaded"), 2000); return true; mainwindow.cpp
31
Implementacja menu File
... bool MainWindow::save() { if (curFile.isEmpty()) { // gdy plik nie byl jeszcze zapisany pod żadną nazwą, wówczas... return saveAs(); } else { // .. w przeciwnym razie zapisujemy go pod bieżącą nazwą return saveFile(curFile); } bool MainWindow::saveFile(const QString &fileName) { if (!spreadsheet->writeFile(fileName)) { statusBar()->showMessage(tr("Saving canceled"), 2000); return false; setCurrentFile(fileName); // uaktualniamy pasek tytułowy w przypadku nowo zapisanego pliku statusBar()->showMessage(tr("File saved"), 2000); return true; … mainwindow.cpp
32
Implementacja menu File
... bool MainWindow::save() { if (curFile.isEmpty()) { // gdy plik nie byl jeszcze zapisany pod żadną nazwą, wówczas... return saveAs(); } else { // .. w przeciwnym razie zapisujemy go pod bieżącą nazwą return saveFile(curFile); } bool MainWindow::saveFile(const QString &fileName) { if (!spreadsheet->writeFile(fileName)) { statusBar()->showMessage(tr("Saving canceled"), 2000); return false; setCurrentFile(fileName); // uaktualniamy pasek tytułowy w przypadku nowo zapisanego pliku statusBar()->showMessage(tr("File saved"), 2000); return true; … mainwindow.cpp
33
Implementacja menu File
… bool MainWindow::saveAs() { QString fileName = QFileDialog::getSaveFileName(this, tr("Save Spreadsheet"), ".", tr("Spreadsheet files (*.sp)")); /* pobieramy nazwę pliku od użytkownika; w przypadku Cancel, metoda zwraca pusty string. dodając dodatkowy argument QFileDialog::DontConfirmOverwrite, metoda nie zapyta użytkownika w przypadku istnienia już pliku o podanej nazwie tylko od razu go nadpisze. */ if (fileName.isEmpty()) return false; return saveFile(fileName); } mainwindow.cpp
34
Implementacja menu File
… void MainWindow::closeEvent(QCloseEvent *event) { if (okToContinue()) /* w przypadku gdy nie ma niczego nowego do zapisania; chęci zapisu, bądź odmowy zapisu zmian w dokumencie przy wyjściu z programu... */ writeSettings(); // zapis aktualnych ustawień programu event->accept(); // zgoda na zamknięcie programu } else // .. w przypadku wybrania Cancel przy pytaniu o zapis -> powrót do programu event->ignore(); // zignorowanie wywołania wydarzenia /* program jest wyłączany z zamknięciem ostatniego okna programu. możemy to zmienić modyfikując właściwość QApplication::quitOnLastWindowClosed na false. Wówczas program zakończy się dopiero przy wywołaniu QApplication::quit() */ mainwindow.cpp
35
Implementacja menu File
… QString MainWindow::strippedName(const QString &fullFileName) { return QFileInfo(fullFileName).fileName(); /* zwracamy tylko nazwę pliku */ } void MainWindow::setCurrentFile(const QString &fileName) { curFile = fileName; setWindowModified(false); /* dopóki dokument nie zostanie zmodyfikowany, ustawiamy zmiany na false */ QString shownName = "Untitled"; if (!curFile.isEmpty()) shownName = strippedName(curFile); /* wywołanie skracania nazwy pliku do niezbędnego minimum, aby wyświetlanie było jak najbardziej przejrzyste */ recentFiles.removeAll(curFile); /* usuwamy wystąpienia pliku w tablicy ostatnio otwartych plików, by uniknąć duplikacji... */ recentFiles.prepend(curFile); /* ... a następnie dodajemy nazwę pliku na początek tablicy ostatnio otwartych plików */ updateRecentFileActions(); // uaktualniamy wpisy w menu File } … mainwindow.cpp
36
Implementacja menu File
… setWindowTitle(tr("%1[*] - %2").arg(shownName).arg(tr("Spreadsheet"))); /* wyświetlanie * przy nazwie pliku w pasku tytułowym w przypadku, gdy nastąpiła modyfikacja pliku od ostatniego zapisu, bądź otwarcia */ /* metoda QString::arg() podmienia kolejno parametry %n w stringu swoimi argumentami. pierwsze wywołanie arg() zastąpi %1 nazwą pliku, a drugie - %2, tekstem "Spreadsheet". oczywiście można to napisać w prostszy sposób: setWindowTitle(shownName + tr("[*] - Spreadsheet")); ale korzystając z arg() ułatwiamy ew. tłumaczenie programu na inne języki. */ } mainwindow.cpp
37
Implementacja menu File
… void MainWindow::updateRecentFileActions() { // wykorzystamy iterator do usuwania nieistniejących plików z recentFiles (klasy QStringList) QMutableStringListIterator i(recentFiles); while (i.hasNext()) if (!QFile::exists(i.next())) i.remove(); } mainwindow.cpp
38
Implementacja menu File
… for (int j = 0; j < MaxRecentFiles; ++j) { if (j < recentFiles.count()) { /* przypisujemy kolejnym plikom stringi składające się ze znaku &,liczby porządkowej(j+1), spacji oraz nazwy pliku. */ QString text = tr("&%1 %2").arg(j + 1).arg(strippedName(recentFiles[j])); recentFileActions[j]->setText(text); /* nadajemy odpowiednim akcjom nazwy, które wygenerowaliśmy ... */ recentFileActions[j]->setData(recentFiles[j]); /* ... oraz przypisujemy im wartości -> odpowiednie korespondujące wartości z recentFiles */ recentFileActions[j]->setVisible(true); } else { recentFileActions[j]->setVisible(false); /* chowamy pozostałe pola w recentFileActions, w przypadku, gdy nie ma wystarczającej ilości ostatnio otwartych plików, by w całości zapełnić tablicę. } separatorAction->setVisible(!recentFiles.isEmpty()); /* dodajemy widoczny separator w przypadku, gdy są ostatnio otwarte pliki */ } … mainwindow.cpp
39
Implementacja menu File
… void MainWindow::openRecentFile() { if (okToContinue()) { QAction *action = qobject_cast<QAction *>(sender()); /* metoda ta wykonuje dynamiczne rzutowanie na meta-informację wygenerowaną przez moc - meta-obiektowy kompilator Qt. zwraca wskaźnik na wskazaną podklasę QObject, lub 0 jeśli nie można rzutować obiektu w ten sopsób. */ if (action) // jeśli rzutowanie się powiodło... loadFile(action->data().toString()); /*... wywołujemy metodę loadFile() z pełną ścieżką do pliku, którą wyłuskalismy z miejsca na które wskazuje action. */ } } … Tak oto prezentuje się nasze menu File: mainwindow.cpp
40
Używanie okien dialogowych
Zaczniemy od okna dialogowego Find z poprzedniego rozdziału. Chcemy, żeby użytkownik mógł bez problemu przełączać się z okna głównego aplikacji do okna Find i z powrotem. Nasze okno Find musi być modeless(niemodalne?) tzn. takie, które działa niezależnie od pozostałych okien danej aplikacji. Jak łatwo można się domyśleć, okno Find pozwala użytkownikowi wyszukać wpisany tekst w arkuszu kalkulacyjnym. Slot find() jest wywoływany, gdy użytkownik wybierze Edit|Find i wówczas pojawia się okno dialogowe Find. Możliwe są 3 sytuacje: 1. Użytkownik uruchomił po raz pierwszy Find 2. Użytkownik uruchomił już wcześniej Find, ale później zamknął. 3. Użytkownik uruchomił Find, które było już uruchomione. Jeśli Find nie było uruchomione, tworzymy je i łączymy jego sygnaly findNext() i findPrevious() z odpowiednimi slotami w Spreadsheet. Na koniec wywołujemy show() i activateWindow() by się upewnić że okno jest widoczne i aktywne. mainwindow.cpp
41
Używanie okien dialogowych
… void MainWindow::find() { if (!findDialog) findDialog = new FindDialog(this); connect(findDialog, SIGNAL (findNext(const QString &, Qt::CaseSensitivity)), spreadsheet, SLOT (findNext(const QString &, Qt::CaseSensitivity))); connect(findDialog, SIGNAL(findPrevious(const QString &, Qt::CaseSensitivity)), spreadsheet, SLOT(findPrevious(const QString &,Qt::CaseSensitivity))); } findDialog->show(); findDialog->activateWindow(); mainwindow.cpp
42
Używanie okien dialogowych
… void MainWindow::goToCell() { GoToCellDialog dialog(this); /* tworzymy zmienną na stosie, która po wykorzystaniu zostanie automatycznie usunięta */ if (dialog.exec()) /* uruchamiamy okno Go To Cell; metoda exec() zwraca true w przypadku kliknięcia OK i false w przypadku Cancel */ QString str = dialog.lineEdit->text().toUpper(); /* pobieramy zawartość okna dialogowego Go To Cell... */ spreadsheet->setCurrentCell(str.mid(1).toInt() - 1, // numer wiersza str[0].unicode() - 'A' ); // nr kolumny // gdzie np. A1 = (0,0), B27 = (26,1) //... i przenosimy się w głównym oknie do odpowiadającej komórki arkusza // mamy pewność że wartość w oknie jest poprawna, bo wykorzystaliśmy w niej specjalny filtr wprowadzanych danych. } mainwindow.cpp
43
Używanie okien dialogowych
… void MainWindow::about() { QMessageBox::about(this, tr("About Spreadsheet"), tr("<h2>Spreadsheet 1.1</h2>" "<p>Copyright © 2006 Software Inc." "<p>Spreadsheet is a small application that " "demonstrates QAction, QMainWindow, QMenuBar, " "QStatusBar, QTableWidget, QToolBar, and many other " "Qt classes.")); /* metoda about() jest bardzo podobna do warning() za wyjątkiem tego, że wykorzystuje ikonkę z okna nadrzędnego, zamiast standardowej ikonki "ostrzeżenia". */ } .. mainwindow.cpp
44
Przechowywanie ustawień programu
Przy uruchamianiu programu, w konstruktorze MainWindow korzystaliśmy z metody readSettings(), aby wczytać zapisane ustawienia programu. Podobnie zamykając program, w closeEvent(), wywoływaliśmy writeSettings(). Teraz napiszemy te metody: … void MainWindow::writeSettings() { QSettings settings("Software Inc.", "Spreadsheet"); /* domyślnie QSettings są przechowywane w różnych miejscach zależnych od platformy systemowej. W Windows jest to rejestr, w Unixie w plikach tekstowych, a w Mac OS X wykorzystywane jest Core Foundation Preferences API. Później na podstawie danych w konstruktorze będziemy mogli odnaleźć ustawienia w systemie i je wczytać. Opcje przechowywane są w postaci pary (nazwa,wartość) */ settings.setValue("geometry", geometry()); // zapisujemy pozycję i rozmiar okna głównego settings.setValue("recentFiles", recentFiles); // zapisujemy listę ostatnio otwieranych plików settings.setValue("showGrid", showGridAction->isChecked()); /* zapisujemy ustawienie showGrid, jako true lub false */ settings.setValue("autoRecalc", autoRecalcAction->isChecked()); /* jw tylko odnośnie opcji autoRecalc */ } mainwindow.cpp
45
Przechowywanie ustawień programu
… void MainWindow::readSettings() { QSettings settings("Software Inc.", "Spreadsheet"); QRect rect = settings.value("geometry", // wczytujemy dane o ostatnim położeniu i rozmiarze okna… QRect(200, 200, 400, 400)).toRect(); /*... lub gdy nie ma zapisanych ustawień wykorzystujemy domyślnie te ustawienia podane jako drugi parametr move(rect.topLeft()); // ustawiamy pozycję okna na podstawie posiadanych danych resize(rect.size()); // ustawiamy rozmiar okna na podstawie posiadanych danych recentFiles = settings.value("recentFiles").toStringList(); updateRecentFileActions(); bool showGrid = settings.value("showGrid", true).toBool(); showGridAction->setChecked(showGrid); bool autoRecalc = settings.value("autoRecalc", true).toBool(); autoRecalcAction->setChecked(autoRecalc); } Ukończyliśmy implementację okna głównego!! Możemy przejść teraz implementacji programu głównego mainwindow.cpp
46
Obsługa wielu dokumentów jednocześnie
main.cpp #include <QApplication> #include "mainwindow.h" /* tak skonstruowana główna fukcja pozwala aplikacji uruchomić tylko jedno okno główne i obsłużyć jeden dokument na raz. */ int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow mainWin; // tworzymy okno, jako zmienną na stosie mainWin.show(); return app.exec(); } main.cpp
47
Obsługa wielu dokumentów jednocześnie
Możemy uruchomić po prostu wiele razy naszą aplikację, aby obsłużyła wiele plików "naraz", ale nie jest to zbyt wygodne. Wolelibyśmy, aby w jednej instancji naszego programu można było uruchomić wiele okien, w których moglibyśmy edytować dokumenty - podobnie jak w przeglądarkach internetowych. Jednak żeby to osiągnąć musimy zmodyfikować nasz program... main.cpp int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow *mainWin = new MainWindow; /* wykorzystujemy new do stworzenia okna. później wykorzystując delete będziemy mogli usunąć okna które zamykamy */ mainWin->show(); return app.exec(); } main.cpp
48
Obsługa wielu dokumentów jednocześnie
Zmiany w Menu File: 1. File|New będzie tworzyć nowe okno główne z pustym dokumentem, zamiast wykorzystywać istniejące okno 2. File|Close będzie zamykać aktualne okno główne 3. File|Exit będzie zamykać wszystkie okna mainwindow.cpp void MainWindow::newFile() { MainWindow *mainWin = new MainWindow; /* nie musimy zapisywać nigdzie wskaźnika do nowego okna, ponieważ Qt obserwuje dla nas wszystkie okna. */ mainWin->show(); } Nowe menu File: mainwindow.cpp
49
Obsługa wielu dokumentów jednocześnie
// dodajemy akcje dla Close i modyfikujemy Exit void MainWindow::createActions() { ... closeAction = new QAction(tr("&Close"), this); closeAction->setShortcut(tr("Ctrl+W")); closeAction->setStatusTip(tr("Close this window")); connect(closeAction, SIGNAL(triggered()), this, SLOT(close())); /* dodajemy akcję zamknięcia pojedynczego okna */ exitAction = new QAction(tr("E&xit"), this); exitAction->setShortcut(tr("Ctrl+Q")); exitAction->setStatusTip(tr("Exit the application")); connect(exitAction, SIGNAL(triggered()), qApp, SLOT(closeAllWindows())); /* zamykamy wszystkie okna przy wywołaniu Exit */ } mainwindow.cpp
50
Obsługa wielu dokumentów jednocześnie
To jednak nie wszystko. Problemem może stać się fakt, iż użytkownik zechce otworzyć ogromną ilość okien, a my nigdzie nie zwalniamy pamięci po zamkniętych oknach, gdyż domyślnym zachowaniem programu jest ukrycie tych okien. Wykorzystamy atrybut Qt::WA_DeleteOnClose w konstruktorze okna głównego... mainwindow.cpp MainWindow::MainWindow() { ... setAttribute(Qt::WA_DeleteOnClose); // przy zamknięciu okna, niszczymy obiekt i zwalniamy pamięć } Jednak wycieki pamięci to nie jedyny problem z jakim musimy się zmierzyć. Każde okno ma własne ustawienia i własne ostatnio otwarte pliki. Oczywiste jest, że lista ostatnio otwartych plików powinna być globalna w całej aplikacji. Możemy to łatwo osiągnąć poprzez dodanie właściwości static przy deklaracji zmiennej recentFiles. Pozostaje jeszcze upewnić się, że kiedykolwiek nie wywołamy updateRecentFileActions() by uaktualnić menu File, metoda ta wywoła się dla wszystkich okien... mainwindow.cpp
51
Obsługa wielu dokumentów jednocześnie
/* podobną składnię możemy wykorzystać przy synchronizacji okien względem opcji Show Grid i Auto-Recalculate */ foreach (QWidget *win, QApplication::topLevelWidgets()) { if (MainWindow *mainWin = qobject_cast<MainWindow *>(win)) mainWin->updateRecentFileActions(); } Aplikacje, które pozwalają na obsługę tylko jednego dokumentu naraz określane są jako aplikacje SDI (single document interface). Alternatywą dla nich są aplikacje MDI (multiple document interface), gdzie aplikacja ma swoje główne okno, w którym zarządza wieloma otwartymi oknami dokumentów. Qt pozwala na łatwe tworzenie zarówno SDI, jak i MDI, na wszystkie obsługujące je platformy systemowe. SDI MDI
52
Splash Screen Splash screen to określenie na pewną grafikę, która pojawia się na ekranie podczas uruchamiania programu. Wiele aplikacji posiada splash screeny. Często programiści używają ich, by zamaskować powolne uruchamianie programu, a inni, by po prostu ubarwić program. Dodawanie splash screenów w Qt jest niezwykle proste - wykorzystamy klasę QSplashScreen. Klasa QSplashScreen wyświetla obrazek zanim pojawi się okno główne programu. Może również wyświetlać napisy na obrazku, aby np. informować użytkownika o postępie w inicjalizacji programu. Zazwyczaj kod splash screena umieszczony jest w funkcji main(), przed wywołaniem QApplication::exec()
53
Splash Screen Main.cpp int main(int argc, char *argv[]) {
QApplication app(argc, argv); QSplashScreen *splash = new QSplashScreen; splash->setPixmap(QPixmap(":/images/splash.png")); // wczytujemy grafikę do splash screena … splash->show(); // ... i wyświetlamy Qt::Alignment topRight = Qt::AlignRight | Qt::AlignTop; splash->showMessage(QObject::tr("Setting up the main window..."), topRight, Qt::white); // wyświetlamy napis o aktualnym stanie inicjalizacji programu MainWindow mainWin; splash->showMessage(QObject::tr("Loading modules..."), topRight, Qt::white); loadModules(); splash->showMessage(QObject::tr("Establishing connections..."), establishConnections(); … main.cpp
54
I to by było na tyle odnośnie 3. rozdziału…
Splash Screen … mainWin.show(); splash->finish(&mainWin); delete splash; return app.exec(); } I to by było na tyle odnośnie 3. rozdziału… main.cpp
55
The End; // That’s all folks!
main.cpp
Podobne prezentacje
© 2024 SlidePlayer.pl Inc.
All rights reserved.