PyQt5 - QWidgets部件进阶教程之字符映射表

来源:互联网 发布:淘宝布头论斤卖 编辑:程序博客网 时间:2024/06/15 15:20

  • 废话
  • 定义CharacterWidget类
  • 定义主视窗类
    • 最终代码
  • 最终效果
  • 问题说明

废话

本案例显示一个字符数组,用户可以通过点击在行编辑内键入文本。行编辑的内容可以随后拷贝进剪贴板,并粘贴到其他应用。这类工具的目的,是允许用户键入那些难以找到或在键盘定位的字符。
该案例包含以下类:
- CharacterWidget类显示用当前字体和风格下的字符
- MainWindow类提供一个标准的主窗口,包括字体风格信息、字符映射图像、行编辑和一个可以提交文本到剪贴板的按钮


定义CharacterWidget类

  • CharacterWidget用来显示在用户指定字体和风格下的字符集,为了灵活性,我们子类化QWidget,并只重写基础渲染和交互特征的函数。
  • 该部件不需要包含其他部件,所以它必须提供自有的sizeHint(),来让内容正确显示。
  • 我们重写paintEvent()来绘制自定义内容,同样重写mousePressEvent()用来允许用户与部件交互。
  • updateFont()和updateStyle()槽,当用户在应用内更改设置时,用来更新部件内字符的字体和风格。
  • 该类定义了characterSelected()信号,当用户在部件内选择了一个字符,会通知应用的其他部分。
  • 出于习惯,不见提供了一个工具提示,用来显示当前字符的值。我们重写了mouseMoveEvent()事件处理程序,并定义了showToolTip()来实现这个特性。
  • columns、displayFont和currentKey用来记录显示的列数、当前字体和部件内当前高亮的字符
from PyQt5.QtCore import *from PyQt5.QtGui import *from PyQt5.QtWidgets import *import sysimport unicodedataclass CharacterWidget(QWidget):    characterSelected = pyqtSignal(str)    def __init__(self):        super(CharacterWidget, self).__init__()        self.displayFont = QFont()        self.squareSize = int(24)        self.columns = int(16)        self.lastKey = int(-1)        self.setMouseTracking(True)
  • 初始化当前键值为-1,表明没有字符会在初始状态被选择。开启鼠标跟踪来允许我们跟踪穿过部件的光标移动。
    def updateFont(self, font):        self.displayFont.setFamily(font.family())        self.squareSize = max(24, QFontMetrics(self.displayFont).xHeight() * 3)        self.adjustSize()        self.update()    def updateSize(self, fontSize):        self.displayFont.setPointSize(int(fontSize))        self.squareSize = max(24, QFontMetrics(self.displayFont).xHeight() * 3)        self.adjustSize()        self.update()    def updateStyle(self, fontStyle):        fontDatabase = QFontDatabase()        oldStrategy = self.displayFont.styleStrategy()        self.displayFont = fontDatabase.font(self.displayFont.family(), fontStyle, self.displayFont.pointSize())        self.displayFont.setStyleStrategy(oldStrategy)        self.squareSize = max(24, QFontMetrics(self.displayFont).xHeight() * 3)        self.adjustSize()        self.update()    def updateFontMerging(self, enable):        if enable:            self.displayFont.setStyleStrategy(QFont.PreferDefault)        else:            self.displayFont.setStyleStrategy(QFont.NoFontMerging)        self.adjustSize()        self.update()
  • 以上四个方法允许设置字体、尺寸、样式和字体合并,最后调用update()进行修改。
    def sizeHint(self):        return QSize(self.columns*self.squareSize, (65536/self.columns)*self.squareSize)
  • 设置固定的sizeHint()
    def paintEvent(self, QPaintEvent):        painter = QPainter()        painter.begin(self)        painter.fillRect(QPaintEvent.rect(), QBrush(Qt.white))        painter.setFont(self.displayFont)        redrawRect = QPaintEvent.rect()        beginRow = int(redrawRect.top()/self.squareSize)        endRow = int(redrawRect.bottom()/self.squareSize)        beginColumn = int(redrawRect.left()/self.squareSize)        endColumn = int(redrawRect.right()/self.squareSize)        painter.setPen(QPen(Qt.gray))        for i in range(beginRow, endRow+1):            for j in range(beginColumn, endColumn+1):                painter.drawRect(j*self.squareSize, i*self.squareSize, self.squareSize, self.squareSize)        fontMetrics = QFontMetrics(self.displayFont)        painter.setPen(QPen(Qt.black))        for r in range(beginRow, endRow+1):            for c in range(beginColumn, endColumn+1):                key = int(r * self.columns + c)                painter.setClipRect(c*self.squareSize, r*self.squareSize, self.squareSize, self.squareSize)                if key == self.lastKey:                    painter.fillRect(c*self.squareSize + 1, r*self.squareSize + 1,                                     self.squareSize, self.squareSize, QBrush(Qt.red))                painter.drawText(c*self.squareSize + (self.squareSize / 2) - fontMetrics.width(chr(key)) / 2,                                 r*self.squareSize + 4 + fontMetrics.ascent(),                                 chr(key))        painter.end()
  • paintEvent()展示如何排列部件内容和显示
  • 为部件创建QPainter,并在所有情况下确保部件的背景色是被绘制的。painter的字体设置为用户指定的显示字体。部件的区域需要重绘,这用来决定那些字体需要显示。
  • 使用整数除法获取每个字符应显示的行列数,并在部件上为每个字符绘制一个方块。
  • 集合中每个字符的标记在每个方块内绘制,并将每个当前被选中的标记显示为红色。
  • 我们不需要考虑视口显示区域和所绘制区域的不同,因为所有超出可见区域的都会被剪切掉。
  • mousePressEvent()定义如何对鼠标点击进行相应,我们只关心当用户用左键点击部件。当这个发生时,我们计算哪个字符被点击,并发射characterSelected()信号。字符的序列会通过整除所点击的字符网格块尺寸的x、y坐标获得。当部件的列数被定义后,我们用该值乘行索引数并加上所在列序列来获得字符序列。如果点击任何鼠标按钮,事件被传送到QWdiget基础类,这可以确保事件可以被其它相关部件适当的处理。
  • mouseMoveEvent()映射鼠标在部件全局坐标的光标位置,决定通过运行计算而被点击字符。
  • 在全局坐标中定义的给定位置显示工具提示。

定义主视窗类

class MainWindow(QMainWindow):    def __init__(self):        super(MainWindow, self).__init__()        self.centralWidget = QWidget()        self.fontLabel = QLabel('Font:')        self.fontCombo = QFontComboBox()        self.sizeLabel = QLabel('Size:')        self.sizeCombo = QComboBox()        self.styleLabel = QLabel('Style:')        self.styleCombo = QComboBox()        self.fontMergingLabel = QLabel('Automatic Font Merging:')        self.fontMerging = QCheckBox()        self.fontMerging.setChecked(True)        self.charaterWidget = CharacterWidget()        self.scrollArea = QScrollArea()        self.scrollArea.setWidget(self.charaterWidget)        self.findStyles(self.fontCombo.currentFont())        self.findSizes(self.fontCombo.currentFont())        self.lineEdit = QLineEdit()        self.clipboard = QApplication.clipboard()        self.clipboardButton = QPushButton('&To Clipboard')        self.fontCombo.currentFontChanged.connect(self.findStyles)        self.fontCombo.currentFontChanged.connect(self.findSizes)        self.fontCombo.currentFontChanged.connect(self.charaterWidget.updateFont)        self.sizeCombo.currentIndexChanged.connect(self.charaterWidget.updateSize)        self.styleCombo.currentIndexChanged.connect(self.charaterWidget.updateStyle)        self.charaterWidget.characterSelected.connect(self.insertCharacter)        self.fontMerging.toggled.connect(self.charaterWidget.updateFontMerging)        self.clipboardButton.clicked.connect(self.updateClipboard)        self.controlsLayout = QHBoxLayout()        self.controlsLayout.addWidget(self.fontLabel)        self.controlsLayout.addWidget(self.fontCombo, 1)        self.controlsLayout.addWidget(self.sizeLabel)        self.controlsLayout.addWidget(self.sizeCombo, 1)        self.controlsLayout.addWidget(self.styleLabel)        self.controlsLayout.addWidget(self.styleCombo, 1)        self.controlsLayout.addWidget(self.fontMergingLabel)        self.controlsLayout.addWidget(self.fontMerging, 1)        self.controlsLayout.addStretch(1)        self.lineLayout = QHBoxLayout()        self.lineLayout.addWidget(self.lineEdit, 1)        self.lineLayout.addSpacing(12)        self.lineLayout.addWidget(self.clipboardButton)        self.centralLayout = QVBoxLayout()        self.centralLayout.addLayout(self.controlsLayout)        self.centralLayout.addWidget(self.scrollArea, 1)        self.centralLayout.addSpacing(4)        self.centralLayout.addLayout(self.lineLayout)        self.centralWidget.setLayout(self.centralLayout)        self.setCentralWidget(self.centralWidget)        self.setWindowTitle('Character Map')
  • 主窗口类提供一个简单的用户界面,只有一个构造器、相应标准部件发射信号的槽和一些简单的用于设置用户界面函数
  • 主窗口包含的各类部件用于控制字符将会如何显示,并定义findFonts()函数。部件通过决定可用样式来调用findStyles()槽,insertCharacter()将一个用户选择的字符插入到窗口的行编辑,updateClipboard()用行编辑的内容同步粘贴板。
  • 构造器内,我们设置视窗的中心部件,用一些标准部件进行填充。同样会构建一个CharacterWidget自定义部件。
  • 为了方便我们可以浏览它的内容,再增加一个QScrollArea。QScrollArea提供一个CharacterWidget的滚动视口。
  • 字体组合框自动弹出一个可用的字体列表,我们在样式组合框中,为当前字体列出可用样式。
  • 行编辑和按钮用于为粘贴板提供文本。
  • 我们也获取一个粘贴板对象,这样就能为其他应用发送用户输入的文本。
  • 案例中大部分信号的发射是通过标准部件,将这些信号与该类中的槽和其他部件的槽进行连接。
  • 字体组合框的currentFontChanged()链接到findStyles()函数,这样列出可用样式就能为每个字体使用。当字体和样式都能被用户改变时,字体组合框的currentFontChanged()信号和样式组合框的currentIndexChanged()则直接链接到字符部件。
  • 最后两个连接允许字符部件中的字符可以被选择,并插入文本到粘贴板。
  • 当用户点击字符时,字符部件发射characterSelected()自定义信号,这会被该类中的insertCharacter()函数处理。当按钮发射clicked()信号时,粘贴板被改变,并通过updateClipboard()函数处理。
  • 构造器中剩下的代码是设置中心部件的布局,和提供视窗标题。
    def findStyles(self, font):        fontDatabase = QFontDatabase()        currentItem = self.styleCombo.currentText()        self.styleCombo.clear()        for style in fontDatabase.styles(font.family()):            self.styleCombo.addItem(style)        styleIndex = self.styleCombo.findText(currentItem)        if styleIndex == -1:            self.styleCombo.setCurrentIndex(0)        else:            self.styleCombo.setCurrentIndex(styleIndex)    def findSizes(self, font):        fontDataBase = QFontDatabase()        currentSize = self.sizeCombo.currentText()        self.sizeCombo.blockSignals(True)        self.sizeCombo.clear()        if fontDataBase.isSmoothlyScalable(font.family(), fontDataBase.styleString(font)):            for size in fontDataBase.standardSizes():                self.sizeCombo.addItem(str(QVariant(size).value()))                self.sizeCombo.setEditable(True)        else:            for size in fontDataBase.smoothSizes(font.family(), fontDataBase.styleString(font)):                self.sizeCombo.addItem(str(QVariant(size)))                self.sizeCombo.setEditable(False)        self.sizeCombo.blockSignals(False)        sizeIndex = self.sizeCombo.findText(currentSize)        if sizeIndex == -1:            self.sizeCombo.setCurrentIndex(max(0, self.sizeCombo.count() / 3))        else:            self.sizeCombo.setCurrentIndex(sizeIndex)    def insertCharacter(self, character):        self.lineEdit.insert(character)    def updateClipboard(self):        self.clipboard.setText(self.lineEdit.text(), QClipboard.Clipboard)        self.clipboard.setText(self.lineEdit.text(), QClipboard.Selection)
  • 字体组合框自动弹出字体系列列表,可以通过findStyles()函数使用每种字体的样式,无论用户选择什么字体,这个函数都会调用。
  • 我们记录当前选择的样式,并清除央视组合框,这样就能插入与当前字体系列相关的样式。
  • 使用字体数据库收集当前字体的样式,并把它们插入到样式组合框。如果原样式对当前字体不可用,则清除当前项。
  • 最后两个函数是相应字符部件和主视窗按钮信号的槽,当用户点击一个字符时,insertCharacter()函数用于插入字符部件的字符,字符插入到行编辑的当前光标位置。
  • 主视窗的”To clipboard”按钮链接到updateClipboard(),当它被点击时,粘贴板包含的行编辑内容将被更新。我们拷贝行编辑的所有文本到粘贴板,但不清除行编辑。

最终代码

from PyQt5.QtCore import *from PyQt5.QtGui import *from PyQt5.QtWidgets import *import sysimport unicodedataclass CharacterWidget(QWidget):    characterSelected = pyqtSignal(str)    def __init__(self):        super(CharacterWidget, self).__init__()        self.displayFont = QFont()        self.squareSize = int(24)        self.columns = int(16)        self.lastKey = int(-1)        self.setMouseTracking(True)    def updateFont(self, font):        self.displayFont.setFamily(font.family())        self.squareSize = max(24, QFontMetrics(self.displayFont).xHeight() * 3)        self.adjustSize()        self.update()    def updateSize(self, fontSize):        self.displayFont.setPointSize(int(fontSize))        self.squareSize = max(24, QFontMetrics(self.displayFont).xHeight() * 3)        self.adjustSize()        self.update()    def updateStyle(self, fontStyle):        fontDatabase = QFontDatabase()        oldStrategy = self.displayFont.styleStrategy()        self.displayFont = fontDatabase.font(self.displayFont.family(), fontStyle, self.displayFont.pointSize())        self.displayFont.setStyleStrategy(oldStrategy)        self.squareSize = max(24, QFontMetrics(self.displayFont).xHeight() * 3)        self.adjustSize()        self.update()    def updateFontMerging(self, enable):        if enable:            self.displayFont.setStyleStrategy(QFont.PreferDefault)        else:            self.displayFont.setStyleStrategy(QFont.NoFontMerging)        self.adjustSize()        self.update()    def sizeHint(self):        return QSize(self.columns*self.squareSize, (65536/self.columns)*self.squareSize)    def paintEvent(self, QPaintEvent):        painter = QPainter()        painter.begin(self)        painter.fillRect(QPaintEvent.rect(), QBrush(Qt.white))        painter.setFont(self.displayFont)        redrawRect = QPaintEvent.rect()        beginRow = int(redrawRect.top()/self.squareSize)        endRow = int(redrawRect.bottom()/self.squareSize)        beginColumn = int(redrawRect.left()/self.squareSize)        endColumn = int(redrawRect.right()/self.squareSize)        painter.setPen(QPen(Qt.gray))        for i in range(beginRow, endRow+1):            for j in range(beginColumn, endColumn+1):                painter.drawRect(j*self.squareSize, i*self.squareSize, self.squareSize, self.squareSize)        fontMetrics = QFontMetrics(self.displayFont)        painter.setPen(QPen(Qt.black))        for r in range(beginRow, endRow+1):            for c in range(beginColumn, endColumn+1):                key = int(r * self.columns + c)                painter.setClipRect(c*self.squareSize, r*self.squareSize, self.squareSize, self.squareSize)                if key == self.lastKey:                    painter.fillRect(c*self.squareSize + 1, r*self.squareSize + 1,                                     self.squareSize, self.squareSize, QBrush(Qt.red))                painter.drawText(c*self.squareSize + (self.squareSize / 2) - fontMetrics.width(chr(key)) / 2,                                 r*self.squareSize + 4 + fontMetrics.ascent(),                                 chr(key))        painter.end()    def mousePressEvent(self, QMouseEvent):        if QMouseEvent.button() == Qt.LeftButton:            self.lastKey = int((QMouseEvent.y()/self.squareSize)*self.columns + QMouseEvent.x()/self.squareSize)            if unicodedata.category(chr(self.lastKey)) != 'Cn':                self.characterSelected.emit(chr(self.lastKey))            self.update()        else:            self.mousePressEvent(QMouseEvent)    def mouseMoveEvent(self, QMouseEvent):        widgetPosition = self.mapFromGlobal(QMouseEvent.globalPos())        key = int((widgetPosition.y()/self.squareSize)*self.columns + widgetPosition.x()/self.squareSize)        text = ("<p>Character: <span style=\"font-size: 24pt; font-family: %s\">" % self.displayFont.family()) \               + chr(key) \               + "</span><p>Value: 0x" \               + hex(key)        QToolTip.showText(QMouseEvent.globalPos(), text)class MainWindow(QMainWindow):    def __init__(self):        super(MainWindow, self).__init__()        self.centralWidget = QWidget()        self.fontLabel = QLabel('Font:')        self.fontCombo = QFontComboBox()        self.sizeLabel = QLabel('Size:')        self.sizeCombo = QComboBox()        self.styleLabel = QLabel('Style:')        self.styleCombo = QComboBox()        self.fontMergingLabel = QLabel('Automatic Font Merging:')        self.fontMerging = QCheckBox()        self.fontMerging.setChecked(True)        self.charaterWidget = CharacterWidget()        self.scrollArea = QScrollArea()        self.scrollArea.setWidget(self.charaterWidget)        self.findStyles(self.fontCombo.currentFont())        self.findSizes(self.fontCombo.currentFont())        self.lineEdit = QLineEdit()        self.clipboard = QApplication.clipboard()        self.clipboardButton = QPushButton('&To Clipboard')        self.fontCombo.currentFontChanged.connect(self.findStyles)        self.fontCombo.currentFontChanged.connect(self.findSizes)        self.fontCombo.currentFontChanged.connect(self.charaterWidget.updateFont)        self.sizeCombo.currentIndexChanged.connect(self.charaterWidget.updateSize)        self.styleCombo.currentIndexChanged.connect(self.charaterWidget.updateStyle)        self.charaterWidget.characterSelected.connect(self.insertCharacter)        self.fontMerging.toggled.connect(self.charaterWidget.updateFontMerging)        self.clipboardButton.clicked.connect(self.updateClipboard)        self.controlsLayout = QHBoxLayout()        self.controlsLayout.addWidget(self.fontLabel)        self.controlsLayout.addWidget(self.fontCombo, 1)        self.controlsLayout.addWidget(self.sizeLabel)        self.controlsLayout.addWidget(self.sizeCombo, 1)        self.controlsLayout.addWidget(self.styleLabel)        self.controlsLayout.addWidget(self.styleCombo, 1)        self.controlsLayout.addWidget(self.fontMergingLabel)        self.controlsLayout.addWidget(self.fontMerging, 1)        self.controlsLayout.addStretch(1)        self.lineLayout = QHBoxLayout()        self.lineLayout.addWidget(self.lineEdit, 1)        self.lineLayout.addSpacing(12)        self.lineLayout.addWidget(self.clipboardButton)        self.centralLayout = QVBoxLayout()        self.centralLayout.addLayout(self.controlsLayout)        self.centralLayout.addWidget(self.scrollArea, 1)        self.centralLayout.addSpacing(4)        self.centralLayout.addLayout(self.lineLayout)        self.centralWidget.setLayout(self.centralLayout)        self.setCentralWidget(self.centralWidget)        self.setWindowTitle('Character Map')    def findStyles(self, font):        fontDatabase = QFontDatabase()        currentItem = self.styleCombo.currentText()        self.styleCombo.clear()        for style in fontDatabase.styles(font.family()):            self.styleCombo.addItem(style)        styleIndex = self.styleCombo.findText(currentItem)        if styleIndex == -1:            self.styleCombo.setCurrentIndex(0)        else:            self.styleCombo.setCurrentIndex(styleIndex)    def findSizes(self, font):        fontDataBase = QFontDatabase()        currentSize = self.sizeCombo.currentText()        self.sizeCombo.blockSignals(True)        self.sizeCombo.clear()        if fontDataBase.isSmoothlyScalable(font.family(), fontDataBase.styleString(font)):            for size in fontDataBase.standardSizes():                self.sizeCombo.addItem(str(QVariant(size).value()))                self.sizeCombo.setEditable(True)        else:            for size in fontDataBase.smoothSizes(font.family(), fontDataBase.styleString(font)):                self.sizeCombo.addItem(str(QVariant(size)))                self.sizeCombo.setEditable(False)        self.sizeCombo.blockSignals(False)        sizeIndex = self.sizeCombo.findText(currentSize)        if sizeIndex == -1:            self.sizeCombo.setCurrentIndex(max(0, self.sizeCombo.count() / 3))        else:            self.sizeCombo.setCurrentIndex(sizeIndex)    def insertCharacter(self, character):        self.lineEdit.insert(character)    def updateClipboard(self):        self.clipboard.setText(self.lineEdit.text(), QClipboard.Clipboard)        self.clipboard.setText(self.lineEdit.text(), QClipboard.Selection)app = QApplication(sys.argv)char = MainWindow()char.show()app.exec_()

最终效果

这里写图片描述


问题说明

该案例存在Bug,经过分析后感觉问题出在mouseMoveEvent()和mousePressEvent()中的算法,目前正在修改,希望可以改进,同样希望广大网友给出建设性意见。

0 0
原创粉丝点击