簡體   English   中英

Python PyQt5 - 是否可以添加一個按鈕以在 QTreeView 內按下?

[英]Python PyQt5 - Is it possible to add a Button to press inside QTreeView?

我想將QPushButton添加到以.pdf結尾的樹視圖中,當我單擊它時,我想返回分配給它的索引的路徑。

使用本機QTreeView甚至可能無法做到這QTreeView但如果有人能指導我朝着正確的方向前進,那就太棒了!

總結更多我想要的是讓QPushButton出現在下面紅色方塊的位置。

看法

“樹視圖”的當前代碼:

from PyQt5.QtMultimediaWidgets import *
from PyQt5.QtMultimedia import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5 import *
import os, sys
class MainMenu(QWidget):
    def __init__(self, parent = None):
        super(MainMenu, self).__init__(parent)
        self.model = QFileSystemModel()
        self.model.setRootPath(QDir.rootPath())
        self.model.setFilter(QDir.NoDotAndDotDot | QDir.AllEntries | QDir.Dirs | QDir.Files)
        self.proxy_model = QSortFilterProxyModel(recursiveFilteringEnabled = True, filterRole = QFileSystemModel.FileNameRole)
        self.proxy_model.setSourceModel(self.model)
        self.model.setReadOnly(False)
        self.model.setNameFilterDisables(False)

        self.indexRoot = self.model.index(self.model.rootPath())

        self.treeView = QTreeView(self)
        self.treeView.setModel(self.proxy_model)

        self.treeView.setRootIndex(self.indexRoot)
        self.treeView.setAnimated(True)
        self.treeView.setIndentation(20)
        self.treeView.setSortingEnabled(True)
        self.treeView.setDragEnabled(False)
        self.treeView.setAcceptDrops(False)
        self.treeView.setDropIndicatorShown(True)
        self.treeView.setEditTriggers(QTreeView.NoEditTriggers)
        for i in range(1, self.treeView.model().columnCount()):
            self.treeView.header().hideSection(i)


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

為此,您可能需要一個項目委托。

這個想法是你將把基本的項目繪制留給基類paint()函數,然后在它上面繪制一個虛擬按鈕。
為了實現這一目標, QStyleOptionButton是用來對付視圖樣式(從獲得的option參數):創建一個樣式選項,從視圖(INIToption.widget ,這將適用於小部件的基本矩形,字體,調色板,等),調整矩形以滿足您的需要,最后繪制它。

為了更好地實現繪圖(鼠標懸停效果,同時確保正確的繪圖更新),您還需要將樹視圖的鼠標跟蹤設置為 True。 這與代碼中解釋的其他檢查一起,允許您繪制虛擬按鈕,包括其懸停或按下狀態。

最后,當按鈕被釋放並且鼠標在其邊界內時,將發出一個buttonClicked信號,並將當前索引作為參數。

單擊時帶有委托的屏幕截圖

class TreeButtonDelegate(QtWidgets.QStyledItemDelegate):
    buttonClicked = QtCore.pyqtSignal(QtCore.QModelIndex, int)

    def __init__(self, fsModel, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fsModel = fsModel

        self.clickedPaths = {}
        self._mousePos = None
        self._pressed = False
        self.minimumButtonWidth = 32

    def getOption(self, option, index):
        btnOption = QtWidgets.QStyleOptionButton()
        # initialize the basic options with the view
        btnOption.initFrom(option.widget)

        clickedCount = self.clickedPaths.get(self.fsModel.filePath(index), 0)
        if clickedCount:
            btnOption.text = '{}'.format(clickedCount)
        else:
            btnOption.text = 'NO'

        # the original option properties should never be touched, so we can't
        # directly use it's "rect"; let's create a new one from it
        btnOption.rect = QtCore.QRect(option.rect)

        # adjust it to the minimum size
        btnOption.rect.setLeft(option.rect.right() - self.minimumButtonWidth)

        style = option.widget.style()
        # get the available space for the contents of the button
        textRect = style.subElementRect(
            QtWidgets.QStyle.SE_PushButtonContents, btnOption)
        # get the margins between the contents and the border, multiplied by 2
        # since they're used for both the left and right side
        margin = style.pixelMetric(
            QtWidgets.QStyle.PM_ButtonMargin, btnOption) * 2

        # the width of the current button text
        textWidth = btnOption.fontMetrics.width(btnOption.text)

        if textRect.width() < textWidth + margin:
            # if the width is too small, adjust the *whole* button rect size
            # to fit the contents
            btnOption.rect.setLeft(btnOption.rect.left() - (
                textWidth - textRect.width() + margin))

        return btnOption

    def editorEvent(self, event, model, option, index):
        # map the proxy index to the fsModel
        srcIndex = index.model().mapToSource(index)
        # I'm just checking if it's a file, if you want to check the extension
        # you might need to use fsModel.fileName(srcIndex)
        if not self.fsModel.isDir(srcIndex):
            if event.type() in (QtCore.QEvent.Enter, QtCore.QEvent.MouseMove):
                self._mousePos = event.pos()
                # request an update of the current index
                option.widget.update(index)
            elif event.type() == QtCore.QEvent.Leave:
                self._mousePos = None
            elif (event.type() in (QtCore.QEvent.MouseButtonPress, QtCore.QEvent.MouseButtonDblClick)
                and event.button() == QtCore.Qt.LeftButton):
                    # check that the click is within the virtual button rectangle
                    if event.pos() in self.getOption(option, srcIndex).rect:
                        self._pressed = True
                    option.widget.update(index)
                    if event.type() == QtCore.QEvent.MouseButtonDblClick:
                        # do not send double click events
                        return True
            elif event.type() == QtCore.QEvent.MouseButtonRelease:
                if self._pressed and event.button() == QtCore.Qt.LeftButton:
                    # emit the click only if the release is within the button rect
                    if event.pos() in self.getOption(option, srcIndex).rect:
                        filePath = self.fsModel.filePath(srcIndex)
                        count = self.clickedPaths.setdefault(filePath, 0)
                        self.buttonClicked.emit(index, count + 1)
                        self.clickedPaths[filePath] += 1
                self._pressed = False
                option.widget.update(index)
        return super().editorEvent(event, model, option, index)

    def paint(self, painter, option, index):
        super().paint(painter, option, index)
        srcIndex = index.model().mapToSource(index)
        if not self.fsModel.isDir(srcIndex):
            btnOption = self.getOption(option, srcIndex)

            # remove the focus rectangle, as it will be inherited from the view
            btnOption.state &= ~QtWidgets.QStyle.State_HasFocus
            if self._mousePos is not None and self._mousePos in btnOption.rect:
                # if the style supports it, some kind of "glowing" border
                # will be shown on the button
                btnOption.state |= QtWidgets.QStyle.State_MouseOver
                if self._pressed == QtCore.Qt.LeftButton:
                    # set the button pressed state
                    btnOption.state |= QtWidgets.QStyle.State_On
            else:
                # ensure that there's no mouse over state (see above)
                btnOption.state &= ~QtWidgets.QStyle.State_MouseOver

            # finally, draw the virtual button
            option.widget.style().drawControl(
                QtWidgets.QStyle.CE_PushButton, btnOption, painter)


class MainMenu(QWidget):
    def __init__(self, parent = None):
        super(MainMenu, self).__init__(parent)
        # ...
        self.treeView = QTreeView(self)
        self.treeView.setMouseTracking(True)
        # ...
        self.treeDelegate = TreeDelegate(self.model)
        self.treeView.setItemDelegateForColumn(0, self.treeDelegate)
        self.treeDelegate.buttonClicked.connect(self.treeButtonClicked)
        # ...


    def treeButtonClicked(self, index, count):
        print('{} clicked {} times'.format(index.data(), count))

注意:我按照您在評論中的要求實現了點擊計數器(並使用了一個輔助函數來適應相應地計算按鈕大小的較長函數),請記住,這並沒有考慮到文件重命名、刪除和/ 或重新創建(或重命名的文件覆蓋現有文件)。 要獲得它,您需要使用比簡單的基於路徑的字典更復雜的方法,可能是通過實現 QFileSystemWatcher 並檢查已刪除/重命名的文件。
另請注意,為了加快速度,我將源文件系統模型添加到委托的 init 中,這樣每次需要繪制或鼠標跟蹤時都不需要找到它。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM