Qt中基于下方元素更新鼠标光标的滚动场景失效问题
我之前做类似的树视图组件时也踩过这个坑,太懂这种憋屈感了——鼠标明明没动,滚完滚轮后下面的元素都变了,光标却还停留在原来的状态,完全不符合用户的预期。
咱们先理清楚为啥原来的方法不管用:你重写的mouseMoveEvent只有在鼠标真的移动时才会触发,滚轮滚动但鼠标静止的话,这个事件根本不会跑;而wheelEvent确实只在滚轮刚开始动的那一下触发,要是是平滑滚动的话,滚动过程中也不会持续触发,自然没法实时更新光标。
至于你提到的定时器方案,不是说不行,但总觉得有点“笨办法”的味道,还会平白消耗没必要的资源。给你两个更优雅的事件驱动方案,完全不用定时器:
方案一:监听滚动条的变化信号
树视图的滚动条不管是垂直还是水平的,只要滚动位置变了,就会发出valueChanged信号。咱们可以在构造函数里把这两个信号和自定义的光标更新函数连起来:
TreeView::TreeView(QWidget *parent) : QTreeView(parent) { // 连接垂直和水平滚动条的变化信号 connect(verticalScrollBar(), &QScrollBar::valueChanged, this, &TreeView::updateCursorOnScroll); connect(horizontalScrollBar(), &QScrollBar::valueChanged, this, &TreeView::updateCursorOnScroll); }
然后实现这个updateCursorOnScroll函数,核心就是获取当前鼠标在视图内容区域的位置,再判断对应的元素:
void TreeView::updateCursorOnScroll() { // 先判断鼠标是不是在视图的内容区域上,避免滚动条拖动时误触发 QPoint globalCursorPos = QCursor::pos(); QPoint viewportPos = viewport()->mapFromGlobal(globalCursorPos); if (!viewport()->rect().contains(viewportPos)) { return; } // 剩下的逻辑和你mouseMoveEvent里的几乎一样 QModelIndex index = indexAt(viewportPos); if (index.isValid() && isItemClickable(index)) { viewport()->setCursor(Qt::PointingHandCursor); } else { viewport()->setCursor(Qt::ArrowCursor); } }
方案二:重写scrollContentsBy函数
这个方法更直接,因为QTreeView不管是通过滚轮、滚动条拖动,还是代码调用滚动,只要内容发生了滚动,就会调用scrollContentsBy函数。咱们只需要重写它,在完成滚动逻辑后更新光标就行:
void TreeView::scrollContentsBy(int dx, int dy) { // 先调用父类的滚动逻辑,保证正常滚动 QTreeView::scrollContentsBy(dx, dy); // 调用和上面一样的光标更新函数 updateCursorOnScroll(); }
这里要注意,不管用哪个方案,你原来的mouseMoveEvent里的代码最好也把setCursor改成设置viewport()的光标,因为视图的内容绘制区域是viewport,直接设置视图的光标有时候会和viewport的光标冲突,统一了更稳妥:
void TreeView::mouseMoveEvent(QMouseEvent* event) { QModelIndex index = indexAt(event->pos()); if (index.isValid() && isItemClickable(index)) { viewport()->setCursor(Qt::PointingHandCursor); } else { viewport()->setCursor(Qt::ArrowCursor); } QTreeView::mouseMoveEvent(event); }
这两个方案都是事件驱动的,只有在真正需要更新光标的时候才会执行逻辑,比定时器那种轮询的方式高效多了,也更符合Qt的设计思路。
内容来源于stack exchange

