Qtプログラミング入門:QFileDialog::getExistingDirectory()徹底解説

2025-05-27

以下に詳しく説明します。

目的

この関数の主な目的は、アプリケーション内でユーザーに特定のディレクトリを選ばせたい場合に、簡単に標準的なディレクトリ選択ダイアログを表示することです。例えば、設定ファイルの保存先、ログファイルの出力先、ダウンロードファイルの保存先などをユーザーに指定させたい場合に便利です。

静的関数であること

getExistingDirectory()は静的関数なので、QFileDialogオブジェクトをインスタンス化することなく直接呼び出すことができます。

QString selectedDirectory = QFileDialog::getExistingDirectory(this, tr("ディレクトリを選択"), QDir::homePath());

引数

getExistingDirectory()にはいくつかのオーバーロードがありますが、一般的に使用されるのは以下の引数を持つものです。

QString QFileDialog::getExistingDirectory(QWidget *parent = nullptr,
                                         const QString &caption = QString(),
                                         const QString &dir = QString(),
                                         QFileDialog::Options options = ShowDirsOnly)
  • QFileDialog::Options options:
    • ダイアログの動作を制御するオプションのフラグを指定します。複数のオプションは|演算子で結合できます。よく使われるオプションには以下のようなものがあります。
      • QFileDialog::ShowDirsOnly: (デフォルト)ディレクトリのみを表示し、ファイルは表示しません。これがgetExistingDirectory()の基本的な動作です。
      • QFileDialog::DontResolveSymlinks: シンボリックリンクを解決しません。
      • QFileDialog::DontUseNativeDialog: プラットフォームネイティブのファイルダイアログではなく、Qtが提供する標準のファイルダイアログを使用します。通常はネイティブダイアログが使用されますが、特定の動作をカスタマイズしたい場合などに使用できます。
  • const QString &dir:
    • ダイアログが最初に開かれるディレクトリのパスを指定します。例えば、QDir::homePath()を指定するとユーザーのホームディレクトリが初期表示されます。空のQStringを渡すと、システムがデフォルトのディレクトリ(通常は最後に開いたディレクトリや現在の作業ディレクトリ)を使用します。
  • const QString &caption:
    • ダイアログのタイトルバーに表示されるキャプション(タイトル)を指定します。ユーザーにダイアログの目的を伝えるために使用されます。
  • QWidget *parent:
    • ダイアログの親ウィジェットを指定します。親を指定することで、ダイアログが親ウィンドウの中心に表示されたり、親ウィンドウが最小化された際にダイアログも最小化されたりするなど、親ウィンドウとの関連付けがなされます。nullptrを指定することも可能ですが、通常は呼び出し元のウィジェット(例: this)を指定します。

戻り値

ユーザーがディレクトリを選択して「開く」(または「選択」)ボタンをクリックした場合、選択されたディレクトリの絶対パスがQStringで返されます。ユーザーがダイアログをキャンセルした場合(「キャンセル」ボタンをクリックしたり、ウィンドウを閉じたりした場合)、空のQStringが返されます。

#include <QApplication>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>
#include <QDir> // QDir::homePath() を使うために必要

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

    QPushButton button("ディレクトリを選択");

    QObject::connect(&button, &QPushButton::clicked, [&]() {
        // ディレクトリ選択ダイアログを表示
        QString selectedDirectory = QFileDialog::getExistingDirectory(
            &button,                          // 親ウィジェット
            QObject::tr("保存先フォルダを選択してください"), // ダイアログのタイトル
            QDir::homePath(),                 // 初期表示ディレクトリ(ユーザーのホームディレクトリ)
            QFileDialog::ShowDirsOnly         // オプション:ディレクトリのみを表示
        );

        if (!selectedDirectory.isEmpty()) {
            QMessageBox::information(
                &button,
                QObject::tr("選択されたディレクトリ"),
                QObject::tr("選択されたディレクトリパス:\n") + selectedDirectory
            );
        } else {
            QMessageBox::warning(
                &button,
                QObject::tr("選択キャンセル"),
                QObject::tr("ディレクトリの選択がキャンセルされました。")
            );
        }
    });

    button.show();

    return a.exec();
}

このコードでは、ボタンをクリックするとディレクトリ選択ダイアログが表示され、ユーザーがディレクトリを選択するとそのパスがメッセージボックスで表示されます。キャンセルした場合はその旨が通知されます。



ユーザーがキャンセルした場合に、その処理が漏れる

エラー/問題

ユーザーがディレクトリ選択ダイアログで「キャンセル」ボタンをクリックしたり、ダイアログを閉じたりした場合に、その後のプログラムの処理が意図しない動作をする。

説明

getExistingDirectory() は、ユーザーがキャンセルした場合に空の QString を返します。この戻り値を適切にチェックしないと、空のパスに対してファイル操作を行おうとしてエラーが発生したり、予期せぬ場所にファイルが保存されたりする可能性があります。

トラブルシューティング

常に getExistingDirectory() の戻り値が空でないかを確認し、空の場合は適切なエラー処理や、その後の処理をスキップするようにします。

QString selectedDirectory = QFileDialog::getExistingDirectory(this, tr("ディレクトリを選択"));

if (selectedDirectory.isEmpty()) {
    // ユーザーがキャンセルした
    QMessageBox::warning(this, tr("キャンセル"), tr("ディレクトリの選択がキャンセルされました。"));
    return; // または、他の適切な処理
}

// ここに、selectedDirectory を使用するロジックを記述
// 例: ファイルを保存する、リストを更新する など
qDebug() << "選択されたディレクトリ:" << selectedDirectory;

初期ディレクトリが適切に表示されない

dir 引数に指定したパスが、ダイアログを開いたときに初期表示されるディレクトリとして反映されない。

いくつかの原因が考えられます。

  • プラットフォームの挙動
    OSによっては、初期ディレクトリの指定が常に尊重されるとは限りません。特に、ユーザーが最後に開いたディレクトリを記憶するような挙動をする場合があります。
  • 無効なパス
    指定したパスが存在しない、またはアクセス権がない。
  • QStandardPaths の利用
    よく使われるシステムディレクトリ(ドキュメント、ダウンロードなど)には QStandardPaths を利用すると安全で移植性の高いパスが得られます。
  • アクセス権の確認
    アプリケーションがそのパスにアクセスできる権限を持っているか確認します。
  • 絶対パスの使用
    相対パスではなく、常に絶対パスを指定するようにします。
  • パスの存在確認
    QDir::exists() などを使用して、指定するパスが実際に存在するかどうかを確認します。
// 存在しないパスを指定した場合
// QString initialDir = "/path/to/nonexistent/directory"; // これは問題を起こす可能性あり

// 確実に存在するパスを指定 (ユーザーのホームディレクトリ)
QString initialDir = QDir::homePath();

// または、ダウンロードディレクトリ
// QString initialDir = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);

QString selectedDirectory = QFileDialog::getExistingDirectory(this, tr("ディレクトリを選択"), initialDir);

ダイアログがアプリケーションの背後に隠れる (Z-order の問題)

QFileDialog がアプリケーションのメインウィンドウの背後に表示されてしまい、ユーザーがダイアログに気づかない、または操作できない。

通常、モーダルダイアログは親ウィジェットの前面に表示されるべきです。しかし、親ウィジェットが適切に指定されていない場合や、特定のOS・ウィンドウマネージャーの環境下で、Z-order(重なり順)の問題が発生することがあります。

  • parent 引数の指定
    getExistingDirectory() の第一引数に、必ず親となる QWidget のポインタ(例: this)を指定します。これにより、ダイアログが親ウィンドウに紐付けられ、前面に表示されやすくなります。
// 良い例: 親ウィジェットを指定
QString selectedDirectory = QFileDialog::getExistingDirectory(this, tr("ディレクトリを選択"));

// 悪い例: 親ウィジェットをnullptrにする(特殊な場合を除き避ける)
// QString selectedDirectory = QFileDialog::getExistingDirectory(nullptr, tr("ディレクトリを選択"));

ネイティブダイアログとQt標準ダイアログの挙動の違い

異なるOSでダイアログの見た目や挙動が異なり、テストが難しい、または期待するUXが得られない。

QFileDialog はデフォルトでプラットフォームネイティブのダイアログを使用しようとします。これはOSの見た目に馴染むという利点がありますが、OS間の挙動の違い(初期ディレクトリの扱いやオプションのサポートなど)を吸収しきれない場合があります。

  • QFileDialog::DontUseNativeDialog オプションの使用
    全てのプラットフォームでQt標準のダイアログ(見た目や挙動が統一されている)を使用したい場合は、QFileDialog::DontUseNativeDialog オプションを指定します。
QString selectedDirectory = QFileDialog::getExistingDirectory(
    this,
    tr("ディレクトリを選択"),
    QDir::homePath(),
    QFileDialog::ShowDirsOnly | QFileDialog::DontUseNativeDialog // Qt標準ダイアログを使用
);

ただし、ネイティブダイアログの方がOSに馴染みやすいというメリットもあるため、これはトレードオフです。

アクセス権の問題

ユーザーが選択したディレクトリに対して、アプリケーションがファイルの読み書きを行おうとしたときに「アクセス拒否」エラーが発生する。

ユーザーがディレクトリを選択できたとしても、アプリケーションがそのディレクトリに対する適切な読み書き権限を持っていない場合があります。これは、OSのセキュリティ設定(UAC、Sandboxなど)や、ネットワークドライブなどの特殊な場所で発生しやすいです。

  • ユーザーへの説明
    なぜそのディレクトリに書き込めないのか、ユーザーに分かりやすく説明し、別のディレクトリを選択するように促します。
  • 適切な権限の要求
    アプリケーションが特定のシステムディレクトリに書き込む必要がある場合、OSの推奨する方法で権限を要求する必要があるかもしれません(例: WindowsのUAC昇格、macOSのサンドボックス設定)。
  • エラーハンドリング
    ファイル操作を行う前に、QFile::exists()QFile::open() の戻り値などをチェックし、エラーが発生した場合はユーザーに通知します。
// 選択されたディレクトリにファイルを書き込む例
QFile file(selectedDirectory + "/my_file.txt");
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
    QMessageBox::critical(this, tr("エラー"), tr("指定されたディレクトリに書き込めません:\n") + file.errorString());
    return;
}
QTextStream out(&file);
out << "Hello, Qt!";
file.close();

非常に長いパス名を持つディレクトリを選択した場合に、ファイル操作が失敗する、またはパスが正しく扱われない。

Windowsでは、260文字のパス長の制限(MAX_PATH)が歴史的に存在しました。Qtは内部的にこの制限を緩和しようとしますが、基盤となるOSのAPIや、一部の古いファイルシステムでは依然として問題が発生する可能性があります。

  • Windows 10/11 の設定
    Windows 10/11では、グループポリシーまたはレジストリ設定でMAX_PATHの制限を解除できますが、これはユーザー側の設定に依存します。
  • ファイルシステムの調査
    ユーザーが使用しているファイルシステムが長いパス名をサポートしているか確認します。
  • ユーザーへの注意喚起
    可能な限り、深い階層や非常に長い名前のディレクトリを避けるよう、ユーザーに促すメッセージを表示することも検討します。


基本的な使用例

最もシンプルにディレクトリ選択ダイアログを表示する例です。

#include <QApplication>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug> // デバッグ出力用

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

    // ボタンの作成
    QPushButton button("ディレクトリを選択");

    // ボタンがクリックされたときの処理
    QObject::connect(&button, &QPushButton::clicked, [&]() {
        // ディレクトリ選択ダイアログを表示
        // 引数: 親ウィジェット, ダイアログタイトル, 初期表示ディレクトリ
        QString selectedDirectory = QFileDialog::getExistingDirectory(
            &button,                          // 親ウィジェット (nullptrでも可だが、推奨)
            QObject::tr("ファイル保存先を選択"), // ダイアログのタイトル
            QDir::homePath()                  // 初期表示ディレクトリをユーザーのホームディレクトリに設定
        );

        // ユーザーがディレクトリを選択したかどうかのチェック
        if (!selectedDirectory.isEmpty()) {
            QMessageBox::information(
                &button,
                QObject::tr("選択結果"),
                QObject::tr("選択されたディレクトリ:\n") + selectedDirectory
            );
            qDebug() << "選択されたディレクトリ:" << selectedDirectory;
        } else {
            QMessageBox::warning(
                &button,
                QObject::tr("キャンセル"),
                QObject::tr("ディレクトリの選択がキャンセルされました。")
            );
            qDebug() << "ディレクトリ選択がキャンセルされました。";
        }
    });

    button.show(); // ボタンを表示

    return app.exec(); // イベントループを開始
}

解説

  • 戻り値が isEmpty() で空でないかチェックし、ユーザーがディレクトリを選択したかキャンセルしたかを判断しています。
  • 第三引数で QDir::homePath() を指定し、ユーザーのホームディレクトリを初期表示ディレクトリとしています。
  • 第二引数でダイアログのタイトルを設定しています。
  • QFileDialog::getExistingDirectory() の第一引数に &button を渡すことで、ダイアログの親ウィジェットを指定しています。これにより、ダイアログは親ウィンドウの前面に表示され、親ウィンドウとの関連付けがなされます。

オプションを指定した使用例

QFileDialog::Options を使って、ダイアログの動作をカスタマイズする例です。ここでは、ネイティブダイアログではなくQt標準のダイアログを使用するオプションを試します。

#include <QApplication>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QStandardPaths> // 標準パスの取得用

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

    QPushButton button("Qt標準ダイアログでディレクトリを選択");

    QObject::connect(&button, &QPushButton::clicked, [&]() {
        // ダウンロードディレクトリを初期パスにする
        QString initialPath = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation);
        if (initialPath.isEmpty()) {
            // ダウンロードディレクトリが見つからない場合はホームディレクトリを使用
            initialPath = QDir::homePath();
        }

        // オプションとして Qt標準ダイアログを使用 (DontUseNativeDialog) を追加
        QString selectedDirectory = QFileDialog::getExistingDirectory(
            &button,
            QObject::tr("ダウンロードフォルダを指定"),
            initialPath,
            QFileDialog::ShowDirsOnly | QFileDialog::DontUseNativeDialog // ここにオプションを追加
        );

        if (!selectedDirectory.isEmpty()) {
            QMessageBox::information(
                &button,
                QObject::tr("選択結果"),
                QObject::tr("選択されたディレクトリ:\n") + selectedDirectory
            );
            qDebug() << "選択されたディレクトリ:" << selectedDirectory;
        } else {
            QMessageBox::warning(
                &button,
                QObject::tr("キャンセル"),
                QObject::tr("ディレクトリの選択がキャンセルされました。")
            );
            qDebug() << "ディレクトリ選択がキャンセルされました。";
        }
    });

    button.show();

    return app.exec();
}

解説

  • 第四引数の optionsQFileDialog::ShowDirsOnly | QFileDialog::DontUseNativeDialog を指定しています。
    • QFileDialog::ShowDirsOnly: これは getExistingDirectory() のデフォルトのオプションですが、明示的に指定することで意図を明確にできます。ディレクトリのみを表示し、ファイルは表示しません。
    • QFileDialog::DontUseNativeDialog: このオプションを指定すると、QtはOS標準のファイルダイアログではなく、Qtが独自に描画するファイルダイアログを使用します。これにより、OS間の見た目や挙動の差異を減らし、より統一されたユーザーエクスペリエンスを提供できます。ただし、OSの見た目に完全に一致しない場合がある点に注意が必要です。
  • QStandardPaths::writableLocation(QStandardPaths::DownloadLocation) を使用して、OSの標準的なダウンロードディレクトリを取得しています。これにより、OS間で移植性の高いパスを指定できます。

ユーザーが選択したディレクトリに実際にファイルを保存する基本的な例です。

#include <QApplication>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QFile>       // ファイル操作用
#include <QTextStream> // テキスト書き込み用
#include <QDateTime>   // ファイル名にタイムスタンプを使用するため

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

    QPushButton button("テキストファイルを保存");

    QObject::connect(&button, &QPushButton::clicked, [&]() {
        QString selectedDirectory = QFileDialog::getExistingDirectory(
            &button,
            QObject::tr("テキストファイルの保存先を選択してください"),
            QDir::currentPath() // アプリケーションの実行ディレクトリを初期パスにする
        );

        if (!selectedDirectory.isEmpty()) {
            // ファイル名を生成 (例: my_log_20250525_202606.txt)
            QString timestamp = QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss");
            QString fileName = QString("my_log_%1.txt").arg(timestamp);
            QString fullPath = selectedDirectory + QDir::separator() + fileName; // パス区切り文字を追加

            QFile file(fullPath);
            if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
                QTextStream out(&file);
                out << "これはテストログです。\n";
                out << "現在の時刻: " << QDateTime::currentDateTime().toString() << "\n";
                file.close();

                QMessageBox::information(
                    &button,
                    QObject::tr("保存完了"),
                    QObject::tr("ファイルを保存しました:\n") + fullPath
                );
                qDebug() << "ファイルが保存されました:" << fullPath;
            } else {
                QMessageBox::critical(
                    &button,
                    QObject::tr("エラー"),
                    QObject::tr("ファイルの書き込みに失敗しました:\n") + file.errorString()
                );
                qDebug() << "ファイル書き込みエラー:" << file.errorString();
            }
        } else {
            QMessageBox::warning(
                &button,
                QObject::tr("キャンセル"),
                QObject::tr("ファイル保存がキャンセルされました。")
            );
            qDebug() << "ファイル保存がキャンセルされました。";
        }
    });

    button.show();

    return app.exec();
}
  • file.open() が成功したかどうかを確認し、失敗した場合は file.errorString() でエラーメッセージを取得・表示しています。これはファイルのアクセス権がない場合などに役立ちます。
  • QDir::separator() を使って、パスとファイル名の間に適切なOS依存の区切り文字(Windowsなら\、Linux/macOSなら/)を挿入しています。これはクロスプラットフォーム開発において非常に重要です。
  • ユーザーがディレクトリを選択した後、QFileQTextStream を使って、そのディレクトリ内に新しいテキストファイルを作成・書き込みしています。
  • QDir::currentPath() を初期ディレクトリとして使用しています。これはアプリケーションが実行されているディレクトリです。


主な代替手段としては、以下のようなアプローチがあります。

QFileDialog クラスのインスタンスを使用する

getExistingDirectory() は静的関数ですが、QFileDialog オブジェクトをインスタンス化して使用することもできます。これにより、より詳細なカスタマイズやイベントベースの処理が可能になります。

利点

  • 非モーダルダイアログとして表示することも理論的には可能(ただし、ディレクトリ選択ダイアログではあまり一般的ではない)
  • fileSelected()directoryEntered() などのシグナルに接続して、ユーザー操作に応じて動的に動作を変更できる
  • より柔軟な設定(例: フィルター、プレビュー機能など、ディレクトリ選択ではあまり使われないがファイルダイアログ全般で使える機能)

欠点

  • 多くの場合は静的関数で十分
  • 静的関数を使うよりもコード量が増える

コード例

#include <QApplication>
#include <QPushButton>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QDir>

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

    QPushButton button("QFileDialog インスタンスでディレクトリを選択");

    QObject::connect(&button, &QPushButton::clicked, [&]() {
        QFileDialog dialog(&button); // QFileDialog オブジェクトをインスタンス化
        dialog.setFileMode(QFileDialog::Directory); // ディレクトリ選択モードに設定
        dialog.setOption(QFileDialog::ShowDirsOnly, true); // ディレクトリのみ表示 (getExistingDirectory() のデフォルトと同じ)
        dialog.setOption(QFileDialog::DontResolveSymlinks); // シンボリックリンクを解決しないオプション
        dialog.setDirectory(QDir::homePath()); // 初期ディレクトリ設定
        dialog.setWindowTitle(QObject::tr("QFileDialogインスタンスでディレクトリを選択"));

        if (dialog.exec()) { // ダイアログを表示し、ユーザーがOKをクリックしたら true を返す
            QStringList selectedDirs = dialog.selectedUrls().at(0).toLocalFile();
            if (!selectedDirs.isEmpty()) {
                QString selectedDirectory = selectedDirs.at(0);
                QMessageBox::information(
                    &button,
                    QObject::tr("選択結果"),
                    QObject::tr("選択されたディレクトリ:\n") + selectedDirectory
                );
                qDebug() << "選択されたディレクトリ (インスタンス):" << selectedDirectory;
            } else {
                // 通常ここには来ない (exec() が true を返すなら selectedUrls() は空でないはず)
                QMessageBox::warning(
                    &button,
                    QObject::tr("エラー"),
                    QObject::tr("ディレクトリが選択されませんでした。")
                );
            }
        } else {
            QMessageBox::warning(
                &button,
                QObject::tr("キャンセル"),
                QObject::tr("ディレクトリの選択がキャンセルされました。")
            );
            qDebug() << "ディレクトリ選択がキャンセルされました。";
        }
    });

    button.show();

    return app.exec();
}

解説

  • 選択結果は dialog.selectedUrls() から取得し、toLocalFile() でローカルファイルパスに変換します。getExistingDirectory() と異なり、URLリストとして返される点に注意が必要です。
  • dialog.exec() を呼び出すことでモーダルダイアログとして表示し、ユーザーの操作を待ちます。
  • dialog.setFileMode(QFileDialog::Directory); でファイルダイアログをディレクトリ選択モードに切り替えます。これが getExistingDirectory() と同じ動作をするための重要な設定です。
  • QFileDialog dialog(&button); でインスタンスを作成します。

独自のディレクトリ選択ウィジェット/ダイアログを作成する

標準のダイアログでは要件を満たせない場合(例: 特定の制約を設けたい、ツリービューで表示したい、カスタムボタンを追加したいなど)、自作のウィジェットやダイアログを作成する方法があります。

利点

  • 特定のビジネスロジックや制約をダイアログ内に組み込める
  • アプリケーションのデザインに完全に統合できる
  • 完全なカスタマイズ性

欠点

  • ファイルのリスト表示やナビゲーション機能を自前で実装する必要がある
  • OSネイティブの見た目や使い勝手を模倣するのが難しい
  • 開発コストが高い

考えられる実装方法

  • パス表示用の QLineEdit と、上層ディレクトリへの移動ボタン、決定/キャンセルボタンなどを配置する。
  • QFileSystemModelQTreeView のモデルとして使用する。
  • QTreeView を使用してファイルシステムを表示する。

コードの骨格 (GUI部分は省略)

#include <QDialog>
#include <QTreeView>
#include <QFileSystemModel>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QLineEdit> // パス表示用

class CustomDirectoryDialog : public QDialog {
    Q_OBJECT
public:
    explicit CustomDirectoryDialog(QWidget *parent = nullptr) : QDialog(parent) {
        setWindowTitle(tr("カスタムディレクトリ選択"));
        QVBoxLayout *mainLayout = new QVBoxLayout(this);

        model = new QFileSystemModel(this);
        model->setRootPath(QDir::homePath()); // 初期パス
        model->setFilter(QDir::Dirs | QDir::NoDotAndDotDot); // ディレクトリのみ表示

        treeView = new QTreeView(this);
        treeView->setModel(model);
        treeView->setRootIndex(model->index(QDir::homePath())); // ルートインデックス設定
        // ツリービューのカラム設定(名前だけ表示するなど)
        for (int i = 1; i < model->columnCount(); ++i) {
            treeView->hideColumn(i);
        }
        treeView->setHeaderHidden(true); // ヘッダーを非表示に

        pathLineEdit = new QLineEdit(this); // 選択パス表示用
        pathLineEdit->setReadOnly(true);

        mainLayout->addWidget(pathLineEdit);
        mainLayout->addWidget(treeView);

        QHBoxLayout *buttonLayout = new QHBoxLayout();
        QPushButton *okButton = new QPushButton(tr("選択"), this);
        QPushButton *cancelButton = new QPushButton(tr("キャンセル"), this);
        buttonLayout->addStretch();
        buttonLayout->addWidget(okButton);
        buttonLayout->addWidget(cancelButton);
        mainLayout->addLayout(buttonLayout);

        connect(okButton, &QPushButton::clicked, this, &QDialog::accept);
        connect(cancelButton, &QPushButton::clicked, this, &QDialog::reject);
        connect(treeView, &QTreeView::clicked, this, &CustomDirectoryDialog::on_treeView_clicked);
        connect(treeView, &QTreeView::doubleClicked, this, &CustomDirectoryDialog::on_treeView_doubleClicked);

        // 初期選択パスを反映
        on_treeView_clicked(treeView->currentIndex());
    }

    QString selectedDirectory() const {
        return pathLineEdit->text();
    }

signals:
    void directorySelected(const QString &path);

private slots:
    void on_treeView_clicked(const QModelIndex &index) {
        if (index.isValid()) {
            QString path = model->fileInfo(index).absoluteFilePath();
            pathLineEdit->setText(path);
        }
    }

    void on_treeView_doubleClicked(const QModelIndex &index) {
        if (index.isValid() && model->fileInfo(index).isDir()) {
            // ダブルクリックでディレクトリを移動
            treeView->setRootIndex(index); // ルートを変更
            pathLineEdit->setText(model->fileInfo(index).absoluteFilePath());
        }
    }

private:
    QFileSystemModel *model;
    QTreeView *treeView;
    QLineEdit *pathLineEdit;
};

// 使用例 (main関数内など)
/*
    CustomDirectoryDialog dialog;
    if (dialog.exec() == QDialog::Accepted) {
        QString selectedDir = dialog.selectedDirectory();
        QMessageBox::information(nullptr, "選択", "カスタム選択: " + selectedDir);
    }
*/

解説

  • OK/キャンセルボタンを配置し、accept()/reject() シグナルに接続します。
  • on_treeView_doubleClicked スロットで、ダブルクリックでサブディレクトリに移動できるようにします。
  • on_treeView_clicked スロットで、ツリービューで選択されたディレクトリのパスを pathLineEdit に表示します。
  • pathLineEdit で現在選択されているディレクトリのパスを表示します。
  • QFileSystemModelQTreeView を組み合わせてファイルシステムツリーを表示します。setFilter(QDir::Dirs | QDir::NoDotAndDotDot) でディレクトリのみ表示し、... は除外します。
  • QDialog を継承してカスタムダイアログを作成します。

このカスタムダイアログは、非常に基本的な骨格であり、実際のアプリケーションで使用するには、初期ディレクトリ設定、エラー処理、UIの細かな調整、ナビゲーション機能(親ディレクトリへ戻るなど)など、多くの機能を追加する必要があります。

非常に特殊なケースですが、Qt GUIに依存しない環境や、特定のOSの非常に特殊なファイル選択ダイアログを使いたい場合に、外部のコマンドラインツール(例: zenity (Linux), osascript (macOS))を QProcess などで呼び出す方法もあります。

利点

  • OSのより深い部分の機能にアクセスできる場合がある
  • Qtに依存しない外部ツールを利用できる

欠点

  • 出力のパースが必要になり、エラーハンドリングが複雑になる
  • セキュリティリスクや、外部ツールの存在に依存する
  • プラットフォーム非依存性が低下する

例 (macOSの osascript を利用)

#include <QApplication>
#include <QPushButton>
#include <QMessageBox>
#include <QProcess>
#include <QDebug>

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

    QPushButton button("osascriptでディレクトリを選択 (macOSのみ)");

    QObject::connect(&button, &QPushButton::clicked, [&]() {
        // macOSのosascriptを使ってディレクトリ選択ダイアログを呼び出す
        // 'choose folder' はディレクトリを選択
        // 'with prompt' はダイアログのタイトル
        // 'default folder' は初期ディレクトリ
        QString script = "tell application \"Finder\"\n"
                         "  set chosenFolder to choose folder with prompt \"ディレクトリを選択してください:\" default folder (path to home folder)\n"
                         "  return (POSIX path of chosenFolder)\n"
                         "end tell";

        QProcess process;
        process.start("osascript", QStringList() << "-e" << script);
        process.waitForFinished(); // プロセスが終了するまで待機

        QString selectedDirectory = process.readAllStandardOutput().trimmed(); // 結果を読み込む
        QString errorOutput = process.readAllStandardError(); // エラーも確認

        if (process.exitCode() == 0 && !selectedDirectory.isEmpty()) {
            QMessageBox::information(
                nullptr, // 親ウィジェットは nullptr でも良い
                QObject::tr("選択結果"),
                QObject::tr("選択されたディレクトリ:\n") + selectedDirectory
            );
            qDebug() << "osascript 選択されたディレクトリ:" << selectedDirectory;
        } else {
            QMessageBox::warning(
                nullptr,
                QObject::tr("キャンセルまたはエラー"),
                QObject::tr("ディレクトリ選択がキャンセルされたか、エラーが発生しました:\n") + errorOutput
            );
            qDebug() << "osascript エラー:" << errorOutput;
        }
    });

    button.show();

    return app.exec();
}

解説

  • この方法は、非常にプラットフォーム依存度が高く、他のOSでは動作しません。
  • waitForFinished() でプロセスが終了するまで待機し、readAllStandardOutput() で標準出力を読み取ります。ディレクトリパスが標準出力に書き込まれます。
  • AppleScript を QStringList-e オプションに渡します。
  • QProcess を使用して osascript コマンドを実行します。

ほとんどのQtアプリケーションでは、QString QFileDialog::getExistingDirectory() または QFileDialog インスタンスを使用した方法が、移植性、使いやすさ、機能性のバランスが最も優れています。

  • OS固有の特殊な要件(非推奨): 外部プロセス呼び出し
  • 完全な自由度・特殊な要件
    独自のカスタムダイアログ
  • より詳細なカスタマイズ・イベント処理
    QFileDialog インスタンス
  • 手軽さ・標準的な挙動
    QString QFileDialog::getExistingDirectory()