python3+PyQt5自定义视图详解


Posted in Python onApril 24, 2018

pyqt提供的几个视图类都可以较好工作,包括QLisView,QTableView和QTreeView。但是对于一些难以用现有的方式来呈现数据,这时,可以创建我们自己的视图子类并将其用做模型数据的可视化来解决这一问题。本文通过Python3+pyqt5实现了python Qt GUI 快速编程的16章的例子。

#!/usr/bin/env python3

import gzip
import os
import platform
import sys
from PyQt5.QtCore import (QAbstractTableModel, QDateTime, QModelIndex,
    QSize, QTimer, QVariant, Qt,pyqtSignal)
from PyQt5.QtGui import ( QColor, QCursor, QFont,
    QFontDatabase, QFontMetrics, QPainter, QPalette, QPixmap)
from PyQt5.QtWidgets import QApplication,QDialog,QHBoxLayout, QLabel, QMessageBox,QScrollArea, QSplitter, QTableView,QWidget


(TIMESTAMP, TEMPERATURE, INLETFLOW, TURBIDITY, CONDUCTIVITY,
 COAGULATION, RAWPH, FLOCCULATEDPH) = range(8)

TIMESTAMPFORMAT = "yyyy-MM-dd hh:mm"


class WaterQualityModel(QAbstractTableModel):

  def __init__(self, filename):
    super(WaterQualityModel, self).__init__()
    self.filename = filename
    self.results = []


  def load(self):
    self.beginResetModel()
    exception = None
    fh = None
    try:
      if not self.filename:
        raise IOError("no filename specified for loading")
      self.results = []
      line_data = gzip.open(self.filename).read()
      for line in line_data.decode("utf8").splitlines():
        parts = line.rstrip().split(",")
        date = QDateTime.fromString(parts[0] + ":00",
                      Qt.ISODate)

        result = [date]
        for part in parts[1:]:
          result.append(float(part))
        self.results.append(result)

    except (IOError, ValueError) as e:
      exception = e
    finally:
      if fh is not None:
        fh.close()
      self.endResetModel()
      if exception is not None:
        raise exception


  def data(self, index, role=Qt.DisplayRole):
    if (not index.isValid() or
      not (0 <= index.row() < len(self.results))):
      return QVariant()
    column = index.column()
    result = self.results[index.row()]
    if role == Qt.DisplayRole:
      item = result[column]
      if column == TIMESTAMP:
        #item = item.toString(TIMESTAMPFORMAT)
        item=item
      else:
        #item = QString("%1").arg(item, 0, "f", 2)
        item = "{0:.2f}".format(item)
      return item
    elif role == Qt.TextAlignmentRole:
      if column != TIMESTAMP:
        return QVariant(int(Qt.AlignRight|Qt.AlignVCenter))
      return QVariant(int(Qt.AlignLeft|Qt.AlignVCenter))
    elif role == Qt.TextColorRole and column == INLETFLOW:
      if result[column] < 0:
        return QVariant(QColor(Qt.red))
    elif (role == Qt.TextColorRole and
       column in (RAWPH, FLOCCULATEDPH)):
      ph = result[column]
      if ph < 7:
        return QVariant(QColor(Qt.red))
      elif ph >= 8:
        return QVariant(QColor(Qt.blue))
      else:
        return QVariant(QColor(Qt.darkGreen))
    return QVariant()


  def headerData(self, section, orientation, role=Qt.DisplayRole):
    if role == Qt.TextAlignmentRole:
      if orientation == Qt.Horizontal:
        return QVariant(int(Qt.AlignCenter))
      return QVariant(int(Qt.AlignRight|Qt.AlignVCenter))
    if role != Qt.DisplayRole:
      return QVariant()
    if orientation == Qt.Horizontal:
      if section == TIMESTAMP:
        return "Timestamp"
      elif section == TEMPERATURE:
        return "\u00B0" +"C"
      elif section == INLETFLOW:
        return "Inflow"
      elif section == TURBIDITY:
        return "NTU"
      elif section == CONDUCTIVITY:
        return "\u03BCS/cm"
      elif section == COAGULATION:
        return "mg/L"
      elif section == RAWPH:
        return "Raw Ph"
      elif section == FLOCCULATEDPH:
        return "Floc Ph"
    return int(section + 1)


  def rowCount(self, index=QModelIndex()):
    return len(self.results)


  def columnCount(self, index=QModelIndex()):
    return 8


class WaterQualityView(QWidget):
  clicked = pyqtSignal(QModelIndex)
  FLOWCHARS = (chr(0x21DC), chr(0x21DD), chr(0x21C9))

  def __init__(self, parent=None):
    super(WaterQualityView, self).__init__(parent)
    self.scrollarea = None
    self.model = None
    self.setFocusPolicy(Qt.StrongFocus)
    self.selectedRow = -1
    self.flowfont = self.font()
    size = self.font().pointSize()
    if platform.system() == "Windows":
      fontDb = QFontDatabase()
      for face in [face.toLower() for face in fontDb.families()]:
        if face.contains("unicode"):
          self.flowfont = QFont(face, size)
          break
      else:
        self.flowfont = QFont("symbol", size)
        WaterQualityView.FLOWCHARS = (chr(0xAC), chr(0xAE),
                       chr(0xDE))


  def setModel(self, model):
    self.model = model
    #self.connect(self.model,
    #    SIGNAL("dataChanged(QModelIndex,QModelIndex)"),
    #    self.setNewSize)
    self.model.dataChanged.connect(self.setNewSize)
    #self.connect(self.model, SIGNAL("modelReset()"), self.setNewSize)
    self.model.modelReset.connect(self.setNewSize)
    self.setNewSize()


  def setNewSize(self):
    self.resize(self.sizeHint())
    self.update()
    self.updateGeometry()


  def minimumSizeHint(self):
    size = self.sizeHint()
    fm = QFontMetrics(self.font())
    size.setHeight(fm.height() * 3)
    return size


  def sizeHint(self):
    fm = QFontMetrics(self.font())
    size = fm.height()
    return QSize(fm.width("9999-99-99 99:99 ") + (size * 4),
           (size / 4) + (size * self.model.rowCount()))


  def paintEvent(self, event):
    if self.model is None:
      return
    fm = QFontMetrics(self.font())
    timestampWidth = fm.width("9999-99-99 99:99 ")
    size = fm.height()
    indicatorSize = int(size * 0.8)
    offset = int(1.5 * (size - indicatorSize))
    minY = event.rect().y()
    maxY = minY + event.rect().height() + size
    minY -= size
    painter = QPainter(self)
    painter.setRenderHint(QPainter.Antialiasing)
    painter.setRenderHint(QPainter.TextAntialiasing)
    y = 0
    for row in range(self.model.rowCount()):
      x = 0
      if minY <= y <= maxY:
        painter.save()
        painter.setPen(self.palette().color(QPalette.Text))
        if row == self.selectedRow:
          painter.fillRect(x, y + (offset * 0.8),
              self.width(), size, self.palette().highlight())
          painter.setPen(self.palette().color(
              QPalette.HighlightedText))
        #timestamp = self.model.data(
            #self.model.index(row, TIMESTAMP)).toDateTime()
        timestamp = self.model.data(self.model.index(row, TIMESTAMP))    
        painter.drawText(x, y + size,
            timestamp.toString(TIMESTAMPFORMAT))
        #print(timestamp.toString(TIMESTAMPFORMAT))
        x += timestampWidth
        temperature = self.model.data(
            self.model.index(row, TEMPERATURE))
        #temperature = temperature.toDouble()[0]
        temperature = float(temperature)
        if temperature < 20:
          color = QColor(0, 0,
              int(255 * (20 - temperature) / 20))
        elif temperature > 25:
          color = QColor(int(255 * temperature / 100), 0, 0)
        else:
          color = QColor(0, int(255 * temperature / 100), 0)
        painter.setPen(Qt.NoPen)
        painter.setBrush(color)
        painter.drawEllipse(x, y + offset, indicatorSize,
                  indicatorSize)
        x += size
        rawPh = self.model.data(self.model.index(row, RAWPH))
        #rawPh = rawPh.toDouble()[0]
        rawPh = float(rawPh)
        if rawPh < 7:
          color = QColor(int(255 * rawPh / 10), 0, 0)
        elif rawPh >= 8:
          color = QColor(0, 0, int(255 * rawPh / 10))
        else:
          color = QColor(0, int(255 * rawPh / 10), 0)
        painter.setBrush(color)
        painter.drawEllipse(x, y + offset, indicatorSize,
                  indicatorSize)
        x += size
        flocPh = self.model.data(
            self.model.index(row, FLOCCULATEDPH))
        #flocPh = flocPh.toDouble()[0]
        flocPh = float(flocPh)
        if flocPh < 7:
          color = QColor(int(255 * flocPh / 10), 0, 0)
        elif flocPh >= 8:
          color = QColor(0, 0, int(255 * flocPh / 10))
        else:
          color = QColor(0, int(255 * flocPh / 10), 0)
        painter.setBrush(color)
        painter.drawEllipse(x, y + offset, indicatorSize,
                  indicatorSize)
        painter.restore()
        painter.save()
        x += size
        flow = self.model.data(
            self.model.index(row, INLETFLOW))
        #flow = flow.toDouble()[0]
        flow = float(flow)
        char = None
        if flow <= 0:
          char = WaterQualityView.FLOWCHARS[0]
        elif flow < 3.6:
          char = WaterQualityView.FLOWCHARS[1]
        elif flow > 4.7:
          char = WaterQualityView.FLOWCHARS[2]
        if char is not None:
          painter.setFont(self.flowfont)
          painter.drawText(x, y + size, char)
        painter.restore()
      y += size
      if y > maxY:
        break


  def mousePressEvent(self, event):
    fm = QFontMetrics(self.font())
    self.selectedRow = event.y() // fm.height()
    self.update()
    #self.emit(SIGNAL("clicked(QModelIndex)"),
    #     self.model.index(self.selectedRow, 0))
    self.clicked.emit(self.model.index(self.selectedRow, 0))



  def keyPressEvent(self, event):
    if self.model is None:
      return
    row = -1
    if event.key() == Qt.Key_Up:
      row = max(0, self.selectedRow - 1)
    elif event.key() == Qt.Key_Down:
      row = min(self.selectedRow + 1, self.model.rowCount() - 1)
    if row != -1 and row != self.selectedRow:
      self.selectedRow = row
      if self.scrollarea is not None:
        fm = QFontMetrics(self.font())
        y = fm.height() * self.selectedRow
        print(y)
        self.scrollarea.ensureVisible(0, y)
      self.update()
      #self.emit(SIGNAL("clicked(QModelIndex)"),
      #     self.model.index(self.selectedRow, 0))
      self.clicked.emit(self.model.index(self.selectedRow, 0))
    else:
      QWidget.keyPressEvent(self, event)


class MainForm(QDialog):

  def __init__(self, parent=None):
    super(MainForm, self).__init__(parent)

    self.model = WaterQualityModel(os.path.join(
        os.path.dirname(__file__), "waterdata.csv.gz"))
    self.tableView = QTableView()
    self.tableView.setAlternatingRowColors(True)
    self.tableView.setModel(self.model)
    self.waterView = WaterQualityView()
    self.waterView.setModel(self.model)
    scrollArea = QScrollArea()
    scrollArea.setBackgroundRole(QPalette.Light)
    scrollArea.setWidget(self.waterView)
    self.waterView.scrollarea = scrollArea

    splitter = QSplitter(Qt.Horizontal)
    splitter.addWidget(self.tableView)
    splitter.addWidget(scrollArea)
    splitter.setSizes([600, 250])
    layout = QHBoxLayout()
    layout.addWidget(splitter)
    self.setLayout(layout)

    self.setWindowTitle("Water Quality Data")
    QTimer.singleShot(0, self.initialLoad)


  def initialLoad(self):
    QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
    splash = QLabel(self)
    pixmap = QPixmap(os.path.join(os.path.dirname(__file__),
        "iss013-e-14802.jpg"))
    #print(os.path.join(os.path.dirname(__file__),
    #    "iss013-e-14802.jpg"))
    splash.setPixmap(pixmap)
    splash.setWindowFlags(Qt.SplashScreen)
    splash.move(self.x() + ((self.width() - pixmap.width()) / 2),
          self.y() + ((self.height() - pixmap.height()) / 2))
    splash.show()
    QApplication.processEvents()
    try:
      self.model.load()
    except IOError as e:
      QMessageBox.warning(self, "Water Quality - Error", e)
    else:
      self.tableView.resizeColumnsToContents()
    splash.close()
    QApplication.processEvents()
    QApplication.restoreOverrideCursor()


app = QApplication(sys.argv)
form = MainForm()
form.resize(850, 620)
form.show()
app.exec_()

运行结果:

python3+PyQt5自定义视图详解

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Python Web框架Flask信号机制(signals)介绍
Jan 01 Python
python使用xlrd模块读写Excel文件的方法
May 06 Python
简单解析Django框架中的表单验证
Jul 17 Python
Python引用类型和值类型的区别与使用解析
Oct 17 Python
Python实现控制台中的进度条功能代码
Dec 22 Python
pyhanlp安装介绍和简单应用
Feb 22 Python
Numpy数组array和矩阵matrix转换方法
Aug 05 Python
Python文件操作函数用法实例详解
Dec 24 Python
基于python实现获取网页图片过程解析
May 11 Python
Python使用jupyter notebook查看ipynb文件过程解析
Jun 02 Python
Django windows使用Apache实现部署流程解析
Oct 12 Python
总结Python常用的魔法方法
May 25 Python
python自动重试第三方包retrying模块的方法
Apr 24 #Python
python3+PyQt5泛型委托详解
Apr 24 #Python
python去除扩展名的实例讲解
Apr 23 #Python
python3 遍历删除特定后缀名文件的方法
Apr 23 #Python
将TensorFlow的模型网络导出为单个文件的方法
Apr 23 #Python
tensorflow1.0学习之模型的保存与恢复(Saver)
Apr 23 #Python
tensorflow 使用flags定义命令行参数的方法
Apr 23 #Python
You might like
PHPExcel中文帮助手册|PHPExcel使用方法(分享)
2017/06/09 PHP
thinkPHP5.1框架使用SemanticUI实现分页功能示例
2019/08/03 PHP
初学JavaScript_03(ExtJs Grid的简单使用)
2008/10/02 Javascript
Jquery iframe内部出滚动条
2010/02/11 Javascript
文本框只能选择数据到文本框禁止手动输入
2013/11/22 Javascript
使用JavaScript进行进制转换将字符串转换为十进制
2014/09/21 Javascript
javascript里使用php代码实例
2014/12/13 Javascript
JavaScript中文件上传API详解
2016/04/01 Javascript
通过javascript进行UTF-8编码的实现方法
2016/06/27 Javascript
网络传输协议(http协议)
2016/11/18 Javascript
jquery的父、子、兄弟节点查找,节点的子节点循环方法
2016/12/07 Javascript
求js数组的最大值和最小值的四种方法
2017/03/03 Javascript
JS给按钮添加跳转功能类似a标签
2017/05/30 Javascript
node实现定时发送邮件的示例代码
2017/08/26 Javascript
Vue实现Layui的集成方法步骤
2020/04/10 Javascript
[37:22]DOTA2上海特级锦标赛D组资格赛#2 Liquid VS VP第一局
2016/02/28 DOTA
python实现的重启关机程序实例
2014/08/21 Python
python编程之requests在网络请求中添加cookies参数方法详解
2017/10/25 Python
详谈套接字中SO_REUSEPORT和SO_REUSEADDR的区别
2018/04/28 Python
Python中单线程、多线程和多进程的效率对比实验实例
2019/05/14 Python
Python3打包exe代码2种方法实例解析
2020/02/17 Python
Tensorflow中的dropout的使用方法
2020/03/13 Python
Python3 shelve对象持久存储原理详解
2020/03/23 Python
Python日志:自定义输出字段 json格式输出方式
2020/04/27 Python
Python爬取网页信息的示例
2020/09/24 Python
整理HTML5移动端开发的常用触摸事件
2016/04/15 HTML / CSS
CK加拿大官网:Calvin Klein加拿大
2020/03/14 全球购物
L’Artisan Parfumeur官网:法国香水品牌
2020/08/11 全球购物
初二政治教学反思
2014/01/12 职场文书
财务主管自我鉴定
2014/01/17 职场文书
工会主席岗位责任制
2014/02/11 职场文书
艺术设计专业求职自荐信
2014/05/19 职场文书
团队口号大全
2014/06/06 职场文书
大学生党员个人对照检查材料范文
2014/09/25 职场文书
先进个人评语大全
2015/01/04 职场文书
Centos7 Shell编程之正则表达式、文本处理工具详解
2022/08/05 Servers