2011-08-18 11 views
4

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

QLabelsとQTreeView.setIndexWidgetを使用して、この質問の推奨事項を実行できました。

Hyperlinks in QTreeView

残念ながら、私のQTreeViewは(項目の1000年代)かなり大きくなり、そしてQLabelsの1000を作成することは遅いことができます。

利点は、私がは、ハイパーリンクのようなに見えるというテキストを描画するために私QTreeViewで委任を使用することができるということです。これは超高速です。

問題は、ハイパーリンク(マウスオーバーハンドカーソル、クリックに反応するなど)のように応答する必要があることですが、そのための最良の方法は何かわかりません。

私は、QTreeViewのclicked()信号に接続するだけで偽装できましたが、セル内のテキストだけでなく、セル全体に応答するため正確に同じではありません。

答えて

2

drawDisplay(あなたは、ほぼゼロからアイテムを再描画しなければならない、あなたは追加のクラスの導出が必要になりますQStyledItemDelegateと)QProxyStyleから:

  • HTMLテキストがQTextDocumentQTextDocument.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を使用して格納するなど)ことでスピードアップすることができます。

+0

素晴らしいです、これを試してみましょう!ちなみに、私は実際にQStyledItemDelegateを使用していました。私は実際にモデル内にテキストを設定していません。モデルアイテムにはデータ変数があり、テキスト(プラスアイコン、カラーリング、グリッドラインなど)はすべてデータに基づいて描画されます。だから、私はすでに基本的に最初からアイテムを再描画しています。 QProxyStyleを使用するのは初めてですが、以前はこれまで使用したことがなく、PyQtでラップされているようにも見えません。 QProxyStyleが必要なのはなぜですか? –

+0

@Brendan QStyledItemDelegateはQStyle関数を使用してアイテムを描画し、QProxyStyleは別のスタイルクラスの一部を再利用してQStyle派生クラスを書き込むことができます。 – alexisdm

+0

ありがとう、私はこれを試して、それは素晴らしい作品! –

1

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

ツリー全体を一度に入力する必要はありません。必要に応じてQLabelsの生成を検討しましたか? およびexpandAllシグナルを含むサブツリーをカバーするのに十分な量を割り当てます。これを拡張するには、QLabelのプールを作成し、必要に応じてそのテキスト(および使用されている場所)を変更します。それを行うための最も簡単な方法は、テキストが別の仮想関数によって描かれているので、QItemDelegateをサブクラス化することであると思わ

関連する問題