このページでは

Scribble の例

「Scribble」のサンプルでは、QWidget のイベントハンドラの一部を再実装し、アプリケーションのウィジェットで生成されるイベントを受け取る方法を示しています。

描画機能を実装するためにマウスイベントハンドラを、アプリケーションを更新するためにペイントイベントハンドラを、アプリケーションの外観を最適化するためにリサイズイベントハンドラを、それぞれ再実装します。さらに、アプリケーションを終了する前に閉じるイベントをインターセプトするために、クローズイベントハンドラも再実装します。

また、このサンプルでは、QPainter を使用して画像をリアルタイムで描画したり、ウィジェットを再描画したりする方法も示しています。

「Scribble」の例のスクリーンショット

Scribble アプリケーションを使用すると、ユーザーは画像を描画することができます。File メニューを使用すると、既存の画像ファイルを開いて編集したり、画像を保存したり、アプリケーションを終了したりすることができます。描画中は、Options メニューから、ペンの色や太さを選択したり、画面をクリアしたりすることができます。さらに、Help メニューでは、Scribbleサンプルに関する具体的な情報や、Qt全般に関する情報を確認できます。

このサンプルは 2 つのクラスで構成されています。

  • ScribbleArea は、QImage を表示し、ユーザーがそこに描画できるようにするカスタムウィジェットです。
  • MainWindow は、ScribbleArea の上部にメニューを提供します。

まず、ScribbleArea クラスについて確認します。次に、ScribbleArea を使用するMainWindow クラスについて確認します。

ScribbleAreaクラスの定義

class ScribbleArea : public QWidget
{
    Q_OBJECT

public:
    ScribbleArea(QWidget *parent = nullptr);

    bool openImage(const QString &fileName);
    bool saveImage(const QString &fileName, const char *fileFormat);
    void setPenColor(const QColor &newColor);
    void setPenWidth(int newWidth);

    bool isModified() const { return modified; }
    QColor penColor() const { return myPenColor; }
    int penWidth() const { return myPenWidth; }

public slots:
    void clearImage();
    void print();

protected:
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;

private:
    void drawLineTo(const QPoint &endPoint);
    void resizeImage(QImage *image, const QSize &newSize);

    bool modified = false;
    bool scribbling = false;
    int myPenWidth = 1;
    QColor myPenColor = Qt::blue;
    QImage image;
    QPoint lastPoint;
};

ScribbleArea クラスはQWidget から継承されています。描画を実装するために、mousePressEvent() 、mouseMoveEvent() 、およびmouseReleaseEvent() 関数を再実装します。また、スクリブル領域を更新するためにpaintEvent() 関数を再実装し、描画対象となるQImage が常にウィジェットと同等以上のサイズであることを保証するためにresizeEvent() 関数を再実装します。

いくつかのパブリック関数が必要です。openImage() はファイルから画像を読み込んでスクライブ領域に表示し、ユーザーが画像を編集できるようにします。save() は現在表示されている画像をファイルに書き出します。clearImage() スロットは、スクライブ領域に表示されている画像をクリアします。実際に描画を行うためのプライベート関数drawLineTo() と、QImage のサイズを変更するためのresizeImage() が必要です。print() スロットは印刷処理を担当します。

また、以下のプライベート変数も必要です:

  • modified 「istrue 」は、落書き領域に表示されている画像に未保存の変更がある場合に真を返します。
  • scribbling ユーザーがスクリブル領域内でマウスの左ボタンを押している間は、true となります。
  • penWidth penColor は、アプリケーションで使用されるペンの現在設定されている幅と色を保持します。
  • image ユーザーが描いた画像を格納します。
  • lastPoint は、最後のマウスクリックまたはマウス移動イベント時のカーソルの位置を保持します。

ScribbleArea クラスの実装

ScribbleArea::ScribbleArea(QWidget *parent)
    : QWidget(parent)
{
    setAttribute(Qt::WA_StaticContents);
}

コンストラクタでは、ウィジェットのQt::WA_StaticContents 属性を設定し、ウィジェットの内容が左上隅を基準としており、ウィジェットのサイズ変更時に変化しないことを指定します。 Qt Widgetsはこの属性を使用して、サイズ変更時の描画イベントを最適化します。これはあくまで最適化のためのものであり、内容が静的で左上隅に固定されているウィジェットにのみ使用すべきです。

bool ScribbleArea::openImage(const QString &fileName)
{
    QImage loadedImage;
    if (!loadedImage.load(fileName))
        return false;

    QSize newSize = loadedImage.size().expandedTo(size());
    resizeImage(&loadedImage, newSize);
    image = loadedImage;
    modified = false;
    update();
    return true;
}

openImage() 関数では、指定された画像を読み込みます。次に、プライベート関数resizeImage() を使用して、読み込まれたQImage を、ウィジェットと同じ大きさ以上になるよう両方向でリサイズし、メンバー変数image に読み込まれた画像を設定します。最後に、QWidget::update()を呼び出して再描画をスケジュールします。

bool ScribbleArea::saveImage(const QString &fileName, const char *fileFormat)
{
    QImage visibleImage = image;
    resizeImage(&visibleImage, size());

    if (visibleImage.save(fileName, fileFormat)) {
        modified = false;
        return true;
    }
    return false;
}

saveImage() 関数は、実際のimage のうち表示されている部分のみをカバーするQImage オブジェクトを作成し、QImage::save()を使用してそれを保存します。画像の保存に成功した場合、未保存のデータがないため、落書き領域のmodified 変数をfalse に設定します。

void ScribbleArea::setPenColor(const QColor &newColor)
{
    myPenColor = newColor;
}

void ScribbleArea::setPenWidth(int newWidth)
{
    myPenWidth = newWidth;
}

setPenColor() およびsetPenWidth() 関数は、現在のペンの色と太さを設定します。これらの値は、今後の描画操作で使用されます。

void ScribbleArea::clearImage()
{
    image.fill(qRgb(255, 255, 255));
    modified = true;
    update();
}

パブリックのclearImage() スロットは、落書き領域に表示されている画像をクリアします。ここでは、画像全体をRGB値(255, 255, 255)に相当する白で塗りつぶすだけです。画像を変更する際、いつものようにmodified をtrue に設定し、再描画をスケジュールします。

void ScribbleArea::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton) {
        lastPoint = event->position().toPoint();
        scribbling = true;
    }
}

void ScribbleArea::mouseMoveEvent(QMouseEvent *event)
{
    if ((event->buttons() & Qt::LeftButton) && scribbling)
        drawLineTo(event->position().toPoint());
}

void ScribbleArea::mouseReleaseEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton && scribbling) {
        drawLineTo(event->position().toPoint());
        scribbling = false;
    }
}

マウスのクリックおよびクリック解除イベントについては、QMouseEvent::button() 関数を使用して、どのボタンがイベントを引き起こしたかを特定します。マウスの移動イベントについては、QMouseEvent::buttons() を使用して、現在どのボタンが押されているか(OR 演算による組み合わせとして)を特定します。

ユーザーがマウスの左ボタンを押した場合、マウスカーソルの位置をlastPoint に格納します。また、ユーザーが現在落書き中であることも記録します。(scribbling 変数が必要な理由は、マウス移動やマウス離しイベントの前に、必ずしも同じウィジェットでのマウス押下イベントが発生するとは限らないためです。)

ユーザーが左ボタンを押したままマウスを移動させたり、ボタンを離したりした場合は、非公開関数drawLineTo() を呼び出して描画を行います。

void ScribbleArea::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    QRect dirtyRect = event->rect();
    painter.drawImage(dirtyRect, image, dirtyRect);
}

paintEvent() 関数の再実装では、単に落書き領域用のQPainter を作成し、画像を描画します。

この時点で、なぜQImage 内で描画し、paintEvent() でQImage を画面にコピーするのではなく、ウィジェット上に直接描画しないのかと疑問に思うかもしれません。これには少なくとも3つの正当な理由があります:

  • ウィンドウシステムでは、ウィジェットをいつでも再描画できることが求められます。たとえば、ウィンドウが最小化されてから元に戻された場合、ウィンドウシステムはウィジェットの内容を忘れてしまい、ペイントイベントを送信してくる可能性があります。つまり、ウィンドウシステムが画像を記憶してくれることを当てにすることはできません。
  • Qtでは通常、paintEvent() の外で描画を行うことは許可されていません。特に、マウスイベントハンドラ内からの描画はできません。(ただし、この挙動はQt::WA_PaintOnScreen ウィジェット属性を使用して変更可能です。)
  • 適切に初期化されていれば、QImage は各色チャンネル(赤、緑、青、アルファ)に8ビットを使用することが保証されていますが、QWidget はモニターの設定によっては、これより低い色深度になる可能性があります。 つまり、24 ビットまたは 32 ビットの画像を読み込んでQWidget に描画し、そのQWidget を再度QImage にコピーすると、一部の情報が失われる可能性があります。
void ScribbleArea::resizeEvent(QResizeEvent *event)
{
    if (width() > image.width() || height() > image.height()) {
        int newWidth = qMax(width() + 128, image.width());
        int newHeight = qMax(height() + 128, image.height());
        resizeImage(&image, QSize(newWidth, newHeight));
        update();
    }
    QWidget::resizeEvent(event);
}

ユーザーが Scribble アプリケーションを起動すると、サイズ変更イベントが発生し、画像が作成されてスクリブル領域に表示されます。 ユーザーがメインウィンドウのサイズを変更するたびに画像のサイズ変更を行うこと(これは非常に非効率的です)を避けるため、この初期画像はアプリケーションのメインウィンドウおよびスクリブル領域よりもわずかに大きくしています。しかし、メインウィンドウがこの初期サイズより大きくなった場合は、画像のサイズを変更する必要があります。

void ScribbleArea::drawLineTo(const QPoint &endPoint)
{
    QPainter painter(&image);
    painter.setPen(QPen(myPenColor, myPenWidth, Qt::SolidLine, Qt::RoundCap,
                        Qt::RoundJoin));
    painter.drawLine(lastPoint, endPoint);
    modified = true;

    int rad = (myPenWidth / 2) + 2;
    update(QRect(lastPoint, endPoint).normalized()
                                     .adjusted(-rad, -rad, +rad, +rad));
    lastPoint = endPoint;
}

drawLineTo() では、前回のマウスクリックまたはマウス移動が発生したときのマウス位置から線を引きます。modified を true に設定し、再描画イベントを発生させ、lastPoint を更新します。これにより、次にdrawLineTo() が呼び出されたときに、中断した箇所から描画を再開できるようになります。

update() 関数を引数なしで呼び出すこともできますが、簡単な最適化として、ウィジェットの完全な再描画を避けるため、更新が必要な落書き領域内の矩形を指定するQRect を引数として渡します。

void ScribbleArea::resizeImage(QImage *image, const QSize &newSize)
{
    if (image->size() == newSize)
        return;

    QImage newImage(newSize, QImage::Format_RGB32);
    newImage.fill(qRgb(255, 255, 255));
    QPainter painter(&newImage);
    painter.drawImage(QPoint(0, 0), *image);
    *image = newImage;
}

QImage には、画像のサイズ変更を行うための適切なAPIがありません。QImage::copy() 関数を使えば対応可能ですが、画像を拡大する際に新しい領域が黒で塗りつぶされてしまい、私たちは白で塗りつぶしたいのです。

そこで、適切なサイズの新しい `QImage ` を作成し、それを白で塗りつぶした上で、`QPainter` を使用して元の画像を描画するという方法を取ります。 新しい画像にはQImage::Format_RGB32 形式が指定されます。これは、各ピクセルが0xffRRGGBB(RR、GG、BBはそれぞれ赤、緑、青の色チャンネル、ffは16進数の255)として格納されることを意味します。

印刷処理は、print() スロットによって行われます。

void ScribbleArea::print()
{
#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printdialog)
    QPrinter printer(QPrinter::HighResolution);

    QPrintDialog printDialog(&printer, this);

必要な出力形式に合わせて高解像度の QPrinter オブジェクトを作成し、QPrintDialog を使用して、ユーザーにページサイズの指定や、ページ上での出力の配置方法を指示してもらいます。

ダイアログが承認されたら、ペイントデバイスへの印刷処理を実行します:

    if (printDialog.exec() == QDialog::Accepted) {
        QPainter painter(&printer);
        QRect rect = painter.viewport();
        QSize size = image.size();
        size.scale(rect.size(), Qt::KeepAspectRatio);
        painter.setViewport(rect.x(), rect.y(), size.width(), size.height());
        painter.setWindow(image.rect());
        painter.drawImage(0, 0, image);
    }
#endif // QT_CONFIG(printdialog)
}

この方法で画像をファイルに印刷するには、単に QPrinter 上に描画するだけです。ペイントデバイスに描画する前に、ページ上の利用可能なスペースに収まるよう画像を拡大縮小します。

MainWindowクラスの定義

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);

protected:
    void closeEvent(QCloseEvent *event) override;

private slots:
    void open();
    void save();
    void penColor();
    void penWidth();
    void about();

private:
    void createActions();
    void createMenus();
    bool maybeSave();
    bool saveFile(const QByteArray &fileFormat);

    ScribbleArea *scribbleArea;

    QMenu *saveAsMenu;
    QMenu *fileMenu;
    QMenu *optionMenu;
    QMenu *helpMenu;

    QAction *openAct;
    QList<QAction *> saveAsActs;
    QAction *exitAct;
    QAction *penColorAct;
    QAction *penWidthAct;
    QAction *printAct;
    QAction *clearScreenAct;
    QAction *aboutAct;
    QAction *aboutQtAct;
};

MainWindow クラスはQMainWindow を継承しています。QWidget のcloseEvent()ハンドラを再実装しています。open() 、save() 、penColor() 、penWidth() の各スロットは、メニュー項目に対応しています。さらに、4つのプライベート関数を作成しています。

ブール値のmaybeSave() 関数を使用して、未保存の変更があるかどうかを確認します。未保存の変更がある場合、ユーザーにそれらの変更を保存する機会を提供します。ユーザーがCancel をクリックした場合、この関数はfalse を返します。saveFile() 関数を使用して、スクリブル領域に現在表示されている画像をユーザーが保存できるようにします。

MainWindow クラスの実装

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), scribbleArea(new ScribbleArea(this))
{
    setCentralWidget(scribbleArea);

    createActions();
    createMenus();

    setWindowTitle(tr("Scribble"));
    resize(500, 500);
}

コンストラクタでは、MainWindow ウィジェットの中心となるウィジェットとして、スクribble領域を作成します。その後、関連するアクションとメニューを作成します。

void MainWindow::closeEvent(QCloseEvent *event)
{
    if (maybeSave())
        event->accept();
    else
        event->ignore();
}

閉じるイベントは、ユーザーがウィンドウを閉じようとした際に、通常は「File|Exit 」をクリックするか、X のタイトルバーボタンをクリックすることで、ウィジェットに送信されます。イベントハンドラを再実装することで、アプリケーションを閉じようとする操作をインターセプトできます。

この例では、closeイベントを利用して、未保存の変更がある場合にユーザーに保存を促します。そのロジックはmaybeSave() 関数内にあります。maybeSave() がtrueを返した場合、変更がないか、ユーザーが正常に保存したことを意味するため、イベントを受け入れます。 その後、アプリケーションは通常通り終了します。maybeSave() がfalseを返した場合、ユーザーがCancel をクリックしたことになるため、イベントを「無視」し、アプリケーションには影響を与えません。

void MainWindow::open()
{
    if (maybeSave()) {
        QString fileName = QFileDialog::getOpenFileName(this,
                                   tr("Open File"), QDir::currentPath());
        if (!fileName.isEmpty())
            scribbleArea->openImage(fileName);
    }
}

open() スロットでは、まず、スクリブル領域に新しい画像が読み込まれる前に、現在表示されている画像への変更を保存する機会をユーザーに与えます。次に、ユーザーにファイルを選択するよう求め、ScribbleArea でそのファイルを読み込みます。

void MainWindow::save()
{
    QAction *action = qobject_cast<QAction *>(sender());
    QByteArray fileFormat = action->data().toByteArray();
    saveFile(fileFormat);
}

save() スロットは、ユーザーがSave As メニュー項目を選択し、さらにフォーマットメニューから項目を選択した際に呼び出されます。まず最初に行うべきことは、QObject::sender() を使用して、どのアクションがシグナルを送信したかを特定することです。この関数は、送信者をQObject ポインタとして返します。送信者がアクションオブジェクトであることがわかっているため、QObject を安全にキャストすることができます。 CスタイルのキャストやC++のstatic_cast<>() を使用することも可能でしたが、防御的プログラミングの手法としてqobject_cast()を使用します。この利点は、オブジェクトの型が間違っている場合、nullポインタが返されることです。nullポインタによるクラッシュは、安全でないキャストによるクラッシュよりも診断がはるかに容易です。

アクションを取得したら、QAction::data() を使用して選択されたフォーマットを抽出します。(アクションが作成される際、QAction::setData() を使用して、QVariant としてアクションに添付する独自のカスタムデータを設定します。これについては、createActions() を解説する際に詳しく説明します。)

フォーマットがわかったので、非公開のsaveFile() 関数を呼び出して、現在表示されている画像を保存します。

void MainWindow::penColor()
{
    QColor newColor = QColorDialog::getColor(scribbleArea->penColor());
    if (newColor.isValid())
        scribbleArea->setPenColor(newColor);
}

penColor() スロットを使用し、QColorDialog でユーザーから新しい色を取得します。ユーザーが新しい色を選択した場合は、それを落書き領域の色に設定します。

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);
}

penWidth() スロットで新しいペン幅を取得するには、QInputDialog を使用します。QInputDialog クラスは、ユーザーから単一の値を取得するためのシンプルな便利なダイアログを提供します。 静的関数QInputDialog::getInt()を使用します。この関数は、QLabel とQSpinBox を組み合わせています。QSpinBox は、落書き領域のペン幅で初期化され、1から50までの範囲(ステップは1、つまり上下の矢印キーで値が1ずつ増減します)を指定できます。

ブール型のok 変数は、ユーザーがOK をクリックした場合はtrue に、Cancel を押した場合はfalse に設定されます。

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>"));
}

about() スロットを実装し、この例が何を示すことを意図しているかを説明するメッセージボックスを作成します。

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() 関数内で、メニュー項目を表すアクションを作成し、それらを適切なスロットに接続します。具体的には、Save As サブメニューにあるアクションを作成します。QImageWriter::supportedImageFormats()を使用して、サポートされている形式のリスト(QList<QByteArray>の形式)を取得します。

次に、リストを反復処理し、各フォーマットに対してアクションを作成します。ファイルフォーマットを引数としてQAction::setData()を呼び出し、後でQAction::data()として取得できるようにします。「...」を切り捨てることでアクションのテキストからファイルフォーマットを推測することも可能でしたが、それは洗練されていない方法だったでしょう。

void MainWindow::createMenus()
{
    saveAsMenu = new QMenu(tr("&Save As"), this);
    for (QAction *action : std::as_const(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() 関数内では、先に作成したフォーマットアクションをsaveAsMenu に追加します。その後、残りのアクションと「saveAsMenu 」サブメニューを、File 、Options 、およびHelp の各メニューに追加します。

QMenu クラスは、メニューバー、コンテキストメニュー、およびその他のポップアップメニューで使用するためのメニューウィジェットを提供します。QMenuBar クラスは、プルダウン式のQMenuのリストを備えた水平方向のメニューバーを提供します。最後に、QMainWindow::menuBar()関数を使用して取得したMainWindow のメニューバーに、File およびOptions のメニューを追加します。

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() では、未保存の変更があるかどうかを確認します。もしあれば、QMessageBox を使用して、画像が変更されたことをユーザーに警告し、変更内容を保存する機会を提供します。

QColorDialog やQFileDialog と同様に、QMessageBox を作成する最も簡単な方法は、その静的関数を使用することです。QMessageBox は、重大度(質問、情報、警告、重大)と複雑度(必要な応答ボタンの数)という2つの軸に沿って整理された、さまざまなメッセージを提供します。ここでは、メッセージがかなり重要であるため、warning() 関数を使用します。

ユーザーが保存を選択した場合は、プライベート関数saveFile() を呼び出します。簡便のため、ファイル形式にはPNGを使用しますが、ユーザーはいつでもCancel を押して、別の形式でファイルを保存することができます。

maybeSave() 関数は、ユーザーがCancel をクリックした場合はfalse を返し、それ以外の場合は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() では、ファイル名の候補を表示したファイルダイアログをポップアップ表示します。静的関数QFileDialog::getSaveFileName() は、ユーザーが選択したファイル名を返します。ファイルは実在していなくても構いません。

サンプルプロジェクト @ code.qt.io

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.