Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Clickable elements or child widgets inside custom-painted delegate

I have a QListView, where I display items using a custom delegate with custom painting. Within each item (i.e. each list row) I want to be able to show a couple of "hyperlinks" which the user could click on and which would then call on some functions.

I have already tried to check the official documentation (e.g. Model/View Programming) as well as quite a lot of googling, but haven't been able to figure out how to accomplish this.

I have two ideas, each with their own problems:

  • I could draw them using child widgets, like a flat QPushButton. How do I then position and display these widgets?
  • I could also draw them as text strings. How do I then make them clickable? Or can I capture click events on the parent QListView and somehow determine coordinates from those? I could then match coordinates to these clickable elements and act accordingly.

My initial approach was to use QListWidget with .setItemWidget(), where I had a proper widget with a layout and child widgets. Unfortunately this was too slow when my list grew to hundreds or thousands of items. That's why I changed to QListView with a delegate.

like image 690
Weetu Avatar asked Feb 23 '12 14:02

Weetu


1 Answers

I seem to be closing in on a solution.

I can receive clicks on the elements by overriding the delegate's .editorEvent(event, model, option, index). I can then find out the event.type(), the clicked row from index.row() and the actual coordinates from event.x() and event.y() (since, if the event type is MouseButtonRelease, the event is a QMouseEvent).

From these, I think I can correlate the coordinates to my elements on screen and act accordingly.

I will update this answer once I have working code.

EDIT

A simple working example, using PySide:

class MyModel(QtGui.QStandardItemModel):
  def __init__(self):
    super(MyModel, self).__init__()
    for i in range(10): self.appendRow(QtGui.QStandardItem("Row %d" % i))

class MyDelegate(QtGui.QStyledItemDelegate):
  def __init__(self, parent=None):
    super(MyDelegate, self).__init__(parent)
    self.links = {}

  def makeLinkFunc(self, row, text):
    def linkFunc(): print("Clicked on %s in row %d" % (text, row))
    return linkFunc

  def paint(self, painter, option, index):
    painter.save()
    textHeight  = QtGui.QFontMetrics(painter.font()).height()

    painter.drawText(option.rect.x()+2, option.rect.y()+2+textHeight, index.data())

    rowLinks = {}
    for i in range(3):
      text = "Link %d" % (3-i)
      linkWidth = QtGui.QFontMetrics(font).width(text)
      x = option.rect.right() - (i+1) * (linkWidth + 10)
      painter.drawText(x, y, text)
      rect = QtCore.QRect(x, y - textHeight, linkWidth, textHeight)
      rowLinks[rect] = self.makeLinkFunc(index.row(), text)

    self.links[index.row()] = rowLinks
    painter.restore()

  def sizeHint(self, option, index):
    hint = super().sizeHint(option, index)
    hint.setHeight(30)
    return hint

  def editorEvent(self, event, model, option, index):
    if event.type() == QtCore.QEvent.MouseButtonRelease:
      for rect, link in self.links[index.row()].items():
        if rect.contains(event.pos()):
          link()
          return True
    return False

listmodel = MyModel()
listview = QtGui.QListView()
listview.setModel(listmodel)
listview.setItemDelegate(MyDelegate(parent=listview))
listview.setSelectionMode(QtGui.QAbstractItemView.NoSelection)
like image 188
Weetu Avatar answered Nov 14 '22 05:11

Weetu