zoukankan      html  css  js  c++  java
  • 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方法。

  • 相关阅读:
    赋值运算符重载
    拷贝构造函数
    sizeof与strlen
    C++函数参数为引用或指针
    Largest Rectangle in Histogram
    二极管作用
    yzoj P1948 取数字问题
    NOIP 2016 愤怒的小鸟 题解
    NOIP 2016 蚯蚓 题解
    NOIP 2016 组合数问题 题解
  • 原文地址:https://www.cnblogs.com/chusiyong/p/12941212.html
Copyright © 2011-2022 走看看