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杀死一个线程的方法
Sep 06 Python
使用Python写一个贪吃蛇游戏实例代码
Aug 21 Python
Python函数装饰器常见使用方法实例详解
Mar 30 Python
Python TestCase中的断言方法介绍
May 02 Python
pytorch 输出中间层特征的实例
Aug 17 Python
python global关键字的用法详解
Sep 05 Python
python实现根据给定坐标点生成多边形mask的例子
Feb 18 Python
Pandas读取csv时如何设置列名
Jun 02 Python
python 写一个性能测试工具(一)
Oct 24 Python
如何利用Python matplotlib绘制雷达图
Dec 21 Python
Python编程编写完善的命令行工具
Sep 15 Python
python数字图像处理数据类型及颜色空间转换
Jun 28 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
php cli 方式 在crotab中运行解决
2010/02/08 PHP
基于php无限分类的深入理解
2013/06/02 PHP
调试WordPress中定时任务的相关PHP脚本示例
2015/12/10 PHP
Zend Framework入门知识点小结
2016/03/19 PHP
php实现的http请求封装示例
2016/11/08 PHP
基于jQueryUI和Corethink实现百度的搜索提示功能
2016/11/09 PHP
php获取当前url地址的方法小结
2017/01/10 PHP
PHP中使用OpenSSL生成证书及加密解密
2017/02/05 PHP
PHP不使用内置函数实现字符串转整型的方法示例
2017/07/03 PHP
Laravel框架实现的上传图片到七牛功能详解
2019/09/06 PHP
javascript中的nextSibling使用陷(da)阱(keng)
2014/05/05 Javascript
js实现Select下拉框具有输入功能的方法
2015/02/06 Javascript
JS实现往下不断流动网页背景的方法
2015/02/27 Javascript
node.js回调函数之阻塞调用与非阻塞调用
2015/11/13 Javascript
讲解JavaScript的Backbone.js框架的MVC结构设计理念
2016/02/14 Javascript
有关easyui-layout中的收缩层无法显示标题的解决办法
2016/05/10 Javascript
解决微信内置浏览器返回上一页强制刷新问题方法
2017/02/05 Javascript
javascript 跨域问题以及解决办法
2017/07/17 Javascript
傻瓜式vuex语法糖kiss-vuex整理
2018/12/21 Javascript
小程序转发探索示例
2019/02/19 Javascript
使用flow来规范javascript的变量类型
2019/09/12 Javascript
JavaScript canvas仿代码流瀑布
2020/02/10 Javascript
在webstorm中配置less的方法详解
2020/09/25 Javascript
Ant Design的可编辑Tree的实现操作
2020/10/31 Javascript
import的本质解析
2017/10/30 Python
python3+PyQt5使用数据库表视图
2018/04/24 Python
python实现字符串中字符分类及个数统计
2018/09/28 Python
django基于存储在前端的token用户认证解析
2019/08/06 Python
UGG英国官方网站:UGG UK
2018/02/08 全球购物
英国最大的笔记本电脑直销专家:Laptops Direct
2019/07/20 全球购物
医药专业推荐信
2013/11/15 职场文书
医务工作者先进事迹材料
2014/01/26 职场文书
幼儿园优秀教师事迹
2014/02/13 职场文书
交通肇事罪辩护词
2015/05/21 职场文书
python基于tkinter实现gif录屏功能
2021/05/19 Python
CSS 鼠标点击拖拽效果的实现代码
2022/12/24 HTML / CSS