Trolltech | Documentation | Qt Quarterly | Look 'n' Feel Q & A »

A QListBox-Based File Browser
by Jacek Surazski
This article presents FileBrowser, a QListBox subclass that allows the user to browse directories and files. Unlike QFileDialog, which works as a modal window, the FileBrowser widget can be embedded in an application's main window or in a dialog, making it more convenient for browsing.

The FileBrowser widget lists the contents of a directory. If the user double-clicks the name of a subdirectory, the FileBrowser updates itself with the contents of that subdirectory (as illustrated below). And by double-clicking "dot dot", the user can return to the parent directory.
Filebrowser1 -> Filebrowser2
Whenever the user highlights a new file, FileBrowser emits a signal that the rest of the application can connect to. For example, the screenshot below shows an application that combines a FileBrowser with a QTextBrowser to provide a simple HTML browser.

Textbrowser

The most straightforward way to implement FileBrowser is to subclass QListBox and add a few members:

    class FileBrowser : public QListBox
    {
        Q_OBJECT
    public:
        FileBrowser(const QString &filter, QWidget *parent = 0, const char *name = 0);
        void setDir(const QString &path);
    
    signals:
        void picked(const QString &fileName);
    
    private slots:
        void itemHighlighted(int index);
        void itemSelected(int index);
    
    private:
        QString nameFilter;
        QString basePath;
    };
    

(An alternative would have been to subclass QWidget and use a child QListBox, but that would require slightly more code.)

FileBrowser's constructor takes a name filter string in addition to the usual parent and name parameters of a QWidget. This string specifies a list of wildcard patterns (for example, "*.jpg *.gif *.png"), and FileBrowser will only show files that match one of these patterns.

The setDir() function sets the directory from which to start browsing. The picked(const QString &) signal is emitted whenever a new entry becomes highlighted, unless it's a directory. In typical applications, this signal would be connected to a slot that displays the contents of the file.

Next come two private slots. They are connected to QListBox's highlighted(int) and selected(int) signals. The highlighted(int) signal is emitted when the user makes a new item the current item (for example, by single-clicking or by pressing arrow keys), while the selected(int) signal is emitted only when the user double-clicks an item or presses Enter. For the FileBrowser widget, we happen to need both---a single-click on a regular file is enough to pick that file, but a double-click (or pressing Enter) is necessary to enter a directory.

Finally, FileBrowser has two private data members: nameFilter stores the filter constructor argument, and basePath holds the path of the directory that is currently displayed in the list.

    FileBrowser::FileBrowser(const QString &filter, QWidget *parent, const char *name)
        : QListBox(parent, name)
    {
        nameFilter = filter;
        setDir(QDir::currentDirPath());
        connect(this, SIGNAL(highlighted(int)), this, SLOT(itemHighlighted(int)));
        connect(this, SIGNAL(selected(int)), this, SLOT(itemSelected(int)));
    }
    

The constructor stores the nameFilter parameter for later use. Then it calls setDir() to populate itself with the contents of the current working directory and connects two signals inherited from QListBox to the appropriate FileBrowser private slots.

    void FileBrowser::setDir(const QString &path)
    {
        QDir dir(path, nameFilter, QDir::DirsFirst);
        dir.setMatchAllDirs(true);
        if (!dir.isReadable())
            return;
        clear();
    
        QStringList entries = dir.entryList();
        QStringList::ConstIterator it = entries.constBegin();
        while (it != entries.constEnd()) {
            if (*it != ".")
                insertItem(*it);
            ++it;
        }
        basePath = dir.canonicalPath();
    }
    

The setDir() function uses a QDir object to retrieve the listing of the directory specified by path. We pass nameFilter to QDir's constructor, so it will only return files whose name matches the pattern. We also pass the DirsFirst flag to specify that we want QDir to return directories first. Then we call setMatchAllDirs(true) to tell QDir to list all subdirectories, rather than just those that match the name filter.

Before we read the directory, we make sure that it exists and is readable. If the directory cannot be read, setDir() returns immediately, leaving the list box unchanged.

Next we clear the QListBox and fill it with the contents of the directory, as returned by QDir::entryList()---but we skip the "dot" directory. We store the current path in the private member basePath for later use.

    void FileBrowser::itemHighlighted(int index)
    {
        QString path = basePath + "/" + text(index);
        if (QFileInfo(path).isFile())
            emit picked(path);
    }
    

Whenever a list box entry is highlighted (single-clicked), the itemHighlighted() slot is called with the entry's index. We pass it to QListBox::text() to retrieve the file name. We use a QFileInfo object to find out whether the selected entry is a regular file. If it is, we emit the picked() signal.

    void FileBrowser::itemSelected(int index)
    {
        QString path = basePath + "/" + text(index);
        if (QFileInfo(path).isDir())
            setDir(path);
    }
    

Whenever a list box entry is selected (double-clicked), the itemSelected() slot is called with the entry's index. If the selected entry is a directory, FileBrowser updates itself with the contents of that directory.

Let's now write a small program that makes use of it:

    int main(int argc, char *argv[])
    {
        QApplication app(argc, argv);
        QSplitter win(Qt::Horizontal);
    
        QString filter = "*.htm *.html *.txt *.xml";
        FileBrowser *fileBrowser = new FileBrowser(filter, &win);
        QTextBrowser *textBrowser = new QTextBrowser(&win);
        QObject::connect(fileBrowser, SIGNAL(picked(const QString &)),
                         textBrowser, SLOT(setSource(const QString &)));
    
        app.setMainWidget(&win);
        win.show();
        return app.exec();
    }
    

This program consists of a FileBrowser on the left-hand side and a QTextBrowser on the right-hand side, with a splitter handle in between. We initialize the FileBrowser with a name filter that corresponds to the file formats supported by QTextBrowser.

A possible improvement would be to display directories in a different style than files, to make them easier to distinguish. This would require using QListBoxPixmap or a custom QListBoxItem subclass for presenting the directory entries.


This document is licensed under the Creative Commons Attribution-Share Alike 2.5 license.

Copyright © 2004 Trolltech Trademarks A Taste of Qt 4 »