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 的描述符 descriptor详解
Feb 27 Python
python条件变量之生产者与消费者操作实例分析
Mar 22 Python
Python编程之序列操作实例详解
Jul 22 Python
python实现支付宝当面付(扫码支付)功能
May 30 Python
python实现周期方波信号频谱图
Jul 21 Python
python+openCV利用摄像头实现人员活动检测
Jun 22 Python
使用Python爬取弹出窗口信息的实例
Mar 14 Python
基于matplotlib xticks用法详解
Apr 16 Python
python 解决selenium 中的 .clear()方法失效问题
Sep 01 Python
Python 实现集合Set的示例
Dec 21 Python
Python基于Opencv识别两张相似图片
Apr 25 Python
Python实现byte转integer
Jun 03 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
全国FM电台频率大全 - 4 山西省
2020/03/11 无线电
PHP 全角转半角实现代码
2010/05/16 PHP
PHP基于phpqrcode生成带LOGO图像的二维码实例
2015/07/10 PHP
php简单实现短网址(短链)还原的方法(测试可用)
2016/05/09 PHP
Javascript 写的简单进度条控件
2008/01/22 Javascript
讨论html与javascript在浏览器中的加载顺序问题
2013/11/27 Javascript
jQuery操作DOM之获取表单控件的值
2015/01/23 Javascript
Javascript removeChild()删除节点及删除子节点的方法
2015/12/27 Javascript
jQuery实现从身份证号中获取出生日期和性别的方法分析
2016/02/25 Javascript
jQuery加密密码到cookie的实现代码
2017/04/18 jQuery
AngularJS学习笔记之表单验证功能实例详解
2017/07/06 Javascript
Angularjs之如何在跨域请求中传输Cookie的方法
2018/06/01 Javascript
Vue-cli3项目配置Vue.config.js实战记录
2018/07/29 Javascript
Vue的click事件防抖和节流处理详解
2019/11/13 Javascript
Vue 中使用 typescript的方法详解
2020/02/17 Javascript
[01:16:28]DOTA2-DPC中国联赛 正赛 iG vs Magma BO3 第二场 2月23日
2021/03/11 DOTA
pyqt和pyside开发图形化界面
2014/01/22 Python
python爬虫教程之爬取百度贴吧并下载的示例
2014/03/07 Python
python进阶教程之异常处理
2014/08/30 Python
Django自定义分页效果
2017/06/27 Python
Django model序列化为json的方法示例
2018/10/16 Python
正则给header的冒号两边参数添加单引号(Python请求用)
2019/08/09 Python
Python操作Excel把数据分给sheet
2020/05/20 Python
python字典的值可以修改吗
2020/06/29 Python
python 带时区的日期格式化操作
2020/10/23 Python
美国购买汽车零件网站:Buy Auto Parts
2018/04/02 全球购物
德国圣伯纳德草药屋:Kräuterhaus Sanct Bernhard(有中文站)
2018/08/05 全球购物
国贸专业的职业规划书
2014/03/15 职场文书
工伤事故赔偿协议书
2014/04/15 职场文书
党的群众路线教育实践活动批评与自我批评范文
2014/10/16 职场文书
农民工工资支付承诺书
2015/05/04 职场文书
导游词之镇江-金山寺
2019/10/14 职场文书
某某店铺的开业庆典主持词范本
2019/11/25 职场文书
详解MySQL集群搭建
2021/05/26 MySQL
Pytorch 如何加速Dataloader提升数据读取速度
2021/05/28 Python
Springboot使用Spring Data JPA实现数据库操作
2021/06/30 Java/Android