from PyQt5 import QtGui, QtCore, QtWidgets
class Window(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.label = QtWidgets.QLabel(self)
self.label.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)
self.label.resize(800, 600)
self.label.setContentsMargins(0, 0, 0, 0);
self.pixmap = QtGui.QPixmap("image.jpg")
self.label.setPixmap(self.pixmap)
self.label.setMinimumSize(1, 1)
self.label.installEventFilter(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.label)
def eventFilter(self, source, event):
if (source is self.label and event.type() == QtCore.QEvent.Resize):
self.label.setPixmap(self.pixmap.scaled(
self.label.size(), QtCore.Qt.KeepAspectRatio))
return super(Window, self).eventFilter(source, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
window.resize(800, 600)
sys.exit(app.exec_())
This is my application, my goal is simple - have an image that fills the whole window and resizes after the window resize.
This code works okay in resizing the image, but the label doesn't cover the whole window, I have those "borders". How can I remove them/resize the label to window size?
I am working on Windows if this changes things.
That is the effect I get now.
I solved it in PyQt4, so I'm not 100% sure if it will work for PyQt5, but I guess it should (some minor modifications might be needed, e.g. import PyQt5 instead of PyQt4).
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.label = QtGui.QLabel(self)
self.label.resize(800, 600)
pixmap1 = QtGui.QPixmap("image.png")
self.pixmap = pixmap1.scaled(self.width(), self.height())
self.label.setPixmap(self.pixmap)
self.label.setMinimumSize(1, 1)
def resizeEvent(self, event):
pixmap1 = QtGui.QPixmap("image.png")
self.pixmap = pixmap1.scaled(self.width(), self.height())
self.label.setPixmap(self.pixmap)
self.label.resize(self.width(), self.height())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
window.resize(800, 600)
sys.exit(app.exec_())
The most important for you is definition of resizeEvent. You could use already defined self.pixmap and just resize it, but the quality of the image would degrade the more resizing you use. Therefore, it's better always create new pixmap scaled to current width and height of the Window.
No need to create a QLabel inside the separate QWidget. You can simply inherit QLabel instead of QWidget. It will make your code more simple and cleaner:
class MyLabelPixmap(QtWidgets.QLabel):
def __init__(self):
QtWidgets.QLabel.__init__(self)
self.setSizePolicy(QtWidgets.QSizePolicy.Ignored, QtWidgets.QSizePolicy.Ignored)
self.resize(800, 600)
self.pixmap = QtGui.QPixmap("image.jpg")
self.setPixmap(self.pixmap)
self.installEventFilter(self)
def eventFilter(self, source, event):
if (source is self and event.type() == QtCore.QEvent.Resize):
self.setPixmap(self.pixmap.scaled(self.size()))
return super(Window, self).eventFilter(source, event)
In case you would like to embed your MyLabelPixmap widget into the QMainWindow just add in your QMainWindow.__init__
self.myLabelPixmap = MyLabelPixmap()
self.setCentralWidget(self.myLabelPixmap)
Related
I found some code on here that shows an example of how you can get the window to resize when the widget is hidden, and it works for me. Here is the code:
from PyQt4 import QtCore, QtGui
import sys
class MainWindow(QtGui.QWidget):
def __init__(self):
self.app = QtGui.QApplication(sys.argv)
super(MainWindow, self).__init__()
self.button = QtGui.QPushButton('Show/Hide')
self.button.setCheckable(True)
self.frame = QtGui.QFrame()
self.frame.setFixedHeight(100)
self.layout = layout = QtGui.QVBoxLayout()
layout2 = QtGui.QVBoxLayout()
self.setLayout(layout)
self.frame.setLayout(layout2)
layout.addWidget(self.button)
layout.addWidget(self.frame)
layout.addStretch(1)
layout2.addWidget(QtGui.QLabel('Yoyoyo'))
self.button.toggled.connect(self.clickAction)
def startup(self):
self.show()
sys.exit(self.app.exec_())
def clickAction(self):
checked = self.button.isChecked()
if checked:
self.frame.show()
else:
self.frame.hide()
QtCore.QTimer.singleShot(0, self.resizeMe)
def resizeMe(self):
self.resize(self.minimumSizeHint())
if __name__ == "__main__":
myApp = MainWindow()
myApp.startup()
I then tried to modify this to match my existing code by separating the mainWindow class and the widget class. Here is the code that does that.
from PySide import QtGui,QtCore
import sys
class MainWindow(QtGui.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.w = testW(self)
self.setCentralWidget(self.w)
self.show()
class testW(QtGui.QWidget):
def __init__(self,parent):
super(testW,self).__init__()
self.parent = parent
self.button = QtGui.QPushButton('Show/Hide')
self.button.setCheckable(True)
self.button.setChecked(True);
self.frame = QtGui.QFrame()
self.frame.setFixedHeight(100)
self.layout = layout = QtGui.QVBoxLayout()
layout2 = QtGui.QVBoxLayout()
self.setLayout(layout)
self.frame.setLayout(layout2)
layout.addWidget(self.button)
layout.addWidget(self.frame)
layout.addStretch(1)
layout2.addWidget(QtGui.QLabel('Yoyoyo'))
self.button.toggled.connect(self.clickAction)
def clickAction(self):
checked = self.button.isChecked()
if checked:
self.frame.show()
else:
self.frame.hide()
QtCore.QTimer.singleShot(0, self.resizeMe)
def resizeMe(self):
self.resize(self.minimumSizeHint())
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
myApp = MainWindow()
sys.exit(app.exec_())
#time.sleep(1)
Running the first code does what I want it to. After I hide the widget, the window resizes to the correct size. The second implementation of the code does not shrink and expand the window when I hide and show the widget. Is this because the MainWindow is in a separate class?
Use size policies for your widgets. For your example you can change UI creation code as follows:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.w = testW(self)
self.w.setSizePolicy(
QtWidgets.QSizePolicy.MinimumExpanding,
QtWidgets.QSizePolicy.MinimumExpanding
)
self.setCentralWidget(self.w)
self.show()
Please note new setSizePolicy call which say Qt layout engine how to change the size of your widget according to its content.
Unfortunately QMainWindow does not respect sizeHint automatically, but it is calculated properly, so you can adjustSize manually:
def clickAction(self):
checked = self.button.isChecked()
if checked:
self.frame.show()
else:
self.frame.hide()
QtCore.QTimer.singleShot(0, self.parent.adjustSize)
You do not need to resize your widget itself, because it will be resized according to the policy. Even sizeHint will be calculated automatically so you need only to call adjustSize of QMainWindow.
PS: I used PySide2 instead of PySide so the imports are different a little bit:
from PySide2 import QtWidgets, QtCore
I want to move the QLabel with the mouse movement (not like Drag&drop, 'object' disappears while moving). Clicked - moved - released. I did it to some extent, but I ran into a problem. QLabel shrinks as I move it or even disappears (like shrinks to 0 width). How to fix it or what more correct approach to do it?
(self.label_pos is needed to keep the mouse position relative inside self.label)
Or its just monitor's refresh rate issue? But in photoshop's gradient editor, that little color stop isn't shrikns. It's choppy because of refresh rate, but always the same size.
This is what I want to see, recorded using a screen capture program. The same thing I see in Photoshop
This is what I see, recorded on my phone. The quality is poor, but the difference is clearly visible anyway.
This Photoshop is also captured on my phone, here the “object” remains the same size, as in the example made using screen capture
Here is code from eyllanesc's answer, 'object' still shrinks :(
self.label = QLabel(self)
self.label.move(100, 100)
self.label.mousePressEvent = self.mouse_on
self.label.mouseReleaseEvent = self.mouse_off
def mouse_on(self, event):
self.bool = True
self.label_pos = event.pos()
def mouse_off(self, event):
self.bool = False
def mouseMoveEvent(self, event):
if self.bool:
self.label.move(event.x()-self.label_pos.x(), event.y()-self.label_pos.y())
Instead of using a QLabel I recommend using QGraphicsRectItem with a QGraphicsView since it is specialized in this type of tasks:
from PyQt5 import QtCore, QtGui, QtWidgets
class Widget(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
brush = QtWidgets.QApplication.palette().brush(QtGui.QPalette.Window)
self.setBackgroundBrush(brush)
rect_item = self.scene().addRect(
QtCore.QRectF(QtCore.QPointF(), QtCore.QSizeF(40, 80))
)
rect_item.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
rect_item.setBrush(QtGui.QBrush(QtGui.QColor("red")))
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.setFixedSize(640, 480)
w.show()
sys.exit(app.exec_())
If you want to just scroll horizontally then overwrite the itemChange method of QGraphicsItem:
from PyQt5 import QtCore, QtGui, QtWidgets
class HorizontalItem(QtWidgets.QGraphicsRectItem):
def __init__(self, rect, parent=None):
super(HorizontalItem, self).__init__(rect, parent)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
def itemChange(self, change, value):
if (
change == QtWidgets.QGraphicsItem.ItemPositionChange
and self.scene()
):
return QtCore.QPointF(value.x(), self.pos().y())
return super(HorizontalItem, self).itemChange(change, value)
class Widget(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
brush = QtWidgets.QApplication.palette().brush(QtGui.QPalette.Window)
self.setBackgroundBrush(brush)
rect_item = HorizontalItem(
QtCore.QRectF(QtCore.QPointF(), QtCore.QSizeF(40, 80))
)
rect_item.setBrush(QtGui.QBrush(QtGui.QColor("red")))
self.scene().addItem(rect_item)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.setFixedSize(640, 480)
w.show()
sys.exit(app.exec_())
In the following code there is an example similar to what you want:
from PyQt5 import QtCore, QtGui, QtWidgets
class HorizontalItem(QtWidgets.QGraphicsRectItem):
def __init__(self, rect, parent=None):
super(HorizontalItem, self).__init__(rect, parent)
self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
def itemChange(self, change, value):
if (
change == QtWidgets.QGraphicsItem.ItemPositionChange
and self.scene()
):
return QtCore.QPointF(value.x(), self.pos().y())
return super(HorizontalItem, self).itemChange(change, value)
class Widget(QtWidgets.QGraphicsView):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.setScene(QtWidgets.QGraphicsScene(self))
self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
brush = QtWidgets.QApplication.palette().brush(QtGui.QPalette.Window)
self.setBackgroundBrush(brush)
self.setFixedSize(640, 480)
size = self.mapToScene(self.viewport().rect()).boundingRect().size()
r = QtCore.QRectF(QtCore.QPointF(), size)
self.setSceneRect(r)
rect = QtCore.QRectF(
QtCore.QPointF(), QtCore.QSizeF(0.8 * r.width(), 80)
)
rect.moveCenter(r.center())
rect_item = self.scene().addRect(rect)
rect_item.setBrush(QtGui.QBrush(QtGui.QColor("salmon")))
item = HorizontalItem(
QtCore.QRectF(
rect.bottomLeft() + QtCore.QPointF(0, 20), QtCore.QSizeF(20, 40)
)
)
item.setBrush(QtGui.QBrush(QtGui.QColor("red")))
self.scene().addItem(item)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Baseline
How I want to create
Hi
I have a simple PyQT5 app. The main window is a QMainWindow which houses a QWidget. The Layout of the QWidget is as follows:
Class Canvas(QWidget):
def __init__(self):
super().__init__()
self.ListOfPlots = []
self.outFile = "temp.prb"
self.initUI()
def initUI(self):
self.headLabel = QLabel("List of Plots:")
self.label = QLabel("",self)
self.setAcceptDrops(True)
self.createPushButtons()
hbox = QHBoxLayout() #Horizontal Layout
#hbox.addStretch(1)
hbox.addWidget(self.combineButton)
hbox.addWidget(self.openButton)
hbox.addWidget(self.resetButton)
self.vbox = QVBoxLayout()
self.vbox.addWidget(self.headLabel)
self.vbox.addWidget(self.label)
self.vbox.addLayout(hbox) ## The horizontal box is placed into vertical layout
self.setLayout(self.vbox)
I want to create a translucent drop area as shown in the second picture with a label indicating drop files here. What would be the most suitable way to do it?
The entire widget is ok to allow drops. I just want a box indicating it is ok to drop here (like an indicator).
You can use dynamic properties to trigger an indicator when it's okay to drop. If you need the background to be semi-transparent, use rgba for your widget's stylesheet background property. background:rgba(255,255,255,90)
from PySide2 import QtWidgets
import sys
from PySide2.QtWidgets import QWidget, QGridLayout, QFrame
class DropZone(QFrame):
def __init__(self, parent=None):
QFrame.__init__(self)
self.setFixedSize(200, 200)
self.setAcceptDrops(True)
self.setObjectName('DropZone')
self.setStyleSheet(
'QFrame#DropZone[Dropindicator=true]{border:3px solid green;background:darkorange;}\nQFrame#DropZone{background:orange;}')
def dragEnterEvent(self, event):
if event.mimeData().hasFormat('text/plain'):
self.setProperty('Dropindicator',True)
print(event.mimeData().text())
self.setStyle(self.style())
...
event.accept()
else:
event.ignore()
def dropEvent(self, event):
event.accept()
if event.isAccepted():
self.setProperty('Dropindicator',False)
self.setStyle(self.style())
class Widget( QWidget):
def __init__(self,parent=None):
QWidget.__init__(self)
gl = QGridLayout()
self.setLayout(gl)
self.dz = DropZone()
self.dz.setParent(self)
gl.addWidget(self.dz)
self.setLayout(gl)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
I'm trying to detect mouse clicks for anywhere inside an area with several widgets. For this I'm using the following code:
custom_widget = CustomWidget()
custom_widget.mouse_pressed_signal.connect(self.on_custom_label_mouse_pressed)
main_layout_vbox.addWidget(custom_widget)
hbox = QtWidgets.QHBoxLayout()
custom_widget.setLayout(hbox)
# Adding several widgets to hbox_l6
class CustomWidget(QtWidgets.QWidget):
mouse_pressed_signal = QtCore.pyqtSignal(QtGui.QMouseEvent)
def __init__(self):
super().__init__()
def mousePressEvent(self, i_qmouseevent):
super(CustomWidget, self).mousePressEvent(i_qmouseevent)
logging.debug("======== CustomWidget - mousePressEvent ========")
self.mouse_pressed_signal.emit(i_qmouseevent)
Problem
This works when clicking in any of the child widgets, but there's a problem: If I click between widgets (so in the area of the hbox layout that is not covered by a widget) the mousePressEvent is not captured
Question
How can I solve this problem? (Or is there another approach that you can recommend?) The important thing is that I am able to capture mouse clicks anywhere inside of custom_widget / hbox (see code above)
If you want to listen to other widget's mousePressEvent you can use an eventFilter as I show below:
from PyQt5 import QtCore, QtGui, QtWidgets
import random
class Widget(QtWidgets.QWidget):
mouse_clicked_signal = QtCore.pyqtSignal(QtGui.QMouseEvent, QtWidgets.QWidget)
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
hlay = QtWidgets.QHBoxLayout(self)
for cls in (QtWidgets.QLabel, QtWidgets.QPushButton, QtWidgets.QFrame, QtWidgets.QWidget):
widget = cls()
color = QtGui.QColor(*random.sample(range(255), 3))
widget.setStyleSheet("background-color: {}".format(color.name()))
hlay.addWidget(widget)
for w in self.findChildren(QtWidgets.QWidget) + [self]:
w.installEventFilter(self)
self.resize(640, 480)
def eventFilter(self, watched, event):
if event.type() == QtCore.QEvent.MouseButtonPress:
self.mouse_clicked_signal.emit(event, watched)
return super(Widget, self).eventFilter(watched, event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.mouse_clicked_signal.connect(print)
w.show()
sys.exit(app.exec_())
Im trying to do simple audio player, but I want use a image(icon) as a pushbutton.
You can subclass QAbstractButton and make a button of your own. Here is a basic simple example:
import sys
from PyQt4.QtGui import *
class PicButton(QAbstractButton):
def __init__(self, pixmap, parent=None):
super(PicButton, self).__init__(parent)
self.pixmap = pixmap
def paintEvent(self, event):
painter = QPainter(self)
painter.drawPixmap(event.rect(), self.pixmap)
def sizeHint(self):
return self.pixmap.size()
app = QApplication(sys.argv)
window = QWidget()
layout = QHBoxLayout(window)
button = PicButton(QPixmap("image.png"))
layout.addWidget(button)
window.show()
sys.exit(app.exec_())
That's not a super easy way, but it gives you a lot of control. You can add second pixmap and draw it only when the mouse pointer is hover over button. You can change current stretching behavior to the centering one. You can make it to have not a rectangular shape and so on...
Button that changes images on mouse hover and when pressed:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class PicButton(QAbstractButton):
def __init__(self, pixmap, pixmap_hover, pixmap_pressed, parent=None):
super(PicButton, self).__init__(parent)
self.pixmap = pixmap
self.pixmap_hover = pixmap_hover
self.pixmap_pressed = pixmap_pressed
self.pressed.connect(self.update)
self.released.connect(self.update)
def paintEvent(self, event):
pix = self.pixmap_hover if self.underMouse() else self.pixmap
if self.isDown():
pix = self.pixmap_pressed
painter = QPainter(self)
painter.drawPixmap(event.rect(), pix)
def enterEvent(self, event):
self.update()
def leaveEvent(self, event):
self.update()
def sizeHint(self):
return QSize(200, 200)
You can use QToolButton with set autoraise property true and there you can set your image also.
I've seen that a lot of people have this problem and decided to write a proper example on how to fix it. You can find it here: An example on how to make QLabel clickable
The solution in my post solves the problem by extending QLabel so that it emits the clicked() signal.
The extended QLabel looks something like this:
class ExtendedQLabel(QLabel):
def __init__(self, parent):
QLabel.__init__(self, parent)
def mouseReleaseEvent(self, ev):
self.emit(SIGNAL('clicked()'))
I hope this helps!
Something like this, maybe?
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
app = QApplication(sys.argv)
widget = QWidget()
layout = QHBoxLayout()
widget.setLayout(layout)
button = QPushButton()
layout.addWidget(button)
icon = QIcon("image.png")
button.setIcon(icon)
widget.show()
app.exec_()
Another option is to use stylesheets. Something like:
from PyQt4 import QtCore, QtGui
import os
...
path = os.getcwd()
self.myButton.setStyleSheet("background-image: url(" + path + "/myImage.png);")
If you want use an image instead of a QPushButton in menubar or in toolbar, you can do it via QAction. Working example:
from PyQt5 import QtWidgets, QtGui, QtCore
import sys
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("Clickable Image in Menubar and Toolbar")
action = QtWidgets.QAction(
QtGui.QIcon("image.png"), "&Clickable Image", self)
action.triggered.connect(self.test)
menubar = self.menuBar()
menubar.addAction(action)
toolbar = QtWidgets.QToolBar("Toolbar")
toolbar.setIconSize(QtCore.QSize(20, 20))
toolbar.addAction(action)
self.addToolBar(toolbar)
def test(self):
print("Image clicked.")
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())