PyQt5模型視圖委托


Model-View-Delegate

模型視圖委托(MVD)是PyQt中特有的設計模式,類似MVC設計模式,將MVC設計模式中的Controller當做MVD中的Delegate,兩者的概念基本相同。不同的是委托不是獨立存在,而是包含在視圖里面。

模型視圖委托設計模式中,模型負責存儲和管理數據;視圖負責顯示數據,其中界面的框架和基礎信息是視圖負責,具體數據的顯示是委托負責;委托不僅僅負責數據的顯示,還有一個重要的功能是負責數據的編輯,如在視圖中雙擊就可以編輯數據。

視圖是怎么獲取模型數據?首先初始化視圖時需要給視圖設置模型,然后通過索引獲取模型中對應位置的數據。

模型

數據的存儲一般是列表,表格和樹,不同的存儲方式有不同的操作和管理方法,為了適應這種差異性,PyQt中提供了一種統一的操作方法,如下圖所示:

對於列表數據:

  • 根節點永遠是NULL

  • row遞增,column是0

對於表結構:

  • 根節點永遠是NULL

  • row和column遞增

對於樹結構:

  • 根節點是NULL,父節點可變

  • row遞增,column是0

模型類:

  • QStandardItemModel 通用存儲,可以存儲任意結構,最常用

  • QStringListModel 存儲一組字符串

  • QDirModel 存儲文件系統

  • QSqlQueryModel 對SQL查詢的結果進行封裝

  • QSqlTableModel 對SQL中的表格進行封裝

例子:

import sys

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QWidget, QTableView, QHBoxLayout

class MyWidget(QWidget):
    def __init__(self):
        super(MyWidget, self).__init__()

        self.mode = QStandardItemModel()
        root = self.mode.invisibleRootItem()

        item1 = QStandardItem()
        item1.setData('1', Qt.DisplayRole)
        item2 = QStandardItem()
        item2.setData('2', Qt.DisplayRole)
        item3 = QStandardItem()
        item3.setData('3', Qt.DisplayRole)
        item4 = QStandardItem()
        item4.setData('4', Qt.DisplayRole)

        root.setChild(0, 0, item1)
        root.setChild(0, 1, item2)
        root.setChild(1, 0, item3)
        root.setChild(1, 1, item4)

        # 表結構存儲
        tableView = QTableView(self)
        tableView.setModel(self.mode)

        layout = QHBoxLayout()
        layout.addWidget(tableView)
        self.setLayout(layout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.resize(500, 300)
    w.move(300, 300)
    w.setWindowTitle('Simple')
    w.show()
    sys.exit(app.exec_())
import sys

from PyQt5.QtCore import Qt
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QWidget, QHBoxLayout, QTreeView

class MyWidget(QWidget):
    def __init__(self):
        super(MyWidget, self).__init__()

        self.mode = QStandardItemModel()
        root = self.mode.invisibleRootItem()

        item1 = QStandardItem()
        item1.setData('1', Qt.DisplayRole)
        item2 = QStandardItem()
        item2.setData('2', Qt.DisplayRole)
        item3 = QStandardItem()
        item3.setData('3', Qt.DisplayRole)
        item4 = QStandardItem()
        item4.setData('4', Qt.DisplayRole)

        # 樹結構存儲
        root.setChild(0, 0, item1)
        item1.setChild(0, 0, item2)
        item1.setChild(1, 0, item3)
        item3.setChild(0, 0, item4)

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

        layout = QHBoxLayout()
        layout.addWidget(treeView)
        self.setLayout(layout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.resize(500, 300)
    w.move(300, 300)
    w.setWindowTitle('Simple')
    w.show()
    sys.exit(app.exec_())

視圖

視圖主要是用來顯示數據,不同的視圖對象用於顯示不同存儲結構的數據,主要的視圖對象如下:

  • QListView 列表形式顯示

  • QTableView 表格形式顯示

  • QTreeView 樹結構顯示

單獨的視圖需要配合模型使用,因此PyQt對視圖進行了再次封裝,直接內部封裝模型,主要對象如下:

  • QListWidget 列表形式顯示的界面

  • QTableWidget 表格形式顯示的界面

  • QTreeWidget 樹結構形式顯示的界面

委托

委托被封裝在視圖里面,主要是負責數據的顯示和編輯功能。

數據的編輯主要涉及的方法:

  • createEditor 在雙擊進入編輯時,創建編輯器,如創建QLineEdit,QTextEdit

  • updateEditorGeometry 設置編輯器顯示的位置和大小

  • setEditorData 更新數據到視圖

  • setModeData 通過索引更新數據到模型

如果需要修改編輯時操作數據的方式,就需要重寫上述方法。

視圖主要是負責顯示,其中涉及的方法:

  • paint 負責繪制

  • editorEvent 負責處理事件

如果要實現自定義視圖顯示,需要重寫paint方法,在paint方法中繪制需要顯示的控件,然后在editorEvent方法中處理事件,更新數據。

例子

在TableView中默認的整型數據編輯使用的是計數器控件,本例中是將計數器變成單行文本控件,實現數據的編輯功能。

import sys

from PyQt5.QtCore import Qt, QVariant
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QWidget, QTableView, QHBoxLayout, QStyledItemDelegate, QLineEdit

class MyTableView(QTableView):
    def __init__(self):
        super(MyTableView, self).__init__()

class MyDelgate(QStyledItemDelegate):
    def __init__(self):
        super(MyDelgate, self).__init__()

    # 創建編輯器
    def createEditor(self, parent, option, index):
        print('createEditor')
        if index.column() == 1:
            return QLineEdit(parent)
        else:
            return QStyledItemDelegate.createEditor(self, parent, option, index)

    # 設置編輯器的位置
    def updateEditorGeometry(self, edit, option, index):
        print('updateEditorGeometry')
        if index.column() == 1:
            edit.setGeometry(option.rect)
        else:
            return QStyledItemDelegate.updateEditorGeometry(self, edit, option, index)

    # 設置數據到模型
    def setModelData(self, edit, model, index):
        print('setModelData')
        if index.column() == 1:
            model.setData(index, int(edit.text()), Qt.DisplayRole)
        else:
            return QStyledItemDelegate.setModelData(self, edit, model, index)

    # 設置數據到視圖
    def setEditorData(self, edit, index):
        print('setEditorData')
        if index.column() == 1:
            edit.setText(str(index.data(Qt.DisplayRole)))
        else:
            return QStyledItemDelegate.setEditorData(self, edit, index)

class MyWidget(QWidget):
    def __init__(self):
        super(MyWidget, self).__init__()

        self.mode = QStandardItemModel()
        root = self.mode.invisibleRootItem()

        item1 = QStandardItem()
        item1.setData('a', Qt.DisplayRole)

        item2 = QStandardItem()
        item2.setData(1, Qt.DisplayRole)

        item3 = QStandardItem()
        item3.setData(False, Qt.DisplayRole)

        item4 = QStandardItem()
        item4.setData('b', Qt.DisplayRole)

        item5 = QStandardItem()
        item5.setData(2, Qt.DisplayRole)

        item6 = QStandardItem()
        item6.setData(True, Qt.DisplayRole)

        root.setChild(0, 0, item1)
        root.setChild(0, 1, item2)
        root.setChild(0, 2, item3)
        root.setChild(1, 0, item4)
        root.setChild(1, 1, item5)
        root.setChild(1, 2, item6)

        tableView = MyTableView()
        tableView.setModel(self.mode)
        tableView.setItemDelegate(MyDelgate())

        layout = QHBoxLayout()
        layout.addWidget(tableView)
        self.setLayout(layout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.resize(500, 300)
    w.move(300, 300)
    w.setWindowTitle('Simple')
    w.show()
    sys.exit(app.exec_())

在TableView中默認的整型顯示是數字字符串,本例中將數字字符串變成進度條顯示。

import sys

from PyQt5.QtCore import Qt, QEvent
from PyQt5.QtGui import QStandardItemModel, QStandardItem
from PyQt5.QtWidgets import QApplication, QWidget, QTableView, QHBoxLayout, QStyledItemDelegate, QStyle, QStyleOptionProgressBar, QStyleOptionButton

class MyTableView(QTableView):
    def __init__(self):
        super(MyTableView, self).__init__()

# 一定要注意復寫方法的返回值
class MyDelgate(QStyledItemDelegate):
    def __init__(self):
        super(MyDelgate, self).__init__()

    # 委托負責具體數據的顯示,因此重新paint方法
    def paint(self, painter, option, index):
        if index.column() == 1:
            style = QStyleOptionProgressBar()
            style.minimum = 0
            style.maximum = 10
            style.progress= index.data(Qt.DisplayRole)
            style.rect = option.rect
            QApplication.style().drawControl(QStyle.CE_ProgressBar, style, painter)

        elif index.column() == 2:
            style = QStyleOptionButton()
            if index.data(Qt.DisplayRole) == True:
                style.state = QStyle.State_On
            else:
                style.state = QStyle.State_Off
            style.state |= QStyle.State_Enabled
            style.rect = option.rect
            style.rect.setX(option.rect.x() + option.rect.width() / 2 - 7)
            QApplication.style().drawControl(QStyle.CE_CheckBox, style, painter)
        else:
            return QStyledItemDelegate.paint(self, painter, option, index)

    def editorEvent(self, event, model, option, index):
        if index.column() == 2:
            if event.type() == QEvent.MouseButtonPress and option.rect.contains(event.pos()):
                data = not index.data(Qt.DisplayRole)
                model.setData(index, data, Qt.DisplayRole)
            return True
        else:
            return QStyledItemDelegate.editorEvent(self, event, model, option, index)

class MyWidget(QWidget):
    def __init__(self):
        super(MyWidget, self).__init__()

        self.mode = QStandardItemModel()
        root = self.mode.invisibleRootItem()

        item1 = QStandardItem()
        item1.setData('a', Qt.DisplayRole)

        item2 = QStandardItem()
        item2.setData(1, Qt.DisplayRole)

        item3 = QStandardItem()
        item3.setData(False, Qt.DisplayRole)

        item4 = QStandardItem()
        item4.setData('b', Qt.DisplayRole)

        item5 = QStandardItem()
        item5.setData(2, Qt.DisplayRole)

        item6 = QStandardItem()
        item6.setData(True, Qt.DisplayRole)

        root.setChild(0, 0, item1)
        root.setChild(0, 1, item2)
        root.setChild(0, 2, item3)
        root.setChild(1, 0, item4)
        root.setChild(1, 1, item5)
        root.setChild(1, 2, item6)

        tableView = MyTableView()
        tableView.setModel(self.mode)
        tableView.setItemDelegate(MyDelgate())

        layout = QHBoxLayout()
        layout.addWidget(tableView)
        self.setLayout(layout)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MyWidget()
    w.resize(500, 300)
    w.move(300, 300)
    w.setWindowTitle('Simple')
    w.show()
    sys.exit(app.exec_())

 總結:如果修改視圖中數據的顯示方式,需要重寫委托的paint方法,editorEvent方法是處理視圖中的點擊事件,根據需要絕對是否重寫;如果要修改視圖中數據的編輯方式,需要重寫createEditor方法、updateEditorGeometry方法、setEditorData方法以及setModeData方法。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM