4

QTreeView でクリック可能なハイパーリンクを表示しようとしています。

この質問の推奨事項に従って、 QLabels と QTreeView.setIndexWidget を使用してこれを行うことができました。

QTreeView のハイパーリンク

残念ながら、私の QTreeView はかなり大きく (数千のアイテム)、数千の QLabels の作成は遅いです。

利点は、QTreeView でデリゲートを使用して、ハイパーリンクのようなテキストを描画できることです。これは超高速です。

今の問題は、ハイパーリンクのように応答する必要があることです (つまり、マウスオーバー ハンド カーソル、クリックへの応答など)。

QTreeView の clicked() シグナルに接続するだけで、それを偽造することができましたが、セル内のテキストだけでなく、セル全体に応答するため、まったく同じではありません。

4

3 に答える 3

2

これを行う最も簡単な方法は、 をサブクラス化することですQItemDelegate。これは、テキストが別の仮想関数によって描画されるためですdrawDisplay(QStyledItemDelegateアイテムを最初から再描画する必要がほとんどあり、 から派生する追加のクラスが必要になりますQProxyStyle)。

  • HTML テキストは と で描画されQTextDocumentますQTextDocument.documentLayout().draw()
  • マウスがアイテムに入ると、同じアイテムが再描画さdrawDisplayれて呼び出され、テキストを描画している場合は位置を保存します (したがって、保存された位置は常に、マウスが置かれているアイテムのテキストの位置になります)。
  • その位置はeditorEvent、ドキュメント内のマウスの相対位置を取得し、ドキュメント内のその位置にあるリンクを で取得するために使用されQAbstractTextDocumentLayout.anchorAtます。
import sys
from PySide.QtCore import *
from PySide.QtGui import *

class LinkItemDelegate(QItemDelegate):
    linkActivated = Signal(str)
    linkHovered = Signal(str)  # to connect to a QStatusBar.showMessage slot

    def __init__(self, parentView):
        QItemDelegate.__init__(self, parentView)
        assert isinstance(parentView, QAbstractItemView), \
            "The first argument must be the view"

        # We need that to receive mouse move events in editorEvent
        parentView.setMouseTracking(True)

        # Revert the mouse cursor when the mouse isn't over 
        # an item but still on the view widget
        parentView.viewportEntered.connect(parentView.unsetCursor)

        # documents[0] will contain the document for the last hovered item
        # documents[1] will be used to draw ordinary (not hovered) items
        self.documents = []
        for i in range(2):
            self.documents.append(QTextDocument(self))
            self.documents[i].setDocumentMargin(0)
        self.lastTextPos = QPoint(0,0)

    def drawDisplay(self, painter, option, rect, text): 
        # Because the state tells only if the mouse is over the row
        # we have to check if it is over the item too
        mouseOver = option.state & QStyle.State_MouseOver \
            and rect.contains(self.parent().viewport() \
                .mapFromGlobal(QCursor.pos())) \
            and option.state & QStyle.State_Enabled

        if mouseOver:
            # Use documents[0] and save the text position for editorEvent
            doc = self.documents[0]                
            self.lastTextPos = rect.topLeft()
            doc.setDefaultStyleSheet("")
        else:
            doc = self.documents[1]
            # Links are decorated by default, so disable it
            # when the mouse is not over the item
            doc.setDefaultStyleSheet("a {text-decoration: none}")

        doc.setDefaultFont(option.font)
        doc.setHtml(text)

        painter.save()
        painter.translate(rect.topLeft())
        ctx = QAbstractTextDocumentLayout.PaintContext()
        ctx.palette = option.palette
        doc.documentLayout().draw(painter, ctx)
        painter.restore()

    def editorEvent(self, event, model, option, index):
        if event.type() not in [QEvent.MouseMove, QEvent.MouseButtonRelease] \
            or not (option.state & QStyle.State_Enabled):
            return False                        
        # Get the link at the mouse position
        # (the explicit QPointF conversion is only needed for PyQt)
        pos = QPointF(event.pos() - self.lastTextPos)
        anchor = self.documents[0].documentLayout().anchorAt(pos)
        if anchor == "":
            self.parent().unsetCursor()
        else:
            self.parent().setCursor(Qt.PointingHandCursor)               
            if event.type() == QEvent.MouseButtonRelease:
                self.linkActivated.emit(anchor)
                return True 
            else:
                self.linkHovered.emit(anchor)
        return False

    def sizeHint(self, option, index):
        # The original size is calculated from the string with the html tags
        # so we need to subtract from it the difference between the width
        # of the text with and without the html tags
        size = QItemDelegate.sizeHint(self, option, index)

        # Use a QTextDocument to strip the tags
        doc = self.documents[1]
        html = index.data() # must add .toString() for PyQt "API 1"
        doc.setHtml(html)        
        plainText = doc.toPlainText()

        fontMetrics = QFontMetrics(option.font)                
        diff = fontMetrics.width(html) - fontMetrics.width(plainText)

        return size - QSize(diff, 0)

内容に対する列の自動サイズ変更 (すべての項目に対して sizeHint を呼び出す) を有効にしない限り、デリゲートがない場合よりも遅くはないようです。
カスタム モデルでは、モデル内にデータを直接キャッシュすることで高速化できる場合があります (たとえば、QTextDocument の代わりにホバーされていないアイテムに QStaticText を使用して保存するなど)。

于 2011-08-29T04:40:10.297 に答える
1

QLabels の使用を避けることはおそらく可能ですが、コードの可読性に影響を与える可能性があります。

ツリー全体を一度に埋める必要はないかもしれません。必要に応じて QLabels を生成することを検討しましたか? expandおよびexpandAllシグナルでサブツリーをカバーするのに十分な量を割り当てます。QLabels のプールを作成し、必要に応じてテキスト (および使用される場所) を変更することで、これを拡張できます。

于 2011-08-22T19:56:20.830 に答える