もう迷わない!Qt QFileDialog::accept()のエラーと解決策

2025-05-27

QFileDialog::accept() は、Qtのファイルダイアログ(ファイルを開く、または保存するダイアログ)の内部で呼び出される仮想関数(virtual function)です。

通常のQtアプリケーションでQFileDialogを使う場合、QFileDialog::getOpenFileName()QFileDialog::getSaveFileName() のような静的ヘルパー関数を使うことがほとんどです。これらの関数は、ユーザーがファイルを選択して「開く」や「保存」ボタンを押すか、「キャンセル」ボタンを押すかによって、結果を返します。

しかし、QFileDialogをサブクラス化して、ファイルダイアログの挙動をカスタマイズしたい場合に、accept() メソッドをオーバーライド(上書き)することが非常に重要になります。

どのような時に使うのか?

QFileDialog::accept() をオーバーライドする主な目的は、ユーザーがダイアログでファイルを選択し、「OK」または「開く/保存」ボタンを押したときに、カスタムなバリデーション(検証)や処理を追加するためです。

例えば、以下のようなケースが考えられます。

    • 特定の拡張子が付いているか確認したい。
    • ファイル名に特定の文字が含まれていないか確認したい。
    • (保存ダイアログの場合)既に存在するファイル名だが、上書き確認を独自に行いたい。
  1. 選択されたパスの検証

    • 特定のディレクトリ構造内でのみファイルを保存/開くことを許可したい。
    • 選択されたファイルが特定の条件(例: ファイルサイズ、読み取り専用かどうか)を満たしているか確認したい。

accept() の動作

QFileDialog::accept() が呼び出されると、通常、ダイアログが閉じられ、ユーザーが選択したファイル名(またはファイル名のリスト)がアプリケーションに返されます。

QFileDialogをサブクラス化してaccept()をオーバーライドした場合、次のように実装します。

// MyCustomFileDialog.h
#include <QFileDialog>

class MyCustomFileDialog : public QFileDialog
{
    Q_OBJECT

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

protected:
    void accept() override; // accept() をオーバーライド
};

// MyCustomFileDialog.cpp
#include "MyCustomFileDialog.h"
#include <QMessageBox>
#include <QDebug> // デバッグ出力用

MyCustomFileDialog::MyCustomFileDialog(QWidget *parent)
    : QFileDialog(parent)
{
    // 初期設定など
    setFileMode(QFileDialog::ExistingFile); // 例: 既存のファイルを選択
    setNameFilter("Text files (*.txt)"); // 例: テキストファイルのみ表示
}

void MyCustomFileDialog::accept()
{
    // ユーザーが選択したファイルパスを取得
    QStringList selectedFiles = selectedFiles();
    if (selectedFiles.isEmpty()) {
        // ファイルが選択されていない場合は、ダイアログを閉じないか、警告を出す
        QMessageBox::warning(this, "エラー", "ファイルを選択してください。");
        return; // accept() の親クラスを呼び出さないことでダイアログを閉じない
    }

    QString filePath = selectedFiles.first();

    // ここでカスタムバリデーションを行う
    if (!filePath.endsWith(".txt", Qt::CaseInsensitive)) {
        QMessageBox::warning(this, "エラー", "テキストファイル (*.txt) を選択してください。");
        return; // バリデーションに失敗した場合、ダイアログを閉じない
    }

    // バリデーションに成功した場合、親クラスの accept() を呼び出してダイアログを閉じる
    QFileDialog::accept(); // または QDialog::accept();
    qDebug() << "ファイルが正常に選択されました:" << filePath;
}

上記の例では、ユーザーが「開く」ボタンを押したときに、選択されたファイルが.txt拡張子を持っているかをチェックしています。もし.txtファイルでなければ、警告メッセージを表示し、ダイアログを閉じません。これにより、ユーザーは正しいファイルを再選択することができます。バリデーションに成功した場合のみ、基底クラスのaccept()を呼び出し、ダイアログを閉じます。



QFileDialog::accept() は、ファイルダイアログの「OK」や「開く/保存」ボタンが押されたときの挙動をカスタマイズするための強力なツールですが、誤った使い方をすると予期せぬ問題を引き起こす可能性があります。

親クラスの accept() (または QDialog::accept()) を呼び忘れる

エラー症状

  • ファイル選択後、本来取得できるはずのファイルパスが空になる、または古い情報が残っている。
  • カスタムバリデーションが成功したにもかかわらず、ダイアログが閉じない。

原因
accept() をオーバーライドした場合、バリデーションに成功したときに、明示的に親クラスの QFileDialog::accept() (または QDialog::accept()) を呼び出す必要があります。 これを呼び出すことで、ダイアログが閉じられ、選択されたファイルパスが適切に処理されます。

トラブルシューティング
accept() メソッドの最後に、バリデーションが成功した場合のパスに以下の行を追加してください。

void MyCustomFileDialog::accept()
{
    // ... カスタムバリデーション ...

    if (バリデーション成功) {
        QFileDialog::accept(); // または QDialog::accept();
    } else {
        // バリデーション失敗時の処理 (警告メッセージ表示など)
    }
}

QFileDialogQDialogを継承しているため、QFileDialog::accept()でもQDialog::accept()でも同じように動作します。

バリデーション失敗時に適切なフィードバックがない

エラー症状

  • ユーザーがなぜファイルが選択できないのか理解できない。
  • ユーザーが間違ったファイルを選択しても、何も反応がないか、ダイアログが閉じてしまう。

原因
バリデーションに失敗した場合、単に親クラスの accept() を呼び出さないだけでは、ユーザーに何が問題だったのかが伝わりません。

トラブルシューティング
バリデーションに失敗した場合、QMessageBox::warning() などを使って、ユーザーにエラー内容を明確に伝えるダイアログを表示することが重要です。

void MyCustomFileDialog::accept()
{
    QStringList selected = selectedFiles();
    if (selected.isEmpty()) {
        QMessageBox::warning(this, "警告", "ファイルが選択されていません。");
        return; // accept() を呼び出さない
    }

    QString filePath = selected.first();
    if (!filePath.endsWith(".csv")) {
        QMessageBox::warning(this, "エラー", "CSVファイルを選択してください。");
        return; // accept() を呼び出さない
    }

    // バリデーション成功
    QFileDialog::accept();
}

ダイアログの状態(ファイルモードなど)を正しく設定していない

エラー症状

  • ファイルではなくフォルダを選択しようとしている。
  • 期待するファイルの種類が表示されない。

原因
QFileDialogの挙動は、setFileMode(), setNameFilter(), setDirectory() などのメソッドで制御されます。accept() をオーバーライドする前に、これらの設定が適切に行われているか確認が必要です。

トラブルシューティング
コンストラクタやダイアログを表示する前に、必要な設定を行います。

MyCustomFileDialog::MyCustomFileDialog(QWidget *parent)
    : QFileDialog(parent)
{
    setFileMode(QFileDialog::ExistingFile); // 既存のファイルを選択
    setNameFilter("Image Files (*.png *.jpg *.jpeg);;All Files (*.*)"); // フィルターを設定
    setDirectory(QDir::homePath()); // 初期ディレクトリを設定
}

selectedFiles() や selectedUrl() の結果が期待と異なる

エラー症状

  • オーバーライドした accept() 内で取得したファイルパスが間違っている、または空になっている。

原因

  • QFileDialog::selectedUrl() はQt 5以降で推奨されるQUrl形式でパスを返します。パスの形式(ローカルファイルパスかURL形式か)に注意してください。
  • QFileDialog::selectedFiles() は、選択されたファイルのパスのリストを返します。通常、ファイルは一つしか選択されないので、selectedFiles().first() で最初の要素を取得しますが、複数ファイル選択モードの場合は注意が必要です。

トラブルシューティング

  • パスを文字列として扱う場合は、QUrl::toLocalFile() を使用してローカルファイルパスに変換することを検討してください。
  • QFileDialog::fileMode()QFileDialog::ExistingFiles (複数ファイル選択) の場合、selectedFiles() の全ての要素を処理する必要があります。
void MyCustomFileDialog::accept()
{
    QStringList files = selectedFiles(); // QFileDialog::ExistingFiles の場合
    // または
    // QUrl url = selectedUrl();
    // QString filePath = url.toLocalFile();

    if (files.isEmpty()) {
        // ...
        return;
    }

    foreach (const QString &file, files) {
        qDebug() << "Selected file:" << file;
        // 各ファイルに対するバリデーションや処理
    }

    QFileDialog::accept();
}

Qtイベントループの理解不足

エラー症状

  • ダイアログが表示されない、またはハングする。

原因
QFileDialogなどのQtのウィジェットは、QApplicationのイベントループが実行されている環境下で動作します。コンソールアプリケーションなど、イベントループがない場所で無理にダイアログを表示しようとすると問題が発生します。

トラブルシューティング

  • ダイアログを表示するコードが、適切なスレッド(通常はメインスレッド)から呼び出されていることを確認してください。
  • Qtアプリケーションのメイン関数で QApplication インスタンスが作成され、a.exec() が呼び出されていることを確認してください。
// main.cpp
#include <QApplication>
#include "MyCustomFileDialog.h"

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

    MyCustomFileDialog dialog;
    if (dialog.exec() == QDialog::Accepted) {
        // ファイル選択が成功した場合の処理
        qDebug() << "Selected files:" << dialog.selectedFiles();
    } else {
        qDebug() << "Dialog cancelled.";
    }

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

オーバーライドした accept() が呼び出されない

エラー症状

  • カスタムバリデーションロジックが実行されない。

原因

  • QFileDialogをサブクラス化して、そのサブクラスのインスタンスを使用せずに、QFileDialog::getOpenFileName()のような静的ヘルパー関数を使用している場合。静的ヘルパー関数は、内部で標準のQFileDialogインスタンスを作成して使用するため、オーバーライドしたaccept()は呼び出されません。

トラブルシューティング
カスタムの挙動が必要な場合は、必ずサブクラスのインスタンスを作成し、exec() メソッドを呼び出すようにしてください。

// 誤った例 (accept()が呼び出されない)
// QString fileName = QFileDialog::getOpenFileName(this, "Open File");

// 正しい例 (MyCustomFileDialogのaccept()が呼び出される)
MyCustomFileDialog dialog(this);
if (dialog.exec() == QDialog::Accepted) {
    // ...
}


QFileDialog::accept()をオーバーライドする主な目的は、ファイルダイアログが閉じられる前に、ユーザーが選択したファイルに対してカスタムなバリデーション(検証)や追加の処理を行うことです。

ここでは、具体的なシナリオに基づいて2つの例を挙げます。

  1. 特定の拡張子を持つファイルのみを許可する(入力バリデーション)
  2. (保存ダイアログの場合)ファイル名に特定の文字が含まれていないかチェックする

例1:特定の拡張子(例:.txt)を持つファイルのみを許可する

この例では、ユーザーがテキストファイル(.txt)以外のファイルを選択した場合に警告を表示し、ダイアログを閉じないようにします。

ファイル構造

  • mycustomfiledialog.cpp
  • mycustomfiledialog.h
  • main.cpp

mycustomfiledialog.h

#ifndef MYCUSTOMFILEDIALOG_H
#define MYCUSTOMFILEDIALOG_H

#include <QFileDialog> // QFileDialog を使用するためにインクルード

// QFileDialog を継承したカスタムクラス
class MyCustomFileDialog : public QFileDialog
{
    Q_OBJECT // Qtのメタオブジェクトシステムを使用するためのマクロ

public:
    // コンストラクタ
    explicit MyCustomFileDialog(QWidget *parent = nullptr);

protected:
    // QFileDialog::accept() をオーバーライド
    void accept() override;
};

#endif // MYCUSTOMFILEDIALOG_H

mycustomfiledialog.cpp

#include "mycustomfiledialog.h"
#include <QMessageBox> // 警告メッセージボックスを表示するためにインクルード
#include <QDebug>      // デバッグ出力のためにインクルード
#include <QFileInfo>   // ファイル情報(拡張子など)を取得するためにインクルード

MyCustomFileDialog::MyCustomFileDialog(QWidget *parent)
    : QFileDialog(parent)
{
    // ダイアログの初期設定
    // 既存のファイルを選択するモードに設定
    setFileMode(QFileDialog::ExistingFile);
    // フィルターを設定(テキストファイルのみを表示)
    setNameFilter(tr("Text files (*.txt);;All files (*.*)"));
    // デフォルトのディレクトリを設定(例: ホームディレクトリ)
    setDirectory(QDir::homePath());
    // ダイアログのタイトルを設定
    setWindowTitle(tr("Select a Text File"));
}

// QFileDialog::accept() のオーバーライド実装
void MyCustomFileDialog::accept()
{
    // ユーザーが選択したファイルのリストを取得
    QStringList selected = selectedFiles();

    // 選択されたファイルがない場合の処理
    if (selected.isEmpty()) {
        QMessageBox::warning(this, tr("No Selection"), tr("Please select a file."));
        // 親クラスのaccept()を呼び出さないことでダイアログを閉じない
        return;
    }

    // 最初の選択されたファイルパスを取得
    QString filePath = selected.first();
    QFileInfo fileInfo(filePath); // ファイル情報オブジェクトを作成

    // ファイルの拡張子が ".txt" でない場合のバリデーション
    if (fileInfo.suffix().compare("txt", Qt::CaseInsensitive) != 0) {
        // 警告メッセージを表示
        QMessageBox::warning(this,
                             tr("Invalid File Type"),
                             tr("Please select a text file (*.txt)."));
        // 親クラスのaccept()を呼び出さないことでダイアログを閉じない
        return;
    }

    // バリデーションが成功した場合、親クラスのaccept()を呼び出してダイアログを閉じる
    // これにより、QFileDialog::Accepted シグナルが発行され、selectedFiles() の結果が有効になる
    QFileDialog::accept();
    qDebug() << "Successfully selected text file:" << filePath;
}

main.cpp

#include <QApplication> // Qtアプリケーションのベースクラス
#include <QDebug>       // デバッグ出力用
#include "mycustomfiledialog.h" // カスタムファイルダイアログのヘッダをインクルード

int main(int argc, char *argv[])
{
    QApplication a(argc, argv); // QApplicationインスタンスの作成

    // MyCustomFileDialogのインスタンスを作成
    MyCustomFileDialog dialog;

    // ダイアログを表示し、ユーザーの操作を待つ
    // exec() はダイアログが閉じられるまでブロックし、結果を返す
    if (dialog.exec() == QDialog::Accepted) {
        // ダイアログが「OK」または「開く」で閉じられた場合
        QStringList selectedFiles = dialog.selectedFiles();
        if (!selectedFiles.isEmpty()) {
            qDebug() << "Main: Final selected file:" << selectedFiles.first();
        }
    } else {
        // ダイアログが「キャンセル」で閉じられた場合
        qDebug() << "Main: File dialog cancelled.";
    }

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

説明

  1. MyCustomFileDialogQFileDialogを継承しています。
  2. コンストラクタで、ダイアログのタイトル、ファイルモード、フィルターを設定しています。
  3. accept()メソッドをオーバーライドし、ユーザーが「開く」ボタンをクリックしたときに実行されるカスタムロジックを記述します。
  4. selectedFiles() で選択されたファイルのリストを取得し、その最初の要素の拡張子をQFileInfoを使ってチェックします。
  5. 拡張子が.txtでなければ、QMessageBox::warning()で警告を表示し、return;することで親クラスのaccept()が呼び出されないようにします。これによりダイアログは開いたままになります。
  6. バリデーションが成功した場合のみ、QFileDialog::accept()を呼び出し、ダイアログを閉じます。
  7. main.cppでは、MyCustomFileDialogのインスタンスを直接作成し、exec()を呼び出すことでカスタムロジックが適用されます。QFileDialog::getOpenFileName()のような静的ヘルパー関数を使った場合は、このオーバーライドは機能しない点に注意してください。

例2:(保存ダイアログの場合)ファイル名に無効な文字が含まれていないかチェックする

この例では、ファイルを保存する際に、ユーザーが入力したファイル名にWindowsで許されない文字(例::*など)が含まれていないかチェックします。

ファイル構造

  • mycustomsavedialog.cpp
  • mycustomsavedialog.h
  • main.cpp

mycustomsavedialog.h

#ifndef MYCUSTOMSAVEDIALOG_H
#define MYCUSTOMSAVEDIALOG_H

#include <QFileDialog>

class MyCustomSaveDialog : public QFileDialog
{
    Q_OBJECT

public:
    explicit MyCustomSaveDialog(QWidget *parent = nullptr);

protected:
    void accept() override;
};

#endif // MYCUSTOMSAVEDIALOG_H

mycustomsavedialog.cpp

#include "mycustomsavedialog.h"
#include <QMessageBox>
#include <QDebug>
#include <QChar> // 文字列の各文字をチェックするために必要

MyCustomSaveDialog::MyCustomSaveDialog(QWidget *parent)
    : QFileDialog(parent)
{
    // 保存ダイアログモードに設定
    setAcceptMode(QFileDialog::AcceptSave);
    // ファイル名が既存の場合に確認を求める
    setOption(QFileDialog::DontConfirmOverwrite, false); // 上書き確認を有効にする
    setNameFilter(tr("Document files (*.doc *.pdf);;All files (*.*)"));
    setWindowTitle(tr("Save Document"));
}

// QFileDialog::accept() のオーバーライド実装
void MyCustomSaveDialog::accept()
{
    // ユーザーが入力した(または選択した)ファイルパスを取得
    QStringList selected = selectedFiles();

    if (selected.isEmpty()) {
        QMessageBox::warning(this, tr("Error"), tr("No file name entered."));
        return;
    }

    QString fullPath = selected.first();
    QFileInfo fileInfo(fullPath);
    QString fileName = fileInfo.fileName(); // ファイル名部分のみを取得

    // 無効なファイル名文字のチェック (Windowsの場合)
    // 一般的なファイル名に使えない文字: \ / : * ? " < > |
    QString invalidChars = "\\/:*?\"<>|";
    for (const QChar &ch : fileName) {
        if (invalidChars.contains(ch)) {
            QMessageBox::warning(this,
                                 tr("Invalid File Name"),
                                 tr("File name contains invalid characters: %1").arg(invalidChars));
            return; // ダイアログを閉じない
        }
    }

    // 必要であれば、その他のバリデーション(例: 拡張子チェックなど)
    // if (!fileName.endsWith(".doc", Qt::CaseInsensitive) && !fileName.endsWith(".pdf", Qt::CaseInsensitive)) {
    //     QMessageBox::warning(this, tr("Invalid Extension"), tr("Please use .doc or .pdf extension."));
    //     return;
    // }

    // バリデーションが成功した場合、親クラスのaccept()を呼び出す
    QFileDialog::accept();
    qDebug() << "Successfully saved file as:" << fullPath;
}

main.cpp (例1とほぼ同じだが、MyCustomSaveDialogを使用)

#include <QApplication>
#include <QDebug>
#include "mycustomsavedialog.h" // カスタム保存ダイアログのヘッダをインクルード

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

    // MyCustomSaveDialogのインスタンスを作成
    MyCustomSaveDialog dialog;

    // 初期ファイル名を設定(オプション)
    dialog.selectFile("MyNewDocument.doc");

    if (dialog.exec() == QDialog::Accepted) {
        QStringList selectedFiles = dialog.selectedFiles();
        if (!selectedFiles.isEmpty()) {
            qDebug() << "Main: Final saved file path:" << selectedFiles.first();
            // ここで実際にファイルを保存するロジックを実装
        }
    } else {
        qDebug() << "Main: Save dialog cancelled.";
    }

    return a.exec();
}
  1. MyCustomSaveDialogQFileDialogを継承しています。
  2. コンストラクタでsetAcceptMode(QFileDialog::AcceptSave)を設定し、保存ダイアログとして機能させます。setOption(QFileDialog::DontConfirmOverwrite, false)で上書き確認ダイアログを有効にしています。
  3. accept()メソッド内で、QFileInfo::fileName()を使ってパスからファイル名部分を抽出し、無効な文字(:*など)が含まれていないかループでチェックしています。
  4. 無効な文字が見つかった場合、警告を表示し、return;でダイアログを閉じないようにします。
  5. バリデーションが成功した場合のみ、QFileDialog::accept()を呼び出し、ダイアログを閉じます。


static関数を使用し、後で結果を検証する

最も一般的でシンプルなアプローチは、QFileDialogの静的ヘルパー関数(例: getOpenFileName()getSaveFileName())を使用し、ダイアログが閉じた後に選択されたファイルパスを検証することです。

メリット

  • サブクラス化不要
    QFileDialogをサブクラス化する必要がありません。
  • ネイティブダイアログ
    ほとんどのプラットフォームで、OSネイティブのファイルダイアログが使用されます。これは、ユーザーにとって馴染み深く、使いやすいです。
  • シンプルさ
    コードが非常に簡潔になります。

デメリット

  • 複雑なバリデーション
    ダイアログのUI自体に干渉するような、より複雑なバリデーション(例: 特定の条件を満たすまで「OK」ボタンを無効にする)はできません。
  • ユーザー体験
    不正なファイルが選択された場合、一度ダイアログが閉じてからエラーメッセージが表示され、再度ダイアログを開き直す必要があるため、ユーザー体験が若干損なわれる可能性があります。

コード例

#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QFileInfo>

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

    // ファイルを開くダイアログを表示
    QString filePath = QFileDialog::getOpenFileName(
        nullptr, // 親ウィジェットがない場合はnullptr
        QObject::tr("テキストファイルを開く"), // ダイアログのタイトル
        QDir::homePath(), // 初期ディレクトリ
        QObject::tr("テキストファイル (*.txt);;全てのファイル (*.*)") // ファイルフィルター
    );

    // ユーザーがファイルを選択し、「開く」を押した場合
    if (!filePath.isEmpty()) {
        QFileInfo fileInfo(filePath);

        // ファイル拡張子のバリデーション
        if (fileInfo.suffix().compare("txt", Qt::CaseInsensitive) != 0) {
            QMessageBox::warning(nullptr,
                                 QObject::tr("無効なファイルタイプ"),
                                 QObject::tr("テキストファイル (*.txt) を選択してください。"));
            qDebug() << "バリデーション失敗: 無効なファイルタイプ";
            // ここで再度ダイアログを表示するロジックを追加することも可能
        } else {
            qDebug() << "ファイルが正常に開かれました:" << filePath;
            // 実際のファイル処理ロジック
        }
    } else {
        // ユーザーがダイアログをキャンセルした場合
        qDebug() << "ファイルダイアログがキャンセルされました。";
    }

    return a.exec();
}

QFileDialog::open()とシグナル・スロット接続を使用する

QFileDialog::exec()の代わりにQFileDialog::open()を使用すると、ダイアログを非同期に表示できます。これにより、ダイアログのfileSelected()filesSelected()シグナルに接続し、選択されたファイルパスを検証することができます。

メリット

  • バリデーションポイント
    ファイルが選択された瞬間に検証ロジックを実行できます。
  • 非同期操作
    ダイアログの表示がメインイベントループをブロックしないため、UIの応答性が維持されます。

デメリット

  • ネイティブダイアログとの相性
    open()を使用した場合でも、デフォルトではネイティブダイアログが使用される傾向があります。ネイティブダイアログはQtウィジェットに直接アクセスできないため、ファイル名の入力欄を監視してリアルタイムにバリデーションするといったことは困難です。
  • ダイアログの制御
    accept()オーバーライドほどダイアログの挙動(例: 不正な選択時にダイアログを閉じない)を直接制御するのは難しい場合があります。検証に失敗した場合、ユーザーに再度選択を促すための追加のロジックが必要になります。

コード例

#include <QApplication>
#include <QFileDialog>
#include <QMessageBox>
#include <QDebug>
#include <QFileInfo>
#include <QPushButton>
#include <QVBoxLayout>

class MyWindow : public QWidget
{
    Q_OBJECT
public:
    MyWindow(QWidget *parent = nullptr) : QWidget(parent)
    {
        QPushButton *openButton = new QPushButton(tr("ファイルを開く"), this);
        QVBoxLayout *layout = new QVBoxLayout(this);
        layout->addWidget(openButton);

        connect(openButton, &QPushButton::clicked, this, &MyWindow::onOpenButtonClicked);
    }

private slots:
    void onOpenButtonClicked()
    {
        QFileDialog *dialog = new QFileDialog(this);
        dialog->setFileMode(QFileDialog::ExistingFile);
        dialog->setNameFilter(tr("Text files (*.txt);;All files (*.*)"));
        dialog->setWindowTitle(tr("テキストファイルを選択"));

        // ファイルが選択されたときに呼び出されるスロットに接続
        connect(dialog, &QFileDialog::fileSelected, this, &MyWindow::onFileSelected);

        // ダイアログが完了したときに呼び出されるスロットに接続
        connect(dialog, &QFileDialog::finished, dialog, &QFileDialog::deleteLater); // ダイアログを自動削除

        dialog->open(); // 非同期でダイアログを開く
    }

    void onFileSelected(const QString &filePath)
    {
        QFileInfo fileInfo(filePath);

        if (fileInfo.suffix().compare("txt", Qt::CaseInsensitive) != 0) {
            QMessageBox::warning(this,
                                 tr("無効なファイルタイプ"),
                                 tr("テキストファイル (*.txt) を選択してください。"));
            qDebug() << "バリデーション失敗: 無効なファイルタイプ";
            // ここでダイアログを閉じるか、再度開くかを決定するロジックが必要になる
            // QFileDialog::reject() を呼ぶことでダイアログを閉じることができますが、
            // その場合、ユーザーは正しいファイルを再選択するために手動で開く必要があります。
            // QFileDialog::reject() を呼ぶ代わりに、そのままにしてユーザーに手動でキャンセルさせることもできます。
        } else {
            qDebug() << "ファイルが正常に選択されました (open()経由):" << filePath;
            // 実際のファイル処理ロジック
            // この場合、ダイアログは自動的に閉じられます (ユーザーが「OK」を押したため)
        }
    }
};

#include "main.moc" // mocファイルを含める

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MyWindow window;
    window.show();
    return a.exec();
}

QDialog::done() をオーバーライドする (非推奨/限定的)

QFileDialogQDialogを継承しており、QDialog::done(int result)をオーバーライドすることもできます。accept()は内部的にdone(QDialog::Accepted)を呼び出すため、done()をオーバーライドすることで同様の検証ロジックを実装できます。

メリット

  • accept()と同様に、ダイアログが閉じられる直前に検証を行えます。

デメリット

  • 公式ドキュメントではaccept()reject()のオーバーライドが推奨されており、done()のオーバーライドは通常、より複雑なカスタムダイアログでのみ検討されます。
  • accept()がより具体的な「OK」ボタンの動作に特化しているのに対し、done()はキャンセルなど他の結果も含むため、どのボタンが押されたかをresult引数で確認する必要があります。

コード例 (概念のみ)

// mycustomfiledialog.h にて
protected:
    void done(int result) override;

// mycustomfiledialog.cpp にて
void MyCustomFileDialog::done(int result)
{
    if (result == QDialog::Accepted) {
        // accept() と同様のバリデーションロジック
        QStringList selected = selectedFiles();
        if (selected.isEmpty() || !selected.first().endsWith(".txt", Qt::CaseInsensitive)) {
            QMessageBox::warning(this, tr("Invalid"), tr("Please select a valid .txt file."));
            // ここでQDialog::done(result)を呼び出さないことでダイアログは開いたまま
            return;
        }
    }
    // バリデーション成功、またはキャンセルされた場合は親クラスのdone()を呼び出す
    QFileDialog::done(result);
}

完全なカスタムファイルダイアログを作成する

最も柔軟な方法ですが、最も複雑です。QFileDialogを使用せず、QDialogを継承して独自のファイルブラウザウィジェット(QTreeViewQListViewQFileSystemModelを組み合わせるなど)を構築する方法です。

メリット

  • リアルタイムバリデーション
    ファイル名入力欄のtextChangedシグナルに接続し、リアルタイムに「OK」ボタンの有効/無効を切り替えるなどの高度なバリデーションが可能です。
  • 完全な制御
    UIのレイアウト、見た目、挙動、バリデーションロジックを完全に自由にカスタマイズできます。

デメリット

  • ネイティブダイアログの恩恵喪失
    OSネイティブのファイルダイアログの使い慣れたインターフェースやパフォーマンスの恩恵を受けられません。
  • 開発コスト
    ゼロからファイルダイアログのロジック(ファイルシステムの走査、アイコン表示、フィルタリングなど)を実装する必要があるため、開発コストが非常に高くなります。
  • ファイル選択と同時に他のカスタムデータを入力させる必要がある場合。
  • 標準のQFileDialogでは実現できない、非常に特殊なファイル選択要件がある場合。
  • 非常に特殊な要件がある場合
    完全なカスタムダイアログの構築を検討しますが、これは最終手段としてください。
  • 非同期操作が必要な場合
    QFileDialog::open()とシグナル・スロット接続を検討します。
  • ユーザー体験を損なわずに検証したい場合
    QFileDialogをサブクラス化し、void QFileDialog::accept()をオーバーライドする方法が最も推奨されます。これにより、不適切なファイルが選択されたときにダイアログを閉じずに警告を表示し、ユーザーに再選択を促すことができます。
  • 簡単な検証で十分な場合
    QFileDialog::getOpenFileName()のようなstatic関数を使用し、ダイアログが閉じた後に結果を検証する方法が最も手軽です。