I would like to implement a class to create a simple widget of fixed size with a scrollbar to display one or more (that's crucial to the problem) images at the same time. Here is the (yet complete but working) code:
from PyQt5 import QtCore, QtWidgets, QtGui
class ImageViewWidget(QtWidgets.QScrollArea):
def __init__(self, parent = None):
super(ImageViewWidget, self).__init__(parent)
self.w = QtWidgets.QFrame()
self.l = QtWidgets.QVBoxLayout()
self.w.setLayout(self.l)
self.setWidget(self.w)
def setImages(self, *images):
self.imageLabel = QtWidgets.QLabel()
self.imageLabel.setScaledContents(True)
self.l.addWidget(self.imageLabel)
if not images[0].isNull():
self.imageLabel.setPixmap(QtGui.QPixmap.fromImage(images[0]))
self.normalSize()
## event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_N, QtCore.Qt.NoModifier)
## QtWidgets.QApplication.sendEvent(self, event)
def normalSize(self):
self.w.adjustSize()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_N:
self.normalSize()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
imageViewer = ImageViewWidget()
imageViewer.resize(800, 600)
imageViewer.show()
image1 = QtGui.QImage('test.png')
imageViewer.setImages(image1)
sys.exit(app.exec_())
The problem is, that the image does not show up at startup resp does not have the desired size. One has to press "n" first, then the image is displayed with its natural size. And of course I would like to have its natural size from the beginning on without the need to press "n" first.
It seems strange to me that pressing "n" and calling self.normalSize() do not have the same effect, and even simulation the key event by the two commented outlines in setImages do not have the same effect as pressing "n" physically.
There are two "solutions":
Show the widget after setting image, that is, move the line imageViewer.show() 2 lines down.
Moving the first 3 lines of the method setImages to the __init__ method.
Both are no reasonable option, since I want to add and remove dynamically QLabels(which is not implemented yet) to display different images, and also the number of images (which are displayed at the same time) can change.
Any suggestions?
Hi I have modified your code.
Added this 2 lines.
self.timerSingleShot = QtCore.QTimer()
self.timerSingleShot.singleShot(1, self.normalSize)
Use with PyQt5 syntax. This syntax is for PyQt4
from PyQt5 import QtCore, QtWidgets, QtGui
class ImageViewWidget(QtWidgets.QScrollArea):
def __init__(self, parent = None):
super(ImageViewWidget, self).__init__(parent)
self.w = QtWidgets.QFrame()
self.l = QtWidgets.QVBoxLayout()
self.w.setLayout(self.l)
self.setWidget(self.w)
# Added this lines
self.timerSingleShot = QtCore.QTimer()
self.timerSingleShot.singleShot(1, self.normalSize)
def setImages(self, *images):
self.imageLabel = QtWidgets.QLabel()
self.imageLabel.setScaledContents(True)
self.l.addWidget(self.imageLabel)
if not images[0].isNull():
self.imageLabel.setPixmap(QtGui.QPixmap.fromImage(images[0]))
#self.normalSize()
## event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_N, QtCore.Qt.NoModifier)
## QtWidgets.QApplication.sendEvent(self, event)
def normalSize(self):
self.w.adjustSize()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_N:
self.normalSize()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
imageViewer = ImageViewWidget()
imageViewer.resize(800, 600)
imageViewer.show()
image1 = QtGui.QImage('test.png')
imageViewer.setImages(image1)
sys.exit(app.exec_())
This will also work. Shifted line :
imageViewer.show()
in your code.
from PyQt5 import QtCore, QtWidgets, QtGui
class ImageViewWidget(QtWidgets.QScrollArea):
def __init__(self, parent = None):
super(ImageViewWidget, self).__init__(parent)
self.w = QtWidgets.QFrame()
self.l = QtWidgets.QVBoxLayout()
self.w.setLayout(self.l)
self.setWidget(self.w)
def setImages(self, *images):
self.imageLabel = QtWidgets.QLabel()
self.imageLabel.setScaledContents(True)
self.l.addWidget(self.imageLabel)
if not images[0].isNull():
self.imageLabel.setPixmap(QtGui.QPixmap.fromImage(images[0]))
#self.normalSize()
## event = QtGui.QKeyEvent(QtCore.QEvent.KeyPress, QtCore.Qt.Key_N, QtCore.Qt.NoModifier)
## QtWidgets.QApplication.sendEvent(self, event)
def normalSize(self):
self.w.adjustSize()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_N:
self.normalSize()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
imageViewer = ImageViewWidget()
imageViewer.resize(800, 600)
image1 = QtGui.QImage('test.png')
imageViewer.setImages(image1)
imageViewer.show()
sys.exit(app.exec_())
Related
I'm trying to add custom animation to QPushbutton without making a custom QPushbutton and overriding its enterEvent() and leaveEvent().
So far I've tried this,
#staticmethod
def addButtonHoverAnimation(button:QPushButton,currentPos:QPoint):
'''
Method to:
=> Add hover animation for provided button
'''
enterShift = QPropertyAnimation(button,b'pos',button)
exitShift = QPropertyAnimation(button,b'pos',button)
def enterEvent(e):
pos=button.pos()
enterShift.setStartValue(pos)
enterShift.setEndValue(QPoint(pos.x()+3,pos.y()+3))
enterShift.setDuration(100)
enterShift.start()
Effects.dropShadow(button,1,2)
def leaveEvent(e):
pos=button.pos()
exitShift.setStartValue(pos)
exitShift.setEndValue(QPoint(pos.x()-3,pos.y()-3))
exitShift.setDuration(100)
exitShift.start()
Effects.dropShadow(button)
button.enterEvent=enterEvent
button.leaveEvent=leaveEvent
But when I move the mouse very quickly in and out of the button before the animation finishes, The button starts to move wierdly towards the North-West direction.
Button Animation Using Dynamic Positions
I figured out this was due to the leaveEvent() being triggered before enterEvent() even finishes and also because the start and end values are dynamic. So, I tried providing currentPos as a static position and using it instead,
#staticmethod
def addButtonHoverAnimation(button:QPushButton,currentPos:QPoint):
'''
Method to:
=> Add hover animation for provided button
'''
enterShift = QPropertyAnimation(button,b'pos',button)
enterShift.setStartValue(currentPos)
enterShift.setEndValue(QPoint(currentPos.x()+3,currentPos.y()+3))
enterShift.setDuration(100)
exitShift = QPropertyAnimation(button,b'pos',button)
exitShift.setStartValue(QPoint(currentPos.x()-3,currentPos.y()-3))
exitShift.setEndValue(currentPos)
exitShift.setDuration(100)
def enterEvent(e):
button.setProperty(b'pos',exitShift.endValue())
enterShift.start()
Effects.dropShadow(button,1,2)
def leaveEvent(e):
exitShift.start()
Effects.dropShadow(button)
button.enterEvent=enterEvent
button.leaveEvent=leaveEvent
On running, as soon as the mouse enters the QPushbutton, it moves to the top-left of its parent widget and the animation starts working fine. I can't figure out why this is happening. But I was able to get that, it only happened when I used any static value in the animation.
Button Animation with Static Position:
Here is an example:
import sys
from PyQt5.QtCore import QEvent, QPoint, QObject, QPropertyAnimation
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
# This is the same method mentioned above
from styling import addButtonHoverAnimation
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
layout=QVBoxLayout()
button1 = QPushButton("Proceed1", self)
layout.addWidget(button1)
button2 = QPushButton("Proceed2", self)
layout.addWidget(button2)
self.setLayout(layout)
self.resize(640, 480)
addButtonHoverAnimation(button1)
addButtonHoverAnimation(button2)
def main():
app = QApplication(sys.argv)
view = Widget()
view.show()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
The problem is that probably when the state is changed from enter to leave (or vice versa) the previous animation still does not end so the position of the widget is not the initial or final position, so when starting the new animation there is a deviation that accumulates. One possible solution is to initialize the position and keep it as a reference.
On the other hand you should not do x.fooMethod = foo_callable since many can fail, in this case it is better to use an eventfilter.
import sys
from dataclasses import dataclass
from functools import cached_property
from PyQt5.QtCore import QEvent, QPoint, QObject, QPropertyAnimation
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
#dataclass
class AnimationManager(QObject):
widget: QWidget
delta: QPoint = QPoint(3, 3)
duration: int = 100
def __post_init__(self):
super().__init__(self.widget)
self._start_value = QPoint()
self._end_value = QPoint()
self.widget.installEventFilter(self)
self.animation.setTargetObject(self.widget)
self.animation.setPropertyName(b"pos")
self.reset()
def reset(self):
self._start_value = self.widget.pos()
self._end_value = self._start_value + self.delta
self.animation.setDuration(self.duration)
#cached_property
def animation(self):
return QPropertyAnimation(self)
def eventFilter(self, obj, event):
if obj is self.widget:
if event.type() == QEvent.Enter:
self.start_enter_animation()
elif event.type() == QEvent.Leave:
self.start_leave_animation()
return super().eventFilter(obj, event)
def start_enter_animation(self):
self.animation.stop()
self.animation.setStartValue(self.widget.pos())
self.animation.setEndValue(self._end_value)
self.animation.start()
def start_leave_animation(self):
self.animation.stop()
self.animation.setStartValue(self.widget.pos())
self.animation.setEndValue(self._start_value)
self.animation.start()
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
button1 = QPushButton("Proceed1", self)
button1.move(100, 100)
button2 = QPushButton("Proceed2", self)
button2.move(200, 200)
self.resize(640, 480)
animation_manager1 = AnimationManager(widget=button1)
animation_manager2 = AnimationManager(widget=button2)
def main():
app = QApplication(sys.argv)
view = Widget()
view.show()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
184 / 5000
Resultados de traducción
If you are using a layout then you must reset the position since the layout does not apply the position change immediately but only when the parent widget applies the changes.
class Widget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
button1 = QPushButton("Proceed1")
button2 = QPushButton("Proceed2")
lay = QVBoxLayout(self)
lay.addWidget(button1)
lay.addWidget(button2)
self.resize(640, 480)
self.animation_manager1 = AnimationManager(widget=button1)
self.animation_manager2 = AnimationManager(widget=button2)
def resizeEvent(self, event):
super().resizeEvent(event)
self.animation_manager1.reset()
self.animation_manager2.reset()
I'm trying to make a button like this
Currently I can create the middle two buttons (single right or single left) using Qt::LeftArrow or Qt::RightArrow from setArrowType(). From the docs, there seems to only be 5 possible types. If this feature is not built in, how can I make a custom double arrow button?
Right now I have this
from PyQt4 import QtCore, QtGui
import sys
class PanningButton(QtGui.QToolButton):
"""Custom button with click, short/long press, and auto-repeat functionality"""
def __init__(self):
QtGui.QToolButton.__init__(self)
self.setArrowType(QtCore.Qt.LeftArrow)
# Enable auto repeat on button hold
self.setAutoRepeat(True)
# Initial delay in ms before auto-repetition kicks in
self.setAutoRepeatDelay(700)
# Length of auto-repetition
self.setAutoRepeatInterval(500)
self.clicked.connect(self.buttonClicked)
self._state = 0
def buttonClicked(self):
# Panning
if self.isDown():
if self._state == 0:
self._state = 1
self.setAutoRepeatInterval(50)
# Mouse release
elif self._state == 1:
self._state = 0
self.setAutoRepeatInterval(125)
def pressed():
global counter
counter += 1
print(counter)
if __name__ == '__main__':
app = QtGui.QApplication([])
counter = 0
panning_button = PanningButton()
panning_button.clicked.connect(pressed)
panning_button.show()
sys.exit(app.exec_())
There are several options:
Use the Qt icons, in this case you can use the standardIcon() of QStyle:
from PyQt4 import QtCore, QtGui
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
button1 = QtGui.QToolButton()
button1.setIcon(
button1.style().standardIcon(QtGui.QStyle.SP_MediaSeekBackward)
)
button2 = QtGui.QToolButton()
button2.setArrowType(QtCore.Qt.LeftArrow)
button3 = QtGui.QToolButton()
button3.setArrowType(QtCore.Qt.RightArrow)
button4 = QtGui.QToolButton()
button4.setIcon(
button1.style().standardIcon(QtGui.QStyle.SP_MediaSeekForward)
)
lay = QtGui.QHBoxLayout(self)
for btn in (button1, button2, button3, button4):
lay.addWidget(btn)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
Create the icons with QPainter
def create_icon():
pixmap = QtGui.QPixmap(QtCore.QSize(128, 128))
pixmap.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(pixmap)
# draw icon
painter.end()
return QtGui.QIcon(pixmap)
Use icons created by an image editor like photoshop, corel draw, gimp, etc.
IMHO the simplest solution is the last case since in the first case you are limited to what Qt provides, in the second case it can be unnecessarily complicated, however the third case is the optimal option.
I have the following window with frames.
I want frame to be highlighted (in my case change its shape) when mouse is in its area.
from PyQt4 import QtGui, QtCore
import sys
app = QtGui.QApplication(sys.argv)
window = QtGui.QWidget()
window_layout = QtGui.QVBoxLayout()
window.setLayout(window_layout)
#fill content
for i in range(10):
label = QtGui.QLabel(str(i))
frame = QtGui.QFrame()
frame_layout = QtGui.QVBoxLayout()
frame.setLayout(frame_layout)
frame_layout.addWidget(label)
window_layout.addWidget(frame)
def layout_widgets(layout):
return (layout.itemAt(i) for i in range(layout.count()))
def mouse_enter(event):
print 'frame enter'
w.widget().setFrameShape(3)
def mouse_leave(event):
print 'frame leave'
w.widget().setFrameShape(0)
for w in layout_widgets(window_layout):
print w.widget()
w.widget().enterEvent = mouse_enter
w.widget().leaveEvent = mouse_leave
window.show()
sys.exit(app.exec_())
It works but only the last frame in layout highlights.
How to make only that frame change its shape where the mouse is?
I've tried the following:
def mouse_enter(event, frame):
print 'frame enter'
frame.setFrameShape(3)
w.widget().enterEvent = functools.partial(mouse_enter, w.widget())
but it gives an error. I have found one more way to do that - signal mapper
but I have no idea how to use it.
The problem in your code the variable w when executing the for is left with the last element, so it will only be executed in the latter. To solve this I have implemented a Frame class that inherits from QFrame where I overwrite the enterEvent and leaveEvent functions.
from PyQt4 import QtGui, QtCore
import sys
class Frame(QtGui.QFrame):
def __init__(self, text, parent=None):
super(Frame, self).__init__(parent=parent)
label = QtGui.QLabel(text)
frame_layout = QtGui.QVBoxLayout()
frame_layout.addWidget(label)
self.setLayout(frame_layout)
def enterEvent(self, event):
self.setFrameShape(3)
def leaveEvent(self, event):
self.setFrameShape(0)
app = QtGui.QApplication(sys.argv)
window = QtGui.QWidget()
window_layout = QtGui.QVBoxLayout()
window.setLayout(window_layout)
for i in range(10):
frame = Frame(str(i))
window_layout.addWidget(frame)
window.show()
sys.exit(app.exec_())
I have a button and a text label. Each time the button is pressed, i would like text placed from a line edit to be placed onto the window. So far I can only get one text to draw onto the window, even if I create another textlabel. Ive tried seeting a click count determining how many times a user has clicked a button but this doesnt work either. Heres what I have so far, any suggestions?
import sys
import os
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyQt4.QtGui import QApplication
class Window(QMainWindow):
def __init__(self, *args):
QMainWindow.__init__(self, *args)
self.centralWidget = QWidget(self)
self.setCentralWidget(self.centralWidget)
self.setGeometry(450,100,350,680)
self.btn1 = QPushButton("Enter", self.centralWidget)
self.btn1.setGeometry(10,50,150, 20)
self.btn1.clicked.connect(self.enter)
self.edit = QtGui.QLineEdit(self)
self.edit.setGeometry(10, 10, 150, 20)
self.label = QtGui.QLabel(self)
self.label.setGeometry(240, 170,150, 20)
def enter(self):
self.label.setText(self.edit.text())
def main(args):
global app
app = App(args)
app.exec_()
class App(QApplication):
def __init__(self, *args):
QApplication.__init__(self, *args)
self.main = Window()
self.connect(self, SIGNAL("lastWindowClosed()"), self.byebye )
self.main.show()
def byebye( self ):
self.exit(0)
if __name__ == "__main__":
main(sys.argv)
There are a few problems with your example code, the main one being that you are trying to manually arrange the widgets, rather than using a layout.
It's hard to tell from your question exactly what you expect the output to be, so I've assumed you want the text from line-edit to be appended to the label, so that you end up with a series of lines.
Here's a simplified version of your example that hopefully does what you want:
from PyQt4 import QtCore, QtGui
class Window(QtGui.QMainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.btn1 = QtGui.QPushButton("Enter", self)
self.btn1.clicked.connect(self.enter)
self.edit = QtGui.QLineEdit(self)
self.label = QtGui.QLabel(self)
self.label.setAlignment(
QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft)
widget = QtGui.QWidget(self)
layout = QtGui.QVBoxLayout(widget)
layout.addWidget(self.edit)
layout.addWidget(self.btn1)
layout.addWidget(self.label)
self.setCentralWidget(widget)
def enter(self):
text = self.edit.text()
if text:
self.label.setText('%s\n%s' % (self.label.text(), text))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(450, 100, 350, 680)
window.show()
sys.exit(app.exec_())
I would like to place a phonon videowidget onto a qgraphicsscene so I can overlay graphics etc. When I run the following I get the sound, but no video playing on the qgraphicsview. Help would be appreciated as I thought I was doing as the examples show. However, I suspect its something in how I have understood videoplayer and videowidget.
For testing I am just playing a video straight from a file.
from PySide import QtGui, QtCore
from PySide.phonon import Phonon
from window import Ui_MainWindow # main GUI window
import os, sys
class DiagramScene(QtGui.QGraphicsScene):
InsertItem, InsertLine, InsertText, MoveItem = range(4)
def __init__(self):
super(DiagramScene, self).__init__()
self.myLineColor = QtCore.Qt.black
self.myMode = "Start"
self.line = None
def mousePressEvent(self, mouseEvent):
if (mouseEvent.button() == QtCore.Qt.LeftButton):
if self.myMode == "Start":
self.line = QtGui.QGraphicsLineItem(QtCore.QLineF(mouseEvent.scenePos(), mouseEvent.scenePos()))
self.addItem(self.line)
elif (mouseEvent.button() == QtCore.Qt.RightButton):
self.addText("Hello")
super(DiagramScene, self).mousePressEvent(mouseEvent)
def mouseMoveEvent(self, mouseEvent):
if self.line:
newLine = QtCore.QLineF(self.line.line().p1(), mouseEvent.scenePos())
self.line.setLine(newLine)
def mouseReleaseEvent(self, mouseEvent):
self.line = None
super(DiagramScene, self).mouseReleaseEvent(mouseEvent)
class QPlayer(QtGui.QWidget):
def __init__(self):
super(QPlayer, self).__init__()
media_src = Phonon.MediaSource("C:\Users\Public\Videos\Sample Videos\Wildlife.wmv")
self.audioOuptut=Phonon.AudioOutput(Phonon.MusicCategory, self)
self.player=Phonon.MediaObject(self)
self.player.setCurrentSource(media_src)
Phonon.createPath(self.player, self.audioOuptut)
self.videoWidget=Phonon.VideoWidget(self)
self.videoWidget.FitInView
Phonon.createPath(self.player, self.videoWidget)
self.player.play()
class Main(QtGui.QMainWindow):
def __init__(self):
super(Main, self).__init__()
self.ui=Ui_MainWindow()
self.ui.setupUi(self)
self.scene = DiagramScene()
self.scene.addWidget(QPlayer())
self.gview = self.ui.gView
self.gview.setScene(self.scene)
def main():
app = QtGui.QApplication(sys.argv)
window=Main()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Ok - think I've sorted it (to an extent). Simple case of:
self.videoWidget.setMinimumSize(640,480)
The video doesn't really run very well - breaks up a lot but at least I can draw on it :)