I have a small Python script that makes a transparent window for displaying a graphic on screen and I'd like to animate that graphic, but am entirely unsure how or where to even start. Here's what I do have at least:
import sys
from PyQt4 import QtGui, Qt, QtCore
class Transparent(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setAttribute(Qt.Qt.WA_NoSystemBackground)
self.setAutoFillBackground(True)
pixmap = QtGui.QPixmap("test1.gif")
pixmap2 = QtGui.QPixmap("test2.gif")
width = pixmap.width()
height = pixmap.height()
self.setWindowTitle("Status")
self.resize(width, height)
self.label = QtGui.QLabel(self)
def animateEvent():
imgnumber = 0
try:
if imgnumber == 1:
self.label.setPixmap(QtGui.QPixmap("test1.gif"))
self.setMask(pixmap.mask())
imgnumber = 0
else:
self.label.setPixmap(QtGui.QPixmap("test2.gif"))
self.setMask(pixmap2.mask())
imgnumber = 1
finally:
QtCore.QTimer.singleShot(1000, animateEvent)
animateEvent()
def paintEvent(self,event):
self.setAttribute(Qt.Qt.WA_NoSystemBackground)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
x = Transparent()
x.show()
app.exec_()
This feels like it has the right ingredients, but the pixmap doesn't update.
I tried QMovie, but then the area of the window that is supposed to be transparent is filled with black instead.
check out this code from www.daniweb.com and see if you can modify it to your needs:
import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MoviePlayer(QWidget):
def __init__(self, gif, parent=None):
super(MoviePlayer, self).__init__(parent)
self.setGeometry(200, 200, 400, 400)
self.setWindowTitle("QMovie to show animated gif")
self.movie_screen = QLabel()
self.movie_screen.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
self.movie_screen.setAlignment(Qt.AlignCenter)
btn_start = QPushButton("Start Animation")
btn_start.clicked.connect(self.start)
btn_stop = QPushButton("Stop Animation")
btn_stop.clicked.connect(self.stop)
main_layout = QVBoxLayout()
main_layout.addWidget(self.movie_screen)
main_layout.addWidget(btn_start)
main_layout.addWidget(btn_stop)
self.setLayout(main_layout)
self.movie = QMovie(gif, QByteArray(), self)
self.movie.setCacheMode(QMovie.CacheAll)
self.movie.setSpeed(100)
self.movie_screen.setMovie(self.movie)
def start(self):
"""
Start animation
"""
self.movie.start()
def stop(self):
"""
Stop the animation
"""
self.movie.stop()
app = QApplication(sys.argv)
player = MoviePlayer("/path/to/image.gif")
player.show()
sys.exit(app.exec_())
This ended up being a simple correction of an oversight in the end.
imgnumber needed to be outside of the def as self.imgnumber and needed to be named self.imgnumber each time it was changed.
First, just make sure your animated gif really does have a proper transparent background. The following code works for me, using this fire image as a source:
class Transparent(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
filename = "test.gif"
size = QtGui.QImage(filename).size()
self.setWindowTitle("Status")
layout = QtGui.QVBoxLayout(self)
layout.setMargin(0)
self.movie = QtGui.QMovie(filename)
self.label = QtGui.QLabel(self)
self.label.setMovie(self.movie)
layout.addWidget(self.label)
self.resize(size)
self.movie.start()
This will create a completely transparent and frameless window, with the animated gif playing in a QMovie. There is no black being drawn behind the image. It should fully see through to what ever is underneath.
It is not so far off from your original code. You shouldn't need to set the mask, or do a paint event.
Related
I have just been experimenting with some animation in PyQt5 and am looking to animate the opacity of a window. I have had success with animating the opacity of buttons and QWidgets withink the window, however when I try to apply the same concept to the main QWidget class it doesn't seem to work. Below is my code for trying to get the animation to work on the window (I am aware this isn't the best looking code but I'm just trying to experiment, but also feel free to tell me about any big errors unrelated to the problem as well as I am also quite new to PyQt5):
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
class Test(QWidget):
def __init__(self):
super().__init__()
self.setStyleSheet("background: green")
self.setGeometry(100, 100, 400, 400)
self.b = QPushButton("Reduce", self, clicked=self.reduce)
self.b.show()
self.b.setStyleSheet("color: yellow")
self.show()
def reduce(self):
self.eff = QGraphicsOpacityEffect(self)
self.eff.setOpacity(1)
self.setGraphicsEffect(self.eff)
self.anim = QPropertyAnimation(self.eff, b"opacity")
self.anim.setDuration(500)
self.anim.setStartValue(1)
self.anim.setEndValue(0)
self.anim.start()
self.t = QTimer()
self.t.timeout.connect(self.loop)
self.t.start(10)
print(self.eff.opacity())
def loop(self):
self.update()
print(self.eff.opacity())
if self.anim.currentValue() == 0:
self.t.stop()
self.update()
if __name__ == "__main__":
window = QApplication(sys.argv)
app = Test()
window.exec_()
I thought maybe I am getting odd results as it isn't possible to do this when the widget is made to be the main widget in the window, however I also don't see why that would be a thing. What seems to happen for me is that the window does nothing and nothing changes, other than the button seems to half freeze, as in I am able to click it and get a result (run the function it is connected too) however no pressing down animation occurs as normally would. However, when I resize the window it will show a change, however this has to be manual as I added a basic loop to constantly resize and it did nothing. And finally when I resize the window, or change the opacity just by changing the opacity of self.eff it changes, but it just makes it all black even at values of 0.8, etc which is what leads me to believe it just isn't possible and some script is defaulting it to black, as the button will work fine while the rest goes black. Any help is appreciated.
Try like this:
import sys
from PyQt5.Qt import *
class Test(QWidget):
def __init__(self):
super().__init__()
self.setStyleSheet("background: green;")
self.resize(400, 400)
self.label = QLabel()
self.pixmap = QPixmap('Ok.png')
self.label.setPixmap(self.pixmap)
self.label.setAlignment(Qt.AlignCenter)
self.label.setStyleSheet("background: #CD113B;")
self.b = QPushButton("Reduce", self, clicked=self.reduce)
self.b.setStyleSheet("background: blue; color: yellow;")
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.b)
def reduce(self):
self.anim = QPropertyAnimation(self, b"opacity")
self.anim.setDuration(3000)
self.anim.setLoopCount(3)
self.anim.setStartValue(0.0)
self.anim.setEndValue(1.0)
self.anim.start()
def windowOpacity(self):
return super().windowOpacity()
def setWindowOpacity(self, opacity):
super().setWindowOpacity(opacity)
opacity = pyqtProperty(float, windowOpacity, setWindowOpacity)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Test()
w.show()
sys.exit(app.exec_())
or so:
import sys
from PyQt5.Qt import *
class Test(QWidget):
def __init__(self):
super().__init__()
self.setStyleSheet("background: green;")
self.resize(400, 400)
self.label = QLabel()
self.pixmap = QPixmap('Ok.png')
self.label.setPixmap(self.pixmap)
self.label.setAlignment(Qt.AlignCenter)
self.label.setStyleSheet("background: #CD113B;")
self.b = QPushButton("Reduce", self, clicked=self.reduce)
self.b.setStyleSheet("background: blue; color: yellow;")
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.b)
def reduce(self):
self.eff = QGraphicsOpacityEffect()
self.eff.setOpacity(1.0)
self.label.setGraphicsEffect(self.eff)
self.anim = QPropertyAnimation(self.eff, b"opacity")
self.anim.setDuration(3000)
self.anim.setLoopCount(3)
self.anim.setStartValue(0.0)
self.anim.setEndValue(1.0)
self.anim.start()
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Test()
w.show()
sys.exit(app.exec_())
Ok.png
I wanted to include a QToolbar in a Qwidget, but I found that I can only create a QToolbar in a QMainWindow. So, instead I want to create a Qlabel with an arrow icon in it. I downloaded an image with transparent background (I suppose). But, in the code, the image is not really transparent as I expected, it looks ugly. Is there any way to show only the arrow without the background. Below is a sample code
import sys
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class App(QMainWindow):
def __init__(self):
super().__init__()
self.title = 'test'
self.left = 0
self.top = 0
self.width = 300
self.height = 500
self.setWindowTitle(self.title)
self.setGeometry(self.left, self.top, self.width, self.height)
self.table_widget = MyTableWidget(self)
self.setCentralWidget(self.table_widget)
self.show()
class MyTableWidget(QWidget):
def __init__(self, parent):
super(QWidget, self).__init__(parent)
self.layout = QVBoxLayout(self)
# Create first tab
label3 = QLabel()
pixmap = QPixmap("index2.png")
smaller_pixmap = pixmap.scaled(32, 32, Qt.KeepAspectRatio, Qt.FastTransformation)
label3.setPixmap(smaller_pixmap)
label3.mouseReleaseEvent = self.on_click
self.layout.addWidget(label3)
self.setLayout(self.layout)
#pyqtSlot()
def on_click(self, event):
print('yes')
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
You are getting an "ugly" image because that image has lines that are partially transparent. I've increased the alpha threshold to better show them:
Those lines are part of the image and Qt cannot "guess" what portions of the image are "important" to you or not.
There's fundamentally no easy way to remove them by code, and even you'd succeed the result would be ugly anyway (some partial transparency is required around the border of the image to keep them smooth) and it wouldn't be worth the effort.
Just look for a different image, or edit it by clipping it to the arrow borders.
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 want to display welcome label in middle of frame, how can I do that? It seems like layout problem as I googled but I haven't got final solution.
Here is the code:
import sys
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Window(QMainWindow):
def __init__(self):
super(Window, self).__init__()
palette = QPalette()
palette.setBrush(QPalette.Background, QBrush(QPixmap("Login page.jpg")))
self.setPalette(palette)
self.setWindowTitle("Login Frame")
self.setWindowIcon(QIcon('logo.png'))
self.setGeometry(50, 50, 500, 300)
self.setFixedSize(500, 300)
self.addWidgets()
def addWidgets(self):
self.lblWelcome = QLabel("Welcome to Railway e-Ticketing System", self)
self.lblWelcome.move(100,30)
wcFont = QFont("Open Sans", 25)
self.lblWelcome.setFont(wcFont)
self.lblUid = QLabel("User ID:", self)
self.lblUid.move(100,80)
font = QFont("Open Sans", 10)
self.lneUid = QLineEdit(self)
self.lneUid.setFont(font)
self.lneUid.setFixedHeight(25)
self.lneUid.setFixedWidth(200)
self.lneUid.move(225, 80)
self.lblPass = QLabel("Password:", self)
self.lblPass.move(100, 130)
self.lnePass = QLineEdit(self)
self.lnePass.setEchoMode(QLineEdit.Password)
self.lnePass.setFixedHeight(25)
self.lnePass.setFixedWidth(200)
self.lnePass.move(225, 130)
self.lblInvalid = QLabel("",self)
self.lblInvalid.move(100, 180)
self.btnLogin = QPushButton("Login",self)
#btnLogin.resize(btnLogin.sizeHint())
self.btnLogin.move(175, 230)
self.btnLogin.clicked.connect(self.authenticate)
#self.authenticate()
self.btnReg = QPushButton("Register", self)
self.btnReg.move(300, 230)
#btnReg.clicked.connect(register)
self.show()
def authenticate(self):
uid = self.lneUid.text()
upass = self.lnePass.text()
if(len(uid.strip()) == 0 or len(upass.strip()) == 0):
palette = QPalette()
palette.setColor(QPalette.Foreground, Qt.darkRed)
self.lblInvalid.setPalette(palette)
self.lblInvalid.setText("*Invalid credentials .")
else:
self.lblInvalid.setText("")
def main():
app = QApplication(sys.argv)
LoginWin = Window()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
And here is the output:
You are using a QMainWindow which already has a layout with a central widget, a toolbar, a menu bar etc. The right way to use it is to define a central Widget, and put all your label and buttons in it. You didn't, so your label is not displayed properly.
But for your login frame, you clearly don't need all of this. You just need a QWidget:
import sys
from PyQt4 import QtCore,QtGui
class LoginFrame(QtGui.QWidget):
def __init__(self):
super(LoginFrame, self).__init__()
...
if __name__=='__main__':
app = QtGui.QApplication(sys.argv)
win = LoginFrame()
win.show()
sys.exit(app.exec_())
Your code should work with a QWidget, but I would still advise reading about box layout. Right now, you're using absolute positioning, which means you have to manually place your widget at a precise position, and you can't resize your window.
A box layout would be more flexible, and practical. For example you can use QFormLayout for the userID and password.
More about layouts on the ZetCode tutorial
Consider the modification of an answer from #ekhumoro as shown below.
There is a mplayer embedded in a QWidget. Now I want overlay some text over the video. But my approach below doesn't work (the background of the label is not transparent such that one sees only the video and the text). Any idea to fix it?
More generally: How can I make transparent labels positioned over custom widgets (in my case the mplayer-widget)?
If it is not possible to achieve exactly what I want, it would be sufficient to have something which just shows freezes the last frame of the video (or a predefined frame) and displays some text over it.
Note that at a later stage I want that the text which overlays the video changes with time, so the solution should have this in mind already.
For transparency things it may be important to note that I use a linux environment and that is should especially work under xmonad.
import mpylayer
from PyQt4 import QtGui, QtCore
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.container = QtGui.QWidget(self)
#self.container.setStyleSheet('background: black')
self.button = QtGui.QPushButton('Open', self)
self.button.clicked.connect(self.handleButton)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.button)
self.layout.addWidget(self.container)
self.mplayer = mpylayer.MPlayerControl(
'mplayer', ['-wid', str(self.container.winId())])
self.label = QtGui.QLabel('Some text\n and more',self)
self.label.move(100,100)
self.label.setGeometry(200,200,900,300)
#This doesn't work
self.label.setAttribute(QtCore.Qt.WA_TranslucentBackground)
#opacity doesn't work
self.label.setStyleSheet("QLabel {font-size: 100px; opacity:0.5}")
def handleButton(self):
path = QtGui.QFileDialog.getOpenFileName()
if not path.isEmpty():
self.mplayer.loadfile(unicode(path))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Here is a screenshot how it my non-working approach looks like:
Here is what I want faked with gimp (maybe I should have used a red font color, but that should be just simple css):
Edit
Here is how I tried to adapt X.Jacobs answer to my example. However it doesn't work. Only if I resize the window the overlayed text/lines appear for just a millisecond over the video and then disappears again (in both cases, if the video is running and if it is pausing).
import mpylayer
from PyQt4 import QtGui, QtCore
class overlayLabel(QtGui.QLabel):
def __init__(self, parent=None):
super(overlayLabel, self).__init__(parent)
self.setAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignVCenter)
self.setText("OVERLAY TEXT")
self.setStyleSheet("QLabel {font-size: 100px;}")
self.setGeometry(200,200,900,300)
class overlay(QtGui.QWidget):
def __init__(self, parent=None):
super(overlay, self).__init__(parent)
palette = QtGui.QPalette(self.palette())
palette.setColor(palette.Background, QtCore.Qt.transparent)
self.setPalette(palette)
def paintEvent(self, event):
painter = QtGui.QPainter()
painter.begin(self)
painter.setRenderHint(QtGui.QPainter.Antialiasing)
painter.fillRect(event.rect(), QtGui.QBrush(QtGui.QColor(255, 255, 255, 27)))
painter.drawLine(self.width()/8, self.height()/8, 7*self.width()/8, 7*self.height()/8)
painter.drawLine(self.width()/8, 7*self.height()/8, 7*self.width()/8, self.height()/8)
painter.setPen(QtGui.QPen(QtCore.Qt.NoPen))
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
self.container = QtGui.QWidget(self)
#self.container.setStyleSheet('background: black')
self.button = QtGui.QPushButton('Open', self)
self.button.clicked.connect(self.handleButton)
self.layout = QtGui.QVBoxLayout(self)
self.layout.addWidget(self.button)
self.layout.addWidget(self.container)
self.mplayer = mpylayer.MPlayerControl(
'mplayer', ['-wid', str(self.container.winId())])
## Both versions don't work:
#self.label = overlay(self.container)
self.label = overlayLabel(self.container)
def handleButton(self):
path = QtGui.QFileDialog.getOpenFileName()
if not path.isEmpty():
self.mplayer.loadfile(unicode(path))
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.resize(640, 480)
window.show()
sys.exit(app.exec_())
Checkout this example overlay widget that you can adapt to your needs:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class overlay(QWidget):
def __init__(self, parent=None):
super(overlay, self).__init__(parent)
palette = QPalette(self.palette())
palette.setColor(palette.Background, Qt.transparent)
self.setPalette(palette)
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
painter.setRenderHint(QPainter.Antialiasing)
painter.fillRect(event.rect(), QBrush(QColor(255, 255, 255, 127)))
painter.drawLine(self.width()/8, self.height()/8, 7*self.width()/8, 7*self.height()/8)
painter.drawLine(self.width()/8, 7*self.height()/8, 7*self.width()/8, self.height()/8)
painter.setPen(QPen(Qt.NoPen))
class windowOverlay(QWidget):
def __init__(self, parent=None):
super(windowOverlay, self).__init__(parent)
self.editor = QTextEdit()
self.editor.setPlainText("OVERLAY"*100)
self.button = QPushButton("Toggle Overlay")
self.verticalLayout = QVBoxLayout(self)
self.verticalLayout.addWidget(self.editor)
self.verticalLayout.addWidget(self.button)
self.overlay = overlay(self.editor)
self.overlay.hide()
self.button.clicked.connect(lambda: self.overlay.setVisible(False) if self.overlay.isVisible() else self.overlay.setVisible(True))
def resizeEvent(self, event):
self.overlay.resize(event.size())
event.accept()
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
main = windowOverlay()
main.show()
sys.exit(app.exec_())
To overlay text use something like this:
class overlayLabel(QLabel):
def __init__(self, parent=None):
super(overlayLabel, self).__init__(parent)
self.setAlignment(Qt.AlignHCenter|Qt.AlignVCenter)
self.setText("OVERLAY TEXT")