Qt開発者必見:QFileDialogのカスタマイズと独自ダイアログ実装の秘訣

2025-05-26

QFileDialog (クラス) とは

QFileDialogは、Qtが提供する標準ダイアログの一つで、ユーザーがファイルシステム上からファイルやディレクトリを選択するためのダイアログボックスを表示するクラスです。アプリケーションでファイルを開いたり、ファイルを保存したりする際に、WindowsのエクスプローラーやmacOSのFinderのようなグラフィカルなインターフェースを提供します。

主な機能と使い方

QFileDialogには、大きく分けて2つの主要な使い方があります。

  1. 静的関数 (Static Functions) の利用
    最も手軽にファイルダイアログを表示する方法です。QFileDialogクラスの静的メソッドを呼び出すだけで、ファイルを開くダイアログやファイルを保存するダイアログを簡単に表示できます。これにより、ほとんどの場合、独自のQFileDialogインスタンスを作成する必要はありません。

    • ファイルを開く場合
      QString fileName = QFileDialog::getOpenFileName(parent, caption, directory, filter);

      • parent: 親ウィジェットを指定します。
      • caption: ダイアログのタイトルバーに表示されるキャプションです。
      • directory: ダイアログが最初に開くディレクトリです。
      • filter: 表示するファイルの種類を制限するフィルターです(例: "Image Files (*.png .jpg);;Text Files (.txt)")。
    • ファイルを保存する場合
      QString fileName = QFileDialog::getSaveFileName(parent, caption, directory, filter);

      • 引数はgetOpenFileNameと同様です。
    • 複数のファイルを開く場合
      QStringList fileNames = QFileDialog::getOpenFileNames(parent, caption, directory, filter);

    • ディレクトリを選択する場合
      QString dirName = QFileDialog::getExistingDirectory(parent, caption, directory);

    これらの静的関数は、プラットフォームネイティブのファイルダイアログを使用しようとします。これにより、OSのルックアンドフィールに合ったダイアログが表示され、ユーザーにとって使い慣れた操作感を提供します。

  2. QFileDialogインスタンスの作成と詳細な設定
    より細かくダイアログの動作を制御したい場合は、QFileDialogのオブジェクトを作成し、各種プロパティを設定して使用します。

    QFileDialog dialog(this);
    dialog.setFileMode(QFileDialog::ExistingFile); // 既存の単一ファイルを選択
    dialog.setNameFilter(tr("Images (*.png *.xpm *.jpg)")); // ファイルフィルターを設定
    dialog.setViewMode(QFileDialog::Detail); // 詳細表示モードに設定
    
    if (dialog.exec()) { // ダイアログを表示し、ユーザーがOKをクリックしたら
        QStringList selectedFiles = dialog.selectedFiles();
        // 選択されたファイルに対して処理を行う
    }
    
    • setAcceptMode(): ダイアログを開くモード(ファイルを開くか、保存するか)を設定します。QFileDialog::AcceptOpenまたはQFileDialog::AcceptSave
    • setFileMode(): ユーザーが何を選択できるかを定義します。
      • QFileDialog::AnyFile: 存在するかどうかに関わらず任意のファイル名。
      • QFileDialog::ExistingFile: 既存の単一ファイル。
      • QFileDialog::Directory: ディレクトリ。
      • QFileDialog::ExistingFiles: 既存の複数のファイル。
    • setNameFilter() / setNameFilters(): 表示するファイルの拡張子などでフィルターをかけます。セミコロン(;;)で区切ることで複数のフィルターを設定できます。
    • setDirectory(): 最初に表示するディレクトリを設定します。
    • setOption(): 特定のオプション(例: シンボリックリンクを解決しない、上書き確認ダイアログを表示しないなど)を設定できます。
    • exec(): モーダルダイアログとして表示し、ユーザーが操作を完了するまでプログラムの実行をブロックします。

主な利点

  • 国際化対応
    tr()関数と組み合わせることで、多言語対応が容易です。
  • 柔軟性
    インスタンスを作成して設定することで、ダイアログの動作を細かくカスタマイズできます。
  • 簡単な使用方法
    静的関数を使用すれば、数行のコードでファイル選択機能を実装できます。
  • クロスプラットフォーム対応
    Windows、macOS、Linuxなど、異なるオペレーティングシステム上でネイティブな見た目と操作感のファイルダイアログを提供します。
  • ファイルパスの取得は、通常QString型で行われます。必要に応じてtoStdString()などのメソッドでstd::stringに変換できますが、エンコーディングに注意が必要です。
  • 静的関数を使用する場合、デフォルトではプラットフォームネイティブのダイアログが優先されます。これにより、Qtのウィジェットで構築されたダイアログではなく、OS標準のダイアログが表示されるため、QFileDialogオブジェクトのレイアウトを直接変更することはできません。Qtウィジェットベースのダイアログを強制的に使用したい場合は、QFileDialog::DontUseNativeDialogオプションを設定する必要があります。


ファイルダイアログが表示されない、またはクラッシュする

原因

  • 環境の問題
    特定のOS環境や設定が原因で、ネイティブダイアログの呼び出しに失敗することがあります。
  • DLLの欠如(Windows)
    リリースビルドで必要なDLLが不足している場合、ダイアログが表示されずにクラッシュすることがあります。
  • 親ウィジェットの無効化
    QFileDialogの静的関数(getOpenFileNameなど)や、インスタンスを作成してexec()を呼び出す際に、無効な、または寿命が終了した親ウィジェット(parent引数)を渡している可能性があります。特に、親ウィジェットが既に削除されている場合や、親ウィジェットがない(nullptrを渡すべき場所で無効なポインタを渡している)場合に発生しやすいです。

トラブルシューティング

  • 必要なDLLの確認
    Windowsの場合、Qtの配布ツール(windeployqt.exeなど)を使用して、必要なDLLがアプリケーションの実行ファイルと同じディレクトリに配置されているか確認してください。
  • デバッグビルドで実行
    デバッグビルドで実行し、クラッシュが発生した場所を特定します。デバッガを使用すると、無効なポインタアクセスやメモリリークなどの原因を特定しやすくなります。
  • QFileDialog::DontUseNativeDialogオプションの利用
    ネイティブダイアログの使用を避け、Qtのウィジェットで構築されたダイアログを強制的に使用するオプションです。
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
                                                    QDir::homePath(),
                                                    tr("All Files (*.*)"),
                                                    nullptr,
                                                    QFileDialog::DontUseNativeDialog);
    
    これで問題が解決する場合、OSのネイティブダイアログとの互換性の問題が原因である可能性が高いです。
  • 親ウィジェットの確認
    getOpenFileName(this, ...)のように、有効なQWidgetのインスタンスを親として渡しているか確認してください。もしクラス外や静的関数から呼び出す場合は、親をnullptrにしてみてください。

ファイルフィルターが期待通りに機能しない

原因

  • フィルター文字列の誤り
    フィルターの記述形式が間違っている、または予期しない文字が含まれている。

トラブルシューティング

  • 全てのファイルを表示するフィルターの追加
    tr("All Files (*.*)") をフィルターリストの最後に追加し、目的のファイルが表示されるか確認します。表示される場合、元のフィルター文字列に問題がある可能性が高いです。
  • フィルター文字列の正確な記述
    • 形式は "表示名 (*.拡張子1 *.拡張子2);;別の表示名 (*.別の拡張子)" です。
    • 例: tr("Image Files (*.png *.jpg *.jpeg);;Text Files (*.txt)")
    • 各フィルターの区切りはセミコロン2つ(;;)です。
    • 拡張子にスペースや不正な文字が含まれていないか確認してください。

最後に開いたディレクトリが記憶されない、または初期ディレクトリが設定できない

原因

  • 無効な初期ディレクトリパス
    setDirectory()や静的関数のdirectory引数に無効なパスや存在しないパスを渡している。
  • 静的関数の制限
    QFileDialog::getOpenFileNameなどの静的関数は、デフォルトでは最後に選択されたディレクトリを記憶しない場合があります(OSのネイティブダイアログの挙動に依存)。

トラブルシューティング

  • 初期ディレクトリパスの検証
    QDir::exists()などで、設定しようとしているディレクトリパスが実際に存在するか確認してください。
  • 最後のディレクトリを手動で記憶
    QSettingsクラスを使用して、アプリケーション終了時に最後のディレクトリを保存し、次回起動時にそのパスをQFileDialogに設定するようにします。
    // 開く時
    QString lastDir = QSettings().value("lastOpenedDirectory", QDir::homePath()).toString();
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), lastDir, tr("All Files (*.*)"));
    if (!fileName.isEmpty()) {
        QFileInfo fileInfo(fileName);
        QSettings().setValue("lastOpenedDirectory", fileInfo.absolutePath());
        // ファイルの処理
    }
    
    // 保存する時も同様
    QString lastSaveDir = QSettings().value("lastSavedDirectory", QDir::homePath()).toString();
    QString saveFileName = QFileDialog::getSaveFileName(this, tr("Save File"), lastSaveDir, tr("Text Files (*.txt)"));
    if (!saveFileName.isEmpty()) {
        QFileInfo fileInfo(saveFileName);
        QSettings().setValue("lastSavedDirectory", fileInfo.absolutePath());
        // ファイルの保存処理
    }
    

ファイルダイアログの見た目がOS標準と異なる、または意図せずQtのダイアログになる

原因

  • 開発環境やQtバージョンの違い
    特定のQtバージョンや開発環境の組み合わせで、ネイティブダイアログの統合に問題がある場合があります。
  • QFileDialog::DontUseNativeDialogオプションの使用
    このオプションを意図的に、または誤って設定している場合、OSネイティブのダイアログではなくQtのウィジェットで構築されたダイアログが強制的に表示されます。

トラブルシューティング

  • OSのテーマや設定
    OSのテーマやアクセシビリティ設定が、ダイアログの見た目に影響を与えることがあります。
  • オプションの確認
    QFileDialog::DontUseNativeDialogオプションを使用しているかどうかを確認し、必要であれば削除します。デフォルトでは、Qtはネイティブダイアログを優先します。

選択したファイル名に特殊文字が含まれる場合のパスの問題

原因

  • 古いQtバージョンやOSのバグ
    非常に古いQtバージョンや特定のOS環境では、UTF-8以外のエンコーディングを正しく扱えないバグがあった可能性があります。

トラブルシューティング

  • QFileやQDirの利用
    ファイル操作には、OSのAPIを直接叩くのではなく、QFileQDirなどQtの提供するクラスを使用してください。これらはパスのエンコーディングを適切に扱います。
  • QtのQStringを常に使用
    ファイルパスの処理には、できる限りQtのQStringを使用し、std::stringなどへの変換は必要な場合のみ行います。QStringは内部でUnicodeを適切に処理します。

QFileDialogがモーダルで、親ウィンドウの操作ができない

原因

  • QFileDialogはデフォルトでモーダルダイアログとして動作します。これは設計上の意図された挙動であり、ユーザーがファイルを選択するかキャンセルするまで、親ウィンドウの操作をブロックします。
  • これはエラーではなく、QFileDialogの標準的な挙動です。もし非同期でファイル選択を行いたい場合は、QFileDialogのインスタンスを作成し、open()メソッドを呼び出した後、fileSelected()rejected()シグナルにスロットを接続して処理を行う必要があります。しかし、通常は静的関数やexec()を使ったモーダルな使用が一般的です。


QFileDialogは、ファイルを開いたり保存したり、ディレクトリを選択したりするための標準的なダイアログを提供します。ここでは、最もよく使われるパターンに焦点を当てて例を示します。

準備: Qtプロジェクトのセットアップ

これらのコード例を実行するには、Qt Creatorなどで基本的なQt Widgetsアプリケーションプロジェクトを作成してください。例えば、MainWindowクラスのメソッド内や、ボタンのクリックイベントなどからこれらのコードを呼び出すことを想定しています。

ファイルを開くダイアログ(単一選択)

最も一般的なケースで、ユーザーに1つのファイルを選択させます。

コード例

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

void MainWindow::on_openFileButton_clicked()
{
    // QFileDialog::getOpenFileName() は静的関数で、手軽に利用できます。
    // 引数: parent, caption, directory, filter, selectedFilter (出力), options
    QString filePath = QFileDialog::getOpenFileName(this,
                                                    tr("Open Image File"), // ダイアログのタイトル
                                                    QDir::homePath(),    // 初期ディレクトリ (例: ユーザーのホームディレクトリ)
                                                    tr("Image Files (*.png *.jpg *.jpeg *.gif);;All Files (*.*)")); // ファイルフィルター

    if (!filePath.isEmpty()) {
        // ファイルが選択された場合
        qDebug() << "Selected file path:" << filePath;
        QMessageBox::information(this, tr("File Selected"),
                                 tr("You selected: %1").arg(filePath));

        // ここで選択されたファイル (filePath) を使った処理を行います
        // 例: 画像ファイルを読み込む、テキストファイルの内容を表示するなど
    } else {
        // ファイル選択がキャンセルされた場合
        qDebug() << "File selection cancelled.";
        QMessageBox::warning(this, tr("No File Selected"),
                             tr("File selection was cancelled."));
    }
}

解説

  • !filePath.isEmpty(): ユーザーがファイルを実際に選択し、「開く」ボタンをクリックした場合、filePathには選択されたファイルのフルパスが格納されます。キャンセルした場合は空文字列になります。
  • QFileDialog::getOpenFileName(): ファイルを開くための静的関数です。
    • this: ダイアログの親ウィジェットを指定します。これにより、ダイアログは親ウィンドウの中心に表示されたり、親ウィンドウが最小化されたときに一緒に最小化されたりします。
    • tr("Open Image File"): ダイアログのタイトルバーに表示されるテキストです。tr()は国際化のためのマクロです。
    • QDir::homePath(): ダイアログが最初に開くディレクトリです。ここではユーザーのホームディレクトリを設定しています。QDir::currentPath()でアプリケーションの現在の作業ディレクトリを設定することもできます。
    • tr("Image Files (*.png *.jpg *.jpeg *.gif);;All Files (*.*)"): ファイルフィルターを設定します。
      • セミコロン2つ(;;)で区切ることで複数のフィルターを指定できます。
      • フィルターの形式は "表示名 (*.拡張子1 *.拡張子2)" です。
      • *.* は全てのファイルを示します。

ファイルを保存するダイアログ

ユーザーにファイル名と保存場所を指定させます。

コード例

#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QFile> // ファイル操作用
#include <QTextStream> // テキスト書き込み用

void MainWindow::on_saveFileButton_clicked()
{
    // QFileDialog::getSaveFileName() も静的関数です。
    QString filePath = QFileDialog::getSaveFileName(this,
                                                    tr("Save Text File"), // ダイアログのタイトル
                                                    QDir::homePath() + "/untitled.txt", // 初期ディレクトリとデフォルトのファイル名
                                                    tr("Text Files (*.txt);;All Files (*.*)")); // ファイルフィルター

    if (!filePath.isEmpty()) {
        qDebug() << "Save file path:" << filePath;
        QMessageBox::information(this, tr("File Saved"),
                                 tr("You chose to save to: %1").arg(filePath));

        // ここで指定されたファイルパス (filePath) にデータを保存します
        QFile file(filePath);
        if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
            QTextStream out(&file);
            out << "This is some sample text to save.\n";
            out << "Hello Qt World!\n";
            file.close();
            QMessageBox::information(this, tr("Success"), tr("File saved successfully!"));
        } else {
            QMessageBox::critical(this, tr("Error"),
                                  tr("Could not open file for writing: %1").arg(file.errorString()));
        }
    } else {
        qDebug() << "File save cancelled.";
        QMessageBox::warning(this, tr("Save Cancelled"),
                             tr("File save operation was cancelled."));
    }
}

解説

  • 保存処理: QFileQTextStreamを使って、実際に指定されたパスにテキストデータを書き込む例を示しています。書き込み権限がない場合など、ファイルが開けない可能性があるのでエラーチェックが重要です。
  • QDir::homePath() + "/untitled.txt": 初期ディレクトリと、ダイアログにデフォルトで表示されるファイル名を指定できます。ユーザーはこれを変更できます。
  • QFileDialog::getSaveFileName(): ファイルを保存するための静的関数です。

複数のファイルを開くダイアログ

ユーザーに複数のファイルを選択させたい場合に使用します。

コード例

#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QStringList> // 複数のファイルパスを格納するために必要

void MainWindow::on_openMultipleFilesButton_clicked()
{
    // QFileDialog::getOpenFileNames() を使用します。戻り値は QStringList です。
    QStringList filePaths = QFileDialog::getOpenFileNames(this,
                                                          tr("Open Multiple Text Files"),
                                                          QDir::homePath(),
                                                          tr("Text Files (*.txt);;All Files (*.*)"));

    if (!filePaths.isEmpty()) {
        qDebug() << "Selected file paths:";
        QString selectedFilesMessage = tr("You selected the following files:\n");
        for (const QString &filePath : filePaths) {
            qDebug() << "  -" << filePath;
            selectedFilesMessage += "  - " + filePath + "\n";
            // ここで各ファイル (filePath) を使った処理を行います
        }
        QMessageBox::information(this, tr("Files Selected"), selectedFilesMessage);
    } else {
        qDebug() << "Multiple file selection cancelled.";
        QMessageBox::warning(this, tr("No Files Selected"),
                             tr("Multiple file selection was cancelled."));
    }
}

解説

  • ループ処理: foreachループ(またはC++11の範囲ベースforループ)を使って、選択された各ファイルパスにアクセスし、それぞれのファイルを処理できます。
  • QStringList filePaths: 選択されたファイルのパスはQStringListQStringのリスト)として返されます。
  • QFileDialog::getOpenFileNames(): 複数のファイルを選択するための静的関数です。

ディレクトリを選択するダイアログ

ユーザーにフォルダ(ディレクトリ)を選択させたい場合に使用します。

コード例

#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QDir> // ディレクトリ操作用

void MainWindow::on_selectDirectoryButton_clicked()
{
    // QFileDialog::getExistingDirectory() を使用します。
    QString directoryPath = QFileDialog::getExistingDirectory(this,
                                                              tr("Select Directory"),
                                                              QDir::homePath()); // 初期ディレクトリ

    if (!directoryPath.isEmpty()) {
        qDebug() << "Selected directory path:" << directoryPath;
        QMessageBox::information(this, tr("Directory Selected"),
                                 tr("You selected: %1").arg(directoryPath));

        // ここで選択されたディレクトリ (directoryPath) を使った処理を行います
        // 例: ディレクトリ内のファイルをリストアップする、新しいファイルを作成する など
        QDir selectedDir(directoryPath);
        qDebug() << "Contents of selected directory:";
        for (const QString &entry : selectedDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot)) {
            qDebug() << "  -" << entry;
        }

    } else {
        qDebug() << "Directory selection cancelled.";
        QMessageBox::warning(this, tr("No Directory Selected"),
                             tr("Directory selection was cancelled."));
    }
}

解説

  • QDirクラス: 選択されたディレクトリパスを使って、そのディレクトリ内のコンテンツを操作する例を示しています。
  • QFileDialog::getExistingDirectory(): 既存のディレクトリを選択するための静的関数です。ファイルを選択するダイアログとは異なり、ファイル名入力欄はありません。

QFileDialogインスタンスを作成して詳細設定を行う

より細かい制御(例えば、ビューモードの変更や特定のオプション設定)が必要な場合、QFileDialogのオブジェクトを直接作成し、メソッドを呼び出して設定します。

コード例

#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>

void MainWindow::on_detailedOpenFileButton_clicked()
{
    // QFileDialog インスタンスを作成
    QFileDialog dialog(this);

    // ダイアログのタイトルを設定
    dialog.setWindowTitle(tr("Open Specific File (Detailed)"));

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

    // ファイルフィルターを設定
    dialog.setNameFilter(tr("Text Documents (*.txt);;Source Files (*.cpp *.h);;All Files (*.*)"));

    // 複数のファイル選択を許可
    dialog.setFileMode(QFileDialog::ExistingFiles); // ExistingFile (単一), ExistingFiles (複数), Directory (ディレクトリのみ), AnyFile (存在しなくてもOK)

    // ビューモードを詳細表示に設定
    dialog.setViewMode(QFileDialog::Detail); // List (リスト表示), Detail (詳細表示), Icon (アイコン表示)

    // 特定のオプションを設定 (例: ネイティブダイアログを使用しない)
    // dialog.setOption(QFileDialog::DontUseNativeDialog, true);
    // dialog.setOption(QFileDialog::ShowDirsOnly, true); // getExistingDirectory() と同じ効果

    // ダイアログを表示し、ユーザーがOKをクリックしたら true を返す
    if (dialog.exec()) {
        QStringList selectedFiles = dialog.selectedFiles();
        if (!selectedFiles.isEmpty()) {
            qDebug() << "Selected files (detailed):" << selectedFiles;
            QString message = tr("Selected files:\n");
            for (const QString &file : selectedFiles) {
                message += "  - " + file + "\n";
            }
            QMessageBox::information(this, tr("Detailed Selection"), message);
        }
    } else {
        qDebug() << "Detailed file selection cancelled.";
    }
}
  • dialog.exec(): ダイアログをモーダル(親ウィンドウをブロックする)で表示し、ユーザーが「開く」または「保存」をクリックするとQDialog::Accepted(通常は1)を、キャンセルするとQDialog::Rejected(通常は0)を返します。
  • setOption(QFileDialog::DontUseNativeDialog, true): この行のコメントを外すと、OSのネイティブダイアログではなくQtが描画するダイアログが強制的に表示されます。トラブルシューティングで役立つことがあります。
  • setViewMode(QFileDialog::Detail): ダイアログ内のファイルリストの表示形式を設定します。
  • setFileMode(QFileDialog::ExistingFiles): ユーザーが複数の既存ファイルを選択できるようにします。この設定により、selectedFiles()メソッドが有効になります。
    • 他のQFileDialog::FileModeオプション: AnyFile, ExistingFile, Directory, ExistingFiles, DirectoryOnlyShowDirsOnlyとほぼ同じ)
  • setWindowTitle(), setDirectory(), setNameFilter(): 静的関数と同じように各種プロパティを設定できます。
  • QFileDialog dialog(this);: QFileDialogのインスタンスを生成します。


QFileDialog をサブクラス化してカスタマイズする

これは、QFileDialogの機能を拡張したいが、ゼロからダイアログを構築するほどではない場合に最も一般的なアプローチです。

特徴

  • 既存のレイアウトにウィジェットを追加するには、layout()メソッドで取得したレイアウト(通常はQGridLayoutにキャストできる)にウィジェットを追加します。
  • ただし、この方法でカスタマイズできるのは、QFileDialog::DontUseNativeDialogオプションを使用した場合の「Qtウィジェットベース」のダイアログのみです。プラットフォームネイティブなダイアログをカスタマイズすることはできません。
  • QFileDialogの既存の構造と機能を利用しつつ、追加のウィジェット(チェックボックス、ラジオボタンなど)や独自のロジックを追加できます。

適用シナリオ

  • ファイルの種類に応じて動的に表示される追加情報パネルを設けたい。
  • 開くダイアログに「読み取り専用で開く」などの追加オプションを提供したい。
  • 保存ダイアログに「圧縮オプション」のチェックボックスを追加したい。

注意点

  • QFileDialogをサブクラス化すると、そのインスタンスではネイティブダイアログが使用されません。
  • QFileDialogの内部レイアウトは非公開であるため、カスタマイズは「ハック的」になる可能性があり、Qtのバージョンアップによって動作が不安定になるリスクがあります。公式には推奨されるカスタマイズ方法ではありませんが、よく使われる手法です。

独自のカスタムファイル/ディレクトリ選択ダイアログを構築する

これは最も柔軟な方法ですが、実装の手間が最もかかります。

特徴

  • 特殊なファイルシステムの操作(例: ネットワークドライブ、クラウドストレージとの連携など)を統合できます。
  • アプリケーション全体のUIテーマと完全に一致させることができます。
  • 外観、レイアウト、動作を完全に制御できます。
  • QDialogを継承し、QTreeView(ファイルシステム表示用)、QLineEdit(パス入力用)、QPushButton(「開く」「保存」「キャンセル」など)などのQtウィジェットを組み合わせて、独自のファイルダイアログをゼロから構築します。

適用シナリオ

  • ファイルシステム以外の抽象的な「アイテム」を選択させたい場合(例: データベース内のレコードをファイルのように見せる)。
  • 標準のファイルダイアログでは提供されない追加機能(例: ファイルのプレビュー、カスタムフィルターロジック)を組み込みたい場合。
  • アプリケーションのブランドやデザインガイドラインに厳密に従う必要がある場合。
  • 非常に特殊なファイル選択ロジックが必要な場合(例: 特定のメタデータを持つファイルのみ表示する)。

実装のポイント

  • QFileInfo: ファイルに関する情報(サイズ、最終更新日時、パーミッションなど)を取得するために使用します。
  • QDir: ディレクトリの操作(内容のリストアップ、パスの結合など)に使用します。
  • QSortFilterProxyModel: QFileSystemModelの上に重ねて、特定の条件でファイルをフィルタリングしたり、ソートしたりできます。
  • QFileSystemModel: ファイルシステムツリーのデータを提供します。QTreeViewに接続してファイルとディレクトリを表示するために使用します。

例(概念的なコードスニペット)

// MyCustomFileDialog.h
#ifndef MYCUSTOMFILEDIALOG_H
#define MYCUSTOMFILEDIALOG_H

#include <QDialog>
#include <QStringList>

class QTreeView;
class QFileSystemModel;
class QLineEdit;
class QPushButton;
class QVBoxLayout;
class QHBoxLayout;
class QDialogButtonBox;

class MyCustomFileDialog : public QDialog
{
    Q_OBJECT

public:
    explicit MyCustomFileDialog(QWidget *parent = nullptr);
    ~MyCustomFileDialog();

    QString selectedFile() const; // 単一ファイル選択の場合
    QStringList selectedFiles() const; // 複数ファイル選択の場合

    void setFileMode(QFileDialog::FileMode mode);
    void setDirectory(const QString &path);
    void setNameFilter(const QString &filter);

private slots:
    void on_treeView_doubleClicked(const QModelIndex &index);
    void on_fileNameLineEdit_textChanged(const QString &text);
    void on_okButton_clicked();
    void on_cancelButton_clicked();

private:
    QTreeView *m_treeView;
    QFileSystemModel *m_fileSystemModel;
    QLineEdit *m_fileNameLineEdit;
    QDialogButtonBox *m_buttonBox;
    QVBoxLayout *m_mainLayout;

    QFileDialog::FileMode m_fileMode;
    QString m_currentDirectory;
    QString m_nameFilter;

    void updateSelection();
    void applyFilter();
};

#endif // MYCUSTOMFILEDIALOG_H
// MyCustomFileDialog.cpp (簡略化)
#include "MyCustomFileDialog.h"
#include <QTreeView>
#include <QFileSystemModel>
#include <QLineEdit>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QDialogButtonBox>
#include <QDir>
#include <QDebug>

MyCustomFileDialog::MyCustomFileDialog(QWidget *parent)
    : QDialog(parent)
    , m_fileMode(QFileDialog::ExistingFile)
{
    setWindowTitle(tr("Custom File Dialog"));
    setMinimumSize(600, 400);

    m_treeView = new QTreeView(this);
    m_fileSystemModel = new QFileSystemModel(this);
    m_fileSystemModel->setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
    m_treeView->setModel(m_fileSystemModel);
    m_treeView->setRootIndex(m_fileSystemModel->index(QDir::homePath()));

    // 列の非表示など、QTreeViewのカスタマイズ
    m_treeView->hideColumn(1); // Size
    m_treeView->hideColumn(2); // Type
    m_treeView->hideColumn(3); // Date Modified

    m_fileNameLineEdit = new QLineEdit(this);

    m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);

    // レイアウト
    m_mainLayout = new QVBoxLayout(this);
    m_mainLayout->addWidget(m_treeView);
    m_mainLayout->addWidget(m_fileNameLineEdit);
    m_mainLayout->addWidget(m_buttonBox);

    // シグナルとスロットの接続
    connect(m_treeView, &QTreeView::doubleClicked, this, &MyCustomFileDialog::on_treeView_doubleClicked);
    connect(m_fileNameLineEdit, &QLineEdit::textChanged, this, &MyCustomFileDialog::on_fileNameLineEdit_textChanged);
    connect(m_buttonBox, &QDialogButtonBox::accepted, this, &MyCustomFileDialog::on_okButton_clicked);
    connect(m_buttonBox, &QDialogButtonBox::rejected, this, &MyCustomFileDialog::on_cancelButton_clicked);
}

MyCustomFileDialog::~MyCustomFileDialog()
{
}

QString MyCustomFileDialog::selectedFile() const
{
    // 単一ファイル選択の場合の処理
    return m_fileNameLineEdit->text(); // 仮
}

QStringList MyCustomFileDialog::selectedFiles() const
{
    // 複数ファイル選択の場合の処理
    return QStringList() << m_fileNameLineEdit->text(); // 仮
}

void MyCustomFileDialog::setFileMode(QFileDialog::FileMode mode)
{
    m_fileMode = mode;
    // ファイルモードに応じたUIの調整(例: ファイル名入力欄の表示/非表示、複数選択の有効化など)
    if (mode == QFileDialog::Directory) {
        m_fileNameLineEdit->hide();
    } else {
        m_fileNameLineEdit->show();
    }
}

void MyCustomFileDialog::setDirectory(const QString &path)
{
    m_currentDirectory = path;
    m_treeView->setRootIndex(m_fileSystemModel->index(path));
}

void MyCustomFileDialog::setNameFilter(const QString &filter)
{
    m_nameFilter = filter;
    // QFileSystemModelにフィルターを適用するロジック(複雑になる可能性があります)
    // 例: QStringList filters = filter.split(";;", Qt::SkipEmptyParts);
    // m_fileSystemModel->setNameFilters(filters);
    // m_fileSystemModel->setNameFilterDisables(false);
}

void MyCustomFileDialog::on_treeView_doubleClicked(const QModelIndex &index)
{
    if (m_fileSystemModel->isDir(index)) {
        // ディレクトリをダブルクリックしたら移動
        m_treeView->setRootIndex(index);
        m_fileNameLineEdit->setText(""); // ファイル名入力欄をクリア
    } else {
        // ファイルをダブルクリックしたら選択(ファイル名入力欄に反映)
        m_fileNameLineEdit->setText(m_fileSystemModel->filePath(index));
        if (m_fileMode == QFileDialog::ExistingFile) {
            accept(); // 単一ファイルモードならダイアログを閉じる
        }
    }
}

void MyCustomFileDialog::on_fileNameLineEdit_textChanged(const QString &text)
{
    // ファイル名入力欄が変更されたときの処理
    // 例: OKボタンの有効/無効化
}

void MyCustomFileDialog::on_okButton_clicked()
{
    // OKボタンがクリックされたら、選択されたパスを確定
    accept();
}

void MyCustomFileDialog::on_cancelButton_clicked()
{
    // キャンセルボタンがクリックされたら、ダイアログを閉じる
    reject();
}

使用例

// MainWindow::on_customFileSelectButton_clicked() などから
MyCustomFileDialog dialog(this);
dialog.setFileMode(QFileDialog::ExistingFile);
dialog.setDirectory(QDir::homePath());
dialog.setNameFilter(tr("My Custom Files (*.dat);;All Files (*.*)"));

if (dialog.exec() == QDialog::Accepted) {
    QString selected = dialog.selectedFile();
    QMessageBox::information(this, tr("Custom Dialog Result"), tr("You selected: %1").arg(selected));
} else {
    QMessageBox::warning(this, tr("Custom Dialog Result"), tr("Dialog cancelled."));
}

プラットフォーム固有のAPIを直接使用する

特徴

  • これにより、Qtが提供するダイアログ(ネイティブダイアログをラッピングしたものを含む)とは完全に異なる、純粋なOSネイティブのダイアログを使用できます。
  • Qtに依存せず、OSが提供するネイティブなファイルダイアログAPI(Windows APIのGetOpenFileName、macOSのNSOpenPanelなど)を直接呼び出す方法です。

適用シナリオ

  • Qt以外のフレームワークとの連携が必要な場合。
  • Qtの配布サイズを最小限に抑えたいが、これは通常、ごくまれなケースです。
  • 非常に特殊なOS固有のダイアログ機能が必要で、Qtのラッパーでは対応できない場合。

注意点

  • Qtのイベントループとの統合が複雑になる場合があります。
  • Qtのパス処理(QString)とOS固有のパス表現(wchar_t*など)の間で変換が必要になり、エンコーディングの問題に注意が必要です。
  • クロスプラットフォーム性が失われます。各OS向けに異なるコードを記述する必要があります。

例(Windows API の場合 - C++のみ):

#include <Windows.h>
#include <CommDlg.h> // OPENFILENAME, GetOpenFileNameなど

// QFileDialogの代替としてWindowsのネイティブダイアログを呼び出す関数
QString getWindowsOpenFileName(QWidget *parent = nullptr)
{
    OPENFILENAME ofn;       // OPENFILENAME構造体
    wchar_t szFile[MAX_PATH] = L""; // ファイルパスバッファ

    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = parent ? (HWND)parent->winId() : NULL; // QtウィジェットからHWNDを取得
    ofn.lpstrFile = szFile;
    ofn.nMaxFile = sizeof(szFile) / sizeof(wchar_t);
    ofn.lpstrFilter = L"Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0"; // フィルター
    ofn.nFilterIndex = 1;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR;

    if (GetOpenFileName(&ofn) == TRUE) {
        return QString::fromWCharArray(ofn.lpstrFile);
    } else {
        return QString();
    }
}

// 使用例
void MainWindow::on_nativeWindowsOpenButton_clicked()
{
    QString filePath = getWindowsOpenFileName(this);
    if (!filePath.isEmpty()) {
        QMessageBox::information(this, tr("Windows Native Dialog"),
                                 tr("Selected: %1").arg(filePath));
    } else {
        QMessageBox::warning(this, tr("Windows Native Dialog"),
                             tr("Selection cancelled."));
    }
}

ほとんどのQtアプリケーションでは、QFileDialogの静的関数またはインスタンスベースの使用で十分です。

  • OS固有の機能への直接アクセスが必要な場合
    プラットフォーム固有のAPIを直接呼び出しますが、クロスプラットフォーム性が失われる点に注意が必要です。
  • 大幅なカスタマイズ、独自のデザイン、特殊なファイルシステムとの連携
    独自のカスタムダイアログをQDialogQFileSystemModelなどを用いて構築します。
  • 少しのカスタマイズや特定のオプション設定
    QFileDialogのインスタンスを作成し、setOption()setFileMode()などを使用します。必要に応じてDontUseNativeDialogを検討しますが、通常は非推奨です。
  • 簡単なファイル選択
    QFileDialog::getOpenFileName()QFileDialog::getSaveFileName() が最も推奨されます。