MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent), scribbleArea(new ScribbleArea(this))
{
setCentralWidget(scribbleArea);
createActions();
createMenus();
setWindowTitle(tr("Scribble"));
resize(500, 500);
}
In the constructor, we create a scribble area which we make the central widget of the
MainWindow
widget. Then we create the associated actions and menus.
void MainWindow::closeEvent(QCloseEvent *event)
{
if (maybeSave())
event->accept();
else
event->ignore();
}
Close events are sent to widgets that the users want to close, usually by clicking File|Exit or by clicking the X title bar button. By reimplementing the event handler, we can intercept attempts to close the application.
In this example, we use the close event to ask the user to save any unsaved changes. The logic for that is located in the
maybeSave()
function. If
maybeSave()
returns true, there are no modifications or the users successfully saved them, and we accept the event. The application can then terminate normally. If
maybeSave()
returns false, the user clicked Cancel, so we “ignore” the event, leaving the application unaffected by it.
void MainWindow::open()
{
if (maybeSave()) {
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open File"), QDir::currentPath());
if (!fileName.isEmpty())
scribbleArea->openImage(fileName);
}
}
在
open()
slot we first give the user the opportunity to save any modifications to the currently displayed image, before a new image is loaded into the scribble area. Then we ask the user to choose a file and we load the file in the
ScribbleArea
.
void MainWindow::save()
{
QAction *action = qobject_cast<QAction *>(sender());
QByteArray fileFormat = action->data().toByteArray();
saveFile(fileFormat);
}
save()
slot is called when the users choose the Save As menu entry, and then choose an entry from the format menu. The first thing we need to do is to find out which action sent the signal using
sender()
. This function returns the sender as a
QObject
pointer. Since we know that the sender is an action object, we can safely cast the
QObject
. We could have used a C-style cast or a C++
static_cast<>()
, but as a defensive programming technique we use a
qobject_cast()
. The advantage is that if the object has the wrong type, a null pointer is returned. Crashes due to null pointers are much easier to diagnose than crashes due to unsafe casts.
Once we have the action, we extract the chosen format using
data()
. (When the actions are created, we use
setData()
to set our own custom data attached to the action, as a
QVariant
. More on this when we review
createActions()
.)
Now that we know the format, we call the private
saveFile()
function to save the currently displayed image.
void MainWindow::penColor()
{
QColor newColor = QColorDialog::getColor(scribbleArea->penColor());
if (newColor.isValid())
scribbleArea->setPenColor(newColor);
}
我们使用
penColor()
slot to retrieve a new color from the user with a
QColorDialog
. If the user chooses a new color, we make it the scribble area’s color.
void MainWindow::penWidth()
{
bool ok;
int newWidth = QInputDialog::getInt(this, tr("Scribble"),
tr("Select pen width:"),
scribbleArea->penWidth(),
1, 50, 1, &ok);
if (ok)
scribbleArea->setPenWidth(newWidth);
}
To retrieve a new pen width in the
penWidth()
slot, we use
QInputDialog
。
QInputDialog
class provides a simple convenience dialog to get a single value from the user. We use the static
getInt()
function, which combines a
QLabel
和
QSpinBox
。
QSpinBox
is initialized with the scribble area’s pen width, allows a range from 1 to 50, a step of 1 (meaning that the up and down arrow increment or decrement the value by 1).
The boolean
ok
variable will be set to
true
if the user clicked OK and to
false
if the user pressed Cancel.
void MainWindow::about()
{
QMessageBox::about(this, tr("About Scribble"),
tr("<p>The <b>Scribble</b> example shows how to use QMainWindow as the "
"base widget for an application, and how to reimplement some of "
"QWidget's event handlers to receive the events generated for "
"the application's widgets:</p><p> We reimplement the mouse event "
"handlers to facilitate drawing, the paint event handler to "
"update the application and the resize event handler to optimize "
"the application's appearance. In addition we reimplement the "
"close event handler to intercept the close events before "
"terminating the application.</p><p> The example also demonstrates "
"how to use QPainter to draw an image in real time, as well as "
"to repaint widgets.</p>"));
}
We implement the
about()
slot to create a message box describing what the example is designed to show.
void MainWindow::createActions()
{
openAct = new QAction(tr("&Open..."), this);
openAct->setShortcuts(QKeySequence::Open);
connect(openAct, &QAction::triggered, this, &MainWindow::open);
const QList<QByteArray> imageFormats = QImageWriter::supportedImageFormats();
for (const QByteArray &format : imageFormats) {
QString text = tr("%1...").arg(QString::fromLatin1(format).toUpper());
QAction *action = new QAction(text, this);
action->setData(format);
connect(action, &QAction::triggered, this, &MainWindow::save);
saveAsActs.append(action);
}
printAct = new QAction(tr("&Print..."), this);
connect(printAct, &QAction::triggered, scribbleArea, &ScribbleArea::print);
exitAct = new QAction(tr("E&xit"), this);
exitAct->setShortcuts(QKeySequence::Quit);
connect(exitAct, &QAction::triggered, this, &MainWindow::close);
penColorAct = new QAction(tr("&Pen Color..."), this);
connect(penColorAct, &QAction::triggered, this, &MainWindow::penColor);
penWidthAct = new QAction(tr("Pen &Width..."), this);
connect(penWidthAct, &QAction::triggered, this, &MainWindow::penWidth);
clearScreenAct = new QAction(tr("&Clear Screen"), this);
clearScreenAct->setShortcut(tr("Ctrl+L"));
connect(clearScreenAct, &QAction::triggered,
scribbleArea, &ScribbleArea::clearImage);
aboutAct = new QAction(tr("&About"), this);
connect(aboutAct, &QAction::triggered, this, &MainWindow::about);
aboutQtAct = new QAction(tr("About &Qt"), this);
connect(aboutQtAct, &QAction::triggered, qApp, &QApplication::aboutQt);
}
在
createAction()
function we create the actions representing the menu entries and connect them to the appropriate slots. In particular we create the actions found in the Save As sub-menu. We use
supportedImageFormats()
to get a list of the supported formats (as a
QList
<
QByteArray
>).
Then we iterate through the list, creating an action for each format. We call
setData()
with the file format, so we can retrieve it later as
data()
. We could also have deduced the file format from the action’s text, by truncating the “…”, but that would have been inelegant.
void MainWindow::createMenus()
{
saveAsMenu = new QMenu(tr("&Save As"), this);
for (QAction *action : qAsConst(saveAsActs))
saveAsMenu->addAction(action);
fileMenu = new QMenu(tr("&File"), this);
fileMenu->addAction(openAct);
fileMenu->addMenu(saveAsMenu);
fileMenu->addAction(printAct);
fileMenu->addSeparator();
fileMenu->addAction(exitAct);
optionMenu = new QMenu(tr("&Options"), this);
optionMenu->addAction(penColorAct);
optionMenu->addAction(penWidthAct);
optionMenu->addSeparator();
optionMenu->addAction(clearScreenAct);
helpMenu = new QMenu(tr("&Help"), this);
helpMenu->addAction(aboutAct);
helpMenu->addAction(aboutQtAct);
menuBar()->addMenu(fileMenu);
menuBar()->addMenu(optionMenu);
menuBar()->addMenu(helpMenu);
}
在
createMenu()
function, we add the previously created format actions to the
saveAsMenu
. Then we add the rest of the actions as well as the
saveAsMenu
sub-menu to the File, Options and Help menus.
QMenu
class provides a menu widget for use in menu bars, context menus, and other popup menus. The
QMenuBar
class provides a horizontal menu bar with a list of pull-down
QMenu
s. At the end we put the File and Options menus in the
MainWindow
‘s menu bar, which we retrieve using the
menuBar()
函数。
bool MainWindow::maybeSave()
{
if (scribbleArea->isModified()) {
QMessageBox::StandardButton ret;
ret = QMessageBox::warning(this, tr("Scribble"),
tr("The image has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::Save | QMessageBox::Discard
| QMessageBox::Cancel);
if (ret == QMessageBox::Save)
return saveFile("png");
else if (ret == QMessageBox::Cancel)
return false;
}
return true;
}
在
mayBeSave()
, we check if there are any unsaved changes. If there are any, we use
QMessageBox
to give the user a warning that the image has been modified and the opportunity to save the modifications.
As with
QColorDialog
and
QFileDialog
, the easiest way to create a
QMessageBox
is to use its static functions.
QMessageBox
provides a range of different messages arranged along two axes: severity (question, information, warning and critical) and complexity (the number of necessary response buttons). Here we use the
warning()
function sice the message is rather important.
If the user chooses to save, we call the private
saveFile()
function. For simplicitly, we use PNG as the file format; the user can always press Cancel and save the file using another format.
maybeSave()
function returns
false
if the user clicks Cancel; otherwise it returns
true
.
bool MainWindow::saveFile(const QByteArray &fileFormat)
{
QString initialPath = QDir::currentPath() + "/untitled." + fileFormat;
QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"),
initialPath,
tr("%1 Files (*.%2);;All Files (*)")
.arg(QString::fromLatin1(fileFormat.toUpper()))
.arg(QString::fromLatin1(fileFormat)));
if (fileName.isEmpty())
return false;
return scribbleArea->saveImage(fileName, fileFormat.constData());
}
在
saveFile()
, we pop up a file dialog with a file name suggestion. The static
getSaveFileName()
function returns a file name selected by the user. The file does not have to exist.
范例工程 @ code.qt.io