QFileDialog::proxyModel()

2025-05-26

QFileDialog とモデル/ビューアーキテクチャ

QtのGUIコンポーネントは、多くの場合「モデル/ビュー」アーキテクチャに基づいて設計されています。これは、データを管理する「モデル」と、そのデータをユーザーに表示する「ビュー」、そしてビューからモデルへの操作を仲介する「デリゲート」という役割分担をしています。

QFileDialog も例外ではありません。内部的には、ファイルシステムの情報(ファイル名、フォルダ、サイズなど)を管理する「モデル」を持っています。通常、このモデルは QFileSystemModel のインスタンスであり、実際のファイルシステムを反映しています。そして、QFileDialog のリストビューやツリービューはそのモデルの情報を表示します。

プロキシモデルとは?

ここで proxyModel の出番です。プロキシモデル(QAbstractProxyModel のサブクラス、よく使われるのは QSortFilterProxyModel)は、元のモデル(ソースモデル)のデータを直接変更することなく、その表示方法や操作をカスタマイズしたい場合に非常に役立ちます。

QFileDialog::proxyModel() メソッドは、現在 QFileDialog で設定されているプロキシモデルへのポインタを返します。もしプロキシモデルが設定されていない場合は、nullptr(C++の場合)が返されます。

proxyModel を使う目的と利点

QFileDialog でプロキシモデルを使用する主な目的は以下の通りです。

  • データの変更/追加(非推奨または高度な使い方)
    理論的には、プロキシモデルを介して表示されるデータを変更したり、仮想的なドライブやフォルダを追加したりすることも可能ですが、これは非常に複雑であり、多くの場合 QFileDialog の内部実装と深く関わるため、バグを引き起こしやすいとされています。通常はフィルタリングや並び替えが主な用途です。
  • 並び替え
    ファイルやフォルダの表示順序をカスタマイズできます。例えば、作成日時順、最終更新日時順など、標準とは異なる方法で並べ替えることができます。
  • フィルタリング
    特定の条件に合致するファイルやフォルダのみを表示したり、逆に特定のファイルを非表示にしたりすることができます。例えば、特定の拡張子を持つファイルだけを表示したり、隠しファイルを表示しないようにしたり、特定のキーワードを含むファイルだけを表示するといったことが可能です。これは、QFileDialog::setNameFilter() よりも高度なフィルタリングが必要な場合に特に有効です。

proxyModel の使用例(概念)

  1. QSortFilterProxyModel を継承したカスタムのプロキシモデルクラスを作成します。
  2. カスタムプロキシモデルの filterAcceptsRow() メソッドをオーバーライドし、表示したいデータの行に対して true を、非表示にしたい行に対して false を返します。
  3. QFileDialog のインスタンスを作成します。
  4. 作成したカスタムプロキシモデルのインスタンスを QFileDialog::setProxyModel() メソッドで設定します。
  5. 重要
    ネイティブのファイルダイアログはプロキシモデルをサポートしないため、QFileDialog::setOption(QFileDialog::DontUseNativeDialog) を呼び出してQt標準のファイルダイアログを使用するように設定する必要があります。
  • プロキシモデルを使用する場合、QFileDialog::setOption(QFileDialog::DontUseNativeDialog) を設定しないと、多くの場合プロキシモデルは機能しません。これは、各OSのネイティブのファイルダイアログはQtのモデル/ビューアーキテクチャの恩恵を受けられないためです。
  • QFileDialog::proxyModel() が返すのは、設定されているプロキシモデルへのポインタです。設定されていない場合は nullptr です。


プロキシモデルが適用されない、または期待通りに動作しない

原因
最も一般的な原因は、ネイティブダイアログが使用されていることです。QFileDialog はデフォルトでOSのネイティブファイルダイアログを使用しようとしますが、ネイティブダイアログはQtのモデル/ビューアーキテクチャやプロキシモデルの概念を理解しません。

トラブルシューティング

  • QFileDialog::DontUseNativeDialog オプションを設定する
    プロキシモデルを使用する場合は、必ず QFileDialog::setOption(QFileDialog::DontUseNativeDialog) を呼び出して、Qt独自のファイルダイアログを使用するように強制する必要があります。これを忘れると、プロキシモデルは一切機能しません。

    QFileDialog dialog(this);
    dialog.setOption(QFileDialog::DontUseNativeDialog); // これが重要!
    MyCustomProxyModel* proxyModel = new MyCustomProxyModel(&dialog);
    dialog.setProxyModel(proxyModel);
    // ...
    dialog.exec();
    

フィルタリングや並び替えがうまくいかない

原因

  • ソースモデルのデータへのアクセスミス
    filterAcceptsRow()lessThan() の中で、ソースモデルのデータに正しくアクセスできていない可能性があります。sourceModel() を介して、正しい QModelIndex を使ってデータ(data() メソッド)を取得しているか確認してください。
  • filterAcceptsRow() または lessThan() の実装ミス
    プロキシモデル(特に QSortFilterProxyModel を継承する場合)の filterAcceptsRow() メソッド(フィルタリング用)や lessThan() メソッド(並び替え用)の実装に論理的な誤りがある可能性があります。

トラブルシューティング

  • 親インデックスの考慮
    filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) を実装する際、sourceParent がルートディレクトリではない場合があることを考慮に入れる必要があります。特にディレクトリツリー全体をフィルタリングするような場合は重要です。
  • QFileSystemModel のパスの確認
    QFileSystemModel をソースモデルとして使用している場合、取得しているパスが正しいか、ファイル名やディレクトリ名が意図した通りかを確認します。特にWindows環境ではパス区切り文字(\/)の違いに注意が必要ですが、Qtは通常これを自動的に処理します。
  • デバッグとログ出力
    filterAcceptsRow()lessThan() の内部で、現在の行や比較対象のデータ、そしてそれらのメソッドが true または false を返しているかどうかのログを出力して確認します。

ファイルダイアログがクラッシュする、または不正なメモリにアクセスする

原因

  • 無効なポインタやデータアクセス
    カスタムのプロキシモデル内で、解放済みのデータや無効なポインタにアクセスしようとしている可能性があります。
  • プロキシモデルのライフサイクル管理ミス
    QFileDialog は、setProxyModel() で設定されたプロキシモデルの所有権を取得しません。つまり、開発者がプロキシモデルのメモリ管理(delete)を担当する必要があります。しかし、QFileDialog が閉じた後もプロキシモデルが参照され続ける場合、または、QFileDialog が閉じられる前にプロキシモデルが解放されてしまう場合に問題が発生します。

トラブルシューティング

  • デバッガの使用
    クラッシュが発生した場合は、デバッガを使用してコールスタックを確認し、どこで問題が発生しているかを特定します。

  • スマートポインタの利用
    std::unique_ptr などのスマートポインタを使用して、プロキシモデルのライフサイクルを自動的に管理することも検討できます。

  • 親オブジェクトの設定
    プロキシモデルを QFileDialog の子オブジェクトとして設定することで、QFileDialog が破棄される際にプロキシモデルも自動的に破棄されるようにすることができます。これが最も推奨される方法です。

    QFileDialog dialog(this);
    dialog.setOption(QFileDialog::DontUseNativeDialog);
    MyCustomProxyModel* proxyModel = new MyCustomProxyModel(&dialog); // ここで親をdialogに設定
    dialog.setProxyModel(proxyModel);
    // ...
    dialog.exec(); // dialogが破棄されるとproxyModelも破棄される
    

QFileDialog::setNameFilter() との競合

原因
QFileDialog::setProxyModel() を使用すると、内部のモデルが置き換えられるため、QFileDialog::setNameFilter() で設定されたフィルタは機能しないことがあります。setNameFilter() はデフォルトの QFileSystemModel に対して動作することを意図しているからです。

トラブルシューティング

  • フィルタ名の表示
    QFileDialog::setNameFilter() は、ダイアログ下部のファイルタイプ選択ドロップダウンに表示される名前を設定するために使用できます。この名前と、プロキシモデルで実際に適用されるフィルタリングロジックを一致させるようにします。
  • プロキシモデルでフィルタリングを実装する
    setNameFilter() の代わりに、プロキシモデルの filterAcceptsRow() メソッド内で、必要なファイル名フィルタリングのロジックを実装します。ユーザーに表示したいフィルタの名前(例: "画像ファイル (*.jpg *.png)")は、QFileDialog::setNameFilter() で設定できますが、実際のフィルタリングはプロキシモデルが担当します。

仮想的な項目(ドライブ、フォルダなど)の追加と動作

原因
QSortFilterProxyModel は、主にフィルタリングと並び替えに特化したプロキシモデルであり、ソースモデルに存在しない新しい項目(仮想的なドライブやフォルダなど)を追加することには向いていません。QSortFilterProxyModel を使用して仮想項目を追加しようとすると、ダブルクリック時の動作がおかしかったり、パス解決に問題が生じたりする可能性があります。

トラブルシューティング

  • QFileDialogを独自に実装する
    非常に特殊な要件がある場合は、QFileDialog を完全に諦め、QTreeViewQListView を使用して独自のファイル選択ダイアログを構築することを検討する方が、最終的に堅牢なソリューションになる場合があります。
  • QAbstractProxyModel の直接継承
    ソースモデルに存在しない項目を追加する必要がある場合は、QSortFilterProxyModel ではなく、直接 QAbstractProxyModel を継承し、mapFromSource(), mapToSource(), index(), parent(), rowCount(), columnCount(), data() などの基本的なモデル関数を適切に実装する必要があります。これは非常に複雑な作業であり、QFileDialog の内部動作を深く理解している必要があります。

QFileDialog::proxyModel() を使用する際のトラブルシューティングの要点は以下の通りです。

  1. QFileDialog::DontUseNativeDialog を必ず設定する。
  2. プロキシモデルのライフサイクル管理(親オブジェクトの設定が最も簡単)。
  3. filterAcceptsRow()lessThan() のロジックを慎重にデバッグする。
  4. 仮想項目の追加は QSortFilterProxyModel には不向き。必要なら QAbstractProxyModel の直接継承を検討するか、独自のダイアログを構築する。


例1: 特定の拡張子を持つファイルのみを表示する

この例では、MyFilterProxyModel というカスタムのプロキシモデルを作成します。このモデルは、filterAcceptsRow() メソッドをオーバーライドして、ファイル名に基づいてフィルタリングを行います。

main.cpp

#include <QApplication>
#include <QFileDialog>
#include <QSortFilterProxyModel>
#include <QFileSystemModel>
#include <QDebug> // デバッグ出力用

// カスタムのプロキシモデルクラス
class MyFilterProxyModel : public QSortFilterProxyModel
{
    Q_OBJECT // Qtのメタオブジェクトシステムを使用するために必要

public:
    explicit MyFilterProxyModel(QObject *parent = nullptr)
        : QSortFilterProxyModel(parent)
    {
    }

protected:
    // このメソッドをオーバーライドしてフィルタリングロジックを実装
    bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override
    {
        // ソースモデル(QFileSystemModel)から元のインデックスを取得
        QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);

        // ファイルシステムモデルのデータを取得
        // QFileSystemModelの場合、QFileSystemModel::FileNameRole でファイル名を取得できます
        QString fileName = sourceModel()->data(index, QFileSystemModel::FileNameRole).toString();

        // QFileSystemModel::FilePathRole でフルパスも取得できます
        // QString filePath = sourceModel()->data(index, QFileSystemModel::FilePathRole).toString();

        // フォルダは常に表示
        if (sourceModel()->data(index, QFileSystemModel::IsDir).toBool()) {
            return true;
        }

        // 特定の拡張子(.txt または .log)を持つファイルのみ表示
        if (fileName.endsWith(".txt", Qt::CaseInsensitive) ||
            fileName.endsWith(".log", Qt::CaseInsensitive)) {
            return true;
        }

        return false; // それ以外のファイルは非表示
    }
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QFileDialog dialog;
    dialog.setWindowTitle("カスタムフィルタリングされたファイルダイアログ");

    // ★重要: ネイティブダイアログではなくQtのウィジェットベースのダイアログを使用する
    // プロキシモデルはネイティブダイアログでは機能しません
    dialog.setOption(QFileDialog::DontUseNativeDialog);

    // プロキシモデルのインスタンスを作成し、QFileDialogを親に設定
    // これにより、dialogが破棄されるときにproxyModelも自動的に破棄されます
    MyFilterProxyModel *proxyModel = new MyFilterProxyModel(&dialog);

    // QFileDialogの内部で使用されるデフォルトのモデル(QFileSystemModel)を取得し、
    // それをプロキシモデルのソースモデルとして設定します。
    // QFileDialogがすでに内部でQFileSystemModelをセットアップしているため、
    // ここで明示的にQFileSystemModelを作成する必要はありません。
    // setProxyModel()を呼び出すことで、QFileDialogは内部のQFileSystemModelを
    // プロキシモデルのソースモデルとして自動的に設定しようとします。
    // QFileDialogのドキュメントによると、setProxyModel()を呼び出すと、
    // QFileDialogは内部のビューにこのプロキシモデルを設定し、
    // プロキシモデルのソースモデルはQFileDialogが管理するQFileSystemModelになります。
    // 厳密には、QFileDialog::setProxyModel()を呼び出すだけで、
    // QFileDialogの内部のQFileSystemModelがproxyModelのソースモデルとして自動的に設定されます。
    // 以下の行はデモンストレーションのためですが、実際には不要な場合が多いです。
    // QFileSystemModel* fileSystemModel = qobject_cast<QFileSystemModel*>(dialog.findChild<QAbstractItemModel*>());
    // if (fileSystemModel) {
    //     proxyModel->setSourceModel(fileSystemModel);
    // }

    // QFileDialogにカスタムプロキシモデルを設定
    dialog.setProxyModel(proxyModel);

    // 初期ディレクトリを設定(オプション)
    dialog.setDirectory(QDir::homePath());

    // ユーザーがファイルのみ選択できるように設定
    dialog.setFileMode(QFileDialog::ExistingFile);

    // ファイルダイアログを表示し、結果を処理
    if (dialog.exec() == QDialog::Accepted) {
        QStringList selectedFiles = dialog.selectedFiles();
        if (!selectedFiles.isEmpty()) {
            qDebug() << "選択されたファイル:" << selectedFiles.first();
        }
    } else {
        qDebug() << "ファイル選択がキャンセルされました。";
    }

    return a.exec();
}

#include "main.moc" // MOCで生成されるファイルをインクルード(Q_OBJECTを使用するため)

.pro ファイル (Qt Creator を使用している場合)

QT += widgets

SOURCES += main.cpp

HEADERS += # 必要であればヘッダーファイルを追加

コンパイルと実行

Qt Creator を使用してプロジェクトを作成し、上記のコードを貼り付けて実行します。

実行すると、通常のファイルダイアログが表示されますが、.txt ファイルと .log ファイル、およびフォルダのみが表示され、他の拡張子のファイルは非表示になっているはずです。

    • QFileDialog dialog;QFileDialog のインスタンスを作成します。
    • dialog.setOption(QFileDialog::DontUseNativeDialog);
      これが最も重要なポイントです。ネイティブのファイルダイアログはプロキシモデルをサポートしないため、Qtのウィジェットベースのダイアログを使用するように強制する必要があります。
    • MyFilterProxyModel *proxyModel = new MyFilterProxyModel(&dialog); でカスタムプロキシモデルのインスタンスを作成します。ここで &dialog を親として設定することで、dialog が破棄されるときに proxyModel も自動的に破棄されるように、メモリ管理をQtに任せることができます。
    • dialog.setProxyModel(proxyModel); で、作成したプロキシモデルを QFileDialog に設定します。QFileDialog は内部的に QFileSystemModel を使用しており、この setProxyModel() を呼び出すことで、QFileDialog はその QFileSystemModelproxyModel のソースモデルとして自動的に設定してくれます。
    • dialog.setDirectory(QDir::homePath()); で初期ディレクトリを設定します。
    • dialog.setFileMode(QFileDialog::ExistingFile); で既存のファイルのみを選択できるように設定します。
    • dialog.exec() を呼び出して、モーダルダイアログとして表示します。
    • ユーザーがファイルを選択して「開く」をクリックした場合、QDialog::Accepted が返され、dialog.selectedFiles() で選択されたファイルのリストを取得できます。

並び替えをカスタマイズする例 (数字を含むファイル名)

ファイル名に数字が含まれる場合、通常の文字列比較では file10.txtfile2.txt の前に来てしまいます。これを自然な順序 (file2.txt, file10.txt) に並べ替えるには、QSortFilterProxyModellessThan() メソッドをオーバーライドします。

// MyNaturalSortProxyModel.h
#include <QSortFilterProxyModel>
#include <QFileInfo>

class MyNaturalSortProxyModel : public QSortFilterProxyModel
{
    Q_OBJECT
public:
    explicit MyNaturalSortProxyModel(QObject *parent = nullptr);

protected:
    bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
};

// MyNaturalSortProxyModel.cpp
#include "MyNaturalSortProxyModel.h"
#include <QRegularExpression> // 自然な順序の比較に使う

MyNaturalSortProxyModel::MyNaturalSortProxyModel(QObject *parent)
    : QSortFilterProxyModel(parent)
{
    // ソースモデルのソートを有効にする
    setDynamicSortFilter(true); // ソースモデルの変更時にプロキシモデルも更新
}

bool MyNaturalSortProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
    // QFileSystemModel の場合、QFileSystemModel::FileNameRole でファイル名を取得
    QString leftFileName = sourceModel()->data(left, QFileSystemModel::FileNameRole).toString();
    QString rightFileName = sourceModel()->data(right, QFileSystemModel::FileNameRole).toString();

    // ディレクトリは常にファイルの前に来るようにする
    bool leftIsDir = sourceModel()->data(left, QFileSystemModel::IsDir).toBool();
    bool rightIsDir = sourceModel()->data(right, QFileSystemModel::IsDir).toBool();

    if (leftIsDir != rightIsDir) {
        return leftIsDir; // leftがディレクトリならtrue(leftが先)
    }

    // ここに自然な順序での比較ロジックを実装
    // 例: 数字部分を数値として比較する
    QRegularExpression re("(\\d+)"); // 数字のグループを抽出

    QStringList leftParts = leftFileName.split(re);
    QStringList rightParts = rightFileName.split(re);

    int minSize = qMin(leftParts.size(), rightParts.size());
    for (int i = 0; i < minSize; ++i) {
        bool leftIsNumber, rightIsNumber;
        int leftNum = leftParts[i].toInt(&leftIsNumber);
        int rightNum = rightParts[i].toInt(&rightIsNumber);

        if (leftIsNumber && rightIsNumber) {
            if (leftNum != rightNum) {
                return leftNum < rightNum;
            }
        } else {
            int cmp = QString::compare(leftParts[i], rightParts[i], Qt::CaseInsensitive);
            if (cmp != 0) {
                return cmp < 0;
            }
        }
    }
    return leftFileName.length() < rightFileName.length(); // 残りの部分で比較
}

// main.cpp での使用例
// ...
// MyNaturalSortProxyModel* proxyModel = new MyNaturalSortProxyModel(&dialog);
// dialog.setProxyModel(proxyModel);
// dialog.setOption(QFileDialog::DontUseNativeDialog);
// dialog.exec();
// ...

この例では、lessThan() をオーバーライドして、ファイル名に数字が含まれる場合の自然な並び替えロジックを実装しています。QRegularExpression を使って数字とそれ以外の部分を分割し、数値と文字列で比較します。



QFileDialog::setNameFilter() / setNameFilters() を使用する

最も一般的なフィルタリング要件(特定のファイル拡張子のみを表示するなど)であれば、proxyModel() を使わなくても QFileDialog の組み込みフィルタ機能で十分です。

特徴

  • 制限
    ファイル名や拡張子以外の複雑な条件(例:ファイルサイズ、最終更新日時、ファイル内容によるフィルタリング)には対応できません。また、並び替えのカスタマイズもできません。
  • ネイティブダイアログ対応
    多くのOSのネイティブファイルダイアログで機能します。これが proxyModel() との大きな違いです。
  • 簡単
    最も手軽にファイルタイプによるフィルタリングを実現できます。

使用例

#include <QApplication>
#include <QFileDialog>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QFileDialog dialog;
    dialog.setWindowTitle("名前フィルタリングされたファイルダイアログ");

    // 単一のフィルタを設定
    // dialog.setNameFilter("画像ファイル (*.png *.jpg *.bmp)");

    // 複数のフィルタを設定
    dialog.setNameFilters(QStringList()
                          << "テキストファイル (*.txt)"
                          << "ログファイル (*.log)"
                          << "全てのファイル (*)");

    // 初期ディレクトリを設定
    dialog.setDirectory(QDir::homePath());

    // 既存のファイルのみ選択可能に設定
    dialog.setFileMode(QFileDialog::ExistingFile);

    if (dialog.exec() == QDialog::Accepted) {
        QStringList selectedFiles = dialog.selectedFiles();
        if (!selectedFiles.isEmpty()) {
            qDebug() << "選択されたファイル:" << selectedFiles.first();
        }
    } else {
        qDebug() << "ファイル選択がキャンセルされました。";
    }

    return a.exec();
}

この方法では、proxyModel() のように DontUseNativeDialog オプションを設定する必要はありません。

QFileDialog::setFilter() を使用する

QDir::Filters を使用して、隠しファイルを表示したり、ディレクトリのみを表示したりといった基本的なフィルタリングを行うことができます。

特徴

  • ネイティブダイアログ対応
    setNameFilter() と同様にネイティブダイアログで機能します。
  • 基本的なファイル属性によるフィルタリング
    隠しファイル、システムファイル、ディレクトリのみの表示などを制御できます。

使用例

#include <QApplication>
#include <QFileDialog>
#include <QDir> // QDir::Filters を使用するため
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QFileDialog dialog;
    dialog.setWindowTitle("QDir::Filters を使ったファイルダイアログ");

    // 隠しファイルも表示するフィルタを設定
    dialog.setFilter(QDir::Files | QDir::Dirs | QDir::Hidden);

    // あるいはディレクトリのみ表示 (FileModeもDirectoryに設定する必要がある)
    // dialog.setFileMode(QFileDialog::Directory);
    // dialog.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); // . と .. を除く

    dialog.setDirectory(QDir::homePath());

    if (dialog.exec() == QDialog::Accepted) {
        QStringList selectedItems = dialog.selectedFiles();
        if (!selectedItems.isEmpty()) {
            qDebug() << "選択されたアイテム:" << selectedItems.first();
        }
    } else {
        qDebug() << "キャンセルされました。";
    }

    return a.exec();
}

独自のファイル選択ダイアログを実装する

QFileDialog の組み込み機能や proxyModel() では対応できないほど複雑な要件がある場合(例:

  • ファイルの内容をプレビュー表示したい。
  • 非常に特殊なフィルタリングやソートロジックが必要。
  • ファイル選択以外のカスタムウィジェットをダイアログ内に配置したい。
  • ファイルシステム以外のソース(データベース、ネットワークドライブなど)から項目を表示したい。

といった場合)、QDialog を継承して完全に独自のファイル選択ダイアログを作成するのが最終手段となります。

特徴

  • 複雑性
    ゼロからファイルシステムビュー、ナビゲーション、選択ロジックを実装する必要があり、開発コストが非常に高くなります。通常は QTreeViewQListViewQFileSystemModel を組み合わせて構築します。
  • 完全なカスタマイズ性
    レイアウト、表示される情報、フィルタリング、ソートロジックなど、すべてを自由に設計できます。

実装の一般的なアプローチ

  1. QDialog を継承したクラスを作成
    これがカスタムファイルダイアログのベースとなります。
  2. QFileSystemModel の使用
    ファイルシステムの内容を表示するために、QFileSystemModel を使用します。
  3. QTreeView または QListView の使用
    QFileSystemModel のデータを表示するためのビューアとして使用します。
  4. フィルタリング/ソートロジック
    • QSortFilterProxyModel をビューに適用することで、QFileDialog::proxyModel() と同じような強力なフィルタリング・ソートを実現できます。この場合、結局 proxyModel の考え方を使うことになりますが、ダイアログ全体を完全に制御できるという点が異なります。
    • あるいは、QFileSystemModelsetNameFilters()setFilter() を直接利用することも可能です。
  5. カスタムウィジェットの追加
    プレビューペイン、追加のオプション、検索バーなどを配置できます。
  6. シグナルとスロットの接続
    ユーザーの選択を処理するために、ビューの clicked()doubleClicked() シグナルなどを接続します。
  7. 結果の返却
    QDialog::AcceptedQDialog::Rejected を返すように accept()reject() スロットを実装し、選択されたファイルパスを外部に提供するメソッド(例: selectedFiles())を用意します。

使用例 (概念的なコード - 完全に動作するものではありません)

#include <QDialog>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QTreeView>
#include <QListView>
#include <QFileSystemModel>
#include <QPushButton>
#include <QLineEdit>
#include <QLabel>
#include <QStandardPaths> // 初期パス取得用
#include <QDebug>

class CustomFileDialog : public QDialog
{
    Q_OBJECT

public:
    explicit CustomFileDialog(QWidget *parent = nullptr) : QDialog(parent)
    {
        setWindowTitle("カスタムファイル選択ダイアログ");
        setMinimumSize(800, 600);

        QVBoxLayout *mainLayout = new QVBoxLayout(this);

        // ファイルシステムモデル
        fileSystemModel = new QFileSystemModel(this);
        fileSystemModel->setRootPath(QDir::homePath()); // 初期ディレクトリ
        fileSystemModel->setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); // 隠しファイルなど含める場合は調整

        // ★ ここで proxyModel を設定することも可能
        // MyCustomProxyModel* proxyModel = new MyCustomProxyModel(this);
        // proxyModel->setSourceModel(fileSystemModel);
        // fileTreeView->setModel(proxyModel);

        // ツリービューでディレクトリとファイルを表示
        fileTreeView = new QTreeView(this);
        fileTreeView->setModel(fileSystemModel);
        fileTreeView->setRootIndex(fileSystemModel->index(QDir::homePath())); // ルートインデックス設定

        // 最初の列(名前)のみ表示し、他の列は非表示にする
        for (int i = 1; i < fileSystemModel->columnCount(); ++i) {
            fileTreeView->hideColumn(i);
        }
        fileTreeView->setHeaderHidden(true); // ヘッダーも非表示

        mainLayout->addWidget(fileTreeView);

        // 現在のパスを表示するラベル
        QLabel *currentPathLabel = new QLabel("現在のパス:");
        currentPathLineEdit = new QLineEdit(this);
        currentPathLineEdit->setReadOnly(true);
        QHBoxLayout *pathLayout = new QHBoxLayout();
        pathLayout->addWidget(currentPathLabel);
        pathLayout->addWidget(currentPathLineEdit);
        mainLayout->addLayout(pathLayout);

        // ファイル名入力欄
        fileNameLineEdit = new QLineEdit(this);
        fileNameLineEdit->setPlaceholderText("ファイル名を入力または選択...");
        mainLayout->addWidget(fileNameLineEdit);

        // ボタン
        QHBoxLayout *buttonLayout = new QHBoxLayout();
        QPushButton *openButton = new QPushButton("開く", this);
        QPushButton *cancelButton = new QPushButton("キャンセル", this);
        buttonLayout->addStretch();
        buttonLayout->addWidget(openButton);
        buttonLayout->addWidget(cancelButton);
        mainLayout->addLayout(buttonLayout);

        // シグナルとスロットの接続
        connect(fileTreeView, &QTreeView::doubleClicked, this, &CustomFileDialog::onTreeViewDoubleClicked);
        connect(fileSystemModel, &QFileSystemModel::directoryLoaded, this, &CustomFileDialog::onDirectoryLoaded);
        connect(openButton, &QPushButton::clicked, this, &CustomFileDialog::accept);
        connect(cancelButton, &QPushButton::clicked, this, &CustomFileDialog::reject);

        // 初期パスをセット
        currentPathLineEdit->setText(fileSystemModel->rootPath());
    }

    QString selectedFile() const {
        return fileNameLineEdit->text();
    }

private slots:
    void onTreeViewDoubleClicked(const QModelIndex &index)
    {
        if (fileSystemModel->isDir(index)) {
            // ディレクトリをダブルクリックした場合、そのディレクトリに移動
            fileTreeView->setRootIndex(index);
            currentPathLineEdit->setText(fileSystemModel->filePath(index));
        } else {
            // ファイルをダブルクリックした場合、ファイル名をQLineEditに表示
            fileNameLineEdit->setText(fileSystemModel->fileName(index));
            accept(); // ダイアログを閉じることも可能
        }
    }

    void onDirectoryLoaded(const QString &path)
    {
        currentPathLineEdit->setText(path);
    }

private:
    QFileSystemModel *fileSystemModel;
    QTreeView *fileTreeView;
    QLineEdit *currentPathLineEdit;
    QLineEdit *fileNameLineEdit;
};

// main.cpp での使用例
/*
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    CustomFileDialog dialog;
    if (dialog.exec() == QDialog::Accepted) {
        qDebug() << "選択されたファイル:" << dialog.selectedFile();
    } else {
        qDebug() << "キャンセルされました。";
    }

    return a.exec();
}
*/
#include "main.moc" // Q_OBJECT を使用する場合

このカスタムダイアログの実装は非常に単純化されていますが、これにより、ファイルダイアログの表示と機能を完全に制御できることがわかります。

QFileDialog::proxyModel() は強力ですが、常に必要というわけではありません。

  • 完全なカスタマイズが必要な場合
    独自の QDialog を実装し、QFileSystemModelQTreeView (または QListView) を組み合わせて使用します。この場合でも、フィルタリングや並び替えには QSortFilterProxyModel を活用できます。
  • より複雑なフィルタリングや並び替え、仮想項目の追加
    QFileDialog::proxyModel() が最適な選択肢です。ただし、QFileDialog::DontUseNativeDialog が必須であることに注意が必要です。
  • 簡単な拡張子フィルタリングや基本的なファイル属性フィルタリング
    QFileDialog::setNameFilter() / setNameFilters()QFileDialog::setFilter() を使用するのが最も簡単で推奨されます。これらはネイティブダイアログでも機能します。