How to implement Open Recent Files in Qt

This article shows how to implement Open Recent Files functionality in Qt. This will be demonstrated on a minimalistic implementation of an image viewer. The situation is illustrated in the figure below:

Note: Please do not use this implementation if you want a proper image viewer. This code was written to explain Open Recent functionality rather than an image viewer. For an image viewer please look at this official Qt example.

To implement Open Recent we need to introduce the following objects and functions:

There are two important points to stress about the current implementation:

Implementation of MainWindow

Below you can see the declaration of the MainWindow with the above mentioned requirements. You can read about the details of these functions below. Please also notice the QLabel *imageLabel that will hold the image that we load from a file.

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = Q_NULLPTR);

private slots:
    void openRecent();
    void open();

private:
    QMenu* fileMenu;
    QMenu* recentFilesMenu;

    QAction* openAction;
    QList recentFileActionList;
    const int maxFileNr;

    QString currentFilePath;
    QLabel *imageLabel;

    void createActionsAndConnections();
    void createMenus();

    void loadFile(const QString& filePath);
    void adjustForCurrentFile(const QString& filePath);
    void updateRecentActionList();
};

In the MainWindow constructor we initialize the value of maxFileNr and create the actions, menus and connections.

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), maxFileNr(4)
{
    imageLabel = new QLabel;
    setCentralWidget(imageLabel);

    createActionsAndConnections();
    createMenus();

    resize(350, 250);
}

In createActionsAndConnections() we initialize the standard openAction and 4 QActions associated with the recentFileActionList. We also connect the openAction to the slot open() and each of the QActions in the recentFileActionList to the slot openRecent().

void MainWindow::createActionsAndConnections(){
    openAction = new QAction(tr("&Open..."), this);
    openAction->setShortcuts(QKeySequence::Open);
    QObject::connect(openAction, &QAction::triggered,
                     this, MainWindow::open);

    QAction* recentFileAction = 0;
    for(auto i = 0; i < maxFileNr; ++i){
        recentFileAction = new QAction(this);
        recentFileAction->setVisible(false);
        QObject::connect(recentFileAction, &QAction::triggered,
                         this, MainWindow::openRecent);
        recentFileActionList.append(recentFileAction);
    }
}

In createMenus() we create the fileMenu and append openAction. We also create the recentFilesMenu in association with the fileMenu and append the actions from the recentFileActionList. At the end, we call updateRecentActionList(). The details of updateRecentActionList() will be discussed later.

void MainWindow::createMenus(){
    fileMenu = menuBar()->addMenu(tr("&File"));
    fileMenu->addAction(openAction);

    recentFilesMenu = fileMenu->addMenu(tr("Open Recent"));
    for(auto i = 0; i < maxFileNr; ++i)
        recentFilesMenu->addAction(recentFileActionList.at(i));

    updateRecentActionList();
}

How to implement open() and openRecent()

In open() we first open a QFileDialog. If the user makes a choice, we call the function loadFile(filePath). Similarly, in openRecentFile() we first identify the QAction that called the slot and then load the corresponding file. The entire file path is stored in action->data(), the name of the file without the path is stored in action->text().

void MainWindow::open(){
    QString filePath = QFileDialog::getOpenFileName(
                       this, tr("Open File"), "",
                       tr("Images (*.png *.xpm *.jpg *.gif)"));
        if (!filePath.isEmpty())
            loadFile(filePath);
}

void MainWindow::openRecent(){
    QAction *action = qobject_cast<QAction *>(sender());
    if (action)
        loadFile(action->data().toString());
}

How to implement loadFile(QString)

The private function loadFile(QString) is called both by the slot open() and openRecent(). The responsibility of loadFile(QString) is:

You can see its implementation below:

void MainWindow::loadFile(const QString &filePath){
    QFile file(filePath);                

    if (!file.open(QFile::ReadOnly)) {
        QMessageBox::warning(this, tr("Recent Files Example"),
                             tr("This file could not be found:\n%1.")
                             .arg(filePath));
        return;
    }

    QPixmap pMap(filePath);
    if (pMap.isNull()) {
        QMessageBox::information(this, tr("Recent Files Example"),
                      tr("Cannot load:\n%1.")
                      .arg(filePath));
        return;
    }

    imageLabel->setPixmap(pMap);
    imageLabel->setAlignment(Qt::AlignCenter);
    adjustForCurrentFile(filePath);
}

How to implement adjustForCurrentFile(QString)

As you can see from the figure above, the function adjustForCurrentFile(QString) is called by the function loadFile(QString). The responsibility of adjustForCurrentFile(QString) is:

If you have not worked with configuration files and QSettings before, read this article on QSettings first. In our case the configuration file is created and identified based on the information that we provided in the main.cpp through setOrganizationName() and setApplicationName(). You do not have to look for the configuration file or know how it looks like, since we are going to retrieve the necessary information from the very same location via QSettings in updateRecentActionList(). Notice that before adding the newly opened file to the top of the recentFilePaths list in the configuration file, we have to first remove all its previous occurrences with removeAll().

void MainWindow::adjustForCurrentFile(const QString &filePath){
    currentFilePath = filePath;
    setWindowFilePath(currentFilePath);

    QSettings settings;
    QStringList recentFilePaths =
            settings.value("recentFiles").toStringList();
    recentFilePaths.removeAll(filePath);
    recentFilePaths.prepend(filePath);
    while (recentFilePaths.size() > maxFileNr)
        recentFilePaths.removeLast();
    settings.setValue("recentFiles", recentFilePaths);

    // see note
    updateRecentActionList();
}

Note: In a normal application you would also have several other actions such as New, Save, Save As (such as in this official example). In particular, in case of New you would probably create a new instance of MainWindow (i.e. you would have several instances of MainWindow present). In such a case you would like to see that an update in the recent files menu of one window causes an update in the recent files menu of all other windows. This is why you should call updateRecentActionList() on all top level windows. This is shown below.

    foreach (QWidget *widget, QApplication::topLevelWidgets()) {
        MainWindow *mainwindow = qobject_cast<MainWindow*>(widget);
        if (mainwindow)
            mainwindow->updateRecentActionList();
    }

Beware, however, that if you run several instances of the program (i.e. multiple processes), the code above won't help you.

Implementation of updateRecentActionList()

The function updateRecentActionList() is called whenever we are loading (or saving) a file. The function updates the recentFileActionList according to the newly updated configuration file. If the list of recently opened files contains less files than the allowed number (maxFileNr), we make the remaining actions invisible. Notice that we have to change both the text and the data of the QActions to account for the name and the path of the newly opened file.

void MainWindow::updateRecentActionList(){
    QSettings settings;
    QStringList recentFilePaths =
            settings.value("recentFiles").toStringList();

    auto itEnd = 0;
    if(recentFilePaths.size() <= maxFileNr)
        itEnd = recentFilePaths.size();
    else
        itEnd = maxFileNr;

    for (auto i = 0; i < itEnd; ++i) {
        QString strippedName = QFileInfo(recentFilePaths.at(i)).fileName();
        recentFileActionList.at(i)->setText(strippedName);
        recentFileActionList.at(i)->setData(recentFilePaths.at(i));
        recentFileActionList.at(i)->setVisible(true);
    }

    for (auto i = itEnd; i < maxFileNr; ++i)
        recentFileActionList.at(i)->setVisible(false);
}
That's it. To try out the example you need to open a couple of files with Open. When you do that, their names will appear under Open recent. Try to add more than 4 files or add a file that already appears in the list. You will see that these cases are well taken care of!

Latest update: 22.02.2017
Created: 2014
© Walletfox.com, 2017