Qt QFileDialog: currentChanged()の代替シグナルと使い分けを徹底解説

2025-05-27

Qtプログラミングにおけるvoid QFileDialog::currentChanged(const QString &path)は、QFileDialog(ファイルダイアログ)クラスが提供するシグナルの一つです。

QFileDialog::currentChanged()とは何か?

このシグナルは、ユーザーがQFileDialog内で現在選択しているファイルまたはディレクトリが変更されたときに発生します。つまり、ユーザーがダイアログ内で別のファイルやフォルダをクリックしたり、キーボードで移動したりして、選択が変更されるたびにこのシグナルが発せられます。

シグナルの詳細

  • 引数: const QString &path
    • path: 新しく選択されたファイルまたはディレクトリのパス(ファイル名を含む完全なパス)がQString型で渡されます。
  • : signal (シグナル)

なぜこのシグナルが重要なのか?

このシグナルは、ファイルダイアログの動作をカスタマイズしたり、ユーザーの選択に基づいて動的な処理を行ったりする際に非常に役立ちます。具体的には以下のような場面で使われます。

  1. プレビュー機能の実装: ユーザーが画像ファイルを選択したときに、その画像のプレビューをダイアログ内に表示するといった機能です。currentChanged()シグナルを受け取って、選択されたファイルのパスを基にプレビューを更新します。
  2. 「開く」ボタンの有効/無効化: 特定の条件を満たすファイル(例: 特定の拡張子を持つファイル)のみを「開く」ボタンで選択可能にする場合、currentChanged()シグナルでパスをチェックし、条件に応じてボタンの有効/無効を切り替えることができます。
  3. カスタムなファイル情報表示: 選択されたファイルやディレクトリに関する追加情報(例: サイズ、更新日時など)をダイアログのどこかに表示したい場合に利用できます。
  4. 動的なフィルターの適用: 選択されたディレクトリの内容に応じて、表示されるファイルの種類を動的に変更するなどの高度なカスタマイズを行う際に、currentChanged()でディレクトリの変更を検知してフィルターを更新することができます。
#include <QApplication>
#include <QFileDialog>
#include <QLabel>
#include <QVBoxLayout>
#include <QPushButton>

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

    // QFileDialogのインスタンスを作成
    QFileDialog dialog;
    dialog.setFileMode(QFileDialog::ExistingFile); // 既存ファイルのみ選択可能
    dialog.setNameFilter("Text files (*.txt);;All files (*)"); // フィルターを設定

    // 選択されたパスを表示するためのQLabel
    QLabel pathLabel("選択されたファイル: なし");

    // "開く"ボタン
    QPushButton openButton("ファイルを開く");

    QVBoxLayout layout;
    layout.addWidget(&pathLabel);
    layout.addWidget(&openButton);

    QWidget window;
    window.setLayout(&layout);
    window.show();

    // QFileDialogのcurrentChangedシグナルとスロットを接続
    // ユーザーが別のファイルを選択するたびに、pathLabelのテキストを更新する
    QObject::connect(&dialog, &QFileDialog::currentChanged,
                     &pathLabel, [&](const QString &path) {
        pathLabel.setText("選択されたファイル: " + path);
    });

    // "開く"ボタンがクリックされたらファイルダイアログを表示
    QObject::connect(&openButton, &QPushButton::clicked,
                     &dialog, &QFileDialog::open); // open()はexec()の非同期版

    // ファイルダイアログがAcceptされたら、選択されたファイルを処理
    QObject::connect(&dialog, &QFileDialog::fileSelected,
                     [&](const QString &file) {
        // ここで選択されたファイル (file) を使った処理を行う
        qDebug() << "ファイルが選択されました: " << file;
        // 例: ファイルの内容を表示する
        // QFile f(file);
        // if (f.open(QIODevice::ReadOnly | QIODevice::Text)) {
        //     QTextStream in(&f);
        //     qDebug() << in.readAll();
        // }
    });

    return app.exec();
}

この例では、QFileDialog::currentChangedシグナルをQLabelのラムダ関数に接続し、ファイルダイアログでユーザーがファイルを選択するたびに、そのパスがQLabelに表示されるようにしています。



シグナルが発火しない、または期待通りに発火しない

考えられる原因

  • ダイアログの表示方法
    QFileDialog::exec()を使ってダイアログを表示している場合、ダイアログがモーダル(同期)で表示されるため、exec()が終了するまでcurrentChanged()シグナルが処理されないか、または意図したタイミングで発火しないことがあります。currentChanged()は通常、ユーザーがダイアログ内で操作している間にリアルタイムで反応することを期待されるシグナルです。
  • イベントループの問題
    Qtアプリケーションのイベントループが適切に実行されていない場合、シグナルが処理されないことがあります。
  • 不適切なシグナル/スロット接続
    最も一般的な原因です。QObject::connect()の引数が間違っている、またはシグナルとスロットのシグネチャが一致していない可能性があります。

トラブルシューティング

  • ダイアログの表示方法

    • QFileDialog::currentChanged()シグナルを利用してダイアログ内の動的なUI更新を行いたい場合は、非同期でダイアログを表示するQFileDialog::open()メソッドを使用することを検討してください。QFileDialog::exec()はダイアログが閉じられるまでブロックします。
    • QFileDialog::open()を使用する場合、ダイアログのaccepted()rejected()シグナルに接続して、ユーザーがダイアログを閉じた後の処理を行います。
    // 良い例:currentChangedでリアルタイム更新したい場合
    QFileDialog* dialog = new QFileDialog(this); // ヒープに確保
    dialog->setFileMode(QFileDialog::ExistingFile);
    
    connect(dialog, &QFileDialog::currentChanged, this, [&](const QString &path) {
        qDebug() << "Current selected path: " << path;
        // 例: 選択されたファイルの情報を表示
        // QLabel* previewLabel = findChild<QLabel*>("previewLabel");
        // if (previewLabel) {
        //     previewLabel->setText("選択中: " + path);
        // }
    });
    
    // ユーザーが「開く」ボタンをクリックした時の処理
    connect(dialog, &QFileDialog::accepted, this, [dialog]() {
        QString selectedFile = dialog->selectedFiles().isEmpty() ? "" : dialog->selectedFiles().first();
        qDebug() << "User selected file: " << selectedFile;
        dialog->deleteLater(); // ダイアログを適切に解放
    });
    
    // ユーザーがキャンセルした時の処理
    connect(dialog, &QFileDialog::rejected, this, [dialog]() {
        qDebug() << "File dialog cancelled.";
        dialog->deleteLater();
    });
    
    dialog->open(); // 非同期でダイアログを表示
    
    • C++11ラムダ式を使用している場合:
      QObject::connect(&fileDialog, &QFileDialog::currentChanged,
                       [&](const QString &path) {
          qDebug() << "currentChanged: " << path; // デバッグ出力で確認
      });
      
    • 伝統的なSIGNAL/SLOTマクロを使用している場合:
      QObject::connect(&fileDialog, SIGNAL(currentChanged(QString)),
                       this, SLOT(myCurrentChangedSlot(QString)));
      
      SIGNALSLOT内の引数型が完全に一致していることを確認してください。const QString &pathではなくQStringと記述するなど、微妙な違いで接続が失敗することがあります。
    • qDebug()を使って接続の成功を確認することもできます。QObject::connect()は成功するとtrueを返します。

path引数が空になる、または期待しないパスになる

考えられる原因

  • ファイルフィルターの影響
    ファイルフィルターを適用している場合、フィルターに一致しないファイルが選択されてもcurrentChangedは発火しますが、そのファイルに対して期待する処理が行えない場合があります。
  • 無効な選択
    ユーザーが何も選択せずにダイアログ内を移動した場合(例: 空のディレクトリに移動)、pathが一時的に空になることがあります。
  • ディレクトリの選択
    QFileDialogがファイルモード(QFileDialog::ExistingFileなど)で、ユーザーがファイルではなくディレクトリを選択した場合、pathがそのディレクトリのパスになることがあります。currentChangedはディレクトリ選択時にも発火します。

トラブルシューティング

  • setFileMode()の確認
    QFileDialog::setFileMode()が目的に合っているか確認してください。
    • QFileDialog::ExistingFile: 既存のファイルのみ選択。
    • QFileDialog::Directory: ディレクトリのみ選択。
    • QFileDialog::AnyFile: 既存のファイル、または新しいファイル名(存在しないファイル)も選択可能。
    • QFileDialog::ExistingFiles: 複数の既存ファイルを選択。
  • パスの検証
    path引数が実際にファイルであるか、または期待する形式のパスであるかを常に検証するコードを追加してください。
    connect(&fileDialog, &QFileDialog::currentChanged,
            [&](const QString &path) {
        if (path.isEmpty()) {
            qDebug() << "Path is empty (no selection or invalid location).";
            return;
        }
        QFileInfo fileInfo(path);
        if (fileInfo.isFile()) {
            qDebug() << "Selected file: " << path;
            // ファイルに対する処理
        } else if (fileInfo.isDir()) {
            qDebug() << "Selected directory: " << path;
            // ディレクトリに対する処理 (例: 表示するファイルを変更するなど)
        } else {
            qDebug() << "Unknown path type: " << path;
        }
    });
    

パフォーマンスの問題(currentChangedの頻繁な発火)

考えられる原因

  • このシグナルに接続されたスロットで、時間のかかる重い処理(例: 大容量ファイルの読み込み、複雑な画像処理など)を行っている場合、UIがフリーズしたり、動作が遅くなったりする可能性があります。
  • ユーザーが非常に多くのファイルを抱えるディレクトリ内で素早く移動したり、キーボードの矢印キーで大量の項目をスクロールしたりすると、currentChangedシグナルが非常に頻繁に発火します。

トラブルシューティング

  • スロットのディバウンス/スロットリング

    • シグナルが頻繁に発火する場合、一定時間内に複数回呼び出されないようにする「ディバウンス(debounce)」や「スロットリング(throttle)」といったテクニックを適用することができます。
    • これは、QTimer::singleShot()を使って実装できます。シグナルが発火するたびにタイマーをリセットし、タイマーがタイムアウトしたとき(つまり、一定時間シグナルが発火しなかったとき)に初めて実際の処理を実行します。
    // ヘッダーファイル (.h)
    class MyWidget : public QWidget {
        Q_OBJECT
    public:
        MyWidget(QWidget *parent = nullptr);
    private slots:
        void handleCurrentChanged(const QString &path);
    private:
        QFileDialog* fileDialog;
        QTimer* debounceTimer;
        QString pendingPath; // 処理待ちのパス
    };
    
    // 実装ファイル (.cpp)
    MyWidget::MyWidget(QWidget *parent) : QWidget(parent) {
        fileDialog = new QFileDialog(this);
        debounceTimer = new QTimer(this);
        debounceTimer->setSingleShot(true);
        debounceTimer->setInterval(300); // 300msのディバウンス時間
    
        connect(fileDialog, &QFileDialog::currentChanged, this, &MyWidget::handleCurrentChanged);
        connect(debounceTimer, &QTimer::timeout, this, [this]() {
            // ここで時間のかかる実際の処理を実行
            qDebug() << "Processing path after debounce: " << pendingPath;
            // 例: 大容量ファイルのプレビュー生成など
        });
        // ... (他のQFileDialogの設定)
    }
    
    void MyWidget::handleCurrentChanged(const QString &path) {
        pendingPath = path;
        debounceTimer->start(); // タイマーをリスタート
    }
    
  • 処理の最適化

    • スロット内の処理をできるだけ高速化してください。
    • プレビュー生成など時間のかかる処理は、非同期で実行することを検討してください(例: QtConcurrentや独自のワーカースレッドを使用)。

QFileDialogが閉じられた後にシグナルが発火する

考えられる原因

  • これは通常起こりませんが、ダイアログのライフサイクル管理が不適切である場合に、解放済みのオブジェクトからシグナルが発火しようとしてクラッシュする可能性があります。

トラブルシューティング

  • シグナル/スロット接続でラムダキャプチャ([this]など)を使用している場合、thisが指すオブジェクトがダイアログよりも先に破棄されないように注意してください。
  • QFileDialogをヒープに確保し、適切なタイミングでdeleteLater()を使用して解放するようにしてください。特にopen()メソッドを使って非同期で表示する場合、ダイアログが不要になったら明示的に解放する必要があります。

QFileDialog::currentChanged()は、ファイルダイアログのユーザーエクスペリエンスを向上させるための強力なツールですが、その動的な性質ゆえに、シグナル/スロットの接続、ダイアログの表示方法、そしてスロット内の処理の効率性に注意を払う必要があります。qDebug()を使ったデバッグ出力と、状況に応じたダイアログの表示方法の選択(exec() vs open())が、トラブルシューティングの鍵となります。 Qt の QFileDialog::currentChanged() シグナルは非常に便利ですが、使用する際にいくつかの一般的なエラーやトラブルシューティングのポイントがあります。

よくある原因:

  • ネイティブダイアログの使用: Qt はデフォルトでプラットフォームのネイティブファイルダイアログを使用しようとします。ネイティブダイアログは Qt のウィジェットツリーとは独立して動作するため、currentChanged() などの特定の Qt シグナルが期待通りに動作しない場合があります。特に高度なカスタマイズ(例: ダイアログ内にカスタムウィジェットを追加する)を行う場合は、QFileDialog::DontUseNativeDialog オプションを設定して、Qt 独自のファイルダイアログを使用するように強制する必要があります。
  • 接続の誤り: シグナルとスロットの接続が正しくない場合(例: シグナルの引数型が一致しない、オブジェクトのライフサイクルが一致しない)。C++11以降の新しいシグナル・スロット記法 (QObject::connect(&sender, &Sender::signal, &receiver, &Receiver::slot);) を使用することをお勧めします。これはコンパイル時にエラーを検知できるためです。
  • イベントループのブロック: currentChanged() シグナルに接続されたスロット内で時間のかかる処理(例:重いファイルI/O、ネットワーク通信)を実行すると、GUIがフリーズしたり、イベントループがブロックされてシグナルの発火が遅延したり、適切に処理されなかったりすることがあります。
  • 静的メソッドの使用: QFileDialog::getOpenFileName()getSaveFileName() といった静的メソッドを使用している場合、これらはモーダルダイアログを即座に表示し、ユーザーが選択を完了するまでブロックします。これらの静的メソッドは、QFileDialogのインスタンスを内部的に生成し、処理が完了すると破棄するため、currentChanged() シグナルは発火しません。currentChanged() を使用するには、QFileDialog のインスタンスを自分で作成し、open() または show() メソッドでダイアログを表示する必要があります。

トラブルシューティング:

  • dialog.setOption(QFileDialog::DontUseNativeDialog); を設定して、Qt の標準ダイアログを使用するように強制し、問題が解決するかどうかを確認します。
  • qDebug() を使用して、シグナルが実際に発火しているか、path 引数が期待通りに渡されているかを確認します。
  • スロット内の処理が重すぎないか確認します。もし重い場合は、別のスレッドに処理をオフロードすることを検討してください。
  • QFileDialog のインスタンスを作成し、open() または show() を使用してダイアログを表示しているか確認します。

パスが期待通りでない、またはファイル名が欠落している

  • パスの区切り文字: Windows 環境でパスの区切り文字(\)をハードコードしている場合、Qt は内部的に / を使用するため、予期しない動作を引き起こすことがあります。常に QDir::toNativeSeparators()QDir::fromNativeSeparators() を使用するか、プラットフォームに依存しない / を使用するようにします。
  • フィルターの問題: setNameFilter()setNameFilters() を使用している場合、フィルターによって表示されるファイルが制限され、ユーザーが期待するファイルを選択できないことがあります。
  • ファイルモードの不一致: QFileDialog::setFileMode() で設定したファイルモード(例: ExistingFile, Directory, AnyFile, ExistingFiles など)と、ユーザーの操作が一致していない場合があります。例えば、ディレクトリを選択するように設定しているのに、ユーザーがファイルを選択しようとしている、あるいはその逆の場合などです。
  • path 引数の内容をqDebug() で出力し、どのようなパスが渡されているか正確に確認します。
  • setNameFilter()setNameFilters() の設定が正しく、目的のファイルタイプを網羅しているか確認します。
  • setFileMode() の設定が目的と合致しているか確認します。

クラッシュまたはメモリリーク

  • スロット内の不正なメモリアクセス: currentChanged() シグナルのスロット内で、無効なポインタにアクセスしたり、解放済みのメモリを使用したりしている場合。
  • 親オブジェクトのライフサイクル: QFileDialog を親なしで作成し、かつ適切なタイミングで deleteLater() などで明示的に削除していない場合、メモリリークや未定義動作の原因となることがあります。
  • 無効なポインタ: QFileDialog のインスタンスが適切に初期化されていない、またはすでに破棄されているにもかかわらず、そのポインタを使用しようとしている場合に発生します。例えば、スタック上に宣言されたQFileDialogがスコープを抜けて破棄された後に、そのダイアログのシグナルに接続されたスロットが発火しようとする、といったケースです。
  • QObjectのライフサイクルについて理解を深めます。特にシグナル・スロット接続が、オブジェクトのライフタイムを超えてしまわないように注意します。
  • デバッガーを使用して、クラッシュが発生している場所を特定します。特に、スタックトレースを確認し、どの関数呼び出しが問題を引き起こしているかを調べます。
  • QFileDialog をヒープ上に作成する場合は、new QFileDialog(...) のように行い、親を設定するか、deleteLater() で明示的に削除するようにします。親を設定することで、親の破棄時に子も自動的に破棄されるため、メモリ管理が容易になります。
  • スロット内の重い処理: 上記「シグナルが発火しない」の項でも触れましたが、currentChanged() のスロット内で時間がかかる処理を行うと、メインスレッドがブロックされ、UIがフリーズします。
  • QApplication::processEvents() の使用: 非常に特殊なケースで、一時的にメインスレッドをブロックしつつもイベント処理を行いたい場合、QApplication::processEvents() を呼び出すことで、GUIの応答性をある程度維持できます。ただし、これは一般的には推奨されず、より良い解決策はマルチスレッド化です。
  • Qt Concurrent や QThreadPool の使用: スロット内の重い処理は、Qt Concurrent や QThreadPool を利用して別スレッドで実行するようにします。処理結果をGUIに反映させる必要がある場合は、その結果をメインスレッドにシグナルで通知し、メインスレッドのスロットでGUIの更新を行うようにします。

QFileDialog::currentChanged() をトラブルシューティングする際の重要なポイントは、以下の3点です。

  1. QFileDialog のインスタンスを適切に作成し、open() または show() を使用しているか? (静的メソッドを使っていないか?)
  2. シグナルとスロットの接続が正しいか? (特にC++11以降の記法を使用し、コンパイル時にエラーが出ていないか?)
  3. スロット内の処理がUIスレッドをブロックしていないか? (重い処理は別スレッドで行っているか?)


例1: 選択されたファイルのパスをリアルタイムで表示する

これは最も基本的な使用例で、ユーザーがダイアログ内で別のファイルやフォルダに移動するたびに、そのパスをアプリケーションのGUIに表示します。

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

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

    // メインウィンドウのセットアップ
    QWidget window;
    QVBoxLayout *layout = new QVBoxLayout(&window);

    QLabel *instructionLabel = new QLabel("ファイルを選択してください:");
    QLabel *selectedPathLabel = new QLabel("選択されたパス: (なし)");
    QPushButton *openDialogButton = new QPushButton("ファイルダイアログを開く");

    layout->addWidget(instructionLabel);
    layout->addWidget(selectedPathLabel);
    layout->addWidget(openDialogButton);

    window.setWindowTitle("QFileDialog::currentChanged 例1");
    window.show();

    // QFileDialog のインスタンスを作成 (重要: 静的メソッドではない)
    QFileDialog *fileDialog = new QFileDialog(&window); // 親を設定
    fileDialog->setFileMode(QFileDialog::ExistingFile); // 既存ファイルのみ選択可能
    fileDialog->setNameFilter("Text files (*.txt);;Image files (*.png *.jpg);;All files (*)");
    // Qt独自のダイアログを使う場合 (ネイティブダイアログだとcurrentChangedが動作しない場合がある)
    // fileDialog->setOption(QFileDialog::DontUseNativeDialog, true);

    // currentChanged シグナルとスロットを接続
    // ユーザーがダイアログ内で選択を変更するたびに、selectedPathLabel を更新
    QObject::connect(fileDialog, &QFileDialog::currentChanged,
                     selectedPathLabel, [&](const QString &path) {
        selectedPathLabel->setText("選択されたパス: " + path);
        qDebug() << "currentChanged: " << path; // デバッグ出力
    });

    // "ファイルダイアログを開く" ボタンがクリックされたらダイアログを表示
    QObject::connect(openDialogButton, &QPushButton::clicked,
                     fileDialog, &QFileDialog::open); // open() は非同期表示

    // ファイルが実際に選択されてダイアログがAcceptされた場合
    QObject::connect(fileDialog, &QFileDialog::fileSelected,
                     [&](const QString &filePath) {
        QMessageBox::information(&window, "ファイル選択完了", "選択されたファイル: " + filePath);
        qDebug() << "ファイルが選択されました: " << filePath;
    });

    // ダイアログがキャンセルされた場合
    QObject::connect(fileDialog, &QFileDialog::rejected,
                     [&]() {
        QMessageBox::warning(&window, "キャンセル", "ファイル選択がキャンセルされました。");
        qDebug() << "ファイル選択がキャンセルされました。";
    });

    return app.exec();
}

解説

  • QObject::connect(fileDialog, &QFileDialog::currentChanged, ...) でシグナルとスロットを接続しています。ラムダ式 [&](const QString &path) { ... } を使用して、selectedPathLabel のテキストを更新しています。
  • fileDialog->open(); でダイアログを非同期に表示します。これにより、ダイアログが開いている間もアプリケーションのイベントループがブロックされず、currentChanged() シグナルが機能します。
  • QFileDialog *fileDialog = new QFileDialog(&window); のように、QFileDialog のインスタンスをヒープに作成しています。getOpenFileName のような静的メソッドは使用しません。

例2: 選択された画像ファイルのプレビューを表示する

ユーザーが画像ファイルを選択したときに、その画像をダイアログのどこかに表示するといった、よりインタラクティブな例です。ただし、QFileDialog 自体にカスタムウィジェットを埋め込む場合は、setOption(QFileDialog::DontUseNativeDialog) を設定してQtの標準ダイアログを使用する必要があります。

#include <QApplication>
#include <QFileDialog>
#include <QLabel>
#include <QVBoxLayout>
#include <QPushButton>
#include <QPixmap>
#include <QFileInfo> // ファイル情報の取得用
#include <QDebug>

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

    // メインウィンドウ
    QWidget window;
    QVBoxLayout *mainLayout = new QVBoxLayout(&window);

    QLabel *infoLabel = new QLabel("ファイルを選択し、プレビューを確認してください:");
    QPushButton *openDialogButton = new QPushButton("画像ファイルを開く");
    QLabel *previewLabel = new QLabel("プレビューなし");
    previewLabel->setAlignment(Qt::AlignCenter);
    previewLabel->setFixedSize(200, 200); // プレビューサイズを固定
    previewLabel->setFrameStyle(QFrame::Box | QFrame::Sunken);

    mainLayout->addWidget(infoLabel);
    mainLayout->addWidget(openDialogButton);
    mainLayout->addWidget(previewLabel);

    window.setWindowTitle("QFileDialog::currentChanged 例2 (画像プレビュー)");
    window.show();

    // QFileDialog の設定
    QFileDialog *fileDialog = new QFileDialog(&window);
    fileDialog->setFileMode(QFileDialog::ExistingFile);
    fileDialog->setNameFilter("Image Files (*.png *.jpg *.jpeg *.gif);;All Files (*)");
    // プレビュー機能を使うため、Qtのネイティブではないダイアログを強制
    fileDialog->setOption(QFileDialog::DontUseNativeDialog, true);

    // currentChanged シグナルを接続
    QObject::connect(fileDialog, &QFileDialog::currentChanged,
                     previewLabel, [&](const QString &path) {
        QFileInfo fileInfo(path);

        // ファイルが存在し、画像ファイルの場合のみプレビュー
        if (fileInfo.exists() && fileInfo.isFile() &&
            (fileInfo.suffix().compare("png", Qt::CaseInsensitive) == 0 ||
             fileInfo.suffix().compare("jpg", Qt::CaseInsensitive) == 0 ||
             fileInfo.suffix().compare("jpeg", Qt::CaseInsensitive) == 0 ||
             fileInfo.suffix().compare("gif", Qt::CaseInsensitive) == 0)) {
            QPixmap pixmap(path);
            if (!pixmap.isNull()) {
                // QLabelのサイズに合わせて画像をスケーリング
                previewLabel->setPixmap(pixmap.scaled(previewLabel->size(),
                                                    Qt::KeepAspectRatio,
                                                    Qt::SmoothTransformation));
            } else {
                previewLabel->setText("画像を読み込めません");
                previewLabel->setPixmap(QPixmap()); // 現在の画像をクリア
            }
        } else {
            previewLabel->setText("画像ファイルではありません\nまたは存在しません");
            previewLabel->setPixmap(QPixmap()); // 現在の画像をクリア
        }
        qDebug() << "currentChanged (preview): " << path;
    });

    QObject::connect(openDialogButton, &QPushButton::clicked,
                     fileDialog, &QFileDialog::open);

    // 選択完了時の処理 (任意)
    QObject::connect(fileDialog, &QFileDialog::fileSelected,
                     [&](const QString &filePath) {
        QMessageBox::information(&window, "選択完了", "選択された画像ファイル: " + filePath);
    });

    return app.exec();
}

解説

  • QFileInfo を使ってファイルが存在するか、ファイルであるか、拡張子は何であるかなどを確認しています。
  • currentChanged スロット内で、選択されたパスが画像ファイルであるかをチェックし、QPixmap を使って画像を読み込み、QLabel に表示しています。
  • fileDialog->setOption(QFileDialog::DontUseNativeDialog, true); が重要なポイントです。これを設定しないと、OSのネイティブダイアログが使われ、Qtのカスタムウィジェットを埋め込んだり、currentChanged が期待通りに動作しなかったりする可能性があります。

ユーザーがディレクトリを変更するたびに、そのディレクトリ内の特定の条件(例:特定の拡張子を持つファイル)を満たすファイルのみを選択可能にするようなロジックを実装できます。

#include <QApplication>
#include <QFileDialog>
#include <QLabel>
#include <QVBoxLayout>
#include <QPushButton>
#include <QDir>
#include <QDebug>

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

    QWidget window;
    QVBoxLayout *mainLayout = new QVBoxLayout(&window);

    QLabel *infoLabel = new QLabel("ディレクトリを変更し、ファイルリストの変化を見てください:");
    QPushButton *openDialogButton = new QPushButton("ディレクトリ選択ダイアログを開く");
    QLabel *currentDirLabel = new QLabel("現在のディレクトリ: (なし)");

    mainLayout->addWidget(infoLabel);
    mainLayout->addWidget(openDialogButton);
    mainLayout->addWidget(currentDirLabel);

    window.setWindowTitle("QFileDialog::currentChanged 例3 (ディレクトリ変更)");
    window.show();

    QFileDialog *fileDialog = new QFileDialog(&window);
    fileDialog->setFileMode(QFileDialog::Directory); // ディレクトリのみ選択可能
    fileDialog->setOption(QFileDialog::ShowDirsOnly, true); // ディレクトリのみを表示
    fileDialog->setOption(QFileDialog::DontUseNativeDialog, true); // Qtの標準ダイアログを使用

    // currentChanged シグナルを接続
    // この例では、ディレクトリの変更を監視し、そのディレクトリ名を表示
    QObject::connect(fileDialog, &QFileDialog::currentChanged,
                     currentDirLabel, [&](const QString &path) {
        QFileInfo fileInfo(path);
        if (fileInfo.isDir()) {
            currentDirLabel->setText("現在のディレクトリ: " + path);
            qDebug() << "currentChanged (dir): " << path;
        } else {
            // ファイルが選択された場合は、親ディレクトリを表示 (ディレクトリモードなのであまりないが)
            currentDirLabel->setText("現在のディレクトリ (ファイル選択): " + fileInfo.absoluteDir().path());
            qDebug() << "currentChanged (file in dir mode): " << path;
        }

        // ここで、現在のディレクトリに基づいてダイアログのフィルターを動的に変更したり、
        // 他のUI要素を更新したりできる
        // 例: このディレクトリにのみ存在する特定のファイルタイプにフィルターを限定するなど
    });

    QObject::connect(openDialogButton, &QPushButton::clicked,
                     fileDialog, &QFileDialog::open);

    QObject::connect(fileDialog, &QFileDialog::directoryEntered,
                     [&](const QString &directory) {
        qDebug() << "ディレクトリに入りました: " << directory;
        // currentChanged と似ているが、directoryEntered はディレクトリへの「入室」時のみ
        // currentChanged は、ダイアログ内でフォーカスが移動するたびに発火する
    });


    QObject::connect(fileDialog, &QFileDialog::fileSelected,
                     [&](const QString &filePath) {
        QMessageBox::information(&window, "ディレクトリ選択完了", "選択されたディレクトリ: " + filePath);
    });

    return app.exec();
}
  • この例では、currentChanged を使って現在のディレクトリパスを表示していますが、より高度なロジックとして、特定のディレクトリに入ったときにのみ表示されるカスタムボタンを追加したり、そのディレクトリの内容に基づいてフィルターを動的に変更したりすることが考えられます。
  • fileDialog->setOption(QFileDialog::ShowDirsOnly, true); は、ファイルではなくディレクトリのみを表示するように設定します。
  • fileDialog->setFileMode(QFileDialog::Directory); でディレクトリ選択モードに設定しています。


以下に、currentChanged() の代替となる主な方法とその利用シナリオを説明します。

QFileDialog::fileSelected(const QString &file)

このシグナルは、ユーザーが実際にファイルを選択し、「開く (Open)」や「保存 (Save)」ボタンを押してダイアログを確定したときに発火します。currentChanged() とは異なり、ユーザーがダイアログ内をブラウズしている間は発火しません。

利用シナリオ

  • 選択がキャンセルされた場合(rejected() シグナル)とは明確に区別したい場合。
  • ファイル選択が完了した後に、そのファイルを開く、保存する、または何らかの処理を開始したい場合。
  • ユーザーが最終的に選択したファイルパスを取得したい場合。

currentChanged() との違い

  • fileSelected() はユーザーが「確定」の操作(例: ファイルを選んで「開く」ボタンを押す)をしたときに一度だけ発火します。
  • currentChanged() はユーザーがダイアログ内でファイルやディレクトリにカーソルを合わせたり、選択を変更したりするたびに発火します。


// ... QFileDialog の設定 ...
QFileDialog *fileDialog = new QFileDialog(this);
fileDialog->setFileMode(QFileDialog::ExistingFile);

QObject::connect(fileDialog, &QFileDialog::fileSelected,
                 this, [&](const QString &filePath) {
    // ユーザーがファイルを確定しました
    qDebug() << "最終的に選択されたファイル: " << filePath;
    // ここでファイルを開いたり、内容を読み込んだりする
});

fileDialog->open(); // または exec() でモーダル表示

QFileDialog::filesSelected(const QStringList &files)

QFileDialog::ExistingFiles モードで複数のファイル選択が可能な場合に、ユーザーが複数のファイルを選択してダイアログを確定したときに発火します。単一のファイル選択の場合は fileSelected() と同じ動作をします。

利用シナリオ

  • ファイルダイアログが複数のファイル選択を許可するように設定されている場合。
  • 複数のファイルを一括で処理したい場合。


// ... QFileDialog の設定 ...
QFileDialog *fileDialog = new QFileDialog(this);
fileDialog->setFileMode(QFileDialog::ExistingFiles); // 複数ファイル選択モード

QObject::connect(fileDialog, &QFileDialog::filesSelected,
                 this, [&](const QStringList &filePaths) {
    // ユーザーが複数のファイルを確定しました
    qDebug() << "選択されたファイルリスト:";
    for (const QString &path : filePaths) {
        qDebug() << "  - " << path;
    }
    // ここで選択されたすべてのファイルを処理する
});

fileDialog->open();

QFileDialog::directoryEntered(const QString &directory)

このシグナルは、ユーザーがファイルダイアログ内で新しいディレクトリに入ったときに発火します。currentChanged() がファイル/ディレクトリの選択変更のたびに発火するのに対し、directoryEntered() はディレクトリの変更に特化しています。

利用シナリオ

  • ディレクトリ固有のカスタムアクション(例: サブディレクトリのツリービューを更新する)を行いたい場合。
  • 特定のディレクトリに入ったときに、そのディレクトリ内のファイルに適用されるフィルターを動的に変更したい場合。
  • ユーザーが新しいディレクトリに移動したときに、そのディレクトリ固有の情報を表示したい場合。

currentChanged() との違い

  • directoryEntered() は、ユーザーがディレクトリに「入った」場合にのみ発火します。同じディレクトリ内の異なるファイルを選択しても発火しません。
  • currentChanged() はファイルもディレクトリも関係なく、選択が変更されるたびに発火します。


// ... QFileDialog の設定 ...
QFileDialog *fileDialog = new QFileDialog(this);
fileDialog->setFileMode(QFileDialog::AnyFile); // ファイルとディレクトリの両方を表示可能

QObject::connect(fileDialog, &QFileDialog::directoryEntered,
                 this, [&](const QString &directoryPath) {
    qDebug() << "新しいディレクトリに入りました: " << directoryPath;
    // 例: このディレクトリに特定のファイルがあるか確認し、メッセージを表示
    QDir dir(directoryPath);
    if (dir.exists("important_config.txt")) {
        qDebug() << "このディレクトリには重要な設定ファイルがあります。";
    }
});

fileDialog->open();

QFileDialog::urlSelected(const QUrl &url) および QFileDialog::urlsSelected(const QList<QUrl> &urls)

利用シナリオ

  • URIスキームに基づくより汎用的なファイル選択を行いたい場合。
  • ローカルファイルシステムだけでなく、ネットワークリソースも扱う必要がある場合。


// ... QFileDialog の設定 ...
QFileDialog *fileDialog = new QFileDialog(this);
fileDialog->setFileMode(QFileDialog::ExistingFile);
fileDialog->setOption(QFileDialog::DontUseCustomDirectoryIcons, true); // URL表示関連

QObject::connect(fileDialog, &QFileDialog::urlSelected,
                 this, [&](const QUrl &url) {
    qDebug() << "選択されたURL: " << url.toString();
    if (url.isLocalFile()) {
        qDebug() << "これはローカルファイルです: " << url.toLocalFile();
    } else {
        qDebug() << "これはネットワークリソースです。";
    }
});

fileDialog->open();

静的メソッド (QFileDialog::getOpenFileName(), getExistingDirectory(), など) の利用

これらのメソッドはモーダルダイアログを表示し、ユーザーが選択を完了するかキャンセルするまでアプリケーションをブロックします。結果は戻り値として直接取得できます。

利用シナリオ

  • 最もシンプルで一般的なファイル選択処理。
  • ダイアログが開いている間、他のUI操作を無効にしたい場合。
  • 複雑なリアルタイムのインタラクションが不要で、単純にユーザーからファイル/ディレクトリパスを取得したいだけの場合。

currentChanged() との違い

  • UIをブロックするため、ダイアログが開いている間は他の操作ができません。
  • 静的メソッドは内部的に QFileDialog インスタンスを生成し、すぐに破棄するため、currentChanged() などのシグナルは使用できません。
QString filePath = QFileDialog::getOpenFileName(this,
                                               tr("ファイルを開く"),
                                               QDir::homePath(),
                                               tr("Text Files (*.txt);;All Files (*)"));
if (!filePath.isEmpty()) {
    qDebug() << "選択されたファイル: " << filePath;
    // ファイルを処理
} else {
    qDebug() << "ファイル選択がキャンセルされました。";
}
  • シンプルなファイル/ディレクトリ選択で、UIのリアルタイム更新が不要な場合: QFileDialog::getOpenFileName() などの静的メソッドが最も簡単です。
  • ディレクトリの変更に特化した処理が必要な場合: QFileDialog::directoryEntered() が役立ちます。
  • 最終的なユーザーの選択を処理する場合: QFileDialog::fileSelected() または filesSelected() を使用します。
  • リアルタイムのフィードバックが必要な場合(例: プレビュー、動的な情報表示): QFileDialog::currentChanged() が最適です。ただし、Qt独自のダイアログを強制する必要がある場合があります (QFileDialog::DontUseNativeDialog)。