En esta página

Ejemplo de Scribble

El ejemplo de Scribble muestra cómo reimplementar algunos de los manejadores de eventos de QWidget para recibir los eventos generados por los widgets de la aplicación.

Reimplementamos los manejadores de eventos del ratón para implementar el dibujo, el manejador de eventos paint para actualizar la aplicación y el manejador de eventos resize para optimizar la apariencia de la aplicación. Además reimplementamos el manejador de eventos close para interceptar los eventos close antes de terminar la aplicación.

El ejemplo también demuestra cómo utilizar QPainter para dibujar una imagen en tiempo real, así como para repintar los widgets.

Captura de pantalla del ejemplo de Scribble

Con la aplicación Scribble los usuarios pueden dibujar una imagen. El menú File ofrece a los usuarios la posibilidad de abrir y editar un archivo de imagen existente, guardar una imagen y salir de la aplicación. Mientras se dibuja, el menú Options permite a los usuarios elegir el color y la anchura del lápiz, así como borrar la pantalla. Además, el menú Help proporciona a los usuarios información sobre el ejemplo Scribble en particular, y sobre Qt en general.

El ejemplo consta de dos clases:

  • ScribbleArea es un widget personalizado que muestra un QImage y permite al usuario dibujar sobre él.
  • MainWindow proporciona un menú sobre el ScribbleArea.

Comenzaremos revisando la clase ScribbleArea. Luego revisaremos la clase MainWindow, que utiliza ScribbleArea.

Definición de la clase 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;
};

La clase ScribbleArea hereda de QWidget. Reimplementamos las funciones mousePressEvent(), mouseMoveEvent() y mouseReleaseEvent() para implementar el dibujo. Reimplementamos la función paintEvent() para actualizar el área de garabatos, y la función resizeEvent() para asegurar que el QImage sobre el que dibujamos es al menos tan grande como el widget en cualquier momento.

Necesitamos varias funciones públicas: openImage() carga una imagen de un archivo en el área de garabatos, permitiendo al usuario editar la imagen; save() escribe la imagen mostrada actualmente en el archivo; clearImage() slot borra la imagen mostrada en el área de garabatos. Necesitamos la función privada drawLineTo() para realizar el dibujo, y resizeImage() para cambiar el tamaño de QImage. La ranura print() se encarga de la impresión.

También necesitamos las siguientes variables privadas:

  • modified es true si hay cambios sin guardar en la imagen mostrada en el área de garabatos.
  • scribbling is true mientras el usuario esté pulsando el botón izquierdo del ratón dentro del área de garabatos.
  • penWidth y penColor mantienen la anchura y el color actualmente configurados para el lápiz utilizado en la aplicación.
  • image almacena la imagen dibujada por el usuario.
  • lastPoint mantiene la posición del cursor en el último evento de pulsación o movimiento del ratón.

Implementación de la Clase ScribbleArea

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

En el constructor, establecemos el atributo Qt::WA_StaticContents para el widget, indicando que los contenidos del widget están enraizados en la esquina superior izquierda y no cambian cuando se redimensiona el widget. Qt utiliza este atributo para optimizar los eventos de pintura en los cambios de tamaño. Esto es puramente una optimización y sólo debería usarse para widgets cuyos contenidos son estáticos y están enraizados en la esquina superior izquierda.

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

En la función openImage(), cargamos la imagen dada. Luego redimensionamos la QImage cargada para que sea al menos tan grande como el widget en ambas direcciones usando la función privada resizeImage() y establecemos la variable miembro image para que sea la imagen cargada. Al final, llamamos a QWidget::update() para programar un repintado.

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

La función saveImage() crea un objeto QImage que cubre sólo la sección visible del image real y lo guarda utilizando QImage::save(). Si la imagen se guarda correctamente, establecemos la variable modified del área de garabatos en false, porque no hay datos sin guardar.

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

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

Las funciones setPenColor() y setPenWidth() establecen el color y la anchura actuales del lápiz. Estos valores se utilizarán para futuras operaciones de dibujo.

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

La función pública clearImage() borra la imagen mostrada en el área de garabatos. Simplemente rellenamos toda la imagen con blanco, que corresponde al valor RGB (255, 255, 255). Como es habitual cuando modificamos la imagen, establecemos modified a true y programamos un repintado.

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

Para los eventos de pulsación y liberación del ratón, utilizamos la función QMouseEvent::button() para averiguar qué botón causó el evento. Para los eventos de movimiento del ratón, utilizamos QMouseEvent::buttons() para averiguar qué botones se mantienen pulsados en ese momento (como una combinación OR).

Si el usuario pulsa el botón izquierdo del ratón, almacenamos la posición del cursor del ratón en lastPoint. También anotamos que el usuario está garabateando en ese momento. (La variable scribbling es necesaria porque no podemos asumir que un evento de mover y soltar el ratón siempre va precedido de un evento de pulsar el ratón en el mismo widget).

Si el usuario mueve el ratón con el botón izquierdo pulsado o suelta el botón, llamamos a la función privada drawLineTo() para dibujar.

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

En la reimplementación de la función paintEvent(), simplemente creamos un QPainter para el área de garabatos, y dibujamos la imagen.

Llegados a este punto, puede que te preguntes por qué no dibujamos directamente en el widget en lugar de dibujar en QImage y copiar el QImage en la pantalla en paintEvent(). Hay al menos tres buenas razones para ello:

  • El sistema de ventanas requiere que podamos redibujar el widget en cualquier momento. Por ejemplo, si la ventana se minimiza y se restaura, el sistema de ventanas podría haber olvidado el contenido del widget y enviarnos un evento paint. En otras palabras, no podemos confiar en que el sistema de ventanas recuerde nuestra imagen.
  • Qt normalmente no nos permite pintar fuera de paintEvent(). En particular, no podemos pintar desde los manejadores de eventos del ratón. (Aunque este comportamiento puede cambiarse usando el atributo widget Qt::WA_PaintOnScreen ).
  • Si se inicializa correctamente, se garantiza que un QImage utiliza 8 bits para cada canal de color (rojo, verde, azul y alfa), mientras que un QWidget puede tener una profundidad de color menor, dependiendo de la configuración del monitor. Esto significa que si cargamos una imagen de 24 o 32 bits y la pintamos en un QWidget, luego copiamos de nuevo el QWidget en un QImage, podríamos perder algo de información.
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);
}

Cuando el usuario inicia la aplicación Scribble, se genera un evento de cambio de tamaño y se crea una imagen que se muestra en el área de garabatos. Hacemos esta imagen inicial ligeramente más grande que la ventana principal de la aplicación y el área de garabatos, para evitar redimensionar siempre la imagen cuando el usuario redimensiona la ventana principal (lo que sería muy ineficiente). Pero cuando la ventana principal supera este tamaño inicial, es necesario redimensionar la imagen.

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

En drawLineTo(), dibujamos una línea desde el punto donde se encontraba el ratón cuando se produjo la última pulsación o movimiento del ratón, establecemos modified a true, generamos un evento repaint, y actualizamos lastPoint para que la próxima vez que se llame a drawLineTo(), continuemos dibujando desde donde lo dejamos.

Podríamos llamar a la función update() sin ningún parámetro, pero como optimización sencilla pasamos un QRect que especifica el rectángulo dentro del garabato que necesita ser actualizado, para evitar un repintado completo del widget.

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 no tiene una buena API para redimensionar una imagen. Hay una función QImage::copy() que podría hacer el truco, pero cuando se usa para expandir una imagen, rellena las nuevas áreas con negro, mientras que nosotros queremos blanco.

Así que el truco consiste en crear una nueva QImage con el tamaño adecuado, rellenarla de blanco y dibujar la imagen antigua sobre ella utilizando QPainter. A la nueva imagen se le da el formato QImage::Format_RGB32, lo que significa que cada píxel se almacena como 0xffRRGGBB (donde RR, GG y BB son los canales de color rojo, verde y azul, ff es el valor hexadecimal 255).

De la impresión se encarga la ranura print():

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

    QPrintDialog printDialog(&printer, this);

Construimos un objeto QPrinter de alta resolución para el formato de salida requerido, utilizando un QPrintDialog para pedir al usuario que especifique un tamaño de página e indique cómo debe formatearse la salida en la página.

Si el diálogo es aceptado, realizamos la tarea de imprimir al dispositivo de pintura:

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

Imprimir una imagen a un archivo de esta manera es simplemente una cuestión de pintar en la QPrinter. Escalamos la imagen para que quepa dentro del espacio disponible en la página antes de pintarla en el dispositivo de pintura.

Definición de la Clase 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;
};

La clase MainWindow hereda de QMainWindow. Reimplementamos el manejador closeEvent() de QWidget. Las ranuras open(), save(), penColor() y penWidth() corresponden a entradas de menú. Además, creamos cuatro funciones privadas.

Utilizamos la función booleana maybeSave() para comprobar si hay cambios sin guardar. Si hay cambios sin guardar, damos al usuario la oportunidad de guardar estos cambios. La función devuelve false si el usuario pulsa Cancel. Utilizamos la función saveFile() para permitir al usuario guardar la imagen que se muestra actualmente en el área de garabatos.

Implementación de la clase MainWindow

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

    createActions();
    createMenus();

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

En el constructor, creamos un área de garabatos que convertimos en el widget central del widget MainWindow. Después creamos las acciones y menús asociados.

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

Los eventos de cierre se envían a los widgets que los usuarios quieren cerrar, normalmente pulsando File|Exit o pulsando el botón de la barra de título X. Reimplementando el manejador de eventos, podemos interceptar los intentos de cerrar la aplicación.

En este ejemplo, utilizamos el evento de cierre para pedir al usuario que guarde los cambios no guardados. La lógica para ello se encuentra en la función maybeSave(). Si maybeSave() devuelve true, no hay modificaciones o el usuario las ha guardado correctamente, y aceptamos el evento. La aplicación puede entonces terminar normalmente. Si maybeSave() devuelve false, el usuario hizo clic en Cancel, por lo que "ignoramos" el evento, dejando la aplicación no afectada por él.

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

En el espacio open() primero damos al usuario la oportunidad de guardar cualquier modificación de la imagen mostrada actualmente, antes de cargar una nueva imagen en el área de garabatos. A continuación, pedimos al usuario que elija un archivo y lo cargamos en ScribbleArea.

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

La ranura save() es llamada cuando los usuarios eligen la entrada de menú Save As, y luego eligen una entrada del menú de formato. Lo primero que tenemos que hacer es averiguar qué acción envió la señal utilizando QObject::sender(). Esta función devuelve el remitente como un puntero QObject. Como sabemos que el remitente es un objeto de acción, podemos hacer un "cast" seguro a QObject. Podríamos haber utilizado un "cast" estilo C o un static_cast<>() C++, pero como técnica de programación defensiva utilizamos un qobject_cast(). La ventaja es que si el objeto tiene un tipo incorrecto, se devuelve un puntero nulo. Los fallos debidos a punteros nulos son mucho más fáciles de diagnosticar que los fallos debidos a castings inseguros.

Una vez que tenemos la acción, extraemos el formato elegido utilizando QAction::data(). (Cuando se crean las acciones, usamos QAction::setData() para establecer nuestros propios datos personalizados adjuntos a la acción, como QVariant. Más sobre esto cuando revisemos createActions()).

Ahora que conocemos el formato, llamamos a la función privada saveFile() para guardar la imagen mostrada actualmente.

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

Usamos la ranura penColor() para recuperar un nuevo color del usuario con un QColorDialog. Si el usuario elige un nuevo color, lo convertimos en el color del área de garabatos.

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

Para recuperar un nuevo ancho de lápiz en la ranura penWidth(), utilizamos QInputDialog. La clase QInputDialog proporciona un sencillo diálogo de conveniencia para obtener un único valor del usuario. Usamos la función estática QInputDialog::getInt(), que combina una QLabel y una QSpinBox. La QSpinBox se inicializa con el ancho del bolígrafo del área de garabatos, permite un rango de 1 a 50, un paso de 1 (lo que significa que las flechas arriba y abajo incrementan o disminuyen el valor en 1).

La variable booleana ok se establecerá en true si el usuario hace clic en OK y en false si el usuario pulsa 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>"));
}

Implementamos la función about() para crear un cuadro de mensaje que describa lo que se pretende mostrar en el ejemplo.

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

En la función createAction() creamos las acciones que representan las entradas del menú y las conectamos a las ranuras apropiadas. En particular, creamos las acciones que se encuentran en el submenú Save As. Usamos QImageWriter::supportedImageFormats() para obtener una lista de los formatos soportados (como QList<QByteArray>).

Luego iteramos a través de la lista, creando una acción para cada formato. Llamamos a QAction::setData() con el formato de archivo, para poder recuperarlo más tarde como QAction::data(). También podríamos haber deducido el formato del archivo a partir del texto de la acción, truncando el "...", pero eso habría sido poco elegante.

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

En la función createMenu(), añadimos las acciones de formato creadas anteriormente a la clase saveAsMenu. A continuación, añadimos el resto de acciones, así como el submenú saveAsMenu a los menús File, Options y Help.

La clase QMenu proporciona un widget de menú para su uso en barras de menú, menús contextuales y otros menús emergentes. La clase QMenuBar proporciona una barra de menús horizontal con una lista de menús desplegables QMenus. Al final ponemos los menús File y Options en la barra de menús MainWindow's, que recuperamos utilizando la función QMainWindow::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;
}

En mayBeSave(), comprobamos si hay cambios sin guardar. Si los hay, utilizamos QMessageBox para dar al usuario un aviso de que la imagen ha sido modificada y la oportunidad de guardar las modificaciones.

Al igual que con QColorDialog y QFileDialog, la forma más sencilla de crear un QMessageBox es utilizar sus funciones estáticas. QMessageBox ofrece una serie de mensajes diferentes ordenados en dos ejes: gravedad (pregunta, información, advertencia y crítico) y complejidad (el número de botones de respuesta necesarios). Aquí utilizamos la función warning() porque el mensaje es bastante importante.

Si el usuario decide guardar, llamamos a la función privada saveFile(). Para simplificar, utilizamos PNG como formato de archivo; el usuario siempre puede pulsar Cancel y guardar el archivo utilizando otro formato.

La función maybeSave() devuelve false si el usuario pulsa Cancel; en caso contrario devuelve 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());
}

En saveFile(), se abre un diálogo de archivo con una sugerencia de nombre de archivo. La función estática QFileDialog::getSaveFileName() devuelve un nombre de fichero seleccionado por el usuario. No es necesario que el archivo exista.

Proyecto de ejemplo @ 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.