PySide2 – Qt – Disable drag selection when using ExtendedSelection on QListWidget / QTreeWidget / QTableWidget

huangapple go评论70阅读模式
英文:

PySide2 - Qt - Disable drag selection when using ExtendedSelection on QListWidget / QTreeWidget / QTableWidget

问题

我正在尝试找到一个解决方案来使用ExtendedSelection(单选和使用Ctrl和Shift键进行多选),但我想要防止在鼠标点击后拖动鼠标上下以进行多选。

工作示例:

import sys
from PySide2.QtWidgets import QListWidget, QApplication, QAbstractItemView

NAMES = ["sparrow", "robin", "crow", "raven", "woodpecker", "hummingbird"]

class CustomListWidget(QListWidget):

  def __init__(self):
    super().__init__()

    self.setSelectionMode(QAbstractItemView.ExtendedSelection)
    for name in NAMES:
        self.addItem(name)

  def mouseMoveEvent(self, event: QMouseEvent):
      if self.state() != QAbstractItemView.DragSelectingState:
          super().mouseMoveEvent(event)

if __name__ == '__main__':
  app = QApplication(sys.argv)
  custom_list = CustomListWidget()
  custom_list.show()
  sys.exit(app.exec_())

我尝试通过覆盖mouseMoveEvent来避免在状态为DragSelectingState时传播鼠标移动事件。这在大多数情况下都有效,但如果用户移动鼠标“非常快”,可能会一次选择几个项目。我想完全禁用拖动选择并保留Ctrl + Shift功能。

英文:

I am trying to find a solution to use the ExtendedSelection (single selection and multiple selection by using ctrl and shift keys), but I want to prevent using the multiple selection by dragging the mouse up&down after a mouse click.

Working example:

import sys
from PySide2.QtWidgets import QListWidget, QApplication, QAbstractItemView

NAMES = ["sparrow", "robin", "crow", "raven", "woodpecker", "hummingbird"]


class CustomListWidget(QListWidget):

  def __init__(self):
    super().__init__()

    self.setSelectionMode(QAbstractItemView.ExtendedSelection)
    for name in NAMES:
        self.addItem(name)

  def mouseMoveEvent(self, event: QMouseEvent) -> None:
      if self.state() != QAbstractItemView.DragSelectingState:
          super().mouseMoveEvent(event)


if __name__ == '__main__':
  app = QApplication(sys.argv)
  custom_list = CustomListWidget()
  custom_list.show()
  sys.exit(app.exec_())

I tried by overriding the mouseMoveEvent to avoid propagating the mouseMove when the state is on DragSelectingState. This works most of the times, but if the user moves the mouse "very-quick", a couple of items can be selected at once. I'd like to completely disable selection by dragging and keep the ctrl + shift functionality.

答案1

得分: 1

ExtendedSelection 模式也会在鼠标移动时更新选择,这意味着更新将在第一个鼠标移动事件处理时立即发生。

在那个时刻,state() 仍然是 NoState:用户刚刚点击了某个项目,但在此之前没有移动鼠标。

请记住,鼠标移动事件并不是连续的,因为没有真正的运动涉及(实际移动的物理距离),它是一种抽象:我们有“运动”的感觉,但光标实际上会跳跃到不同的位置,给出了运动的错觉,类似于动画中的卡通。

因此,如果鼠标在点击后移动得很快,甚至第一个事件实际上可能位于一个新的索引上。请注意,如果您在索引的精确边缘点击并稍微移动鼠标以超出该索引,这也会发生。

现在,当鼠标移动时实际更新扩展模式中的选择的是对 setSelection() 的调用,它使用包含了点击的索引和新鼠标位置下的索引的 visualRect()

幸运的是,setSelection() 是一个虚拟函数,这意味着我们可以覆盖它,Qt 将调用我们自己的实现:我们只需使用高度为 0 的矩形调用基本实现。

请注意,理论上我们不应该更新给定的矩形(它可能被其他函数使用,包括实际调用 setSelection() 的函数),因此最好基于它使用一个新的矩形:

def setSelection(self, rect, flags):
    super().setSelection(QRect(rect.topLeft(), QSize(0, 0)), flags)
英文:

Since the ExtendedSelection mode also updates the selection on mouse movements, this means that the update also happens as soon as the very first mouse move event is handled.

At that point, the state() is still NoState: the user has just clicked some item, but has not moved the mouse before.

Remember that mouse move event are not continuous, as there's no real movement involved (a physical distance that is actually traveled), it's an abstraction: we have the perception of "movement", but the cursor actually "jumps" to a different position, giving the illusion of movement, similarly to an animation in a cartoon.

So, if the mouse is moved very fast after being clicked, even the first event could actually be on a new index. Note that this also happens if you click at the exact edge of an index and slightly move the mouse outside of that index.

Now, what actually updates the selection in extended mode when the mouse is moved, is a call to setSelection(), which uses the visualRect() that contains the clicked index and the one under the new mouse position.

Luckily, setSelection() is a virtual function, meaning that we can override it and Qt will call our own implementation: we just call the base implementation using a rectangle that has 0 height.

Note that, theoretically, we should not update the given rectangle (it might be used by some other function, including the one that actually called setSelection()), so it's better to use a new one based on it:

    def setSelection(self, rect, flags):
        super().setSelection(QRect(rect.topLeft(), QSize(0, 0)), flags)

huangapple
  • 本文由 发表于 2023年6月29日 17:50:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/76579930.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定