I am following an example from the PyQt5 book by Martin Fitzpatrick. When I run the following code, the background is black and the line is not drawn:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets, uic
from PyQt5.QtCore import Qt
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.label = QtWidgets.QLabel()
canvas = QtGui.QPixmap(400, 300)
self.label.setPixmap(canvas)
self.setCentralWidget(self.label)
self.draw_something()
def draw_something(self):
painter = QtGui.QPainter(self.label.pixmap())
painter.drawLine(10, 10, 300, 200)
painter.end()
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec_()
The expected result is on the left:
By default the memory that a QPixmap uses is not cleaned by efficiency so it has the bytes not altered as indicated by the docs:
QPixmap::QPixmap(int width, int height) Constructs a pixmap with the
given width and height. If either width or height is zero, a null
pixmap is constructed.
Warning: This will create a QPixmap with uninitialized data. Call
fill() to fill the pixmap with an appropriate color before drawing
onto it with QPainter.
(emphasis mine)
The solution is to use fill to set the background color:
canvas = QtGui.QPixmap(400, 300)
canvas.fill(QtGui.QColor("white"))
Related
I'm trying to make an application in python, with the visible window a pyglet one. The problem is, I need graphics capability AND interactions with HTML pages at the same time. I'm going to use a PyQt6 to communicate with the HTML. So the question is, how do I get a PyQT6 window to render INSIDE a Pyglet window?
My current code:
import pyglet
from pyglet.gl import *
import sys
from PyQt6.QtWidgets import QApplication, QWidget
app = QApplication(sys.argv)
w = QWidget()
w.resize(250, 200)
w.move(300, 300)
w.setWindowTitle('Simple')
w.show()
window = pyglet.window.Window(800, 600, "Radium")
#window.event
def on_draw():
window.clear()
# Render window with OpenGL
# ...
pyglet.app.run()
sys.exit(app.exec())
I am not sure if you can render PyQT inside a Pyglet Window. It has it's own rendering and drawing system that can't be accessed at a low level required to integrate it into a pyglet Window.
You can however do the reverse, OpenGL (and by extension, use pyglet) in QT. However this also means you need to be experienced in both to really make it work well.
I have made a runnable example using PyQt6:
import sys
import pyglet
#from PyQt5 import QtGui
#from PyQt5 import QtCore, QtWidgets
#from PyQt5.QtOpenGL import QGLWidget as OpenGLWidget
from PyQt6 import QtGui
from PyQt6 import QtCore, QtWidgets
from PyQt6.QtOpenGLWidgets import QOpenGLWidget as OpenGLWidget
from pyglet.gl import glClear, GL_COLOR_BUFFER_BIT, GL_DEPTH_BUFFER_BIT
import random
"""An example showing how to use pyglet in QT, utilizing the OGLWidget.
Since this relies on the QT Window, any events called on Pyglet Window
will NOT be called.
This includes mouse, keyboard, tablet, and anything else relating to the Window
itself. These must be handled by QT itself.
This just allows user to create and use pyglet related things such as sprites, shapes,
batches, clock scheduling, sound, etc.
"""
class MainWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__()
self.setWindowTitle("Pyglet and QT Example")
self.shapes = []
width, height = 640, 480
self.opengl = PygletWidget(width, height)
self.sprite_button = QtWidgets.QPushButton('Create Rectangle', self)
self.sprite_button.clicked.connect(self.create_sprite_click)
self.clear_sprite_button = QtWidgets.QPushButton('Clear Shapes', self)
self.clear_sprite_button.clicked.connect(self.clear_sprite_click)
mainLayout = QtWidgets.QVBoxLayout()
mainLayout.addWidget(self.opengl)
mainLayout.addWidget(self.sprite_button)
mainLayout.addWidget(self.clear_sprite_button)
self.setLayout(mainLayout)
def create_sprite_click(self):
gl_width, gl_height = self.opengl.size().width(), self.opengl.size().height()
width = random.randint(50, 100)
height = random.randint(50, 100)
x = random.randint(0, gl_width-width)
y = random.randint(0, gl_height-height)
color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))
shape = pyglet.shapes.Rectangle(x, y, width, height, color=color, batch=self.opengl.batch)
shape.opacity = random.randint(100, 255)
self.shapes.append(shape)
def clear_sprite_click(self):
for shape in self.shapes:
shape.delete()
self.shapes.clear()
class PygletWidget(OpenGLWidget):
def __init__(self, width, height, parent=None):
super().__init__(parent)
self.setMinimumSize(width, height)
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self._pyglet_update)
self.timer.setInterval(0)
self.timer.start()
def _pyglet_update(self):
# Tick the pyglet clock, so scheduled events can work.
pyglet.clock.tick()
# Force widget to update, otherwise paintGL will not be called.
self.update() # self.updateGL() for pyqt5
def paintGL(self):
"""Pyglet equivalent of on_draw event for window"""
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
self.batch.draw()
def initializeGL(self):
"""Call anything that needs a context to be created."""
self.batch = pyglet.graphics.Batch()
size = self.size()
w, h = size.width(), size.height()
self.projection = pyglet.window.Projection2D()
self.projection.set(w, h, w, h)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
ui = MainWidget(window)
ui.show() # Calls initializeGL. Do not do any GL stuff before this is called.
app.exec() # exec_ in 5.
When I'm trying to make my app, I stumbled upon this unexpected behavior where when I re-display a new QPixmap in a QLabel. I tried to simplify the code and ended up with the code below. I also attached the video of the behavior.
I provided here a replicable example (It just needs some .jpg file in the same directory):
import sys
import os
import random
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel, QSizePolicy
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
class AppDemo(QWidget):
def __init__(self):
super().__init__()
self.setGeometry(200, 200, 400, 400)
current_working_dir = os.path.abspath('')
dir_files = os.listdir(current_working_dir)
# Saving .jpg from the dir
self.picture = []
for file in dir_files:
if file.endswith(".jpg"):
self.picture.append(file)
self.label = QLabel()
self.label.setStyleSheet("border: 1px solid black;") # <- for the debugging
self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding)
self.label.setPixmap(self.random_picture_selector())
button = QPushButton("Reload Picture")
button.clicked.connect(self.reload_picture)
layout = QVBoxLayout(self)
layout.addWidget(button)
layout.addWidget(self.label)
def reload_picture(self):
self.label.setPixmap(self.random_picture_selector())
def random_picture_selector(self):
rnd_picture = random.choice(self.picture)
pixmap = QPixmap(rnd_picture)
pixmap = pixmap.scaledToWidth(self.label.width(), Qt.SmoothTransformation)
# pixmap = pixmap.scaled(self.label.width(), self.label.height(), Qt.KeepAspectRatio) # <- even this is not working
return pixmap
if __name__ == '__main__':
app = QApplication(sys.argv)
demo = AppDemo()
demo.show()
sys.exit(app.exec_())
Additional Infos:
When simplifying the code I realized that the problem disappears when I removed these following lines. (although I'm not very sure that these part of the code really causes the problem)
pixmap = pixmap.scaledToWidth(self.label.width(), Qt.SmoothTransformation)
# pixmap = pixmap.scaled(self.label.width(), self.label.height(), Qt.KeepAspectRatio) # <- even this is not working
I really have no idea what causes the problem even after looking for the Docs of QPixmap and QLabel.
The problem is caused by the stylesheet border. If you just print the pixmap and label size after setting the pixmap, you'll see that the label width is increased by 2 pixels, which is the sum of the left and right border.
You either remove the border, or you use the contentsRect():
width = self.label.contentsRect().width()
pixmap = pixmap.scaledToWidth(width, Qt.SmoothTransformation)
Read more about the Box Model in the Qt style sheet documentation.
When I put buttons inside a scene and inside a QGraphicsView, the region to the right of the button incorrectly turn gray.
I can reproduce this in Windows and Linux. Are there any tricks to get rid of this unwanted feature?
from PyQt5.QtWidgets import (QApplication, QGraphicsView, QGraphicsScene,
QPushButton, QLabel)
from PyQt5.QtCore import (Qt, QRectF)
from PyQt5 import QtCore
class MyView(QGraphicsView):
def __init__(self, parent = None):
super(MyView, self).__init__(parent)
self.button1 = QPushButton('Button1')
self.button1.setGeometry(-60, -60, 80, 40)
self.button2 = QPushButton('Button2')
self.button2.setGeometry(10, 10, 80, 40)
version = 'PYQT_VERSION_STR: ' + QtCore.PYQT_VERSION_STR + '\n'
version += 'QT_VERSION_STR: ' + QtCore.QT_VERSION_STR + '\n'
self.label = QLabel(version)
self.label.setGeometry(-100, 80, 160, 80)
self.setScene(QGraphicsScene())
self.scene().addWidget(self.button1)
self.scene().addWidget(self.button2)
self.scene().addWidget(self.label)
self.scene().setSceneRect(QRectF(-150, -150, 300, 300))
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
widget = MyView()
widget.show()
sys.exit(app.exec_())
When a widget is added to a graphics scene, its proxy uses the minimumSizeHint() of the widget as the minimum size for its geometry, no matter if you resize the widget to a smaller size (I don't know if it's a bug or it's done by design).
This has the following result:
you cannot set a geometry for the proxy smaller than the minimum size [hint] of the source widget;
resizing the widget to a size smaller than the minimum [hint] will only resize the widget, but the proxy will still use that minimum size [hint];
For example, QPushButton has a minimum size hint that is about 80x30 (the actual values depend on the style and font in use), so even if you resize the button to a smaller size, its proxy will still be 80x30.
To avoid that, you can manually set the minimum size of the widget to reasonable values, or subclass the widget and override minimumSizeHint().
I would like to ask how do I go about including a folium map into PyQt 5 window application such that the map does not take up the whole window. I have found a similar post on StackOverflow "How to show Folium map inside a PyQt5 GUI?", however, the solution code provided shown the folium map takes up the whole of the PyQt 5 window application.
So my question is how do I include the folium map but only takes up a portion of the PyQt 5 window application? As shown below, I am trying to include the map into the rectangle area. *The rectangle black box is drawn on paint for reference purposes.
FYI I have tried out the solution code from the StackOverflow post but I can't seem to be able to resize the map.
WANTED OUTPUT
CURRENT CODE FOR REFERENCE
from PyQt5.QtWidgets import QMainWindow, QApplication, QPushButton
from PyQt5 import QtWebEngineWidgets
import sys
from PyQt5 import QtGui
from PyQt5.QtCore import QRect
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.title = "MAP PROJECT"
self.left = 200
self.top = 100
self.width = 1500
self.height = 800
self.initWindow()
def initWindow(self):
# set window title
self.setWindowTitle(self.title)
# set window geometry
# self.setGeometry(self.left, self.top, self.width, self.height)
# Disable PyQt 5 application from resizing
self.setFixedSize(self.width, self.height)
self.buttonUI()
self.show()
def buttonUI(self):
shortPathButton = QPushButton("Find shortest path", self)
# (set button location (x, x) set button size (y, y)
shortPathButton.setGeometry(QRect(30, 300, 120, 50))
button2 = QPushButton("Another path", self)
# (set button location (x, x) set button size (y, y)
button2.setGeometry(QRect(30, 370, 120, 50))
button3 = QPushButton("Another path", self)
# (set button location (x, x) set button size (y, y)
button3.setGeometry(QRect(30, 440, 120, 50))
# Below code is to connect the button to the function
# button.clicked.connect(self.ClickMe)
# Create function for shortest path (A* algorithm)
"""def ClickMe(self):
print("Hello World")"""
if __name__ == "__main__":
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
The problem has nothing to do with a QWebEngineView or folium but how to place widgets inside the window, if so, then a solution is to use layouts in this case I will use the following structure: First a central widget is established, inside this one QHBoxLayout , and in the QHBoxLayout a QWidget is added as a container to the left side where a QVBoxLayout will be placed where the buttons will be, and to the right side the QWebEngineView:
import io
import sys
import folium
from PyQt5 import QtCore, QtGui, QtWidgets, QtWebEngineWidgets
class Window(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.initWindow()
def initWindow(self):
self.setWindowTitle(self.tr("MAP PROJECT"))
self.setFixedSize(1500, 800)
self.buttonUI()
def buttonUI(self):
shortPathButton = QtWidgets.QPushButton(self.tr("Find shortest path"))
button2 = QtWidgets.QPushButton(self.tr("Another path"))
button3 = QtWidgets.QPushButton(self.tr("Another path"))
shortPathButton.setFixedSize(120, 50)
button2.setFixedSize(120, 50)
button3.setFixedSize(120, 50)
self.view = QtWebEngineWidgets.QWebEngineView()
self.view.setContentsMargins(50, 50, 50, 50)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QHBoxLayout(central_widget)
button_container = QtWidgets.QWidget()
vlay = QtWidgets.QVBoxLayout(button_container)
vlay.setSpacing(20)
vlay.addStretch()
vlay.addWidget(shortPathButton)
vlay.addWidget(button2)
vlay.addWidget(button3)
vlay.addStretch()
lay.addWidget(button_container)
lay.addWidget(self.view, stretch=1)
m = folium.Map(
location=[45.5236, -122.6750], tiles="Stamen Toner", zoom_start=13
)
data = io.BytesIO()
m.save(data, close_file=False)
self.view.setHtml(data.getvalue().decode())
if __name__ == "__main__":
App = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(App.exec())
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)