I'm trying to create a character map visualization tool with PyQt5. With fontTools library, I'm extracting the UNICODE code points supported in a given ttf file. Then using QPainter.drawText
I'm drawing the glyphs on labels. The labels are stored in a QGridLayout
and the layout is in a QScrollArea
Everything works fine, except when I try to scroll. The drawn images are overlapped whenever I try to scroll too fast. It looks like this.
The labels are redrawn properly the moment the window loses focus.
Here's an MWE of what I've so far.
import sys
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QFontDatabase, QFont, QColor, QPainter
from fontTools.ttLib import TTFont
class App(QWidget):
def __init__(self):
super().__init__()
self.fileName = "mukti.ttf" #the ttf file is located in the same path as the script
self.initUI()
def initUI(self):
self.setWindowTitle("Glyph Viewer")
self.setFixedSize(640, 480)
self.move(100, 100)
vBox = QtWidgets.QVBoxLayout()
self.glyphView = GlyphView()
vBox.addWidget(self.glyphView)
self.setLayout(vBox)
self.showGlyphs()
self.show()
def showGlyphs(self):
#Using the fontTools libray, the unicode blocks are obtained from the ttf file
font = TTFont(self.fileName)
charMaps = list()
for cmap in font['cmap'].tables:
charMaps.append(cmap.cmap)
charMap = charMaps[0]
fontDB = QFontDatabase()
fontID = fontDB.addApplicationFont("mukti.ttf")
fonts = fontDB.applicationFontFamilies(fontID)
qFont = QFont(fonts[0])
qFont.setPointSize(28)
self.glyphView.populateGrid(charMap, qFont)
class GlyphView(QtWidgets.QScrollArea):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.setWidgetResizable(True)
def populateGrid(self, charMap, qFont):
glyphArea = QtWidgets.QWidget(self)
gridLayout = QtWidgets.QGridLayout()
glyphArea.setLayout(gridLayout)
row, col = 1, 1
for char in charMap:
uni = charMap[char]
gridLayout.addWidget(Glyph(qFont, chr(char)), row, col)
if not col % 4:
col = 1
row += 1
else:
col += 1
self.setWidget(glyphArea)
class Glyph(QtWidgets.QLabel):
def __init__(self, font, char):
super().__init__()
self.font = font
self.char = char
self.initUI()
def initUI(self):
self.setFixedSize(48, 48)
self.setToolTip(self.char)
def paintEvent(self, event):
qp = QPainter(self)
qp.setBrush(QColor(0,0,0))
qp.drawRect(0, 0, 48, 48)
qp.setFont(self.font)
qp.setPen(QColor(255, 255, 255))
qp.drawText(event.rect(), Qt.AlignCenter, self.char)
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
I'm not sure what is causing this. Any help is appreciated!
The paintEvent()
method gives us an object that belongs to QPaintEvent
, that object provides a QRect
through the rect()
method, that QRect
is the area that is currently visible, and that information could be used to optimize the painting, for example let's say we have a widget that shows texts in several lines, if the text is large few lines will look so painting all is a waste of resources, so with a proper calculation using the aforementioned QRect
we could get those lines and paint that part spending few resources. In this answer I show an example of the use of event.rect().
In your case there is no need to use event.rect()
since you would be painting the text in a part of the widget widget. what you should use is self.rect()
:
def paintEvent(self, event):
qp = QPainter(self)
qp.setBrush(QColor(0,0,0))
qp.drawRect(0, 0, 48, 48)
qp.setFont(self.font())
qp.setPen(QColor(255, 255, 255))
# change event.rect() to self.rect()
qp.drawText(self.rect(), Qt.AlignCenter, self.text())
I also see unnecessary to overwrite paintEvent()
method since you can point directly to the QLabel
the font, the text and the alignment:
class Glyph(QtWidgets.QLabel):
def __init__(self, font, char):
super().__init__(font=font, text=char, alignment=Qt.AlignCenter, toolTip=char)