温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

python3+PyQt5自定义视图详解

发布时间:2020-08-27 07:44:34 来源:脚本之家 阅读:192 作者:basisworker 栏目:开发技术

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自定义视图详解

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持亿速云。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI