【Qt】QListWidgetのhover効果を極める!itemEntered()徹底解説と応用テクニック

2025-05-31

Qtプログラミングにおけるvoid QListWidget::itemEntered()は、QListWidgetというリスト表示ウィジェットで、マウスカーソルがリスト内の特定のアイテムの領域に入ったときに発生するシグナルです。

もう少し詳しく説明すると、以下のようになります。

  • 引数について itemEntered(QListWidgetItem *item)のように、引数としてQListWidgetItem *itemを受け取ります。これは、マウスカーソルが侵入したそのアイテム自身へのポインタです。このポインタを使って、どのアイテムにマウスが乗ったのかを識別し、そのアイテムのテキストやデータなどを取得することができます。

  • itemEntered()とは? これはQListWidgetが持つ「シグナル」です。シグナルは、特定のイベント(出来事)が発生したことを他のオブジェクトに伝えるための仕組みです。itemEntered()シグナルは、以下のような条件で発生します。

    • マウスカーソルがQListWidgetいずれかのアイテムの表示領域に侵入した場合。
    • このシグナルが発せられるのは、通常、QListWidgetsetMouseTracking(true)が設定されている場合、またはマウスボタンが押された状態でアイテム上を移動した場合です。setMouseTracking(true)を設定することで、マウスがウィジェット上を移動する際に常にマウスイベントを追跡するようになります。
  • QListWidgetとは? QListWidgetは、Qtフレームワークが提供するウィジェットの一つで、項目(アイテム)を縦に並べて表示するためのものです。Windowsのエクスプローラーなどで見かけるリスト表示に近い機能を簡単に実現できます。



シグナルが全く発火しない (Mouse Tracking の問題)

最も一般的な問題は、itemEntered() シグナルがまったく発火しないケースです。 itemEntered() シグナルは、QListWidgetmouseTracking プロパティが true に設定されていない限り、マウスボタンが押された状態でアイテム上を移動した場合にのみ発火します。

エラー/症状

  • マウスをアイテムに重ねても、itemEntered() に接続したスロットが呼び出されない。

トラブルシューティング

  1. setMouseTracking(true) を設定する
    QListWidget オブジェクトに対して、明示的に setMouseTracking(true) を呼び出す必要があります。
    QListWidget* myListWidget = new QListWidget(this);
    myListWidget->setMouseTracking(true); // これが重要!
    // ... アイテムの追加やシグナル・スロット接続など
    
  2. 親ウィジェットの mouseTracking
    QListWidget が別のウィジェットに配置されている場合、親ウィジェットの mouseTracking 設定も影響する可能性があります。しかし、通常は QListWidget 自体に設定すれば十分です。

シグナルが意図せず頻繁に発火する、または「ちらつく」

マウスカーソルがアイテム上を少し移動するだけで何度もシグナルが発火したり、意図しないタイミングで発火したりする場合があります。

エラー/症状

  • マウスをゆっくり動かしているのに、シグナルが不規則に発火する。
  • マウスがアイテムの境界線をまたぐたびに、何度も itemEntered() が呼ばれる。

トラブルシューティング

  1. itemEntered() の性質を理解する
    itemEntered() は「アイテムの領域に入ったとき」に発火するシグナルであり、mouseMoveEvent のようにピクセル単位で発火するわけではありません。しかし、Qt のイベント処理の仕組み上、わずかな動きでも複数回発火することがあります。
  2. 状態管理で重複処理を防ぐ
    • 現在マウスが乗っているアイテムを追跡するための QListWidgetItem* メンバー変数を持ちます。
    • itemEntered() スロット内で、新しく入ったアイテムが前回のアイテムと同じであれば処理をスキップするようにします。
    • マウスが QListWidget の領域を完全に離れたときに、この追跡変数をリセットする処理も必要です。QListWidget には直接的な itemLeft() シグナルはありませんが、QAbstractItemView::viewportEntered()QWidget::leaveEvent() を組み合わせて使用したり、カスタムデリゲートやイベントフィルターを実装したりすることで対応できます。
    // 例:MainWindowクラスのヘッダファイル
    class MainWindow : public QMainWindow
    {
        Q_OBJECT
    public:
        MainWindow(QWidget *parent = nullptr);
        ~MainWindow();
    
    protected:
        void leaveEvent(QEvent *event) override; // QWidgetのイベントをオーバーライド
    
    private slots:
        void onItemEntered(QListWidgetItem *item);
    
    private:
        QListWidget* myListWidget;
        QListWidgetItem* currentHoveredItem; // 現在ホバーしているアイテムを追跡
    };
    
    // 例:MainWindowクラスの実装ファイル
    MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
    {
        // ... UIセットアップ ...
        myListWidget = new QListWidget(this);
        myListWidget->setMouseTracking(true);
        connect(myListWidget, &QListWidget::itemEntered, this, &MainWindow::onItemEntered);
        currentHoveredItem = nullptr; // 初期化
    }
    
    void MainWindow::onItemEntered(QListWidgetItem *item)
    {
        if (item == currentHoveredItem) {
            // 同じアイテムに再度入った場合は何もしない
            return;
        }
        currentHoveredItem = item;
        // ここでアイテムにマウスが乗ったときの処理を実行
        qDebug() << "Mouse entered item:" << item->text();
    }
    
    void MainWindow::leaveEvent(QEvent *event)
    {
        // ウィジェットからマウスが離れたときにホバー状態をリセット
        if (event->type() == QEvent::Leave && currentHoveredItem != nullptr) {
            currentHoveredItem = nullptr;
            qDebug() << "Mouse left QListWidget viewport.";
            // ここでホバー解除時の処理を実行(例:以前の強調表示を元に戻す)
        }
        QMainWindow::leaveEvent(event);
    }
    
    注意: leaveEventQListWidget のビューポート全体からマウスが離れたときに発火します。個々のアイテムから離れたことを検出するには、より高度なイベントフィルターやカスタムデリゲートの利用を検討する必要があります。

アイテム内に他のウィジェットがある場合の競合

QListWidgetItemsetItemWidget() を使用して、ボタンやチェックボックスなどの別のウィジェットを埋め込んでいる場合、それらのウィジェットがマウスイベントを「消費」してしまい、itemEntered() シグナルが正しく発火しないことがあります。

エラー/症状

  • QListWidget のアイテムにボタンなどを配置すると、その部分では itemEntered() が発火しない。

トラブルシューティング

  1. イベントフィルターを使用する
    QListWidget またはそのビューポートにイベントフィルターをインストールし、マウスイベントをインターセプトして処理する方法です。
    // MainWindowのコンストラクタ内で
    myListWidget->viewport()->installEventFilter(this);
    
    // MainWindowクラスの実装ファイルでイベントフィルターをオーバーライド
    bool MainWindow::eventFilter(QObject *obj, QEvent *event)
    {
        if (obj == myListWidget->viewport() && event->type() == QEvent::MouseMove) {
            QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
            QListWidgetItem *item = myListWidget->itemAt(mouseEvent->pos());
            if (item) {
                // itemEntered() と同じような処理をここで実行
                // ただし、重複処理防止のロジックは必要
                if (item != currentHoveredItem) {
                    currentHoveredItem = item;
                    qDebug() << "Mouse moved over item (via event filter):" << item->text();
                }
            } else {
                // アイテムから外れた場合 (リストの空白部分など)
                if (currentHoveredItem != nullptr) {
                    currentHoveredItem = nullptr;
                    qDebug() << "Mouse left item (via event filter).";
                }
            }
            // イベントをQtに渡すかどうかの判断。trueを返すとイベントはそこで止まる。
            // 通常はfalseを返して、他のイベントハンドラにも処理を続けさせる。
            return false;
        }
        return QMainWindow::eventFilter(obj, event);
    }
    
    この方法は、子ウィジェットがイベントを消費していても、親ウィジェット(QListWidget のビューポート)でイベントを捕捉できるため有効です。
  2. カスタムデリゲートを検討する
    QListWidget をより柔軟に制御したい場合、QStyledItemDelegate を継承したカスタムデリゲートを実装し、editorEvent() などをオーバーライドしてマウスイベントを処理することも可能です。ただし、これはより複雑なアプローチになります。
  3. QListWidgetItem の代わりに QListWidget::itemWidget() を使用する際の注意
    itemWidget() で取得したウィジェットは、QListWidget のアイテムとは異なる独立したウィジェットとして扱われます。したがって、itemEntered()QListWidget のアイテム領域に対して発火するため、itemWidget() で設定したウィジェット内のマウスイベントは別途そのウィジェットで処理する必要があります。

基本的なことですが、シグナルとスロットの接続が正しく行われていない場合も、当然シグナルは発火しません。

エラー/症状

  • 上記のトラブルシューティングを試してもシグナルが発火しない。

トラブルシューティング

  1. connect() の確認
    シグナルとスロットの接続が正しく記述されているかを確認します。
    • Qt5以降の新しい記法:
      connect(myListWidget, &QListWidget::itemEntered, this, &MyClass::onItemEntered);
      
    • 古い記法 (SIGNAL/SLOT マクロ):
      connect(myListWidget, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(onItemEntered(QListWidgetItem*)));
      
      特に古い記法の場合、引数の型が一致しないと接続に失敗します。
  2. スロットの存在と可視性
    接続先のスロット (onItemEntered など) が存在し、public slots: または private slots: セクションに正しく定義されていることを確認します。

QListWidget::itemEntered() のトラブルシューティングのキーは、以下の点に集約されます。

  • シグナル・スロットの接続が正しいか。
  • アイテム内に他のウィジェットを配置している場合、イベントの伝播が阻害されていないか(イベントフィルターの検討)。
  • イベントの重複発生を防ぐための状態管理(ホバー中のアイテムの追跡)を行っているか。
  • setMouseTracking(true) の設定忘れがないか。


例1:マウスが乗ったアイテムのテキストをステータスバーに表示する

これは itemEntered() シグナルの最も基本的なユースケースです。ユーザーがどのアイテムにマウスを置いているのかを視覚的にフィードバックします。

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QListWidget>
#include <QListWidgetItem> // QListWidgetItem のために必要

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT // シグナル・スロットを使用するために必要

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    // itemEntered() シグナルを受け取るスロット
    void onItemEntered(QListWidgetItem *item);

private:
    Ui::MainWindow *ui;
    QListWidget *myListWidget;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h" // UIファイルからの生成ヘッダ
#include <QDebug> // デバッグ出力用

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // QListWidget を作成し、MainWindow の中央ウィジェットとして設定
    myListWidget = new QListWidget(this);
    setCentralWidget(myListWidget);

    // QListWidget にアイテムを追加
    myListWidget->addItem("Apple");
    myListWidget->addItem("Banana");
    myListWidget->addItem("Orange");
    myListWidget->addItem("Grape");
    myListWidget->addItem("Pineapple");

    // ★重要★ マウストラッキングを有効にする
    // これがないと、マウスボタンを押しながら移動しないと itemEntered() は発火しない
    myListWidget->setMouseTracking(true);

    // itemEntered() シグナルを onItemEntered スロットに接続
    // マウスカーソルがアイテムの領域に入ると onItemEntered が呼び出される
    connect(myListWidget, &QListWidget::itemEntered, this, &MainWindow::onItemEntered);

    // ステータスバーを準備(情報表示用)
    statusBar()->showMessage("マウスカーソルをアイテムに合わせてください");
}

MainWindow::~MainWindow()
{
    delete ui;
}

// itemEntered() シグナルが発火したときに呼び出されるスロット
void MainWindow::onItemEntered(QListWidgetItem *item)
{
    if (item) { // item がnullptrでないことを確認
        // マウスが乗ったアイテムのテキストをデバッグ出力
        qDebug() << "マウスがアイテムに入りました: " << item->text();

        // マウスが乗ったアイテムのテキストをステータスバーに表示
        statusBar()->showMessage("選択中のアイテム: " + item->text());
    }
}

解説

  1. myListWidget->setMouseTracking(true); が非常に重要です。これを設定しないと、マウスボタンを押しながらアイテム上をドラッグしない限り、itemEntered() シグナルは発火しません。
  2. connect(myListWidget, &QListWidget::itemEntered, this, &MainWindow::onItemEntered); でシグナルとスロットを接続しています。
  3. onItemEntered スロットは、マウスが乗った QListWidgetItem へのポインタを受け取ります。このポインタを使って、アイテムのテキスト (item->text()) などを取得し、ステータスバーに表示しています。

例2:マウスが乗ったアイテムの背景色を変更する(状態管理あり)

マウスがアイテムに乗ったときにそのアイテムを強調表示し、別のアイテムに移動したときに前のアイテムの強調表示を解除する、というより高度な例です。例1のコードでは、マウスが異なるアイテムに移動しても、以前のアイテムの状態を元に戻すことができません。この例では、currentHoveredItem というメンバー変数を使って、現在マウスが乗っているアイテムを追跡します。

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QListWidget>
#include <QListWidgetItem>
#include <QEvent> // イベントフィルターのために必要

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void onItemEntered(QListWidgetItem *item);

protected:
    // QWidget のイベントフィルターをオーバーライドして、QListWidget のビューポートからのマウス退出イベントを捕捉
    bool eventFilter(QObject *obj, QEvent *event) override;

private:
    Ui::MainWindow *ui;
    QListWidget *myListWidget;
    QListWidgetItem *currentHoveredItem; // 現在マウスが乗っているアイテムを追跡
    QColor defaultItemColor; // アイテムのデフォルト背景色を保持
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QDebug>
#include <QColor> // QColor のために必要

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    myListWidget = new QListWidget(this);
    setCentralWidget(myListWidget);

    // アイテムを追加し、デフォルトの色を保存
    // アイテムの背景色を取得するには、QListWidgetItem のデータロールを使用します
    // または、明確なデフォルト色を設定します。
    QListWidgetItem* item1 = new QListWidgetItem("Apple");
    myListWidget->addItem(item1);
    defaultItemColor = item1->background().color(); // 最初のアイテムの背景色を取得

    myListWidget->addItem("Banana");
    myListWidget->addItem("Orange");
    myListWidget->addItem("Grape");
    myListWidget->addItem("Pineapple");

    myListWidget->setMouseTracking(true);

    connect(myListWidget, &QListWidget::itemEntered, this, &MainWindow::onItemEntered);

    currentHoveredItem = nullptr; // 初期状態ではどのアイテムにもホバーしていない

    // QListWidget のビューポートにイベントフィルターをインストール
    // これにより、マウスが QListWidget の領域から出たイベントを捕捉できる
    myListWidget->viewport()->installEventFilter(this);

    statusBar()->showMessage("マウスカーソルをアイテムに合わせて背景色を変更");
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::onItemEntered(QListWidgetItem *item)
{
    if (!item) {
        return; // 無効なアイテムなら何もしない
    }

    // ★重要★ 現在ホバーしているアイテムと違うアイテムに入った場合のみ処理
    if (item == currentHoveredItem) {
        return; // 同じアイテムに再度入った場合は何もしない
    }

    // 前にホバーしていたアイテムがあれば、その色を元に戻す
    if (currentHoveredItem) {
        currentHoveredItem->setBackground(defaultItemColor);
        qDebug() << "前のアイテムの色を戻しました: " << currentHoveredItem->text();
    }

    // 新しくホバーしたアイテムの色を変更
    item->setBackground(Qt::yellow); // 黄色に設定
    qDebug() << "新しいアイテムの色を変更しました: " << item->text();

    // 現在ホバーしているアイテムを更新
    currentHoveredItem = item;

    statusBar()->showMessage("現在ホバー中: " + item->text());
}

// イベントフィルターのオーバーライド
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
    // QListWidget のビューポートからのマウス退出イベントを捕捉
    if (obj == myListWidget->viewport() && event->type() == QEvent::Leave) {
        // マウスが QListWidget のビューポートから完全に離れた場合
        if (currentHoveredItem) {
            currentHoveredItem->setBackground(defaultItemColor); // 最後のアイテムの色を戻す
            qDebug() << "QListWidget からマウスが離れました。最後のアイテムの色を戻しました。";
            currentHoveredItem = nullptr; // ホバー状態をリセット
            statusBar()->showMessage("マウスがリストを離れました");
        }
    }
    // その他のイベントは親クラスのイベントフィルターに渡す
    return QMainWindow::eventFilter(obj, event);
}

解説

  1. currentHoveredItem の導入
    現在マウスが乗っているアイテムを保持するためのポインタです。これにより、新しいアイテムにマウスが移動したときに、前のアイテムの状態をリセットできます。
  2. 重複処理の防止
    if (item == currentHoveredItem) のチェックにより、マウスが同じアイテムの領域内で細かく動いても、不要な処理が繰り返されるのを防ぎます。
  3. eventFilter() の利用
    QListWidget のビューポート (myListWidget->viewport()) にイベントフィルターをインストールし、QEvent::Leave タイプ(マウスがウィジェットの領域から離れたイベント)を捕捉しています。これにより、ユーザーが QListWidget 全体からマウスを離したときに、最後に強調表示されていたアイテムの色を元に戻すことができます。
  4. defaultItemColor を初期化時に取得し、アイテムのデフォルト背景色として保持しています。

この例では、特定の条件を満たすアイテム(例えば、特定のテキストを含むアイテム)にマウスが乗ったときに、特別な処理を実行する方法を示します。

mainwindow.h (変更なし)

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QDebug>
#include <QMessageBox> // メッセージボックス表示用

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    myListWidget = new QListWidget(this);
    setCentralWidget(myListWidget);

    myListWidget->addItem("Apple");
    myListWidget->addItem("Special Item - Click Me!"); // 特別なアイテム
    myListWidget->addItem("Orange");
    myListWidget->addItem("Grape");
    myListWidget->addItem("Another Item");

    myListWidget->setMouseTracking(true);

    connect(myListWidget, &QListWidget::itemEntered, this, &MainWindow::onItemEntered);

    statusBar()->showMessage("マウスカーソルをアイテムに合わせて特別なメッセージを表示");
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::onItemEntered(QListWidgetItem *item)
{
    if (item) {
        qDebug() << "マウスがアイテムに入りました: " << item->text();
        statusBar()->showMessage("現在ホバー中: " + item->text());

        // 特定のアイテムにマウスが乗った場合の特別な処理
        if (item->text() == "Special Item - Click Me!") {
            statusBar()->showMessage("注意!特別なアイテムです!");
            // 例として、このアイテムにマウスが乗ったときだけメッセージボックスを表示
            // ただし、頻繁に表示されると邪魔なので、実際のアプリケーションでは控えるか、
            // 別途フラグなどで制御することを推奨
            // QMessageBox::information(this, "特別", "このアイテムは特別です!");
        } else if (item->text().contains("Another")) {
            statusBar()->showMessage("これは別の種類のアイテムです。");
        }
    }
}
  1. onItemEntered スロット内で、item->text() をチェックして、特定のテキストを持つアイテムにマウスが乗った場合に異なる処理(例:ステータスバーのメッセージ変更、デバッグ出力、あるいはメッセージボックスの表示)を実行しています。


QAbstractItemView::viewportEntered() と QAbstractItemView::itemAt() を組み合わせる

QListWidgetQAbstractItemView を継承しているため、QAbstractItemView が提供するシグナルやメソッドも利用できます。

  • QListWidgetItem* QListWidget::itemAt(const QPoint &p) const メソッド
    指定された座標 p に位置するアイテムのポインタを返します。アイテムがない場合は nullptr を返します。

  • void QAbstractItemView::viewportEntered() シグナル
    このシグナルは、マウスカーソルがビューポート(リストが表示されている領域)に入ったときに一度だけ発火します。itemEntered() のように個々のアイテムに入るたびに発火するわけではありません。

代替方法の考え方

  1. QListWidget::setMouseTracking(true) を設定します。
  2. QWidget::mouseMoveEvent(QMouseEvent *event) をオーバーライドするか、QListWidget のビューポートにイベントフィルターをインストールします。
  3. mouseMoveEvent またはイベントフィルター内で、マウスの現在の位置 (event->pos()) を使用して itemAt() を呼び出し、どのアイテムにマウスが乗っているかを常にチェックします。
  4. 前回のアイテムと異なるアイテムにマウスが移動した場合に、目的の処理を実行します。このとき、現在のホバーアイテムを追跡するメンバー変数が必要になります(前述の itemEntered() の例2と同様)。

例:mouseMoveEvent をオーバーライドする方法

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QListWidget>
#include <QListWidgetItem>
#include <QMouseEvent> // QMouseEvent のために必要

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    // マウス移動イベントをオーバーライド
    void mouseMoveEvent(QMouseEvent *event) override;
    // QListWidget のビューポートからのマウス退出イベントを捕捉
    bool eventFilter(QObject *obj, QEvent *event) override;

private:
    Ui::MainWindow *ui;
    QListWidget *myListWidget;
    QListWidgetItem *currentHoveredItem; // 現在ホバーしているアイテムを追跡
    QColor defaultItemColor;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include <QDebug>
#include <QColor>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    myListWidget = new QListWidget(this);
    setCentralWidget(myListWidget);

    QListWidgetItem* item1 = new QListWidgetItem("Apple");
    myListWidget->addItem(item1);
    defaultItemColor = item1->background().color();

    myListWidget->addItem("Banana");
    myListWidget->addItem("Orange");
    myListWidget->addItem("Grape");
    myListWidget->addItem("Pineapple");

    myListWidget->setMouseTracking(true); // ★重要★ マウストラッキングを有効にする
    currentHoveredItem = nullptr;

    // QListWidget のビューポートにイベントフィルターをインストール (leaveEvent 捕捉のため)
    myListWidget->viewport()->installEventFilter(this);

    statusBar()->showMessage("マウス移動イベントでアイテムを検出");
}

MainWindow::~MainWindow()
{
    delete ui;
}

// QWidget の mouseMoveEvent をオーバーライド
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
    // マウスイベントが myListWidget の領域内で発生したかを確認
    if (myListWidget->geometry().contains(event->pos())) {
        // QListWidget のローカル座標に変換
        QPoint localPos = myListWidget->mapFromGlobal(event->globalPos());
        QListWidgetItem *item = myListWidget->itemAt(localPos);

        if (item) {
            // 新しいアイテムにホバーしたことを検出
            if (item != currentHoveredItem) {
                // 前のアイテムの色を元に戻す
                if (currentHoveredItem) {
                    currentHoveredItem->setBackground(defaultItemColor);
                }
                // 新しいアイテムの色を変更
                item->setBackground(Qt::cyan); // シアン色に設定
                currentHoveredItem = item;
                qDebug() << "Mouse moved over item: " << item->text();
                statusBar()->showMessage("ホバー中: " + item->text());
            }
        } else {
            // アイテムのない領域(リストの空白部分など)にマウスが移動した場合
            if (currentHoveredItem) {
                currentHoveredItem->setBackground(defaultItemColor); // 前のアイテムの色を元に戻す
                currentHoveredItem = nullptr;
                qDebug() << "Mouse left item area within QListWidget.";
                statusBar()->showMessage("リストの空白部分");
            }
        }
    }
    QMainWindow::mouseMoveEvent(event); // 親クラスのイベントハンドラを呼び出す
}

// イベントフィルターのオーバーライド (リスト全体からマウスが離れた時用)
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
    if (obj == myListWidget->viewport() && event->type() == QEvent::Leave) {
        if (currentHoveredItem) {
            currentHoveredItem->setBackground(defaultItemColor);
            currentHoveredItem = nullptr;
            qDebug() << "Mouse left QListWidget viewport (via event filter).";
            statusBar()->showMessage("マウスがリストを離れました");
        }
    }
    return QMainWindow::eventFilter(obj, event);
}

利点

  • リスト内の「アイテムがない空白領域」にマウスが移動した場合も検出できます。
  • itemEntered() が発火しない、あるいは遅延があるケースでも、よりきめ細かくマウスの位置を追跡できます。

欠点

  • コードが少し複雑になります。
  • mouseMoveEvent は非常に頻繁に発火するため、パフォーマンスに注意が必要です。不要な処理を何度も行わないよう、currentHoveredItem のような状態管理が不可欠です。

QStyledItemDelegate を使用する (より高度な描画・イベント処理)

QStyledItemDelegate は、アイテムの描画やイベント処理をカスタマイズするための強力な方法です。特に、アイテム内に独自のウィジェットを描画したい場合や、複雑なホバーエフェクトを実現したい場合に有効です。

代替方法の考え方

  1. QStyledItemDelegate を継承するカスタムデリゲートクラスを作成します。
  2. カスタムデリゲート内で、editorEvent() メソッドをオーバーライドします。このメソッドは、アイテム上で発生する様々なイベント(マウスイベント、キーイベントなど)を処理できます。
  3. editorEvent() 内でマウスの移動イベント (QEvent::MouseMove) を捕捉し、QListWidgetItem の状態(QStyle::State_MouseOver)を更新します。
  4. paint() メソッドをオーバーライドし、QStyleOptionViewItemstate をチェックして QStyle::State_MouseOver がセットされていれば、ホバー状態に応じた描画(背景色の変更など)を行います。

例:カスタムデリゲート (簡略化)

customdelegate.h

#ifndef CUSTOMDELEGATE_H
#define CUSTOMDELEGATE_H

#include <QStyledItemDelegate>
#include <QListWidget> // QListWidget のコンテキストを得るため

class CustomDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    explicit CustomDelegate(QObject *parent = nullptr);

    // アイテムの描画をカスタマイズ
    void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override;

    // アイテム上のイベントを処理
    bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) override;
};

#endif // CUSTOMDELEGATE_H

customdelegate.cpp

#include "customdelegate.h"
#include <QPainter>
#include <QEvent>
#include <QMouseEvent>
#include <QApplication> // QApplication::style() のために必要

CustomDelegate::CustomDelegate(QObject *parent)
    : QStyledItemDelegate(parent)
{
}

void CustomDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    // ホバー状態のチェック
    if (option.state & QStyle::State_MouseOver) {
        // ホバー時の背景色を設定 (例: シアン)
        painter->fillRect(option.rect, Qt::cyan);
    } else {
        // デフォルトの背景色またはモデルからの背景色を使用
        QVariant background = index.data(Qt::BackgroundRole);
        if (background.isValid()) {
            painter->fillRect(option.rect, background.value<QColor>());
        }
    }

    // アイテムのテキストを描画
    QStyleOptionViewItem opt = option; // option は const なのでコピーして変更
    initStyleOption(&opt, index); // デフォルトのスタイルオプションを設定

    // テキストが描画される前に背景を塗っているため、テキストの描画はデフォルトに任せる
    QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
}

bool CustomDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index)
{
    if (event->type() == QEvent::MouseMove) {
        // QMouseEvent にキャスト
        QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);

        // 必要に応じて、ここで特定のアイテムに対するカスタムロジックを実行
        qDebug() << "Delegate MouseMove over item: " << index.data().toString();

        // 描画の更新をトリガーするために、ビューポートのアップデートを要求
        // QAbstractItemView からこのデリゲートが設定されているビューを取得
        QAbstractItemView* view = qobject_cast<QAbstractItemView*>(parent());
        if (view) {
            view->viewport()->update();
        }
    }
    // 親クラスの editorEvent を呼び出す (デフォルトのイベント処理を継続させる)
    return QStyledItemDelegate::editorEvent(event, model, option, index);
}

mainwindow.cpp (デリゲートの適用)

#include "mainwindow.h"
#include "./ui_mainwindow.h"
#include "customdelegate.h" // カスタムデリゲートのヘッダ
#include <QDebug>
#include <QColor>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    myListWidget = new QListWidget(this);
    setCentralWidget(myListWidget);

    myListWidget->addItem("Apple");
    myListWidget->addItem("Banana");
    myListWidget->addItem("Orange");
    myListWidget->addItem("Grape");
    myListWidget->addItem("Pineapple");

    // ★重要★ カスタムデリゲートを設定
    myListWidget->setItemDelegate(new CustomDelegate(myListWidget));
    myListWidget->setMouseTracking(true); // デリゲートもマウストラッキングが必要

    // itemEntered シグナルはここでは使用しない (デリゲートで処理するため)
    // connect(myListWidget, &QListWidget::itemEntered, this, &MainWindow::onItemEntered);

    statusBar()->showMessage("カスタムデリゲートでホバーエフェクト");
}

MainWindow::~MainWindow()
{
    delete ui;
}
// onItemEntered スロットは不要になるか、別の目的で使う
// void MainWindow::onItemEntered(QListWidgetItem *item) { ... }

// イベントフィルターも不要になることが多いが、リスト全体の leaveEvent は必要に応じて残す
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
    // QListWidget のビューポートからのマウス退出イベントを捕捉 (デリゲートでは個々のアイテムの退出は扱えないため)
    if (obj == myListWidget->viewport() && event->type() == QEvent::Leave) {
        // ここで何か全体のホバー解除処理が必要なら実装
        myListWidget->viewport()->update(); // デリゲートに再描画を促す
        qDebug() << "Mouse left QListWidget viewport (via event filter).";
    }
    return QMainWindow::eventFilter(obj, event);
}

利点

  • itemEntered() シグナルとは異なり、アイテム内のイベントを直接処理できるため、アイテムウィジェットを使用している場合でもスムーズなイベント処理が可能です。
  • より詳細な描画制御が可能になり、複雑なホバーエフェクトやアイテム内のカスタムウィジェットの描画を直接行えます。

欠点

  • コードの複雑さが増します。
  • 学習コストが高い。デリゲートの概念や paint()editorEvent() の動作を理解する必要があります。

QListWidget::itemWidget() を使用している場合の考慮

QListWidgetItem の代わりに QListWidget::setItemWidget(QListWidgetItem *item, QWidget *widget) を使って、完全に独立したウィジェットをアイテムとして埋め込んでいる場合、その埋め込んだウィジェットは Qt の標準的なウィジェットイベントモデルに従います。

代替方法の考え方

  • 埋め込んだウィジェットから、親の QListWidget のアイテムを識別する必要がある場合は、ウィジェットにアイテムへのポインタを保存したり、イベントフィルターを親の QListWidget に適用してマウスイベントからアイテムを逆引きしたりするなどの工夫が必要です。
  • 埋め込んだウィジェット(例えば QPushButton やカスタムウィジェット)自体に setMouseTracking(true) を設定し、そのウィジェットの mouseMoveEvent()enterEvent() / leaveEvent() をオーバーライドするか、そのウィジェットのシグナル(例: QPushButton::hovered シグナル)を利用します。

利点

  • 埋め込んだウィジェットの標準的なイベント処理を利用できる。

欠点

  • QListWidget のイベントシステムとは切り離されるため、アイテムの識別が少し複雑になる場合がある。
  • アイテム自体が別のウィジェットである場合
    そのウィジェット自身のイベント処理。
  • アイテムの描画を高度にカスタマイズしたい、またはアイテム内のウィジェットと連携して複雑なホバーエフェクトを実現したい
    QStyledItemDelegate
  • アイテムの退出イベントも捕捉したい、またはアイテムの空白部分の検出もしたい
    itemEntered()QWidget::eventFilter() (QEvent::Leave を捕捉) の組み合わせ、または mouseMoveEvent() + itemAt()
  • 最も簡単で基本的なホバー検出
    QListWidget::itemEntered()setMouseTracking(true)