Qt自定义控件 —— 子控件与父控件的鼠标事件问题

目录

0、引言:

1、问题描述:

2、解决方案:

3、解决后效果:


0、引言:

Qt自定义控件 —— 颜色选择组合控件https://blog.csdn.net/YMGogre/article/details/128955257一文中我们创建了自定义的组合控件,在该控件中包含了三个子控件 —— QGraphicsViewQLineEditQPushButton。当我们通过 setItemWidget() 方法将自定义控件设置到 QTreeWidgetQTableWidgetQListWidgetitem(QTreeWidegtItemQTableWidgetItemQListWidgetItem) 中时,可能会发现下面这种奇怪的现象(以 QTreeWidget 的 QTreeWidegtItem 为例):

1、问题描述:

        可以看到,当 Qt 自带的控件(比如“网格”项第二列的 QCheckBox、“平面单元格数”项第二列的 QSpinBox 等)通过 setItemWidget() 方法设置到 QTreeWidegtItem 中时,我们去点击这些 Qt 自带的控件那么其所在的 QTreeWidegtItem 也会被选中;而对于我们自定义的控件,就有那么些许不同了:

  1. 对于自定义控件中的三个子控件 —— QGraphicsViewQLineEditQPushButton,我们去点击它们时,自定义控件所在的 QTreeWidegtItem 会被选中;
  2. 当我们点击自定义控件区域内、三个子控件区域外的自定义控件区域时,和 Qt 自带的控件一样,自定义控件所在的 QTreeWidegtItem 会被选中;

        由上面两点我们基本就可以判断出问题所在了:

  • setItemWidget() 方法在把控件设置到 QTreeWidegtItem 中的同时也会建立一种绑定关系,这使得我们在点击控件对象(比如上面 GIF 图中这个自定义控件的空白区域)时会自动去选中其所在的 QTreeWidegtItem。这是Qt框架自动完成的事儿,我们无需手动去做。
  • 当自定义控件中存在子控件时,在任意子控件上点击,此时点击响应会响应到子控件上;而自定义控件本身不会产生任何响应。
  • 所以,QTreeWidegtItem、自定义控件、自定义控件的子控件三者的关系应该如下图所示:

2、解决方案:

        我们已经理解了 setItemWidget() 方法已经将自定义控件和 QTreeWidget 项建立了关联了,既然子控件和自定义控件之间没有鼠标事件关联,那我们可以重写自定义控件的 mousePressEvent() 方法或者 eventFilter() 方法来达到我们的目的,这里笔者选择重写事件过滤器(eventFilter)方法:

  1. 在自定义控件的 .h 文件内包含相关头文件并声明重写事件过滤器方法:
    #include <QEvent>
    #include <QMouseEvent>
    #include <QPushButton>
    
    ...
    
    protected:
        bool eventFilter(QObject *watchedm, QEvent *event) override;    //重写事件过滤器方法
  2. .cpp 文件内自定义控件的构造函数中为需要的子控件安装事件过滤器:
    ...
    
    /*QGraphicsView的鼠标事件会传递给它所显示的QGraphicsScene,然后再传递给场景中的QGraphicsItem。
    由于我们并没有设置任何的QGraphicsScene场景,所以我们设置该控件属性:传递给父对象做鼠标事件处理*/
    ui->ColorDisplay->setAttribute(Qt::WA_TransparentForMouseEvents);
    
    /*****************************************
     * 为自定义控件的子控件安装该自定义控件的事件过滤器
     ****************************************/
    ui->ColorLineEdit->installEventFilter(this);
    ui->btn_SelectColor->installEventFilter(this);
  3. 在事件过滤器方法中检测鼠标点击事件,通过代码选中自定义控件,随后传递事件给子控件让其正常响应点击:
    /**
     * @brief 重写事件过滤器方法
     * @attention 这里简要说下为什么要重写该方法,这是因为当我们的自定义控件中有其他子控件时,
     * 点击子控件的点击响应是相应到子控件上的,而通常我们希望所有子控件的父对象(也就是自定义控
     * 件本身)也会在点击这些子控件时有响应
     * @param QMouseEvent *event ———— 事件对象
     */
    bool MyPalette::eventFilter(QObject *watched, QEvent *event)
    {
      //拦截子控件的鼠标按下或释放事件
      if(event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease)
      {
        //QEvent类型转换为QMouseEvent类型
        QMouseEvent *mouseevent = static_cast<QMouseEvent *>(event);
        this->QWidget::mousePressEvent(mouseevent);    //调用基类的mousePressEvent方法
        //如果事件的被监视对象是QPushButton
        if(qobject_cast<QPushButton *>(watched))
          this->setFocus();     //设置自定义控件本身获得焦点
    
        return false;     //传递事件给子控件,让其正常响应点击
        //return true;    //事件处理完毕(不传递事件给子控件,只响应自定义控件本身被选中)
      }
      //其他类型的事件交由基类处理
      return QWidget::eventFilter(watched, event);
    }

        由上面三个步骤可以看到,我们设置了 QGraphicsView 由其父对象(自定义控件对象)做鼠标事件处理;而 QLineEdit 和 QPushButton 则是安装了事件过滤器并拦截这两个子控件的鼠标按下或释放事件。

        这是因为 QGraphicsView 只是作为显示颜色的控件,它无需与用户交互,所以让它的父对象来做事件处理就行了;而  QLineEdit 和 QPushButton 自己也需要响应鼠标事件(比如点击  QLineEdit 文本框进入编辑模式以及点击 QPushButton 按钮打开窗口),所以需要安装事件过滤器对自定义控件做处理并在处理完成后将事件传递给子控件让其正常响应。

        此外,在事件过滤器方法中可能会产生疑惑的代码:

//如果事件的被监视对象是QPushButton
if(qobject_cast<QPushButton *>(watched))
  this->setFocus();     //设置自定义控件本身获得焦点
  • 为什么只有被监视对象是 QPushButton 时才设置自定义控件获得焦点?QLineEdit 和 QPushButton 不都安装了事件过滤器吗?

答:因为 QLineEdit 的操作是点击文本框,进入编辑模式。而那个时候文本框是获得焦点的对象,如果让自定义控件获得了焦点点击文本框就不会进入编辑模式了。

  • 为什么需要这两句代码呢?

答:其实我也不太懂,即便使用 QWidget::mousePressEvent(mouseevent); 调用了基类的mousePressEvent方法仍然无法在点击按钮时选中自定义控件本身,所以还是用了这两句代码让自定义控件获得焦点。(欢迎大佬评论区指点迷津🙁)

3、解决后效果:

  • 3
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
Qt中,可以通过自定义控件鼠标事件来实现对鼠标操作的响应。下面是一般的步骤: 1. 继承QWidget或QAbstractButton等基类,创建自定义控件类。 2. 重写自定义控件类的鼠标事件处理函数,如mousePressEvent、mouseReleaseEvent、mouseMoveEvent等。 3. 在重写的鼠标事件处理函数中,根据需要实现相应的功能逻辑。 例如,如果你想在自定义控件上实现鼠标点击事件的响应,可以按照以下步骤进行: 1. 创建一个继承自QWidget自定义控件类,例如MyWidget。 2. 在MyWidget类中重写mousePressEvent函数,该函数会在鼠标按下时被调用。 3. 在mousePressEvent函数中实现你想要的功能,比如显示一个提示框或改变控件的状态等。 下面是一个简单的示例代码: ```cpp #include <QWidget> #include <QMouseEvent> class MyWidget : public QWidget { Q_OBJECT public: MyWidget(QWidget *parent = nullptr) : QWidget(parent) {} protected: void mousePressEvent(QMouseEvent *event) override { if (event->button() == Qt::LeftButton) { // 左键点击事件处理逻辑 // 例如显示一个提示框 qDebug() << "Left button pressed!"; } // 调用类的事件处理函数,保证其他事件正常处理 QWidget::mousePressEvent(event); } }; ``` 在上述示例中,我们重写了MyWidget类的mousePressEvent函数,并在函数中判断鼠标按下的按钮是否为左键,如果是则输出一条调试信息。同时,我们还调用了类的mousePressEvent函数,以确保其他事件的正常处理。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值