<?xml version="1.0" encoding="iso-8859-1"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <!-- /home/gvatteka/dev/qtjambi/4.3/scripts/../doc/src/examples/application.qdoc --> <head> <title>Application Example</title> <link href="classic.css" rel="stylesheet" type="text/css" /> </head> <body> <h1 align="center">Application Example<br /><small></small></h1> <p>The Application example shows how to implement a standard GUI application with menus, toolbars, and a status bar. The example itself is a simple text editor program built around <a href="gui/QTextEdit.html"><tt>QTextEdit</tt></a>.</p> <p align="center"><img src="images/application.png" alt="Screenshot of the Application example" /></p><p>Nearly all of the code for the Application example is in the <tt>MainWindow</tt> class, which inherits <a href="gui/QMainWindow.html"><tt>QMainWindow</tt></a>. <a href="gui/QMainWindow.html"><tt>QMainWindow</tt></a> provides the framework for windows that have menus, toolbars, dock windows, and a status bar. The application provides <b>File</b>, <b>Edit</b>, and <b>Help</b> entries in the menu bar, with the following popup menus:</p> <p align="center"><img src="images/application-menus.png" alt="The Application example's menu system" /></p><p>The status bar at the bottom of the main window shows a description of the menu item or toolbar button under the cursor.</p> <p>To keep the example simple, recently opened files aren't shown in the <b>File</b> menu, even though this feature is desired in 90% of applications. The Recent Files</tt> example shows how to implement this. Furthermore, this example can only load one file at a time. The SDI</tt> and MDI</tt> examples shows how to lift these restrictions.</p> <a name="importing-the-qt-classes"></a> <h2>Importing the Qt Classes</h2> <pre> import com.trolltech.qt.QVariant; import com.trolltech.qt.core.*; import com.trolltech.qt.gui.*;</pre> <p>We start by importing all the classes in the <tt>QtCore</tt> and <tt>QtGui</tt> modules. This saves us from the trouble of having to import every class we use individually.</p> <a name="application-class-implementation"></a> <h2>Application Class Implementation</h2> <p>The entire example is implemented in a single subclass of <a href="gui/QMainWindow.html"><tt>QMainWindow</tt></a>:</p> <pre> public class Application extends QMainWindow { private String curFile; private QTextEdit textEdit; private QMenu fileMenu; private QMenu editMenu; private QMenu helpMenu; private QToolBar fileToolBar; private QToolBar editToolBar; private QAction newAct; private QAction openAct; private QAction saveAct; private QAction saveAsAct; private QAction exitAct; private QAction cutAct; private QAction copyAct; private QAction pasteAct; private QAction aboutAct; private QAction aboutQtAct; private String rsrcPath = "classpath:com/trolltech/images";</pre> <p>We declare various variables for widgets, menus, toolbars, and actions that will be explained later.</p> <p>In the constructor, we start by creating a menu bar and setting it as the menu bar for the main window:</p> <pre> public Application() { QMenuBar menuBar = new QMenuBar(); setMenuBar(menuBar);</pre> <p>We also create a <a href="gui/QTextEdit.html"><tt>QTextEdit</tt></a> widget, and call setCentralWidget() to make it occupy the central area of the main window, between the toolbars and the status bar.</p> <p><a href="gui/QAction.html">Actions</tt></a> are usually reused in menus and toolbars to provide a consistent user interface. The actions used in the main window are set up before any of the standard menus, toolbars or other window elements.</p> <pre> try { createActions(); } catch (Exception e) { e.printStackTrace(); } createMenus(); createToolBars(); createStatusBar();</pre> <p>We call <tt>createActions()</tt>, <tt>createMenus()</tt>, <tt>createToolBars()</tt>, and <tt>createStatusBar()</tt>, four private methods that set up the user interface.</p> <pre> textEdit.document().contentsChanged.connect(this, "documentWasModified()"); setCurrentFile(""); }</pre> <p>We establish a signal-slot connection between the <a href="gui/QTextEdit.html"><tt>QTextEdit</tt></a>'s document object and our <tt>documentWasModified()</tt> slot. Whenever the user modifies the text in the <a href="gui/QTextEdit.html"><tt>QTextEdit</tt></a>, we want to update the title bar to show that the file was modified.</p> <p>We reimplement QWidget::closeEvent() to detect when the user attempts to close the window, and warn the user about unsaved changes:</p> <a name="close-event-handler"></a><pre> public void closeEvent(QCloseEvent event) { if (maybeSave()) { writeSettings(); event.accept(); } else { event.ignore(); } }</pre> <p>When the user attempts to close the window, we call the private method <tt>maybeSave()</tt> to give the user the possibility to save pending changes. The method returns true if the user wants the application to close; otherwise, it returns false. In the first case, we save the user's preferences to disk and accept the close event; in the second case, we ignore the close event, meaning that the application will stay up and running as if nothing happened.</p> <pre> public void newFile() { if (maybeSave()) { textEdit.clear(); setCurrentFile(""); } }</pre> <p>The <tt>newFile()</tt> slot is invoked when the user selects <b>File|New</b> from the menu. We call <tt>maybeSave()</tt> to save any pending changes and if the user accepts to go on, we clear the <a href="gui/QTextEdit.html"><tt>QTextEdit</tt></a> and call the private method <tt>setCurrentFile()</tt> to update the window title and clear the windowModified flag.</p> <pre> public void open() { if (maybeSave()) { String fileName = QFileDialog.getOpenFileName(this); if (fileName.length() != 0) loadFile(fileName); } }</pre> <p>The <tt>open()</tt> slot is invoked when the user clicks <b>File|Open</b>. We pop up a <a href="gui/QFileDialog.html"><tt>QFileDialog</tt></a> asking the user to choose a file. If the user chooses a file (i.e., <tt>fileName</tt> is not an empty string), we call the private method <tt>loadFile()</tt> to actually load the file.</p> <pre> public boolean save() { if (curFile.length() == 0) { return saveAs(); } else { return saveFile(curFile); } }</pre> <p>The <tt>save()</tt> slot is invoked when the user clicks <b>File|Save</b>. If the user hasn't provided a name for the file yet, we call <tt>saveAs()</tt>; otherwise, we call the private method <tt>saveFile()</tt> to actually save the file.</p> <pre> public boolean saveAs() { String fileName = QFileDialog.getSaveFileName(this); if (fileName.length() == 0) return false; return saveFile(fileName); }</pre> <p>In <tt>saveAs()</tt>, we start by popping up a <a href="gui/QFileDialog.html"><tt>QFileDialog</tt></a> asking the user to provide a name. If the user clicks <b>Cancel</b>, the returned file name is empty, and we do nothing.</p> <pre> public void about() { QMessageBox.about(this, tr("About Application"), tr("The <b>Application</b> example demonstrates how to " + "write modern GUI applications using Qt, with a menu bar, " + "toolbars, and a status bar.")); }</pre> <p>The application's About box is done using one statement, using the about() static method and relying on its support for an HTML subset.</p> <p>The tr() call around the literal string marks the string for translation. It is a good habit to call tr() on all user-visible strings, in case you later decide to translate your application to other languages. The Internationalization with Qt</tt> overview convers tr() in more detail.</p> <pre> public void documentWasModified() { setWindowModified(textEdit.document().isModified()); }</pre> <p>The <tt>documentWasModified()</tt> slot is invoked each time the text in the <a href="gui/QTextEdit.html"><tt>QTextEdit</tt></a> changes because of user edits. We call QWidget.</tt>setWindowModified() to make the title bar show that the file was modified. How this is done varies on each platform.</p> <pre> private void createActions() { newAct = new QAction(new QIcon(rsrcPath + "/new.png"), tr("&New"), this); newAct.setShortcut(new QKeySequence(tr("Ctrl+N"))); newAct.setStatusTip(tr("Create a new file")); newAct.triggered.connect(this, "newFile()"); openAct = new QAction(new QIcon(rsrcPath + "/open.png"), tr("&Open..."), this); openAct.setShortcut(tr("Ctrl+O")); openAct.setStatusTip(tr("Open an existing file")); openAct.triggered.connect(this, "open()"); ... aboutQtAct = new QAction(tr("About &Qt"), this); aboutQtAct.setStatusTip(tr("Show the Qt library's About box")); aboutQtAct.triggered.connect(QApplication.instance(), "aboutQt()");</pre> <p>The <tt>createActions()</tt> private method, which is called from the <tt>MainWindow</tt> constructor, creates <a href="gui/QAction.html"><tt>QAction</tt></a>s. The code is very repetitive, so we show only the actions corresponding to <b>File|New</b>, <b>File|Open</b>, and <b>Help|About Qt</b>.</p> <p>A <a href="gui/QAction.html"><tt>QAction</tt></a> is an object that represents one user action, such as saving a file or invoking a dialog. An action can be put in a <a href="gui/QMenu.html"><tt>QMenu</tt></a> or a <a href="gui/QToolBar.html"><tt>QToolBar</tt></a>, or both, or in any other widget that reimplements QWidget::actionEvent().</p> <p>An action has a text that is shown in the menu, an icon, a shortcut key, a tooltip, a status tip (shown in the status bar), a "What's This?" text, and more. It emits a QAction.</tt>triggered() signal whenever the user invokes the action (e.g., by clicking the associated menu item or toolbar button). We connect this signal to a slot that performs the actual action.</p> <p>The code above contains one more idiom that must be explained. For some of the actions, we specify an icon as a <a href="gui/QIcon.html"><tt>QIcon</tt></a> to the <a href="gui/QAction.html"><tt>QAction</tt></a> constructor. The <a href="gui/QIcon.html"><tt>QIcon</tt></a> constructor takes the file name of an image that it tries to load. Here, the file name starts with <tt>:</tt>. Such file names aren't ordinary file names, but rather path in the executable's stored resources. We'll come back to this when we review the <tt>application.qrc</tt> file that's part of the project.</p> <pre> cutAct.setEnabled(false); copyAct.setEnabled(false); textEdit.copyAvailable.connect(cutAct, "setEnabled(boolean)"); textEdit.copyAvailable.connect(copyAct, "setEnabled(boolean)"); }</pre> <p>The <b>Edit|Cut</b> and <b>Edit|Copy</b> actions must be available only when the <a href="gui/QTextEdit.html"><tt>QTextEdit</tt></a> contains selected text. We disable them by default and connect the QTextEdit.</tt>copyAvailable() signal to the action's setEnabled() slot, ensuring that the actions are disabled when the text editor has no selection.</p> <pre> private void createMenus() { fileMenu = menuBar().addMenu(tr("&File")); fileMenu.addAction(newAct); fileMenu.addAction(openAct); fileMenu.addAction(saveAct); fileMenu.addAction(saveAsAct); fileMenu.addSeparator(); fileMenu.addAction(exitAct); editMenu = menuBar().addMenu(tr("&Edit")); editMenu.addAction(cutAct); editMenu.addAction(copyAct); editMenu.addAction(pasteAct); menuBar().addSeparator(); helpMenu = menuBar().addMenu(tr("&Help")); helpMenu.addAction(aboutAct); helpMenu.addAction(aboutQtAct); }</pre> <p>Creating actions isn't sufficient to make them available to the user; we must also add them to the menu system. This is what <tt>createMenus()</tt> does. We create a <b>File</b>, an <b>Edit</b>, and a <b>Help</b> menu. QMainWindow.</tt>menuBar() lets us access the window's menu bar widget. We don't have to worry about creating the menu bar ourselves; the first time we call this method, the <a href="gui/QMenuBar.html"><tt>QMenuBar</tt></a> is created.</p> <p>Just before we create the <b>Help</b> menu, we call QMenuBar.</tt>addSeparator(). This has no effect for most widget styles (e.g., Windows and Mac OS X styles), but for Motif-based styles this makes sure that <b>Help</b> is pushed to the right side of the menu bar. Try running the application with various styles and see the results:</p> <pre> application -style=windows application -style=motif application -style=cde</pre> <p>Let's now review the toolbars:</p> <pre> private void createToolBars() { fileToolBar = addToolBar(tr("File")); fileToolBar.addAction(newAct); fileToolBar.addAction(openAct); fileToolBar.addAction(saveAct); editToolBar = addToolBar(tr("Edit")); editToolBar.addAction(cutAct); editToolBar.addAction(copyAct); editToolBar.addAction(pasteAct); }</pre> <p>Creating toolbars is very similar to creating menus. The same actions that we put in the menus can be reused in the toolbars.</p> <pre> private void createStatusBar() { statusBar().showMessage(tr("Ready")); }</pre> <p>QMainWindow::statusBar() returns a pointer to the main window's <a href="gui/QStatusBar.html"><tt>QStatusBar</tt></a> widget. Like with <tt>QMainWindow::menuBar</tt>, the widget is automatically created the first time the method is called.</p> <pre> private boolean maybeSave() { if (textEdit.document().isModified()) { QMessageBox.StandardButton ret = QMessageBox.warning(this, tr("Application"), tr("The document has been modified.\n" + "Save your changes?"), new QMessageBox.StandardButtons(QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.Discard, QMessageBox.StandardButton.Cancel)); if (ret == QMessageBox.StandardButton.Ok) { return save(); } else if (ret == QMessageBox.StandardButton.Cancel) { return false; } } return true; }</pre> <p>The <tt>maybeSave()</tt> method is called to save pending changes. If there are pending changes, it pops up a <a href="gui/QMessageBox.html"><tt>QMessageBox</tt></a> giving the user to save the document. The options are QMessageBox.Yes</tt>, QMessageBox.No</tt>, and QMessageBox.Cancel</tt>. The <b>Yes</b> button is made the default button (the button that is invoked when the user presses <b>Return</b>) using the QMessageBox.Default</tt> flag; the <b>Cancel</b> button is made the escape button (the button that is invoked when the user presses <b>Esc</b>) using the QMessageBox.Escape</tt> flag.</p> <p>The <tt>maybeSave()</tt> method returns <tt>true</tt> in all cases, except when the user clicks <b>Cancel</b>. The caller must check the return value and stop whatever it was doing if the return value is <tt>false</tt>.</p> <pre> public void loadFile(String fileName) { QFile file = new QFile(fileName); if (!file.open(new QFile.OpenMode(QFile.OpenModeFlag.ReadOnly, QFile.OpenModeFlag.Text))) { QMessageBox.warning(this, tr("Application"), String.format(tr("Cannot read file %1$s:\n%2$s."), fileName, file.errorString())); return; } QTextStream in = new QTextStream(file); QApplication.setOverrideCursor(new QCursor(Qt.CursorShape.WaitCursor)); textEdit.setPlainText(in.readAll()); QApplication.restoreOverrideCursor(); setCurrentFile(fileName); statusBar().showMessage(tr("File loaded"), 2000); }</pre> <p>In <tt>loadFile()</tt>, we use <a href="core/QFile.html"><tt>QFile</tt></a> and <a href="core/QTextStream.html"><tt>QTextStream</tt></a> to read in the data. The <a href="core/QFile.html"><tt>QFile</tt></a> object provides access to the bytes stored in a file.</p> <p>We start by opening the file in read-only mode. The QFile::Text flag indicates that the file is a text file, not a binary file. On Unix and Mac OS X, this makes no difference, but on Windows, it ensures that the "\r\n" end-of-line sequence is converted to "\n" when reading.</p> <p>If we successfully opened the file, we use a <a href="core/QTextStream.html"><tt>QTextStream</tt></a> object to read in the data. <a href="core/QTextStream.html"><tt>QTextStream</tt></a> automatically converts the 8-bit data into a Unicode <a href="porting4.html#qstring"><tt>QString</tt></a> and supports various encodings. If no encoding is specified, <a href="core/QTextStream.html"><tt>QTextStream</tt></a> assumes the file is written using the system's default 8-bit encoding (for example, Latin-1; see QTextCodec.</tt>codecForLocale() for details).</p> <p>Since the call to QTextStream.</tt>readAll() might take some time, we set the cursor to be Qt.</tt>WaitCursor for the entire application while it goes on.</p> <p>At the end, we call the private <tt>setCurrentFile()</tt> method, which we'll cover in a moment, and we display the string "File loaded" in the status bar for 2 seconds (2000 milliseconds).</p> <pre> public boolean saveFile(String fileName) { QFile file = new QFile(fileName); if (!file.open(new QFile.OpenMode(QFile.OpenModeFlag.WriteOnly, QFile.OpenModeFlag.Text))) { QMessageBox.warning(this, tr("Application"), String.format(tr("Cannot write file %1$s:\n%2$s."), fileName, file.errorString())); return false; } QTextStream out = new QTextStream(file); QApplication.setOverrideCursor(new QCursor(Qt.CursorShape.WaitCursor)); out.writeString(textEdit.toPlainText()); QApplication.restoreOverrideCursor(); setCurrentFile(fileName); statusBar().showMessage(tr("File saved"), 2000); file.close(); return true; }</pre> <p>Saving a file is very similar to loading one. Here, the QFile.Text</tt> flag ensures that on Windows, "\n" is converted into "\r\n" to conform to the Windows convension.</p> <pre> public void setCurrentFile(String fileName) { curFile = fileName; textEdit.document().setModified(false); setWindowModified(false); String shownName; if (curFile.length() == 0) shownName = "untitled.txt"; else shownName = strippedName(curFile); setWindowTitle(String.format(tr("%1$s[*] - %2$s"), shownName, tr("Application"))); }</pre> <p>The <tt>setCurrentFile()</tt> method is called to reset the state of a few variables when a file is loaded or saved, or when the user starts editing a new file (in which case <tt>fileName</tt> is empty). We update the <tt>curFile</tt> variable, clear the QTextDocument.modified</tt> flag and the associated <tt>QWidget.windowModified</tt> flag, and update the window title to contain the new file name (or <tt>untitled.txt</tt>).</p> <p>The <tt>strippedName()</tt> method call around <tt>curFile</tt> in the setWindowTitle() call shortens the file name to exclude the path. Here's the method:</p> <pre> private static String strippedName(String fullFileName) { return new QFileInfo(fullFileName).fileName(); }</pre> <a name="the-main-function"></a> <h2>The main() Function</h2> <p>The <tt>main()</tt> function for this application is typical of applications that contain one main window:</p> <pre> public static void main(String[] args) { QApplication.initialize(args); Application application = new Application(); application.show(); QApplication.exec(); }</pre> <p /><address><hr /><div align="center"> <table width="100%" cellspacing="0" border="0"><tr class="address"> <td width="30%">Copyright © 2007 <a href="trolltech.html">Trolltech</a></td> <td width="40%" align="center"><a href="trademarks.html">Trademarks</a></td> <td width="30%" align="right"><div align="right">Qt Jambi </div></td> </tr></table></div></address></body> </html>