I have been trying for many days to figure out a way to create a transparent Qtextedit with opaque text. Because the term "transparency" is often ambiguous, I define Qtextedit"transparency" as being able to see the text in the Qtextedit overlaid upon whatever is directly behind the main window (such as the desktop background, windows media player etc.) If possible I would like to be able to set the transparency at various levels and cross system compatible, but this is not required.
I am an extreme beginner, as I have only been using pyqt4 for 3 weeks and python 3.x for a few months and this is all the experience with programming that I have obtained in my existence. I have been attempting to decipher the Pyqt documentation with regard to this matter, but it is written in a way that seems to assume that one has been a gui programer for decades, not to mention having knowlege of C++. Furthermore, when this question is asked online it never seems to be resolved in way that is either: a) well documented or b) generalizable
This is very surprising because it seems like a basic operation that people would want to do
This solution works but doesn't seem to be directly useful for anything but displaying transparent images. I also don't really understand it all that well, as simply changing the base class from QWidget to QMainWindow makes the whole thing fail
http://www.loopbacking.info/blog/2008/07/11/transparent-windows-howto/
The following link embodies the common ways people suggest to solve problems similar to this, their pitfalls and why they don't work, but unfortunately they use the C++ version of Qt and are also a bit advanced for my skills at this point.
http://www.qtcentre.org/threads/18072-How-to-set-Qt-window-transparent
My system is windows 7 ultimate 32 bit on a dell latitude d830 with a Quadro NVS 140 whose driver version is current as of this post (Verde 275.33) My version of Pyqt is 4.8 (PyQt-Py3.2-x86-gpl-4.8.5-1.exe Windows 32 bit installer) I am also using Python 3.2.1 (Open Source version)
A basic example of my code lies beneath with the relevant (and failed) lines commented out:
When I tried the commented out code the color I generally just saw blackness. Also, when I resized my windows the darkness would randomly change intensity and the display of the main window seemed to get corrupted when maximized.
I would greatly appreciate any help on this matter!
import sys
import PyQt4
from PyQt4 import QtGui, QtCore
class Transparent(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
self.initialize()
def initialize(self):
#self.colorset(self,'Window',200,255,100,20)
#self.colorset(self,'Base',200,255,100,20)
#self.setBackgroundRole(QtGui.QPalette.Base)
#self.setAttribute(QtCore.Qt.WA_NoSystemBackground)
#self.setAutoFillBackground(True)
#self.mask()
self.setWindowTitle("Chernobyl-like Failure")
self.answerlabel = QtGui.QLabel('Text Response Display')
self.answerlabel.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Raised)
self.answerlabel.setMinimumHeight(25)
self.questionlabel = QtGui.QLabel("Question:")
self.questionlabel.setFrameStyle(QtGui.QFrame.Panel | QtGui.QFrame.Raised)
self.questionbox = QtGui.QLineEdit()
self.questionbox.setMinimumWidth(500)
self.askbutton = QtGui.QPushButton("Ask it!")
self.historybox = QtGui.QTextEdit('Question & Answer history will be displayed here')
self.historybox.setReadOnly(True)
#self.colorset(self.historybox,'Base',200,255,100,127)
self.grid = QtGui.QGridLayout()
widgetlist = [['answerlabel',0,0,1,3],['questionlabel',1,0,1,1],
['questionbox',1,1,1,1],['askbutton',1,2,1,1],['historybox',2,0,1,3]]
for widget in widgetlist:
self.grid.addWidget(eval("self.{0}".format(widget[0])),*widget[1:])
self.centralwidget = QtGui.QFrame()
self.centralwidget.setFrameStyle(QtGui.QFrame.Box|QtGui.QFrame.Raised)
self.centralwidget.setLineWidth(5)
self.centralwidget.setLayout(self.grid)
#self.colorset(self.centralwidget,'Base',200,255,100,127)
self.setCentralWidget(self.centralwidget)
def colorset(self,widget,part,h,s,l,a):
pal = widget.palette()
color = QtGui.QColor()
color.setHsl(h,s,l,a)
pal.setColor(eval('QtGui.QPalette.{0}'.format(part)),color)
widget.setPalette(pal)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main_window = Transparent()
main_window.show()
sys.exit(app.exec_())
To make your main window transparent, you have to set the Qt.WA_TranslucentBackground attribute (using setAttribute(Qt.WA_TranslucentBackground)). Under Windows, you also must set the Qt.FramelessWindowHint attribute on your main window. According to the docs, however, "The user cannot move or resize a borderless window via the window system." So, if you want that functionality, you have to implement it manually. Here is a thread giving an example of that in C++.
Once you have a transparent MainWindow you can control the opacity of it and any child widgets by setting the background color to an RGBA value. Here is a dumb example,
from PyQt4 import QtGui, QtCore
import sys
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
frame = QtGui.QFrame(parent=self)
frame.setStyleSheet("QFrame {background: rgba(0,255,0,20%)}")
box=QtGui.QHBoxLayout()
edit = QtGui.QTextEdit()
edit.setStyleSheet("background: rgba(0,0,255,20%)")
box.addWidget(edit)
edit2=QtGui.QTextEdit()
edit2.setStyleSheet("background: rgb(255,0,0)")
box.addWidget(edit2)
frame.setLayout(box)
pushbutton = QtGui.QPushButton('Quit')
pushbutton.clicked.connect(self.close)
box.addWidget(pushbutton)
self.setCentralWidget(frame)
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Main()
main.show()
app.exec_()
Related
I'm implementing a custom widget to use it as a title bar on a dockable window. My problem arises only on Windows, namely, the window border disappears when the dock window is afloat.
Seems the problem is that, on Windows only, the window flags are changed. I.e. when I do this:
print dock_window.windowFlags()
dock_window.setTitleBarWidget(title_bar)
print dock_window.windowFlags()
it prints out different setting for the flags before and after.
However, it stays the same on linux and the borders remain unchanged.
My question is, how to restore the window border?
UPDATE: Since the custom title bar overrides the flags for the border when the dock window is floating, how can I edit the dock window so it has some kind of border?
(It is crucial for the dock window to have a custom title bar when floating.)
According to this answer this is expected behavior.
From the documentation of setTitleBarWidget:
If a title bar widget is set, QDockWidget will not use native window
decorations when it is floated.
So Linux does it the wrong way then?
Anyway as a workaround for Windows I implemented the idea (unsetting the title bar widget before floating) from the answer in PySide/PyQt.
from PySide import QtGui, QtCore
class MyDockWidget(QtGui.QDockWidget):
def __init__(self, title_widget):
super().__init__()
self.title_widget = title_widget
self.toggle_title_widget(False)
self.topLevelChanged.connect(self.toggle_title_widget)
def toggle_title_widget(self, off):
if off:
self.setTitleBarWidget(None)
else:
self.setTitleBarWidget(self.title_widget)
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
t = QtGui.QLabel('Title')
d = MyDockWidget(t)
w.addDockWidget(QtCore.Qt.LeftDockWidgetArea, d)
w.show()
app.exec_()
At least it keeps the standard decoration when floating.
I found this to be an unresolved bug in QT and I don't see this as expected behavior.
I found multiple cases of people stumbling on this issue eg1, eg2 and others.
Some recommend unsetting and setting the setTitleBarWidget as in Trilarion's answer.
This, however, removes the custom title bar and was not ok with me.
others recommend setting flags on topLevelChanged event: window.setWindowFlags(Qt::Window | Qt::FramelessWindowHint);. this adds the usual title bar to the dock widget, which again is not what I personally want.
the best solution I found is w->setWindowFlags(Qt::Tool|Qt::CustomizeWindowHint);. This uses Qt.CustomizeWindowHint instead of Qt.FramelessWindowHint and does not produce a huge title bar, merely a small bar.
Implementation
from PyQt5.QtCore import Qt
....
def dockfloatevent(isfloating):
if isfloating:
dock.setWindowFlags(Qt.Tool | Qt.CustomizeWindowHint)
dock.topLevelChanged.connect(dockfloatevent)
I am not using the most up to date Qt, but from what I can tell this is still an issue? If someone has a Qt account maybe post something to the above bug link? I have already wasted many hours on this and don't feel like pushing it further :|
I'm trying to take a screenshot of the current active window in PyQt5. I know the generic method to take an screenshot of any window is QScreen::grabWindow(winID), for which winID is an implementation-specific ID depending on the window system. Since I'm running X and KDE, I plan to eventual use CTypes to call Xlib, but for now, I simply execute "xdotool getactivewindow" to obtain the windowID in a shell.
For a minimum exmaple, I created a QMainWindow with a QTimer. When the timer is fired, I identify the active window ID by executing "xdotool getactivewindow", get its return value, call grabWindow() to capture the active window, and display the screetshot in a QLabel. On startup, I also set my window a fixed 500x500 size for observation, and activate Qt.WindowStaysOnTopHint flag, so that my window is still visible when it's not in focus. To put them together, the implementation is the following code.
from PyQt5 import QtCore, QtGui, QtWidgets
import subprocess
class ScreenCapture(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.setFixedHeight(500)
self.setFixedWidth(500)
self.label = QtWidgets.QLabel(self)
self.timer = QtCore.QTimer(self)
self.timer.setInterval(500)
self.timer.timeout.connect(self.timer_handler)
self.timer.start()
self.screen = QtWidgets.QApplication.primaryScreen()
#QtCore.pyqtSlot()
def timer_handler(self):
window = int(subprocess.check_output(["xdotool", "getactivewindow"]).decode("ascii"))
self.screenshot = self.screen.grabWindow(window)
self.label.setPixmap(self.screenshot)
self.label.setFixedSize(self.screenshot.size())
if __name__ == '__main__':
app = QtWidgets.QApplication([])
window = ScreenCapture()
window.show()
app.exec()
To test the implementation, I started the script and clicked another window. It appears to work without problems if there is no overlap between my application window and the active window. See the following screenshot, when Firefox (right) is selected, my application is able to capture the active window of Firefox and display it in the QLabel.
However, the screenshot doesn't work as expected if there is an overlap between the application window and the active window. The window of the application itself will be captured, and creates a positive feedback.
If there is an overlap between the application window and the active window. The window of the application itself will be captured, and creates a positive feedback.
I've already disabled the 3D composite in KDE's settings, but the problem remains. The examples above are taken with all composite effects disabled.
Question
Why isn't this implementation working correctly when the application window and the active window are overlapped? I suspect it's an issue caused by some forms of unwanted interaction between graphics systems (Qt toolkit, window manager, X, etc), but I'm not sure.
Is it even possible solve this problem? (Note: I know I can hide() before the screenshot and show() it again, but it doesn't really solve this problem, which is taking a screenshot even if an overlap exists.)
As pointed out by #eyllanesc, it appears that it is not possible to do it in Qt, at least not with QScreen::grabWindow, because grabWindow() doesn't actually grab the window itself, but merely the area occupied by the window. The documentation contains the following warning.
The grabWindow() function grabs pixels from the screen, not from the window, i.e. if there is another window partially or entirely over the one you grab, you get pixels from the overlying window, too. The mouse cursor is generally not grabbed.
The conclusion is that it's impossible do to it in pure Qt. It's only possible to implement such a functionality by writing a low-level X program. Since the question asks for a solution "in Qt", any answer that potentially involves deeper, low-level X solutions are out-of-scope. This question can be marked as resolved.
The lesson to learn here: Always check the documentation before using a function or method.
Update: I managed to solve the problem by reading the window directly from X via Xlib. Somewhat ironically, my solution uses GTK to grab the window and sends its result to Qt... Anyway, you can write the same program with Xlib directly if you don't want to use GTK, but I used GTK since the Xlib-related functions in GDK is pretty convenient to demonstrate the basic concept.
To get a screenshot, we first convert our window ID to an GdkWindow suitable for use within GDK, and we call Gdk.pixbuf_get_from_window() to grab the window and store it in a gdk_pixbuf. Finally, we call save_to_bufferv() to convert the raw pixbuf to a suitable image format and store it in a buffer. At this point, the image in the buffer is suitable to use in any program, including Qt.
The documentation contains the following warning:
If the window is off the screen, then there is no image data in the obscured/offscreen regions to be placed in the pixbuf. The contents of portions of the pixbuf corresponding to the offscreen region are undefined.
If the window you’re obtaining data from is partially obscured by other windows, then the contents of the pixbuf areas corresponding to the obscured regions are undefined.
If the window is not mapped (typically because it’s iconified/minimized or not on the current workspace), then NULL will be returned.
If memory can’t be allocated for the return value, NULL will be returned instead.
It also has some remarks about compositing,
gdk_display_supports_composite has been deprecated since version 3.16 and should not be used in newly-written code.
Compositing is an outdated technology that only ever worked on X11.
So basically, it's only possible to grab a partially obscured window under X11 (not possible in Wayland!), with a compositing window manager. I tested it without compositing, and found the window is blacked-out when compositing is disabled. But when composition is enabled, it seems to work without problem. It may or may not work for your application. But I think if you are using compositing under X11, it probably will work.
from PyQt5 import QtCore, QtGui, QtWidgets
import subprocess
class ScreenCapture(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
self.setFixedHeight(500)
self.setFixedWidth(500)
self.label = QtWidgets.QLabel(self)
self.screen = QtWidgets.QApplication.primaryScreen()
self.timer = QtCore.QTimer(self)
self.timer.setInterval(500)
self.timer.timeout.connect(self.timer_handler)
self.timer.start()
#staticmethod
def grab_screenshot():
from gi.repository import Gdk, GdkX11
window_id = int(subprocess.check_output(["xdotool", "getactivewindow"]).decode("ascii"))
display = GdkX11.X11Display.get_default()
window = GdkX11.X11Window.foreign_new_for_display(display, window_id)
x, y, width, height = window.get_geometry()
pb = Gdk.pixbuf_get_from_window(window, 0, 0, width, height)
if pb:
buf = pb.save_to_bufferv("bmp", (), ())
return buf[1]
else:
return
#QtCore.pyqtSlot()
def timer_handler(self):
screenshot = self.grab_screenshot()
self.pixmap = QtGui.QPixmap()
if not self.pixmap:
return
self.pixmap.loadFromData(screenshot)
self.label.setPixmap(self.pixmap)
self.label.setFixedSize(self.pixmap.size())
if __name__ == '__main__':
app = QtWidgets.QApplication([])
window = ScreenCapture()
window.show()
app.exec()
Now it captures an active window perfectly, even if there are overlapping windows on top of it.
I'm implementing a custom widget to use it as a title bar on a dockable window. My problem arises only on Windows, namely, the window border disappears when the dock window is afloat.
Seems the problem is that, on Windows only, the window flags are changed. I.e. when I do this:
print dock_window.windowFlags()
dock_window.setTitleBarWidget(title_bar)
print dock_window.windowFlags()
it prints out different setting for the flags before and after.
However, it stays the same on linux and the borders remain unchanged.
My question is, how to restore the window border?
UPDATE: Since the custom title bar overrides the flags for the border when the dock window is floating, how can I edit the dock window so it has some kind of border?
(It is crucial for the dock window to have a custom title bar when floating.)
According to this answer this is expected behavior.
From the documentation of setTitleBarWidget:
If a title bar widget is set, QDockWidget will not use native window
decorations when it is floated.
So Linux does it the wrong way then?
Anyway as a workaround for Windows I implemented the idea (unsetting the title bar widget before floating) from the answer in PySide/PyQt.
from PySide import QtGui, QtCore
class MyDockWidget(QtGui.QDockWidget):
def __init__(self, title_widget):
super().__init__()
self.title_widget = title_widget
self.toggle_title_widget(False)
self.topLevelChanged.connect(self.toggle_title_widget)
def toggle_title_widget(self, off):
if off:
self.setTitleBarWidget(None)
else:
self.setTitleBarWidget(self.title_widget)
app = QtGui.QApplication([])
w = QtGui.QMainWindow()
t = QtGui.QLabel('Title')
d = MyDockWidget(t)
w.addDockWidget(QtCore.Qt.LeftDockWidgetArea, d)
w.show()
app.exec_()
At least it keeps the standard decoration when floating.
I found this to be an unresolved bug in QT and I don't see this as expected behavior.
I found multiple cases of people stumbling on this issue eg1, eg2 and others.
Some recommend unsetting and setting the setTitleBarWidget as in Trilarion's answer.
This, however, removes the custom title bar and was not ok with me.
others recommend setting flags on topLevelChanged event: window.setWindowFlags(Qt::Window | Qt::FramelessWindowHint);. this adds the usual title bar to the dock widget, which again is not what I personally want.
the best solution I found is w->setWindowFlags(Qt::Tool|Qt::CustomizeWindowHint);. This uses Qt.CustomizeWindowHint instead of Qt.FramelessWindowHint and does not produce a huge title bar, merely a small bar.
Implementation
from PyQt5.QtCore import Qt
....
def dockfloatevent(isfloating):
if isfloating:
dock.setWindowFlags(Qt.Tool | Qt.CustomizeWindowHint)
dock.topLevelChanged.connect(dockfloatevent)
I am not using the most up to date Qt, but from what I can tell this is still an issue? If someone has a Qt account maybe post something to the above bug link? I have already wasted many hours on this and don't feel like pushing it further :|
I want to make a main window application in PyQt that its size is fixed.
I tried to find a way how to overload sizehint() method of the QMainWindow's layout in order to set the size returned by sizeHint() but didn't know how to do it.
After some researching i have learned that i can set the size of a QMainWindow with QWidget's function setFixedSize(QSize).
But still i'm curious how it can be done the other way.
It could be a lack of knowledge in Python programming.
Just for your curiosity you could do this:
from PyQt4 import QtGui as gui, QtCore as core
class MainWindow(gui.QMainWindow):
def __init__(self):
super(MainWindow,self).__init__()
self.setCentralWidget(gui.QPushButton("Push me!"))
def sizeHint(self):
return core.QSize(300,500)
app = gui.QApplication([])
mw = MainWindow()
mw.show()
app.exec_()
You should keep in mind that sizeHint is only used to get a default size for the widgets when it is applicable. I mean when there is some extra space that could be filled or when there is more than one widget that share space in a layout, just experiment with it an layouts and you will see, could be very helpful! ;)
Finally, it is not a valid approach for setting a fixed size, for that just use setFixedSize as you posted.
I have a QT application written in python using PySide and I stumbled across a little problem regarding the showFullScreen method of the QGLWidget (although the problem occurs with every other widget too):
The problem is, that the widget doesn't have its 'final' resolution after the program returns from showFullScreen.
The switch seems to be triggered asynchronously between 5 and 10 milliseconds later.
This is a problem for me because I have to do some layout calculations which depend on the widget's size after it is shown.
Below is a little reproducer which subclasses QGLWidget. Using this reproducer you will take notice, that resizeEvent will be called twice after showFullScreen.
I'm looking for a convinient way of knowing which resizeEvent is the 'final' one, or a way of knowing, when the widget really is in fullscreen mode. Is there maybe any signals I could connect to?
Thanks a lot for any help on this.
#!/usr/bin/python
import sys
from PySide.QtGui import QApplication
from PySide.QtCore import QTimer
from PySide.QtOpenGL import QGLWidget
class TestWidget(QGLWidget):
def __init__(self, parent=None):
super(TestWidget, self).__init__(parent)
self._timer = QTimer()
self._timer.setInterval(5)
self._timer.timeout.connect(self.showsize)
self._timer.start()
def resizeEvent(self, event):
print "Resize event:", event.size().width(), event.size().height()
def showsize(self):
w = widget.size().width()
print "Timer: ", w, widget.size().height()
if w == 1680:
self._timer.stop()
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = TestWidget()
widget.showFullScreen()
print "After showFullScreen:", widget.size().width(), widget.size().height()
# this will always be 640 480...1680 1050 is what I'm expecting
app.exec_()
One possibility is to check that if the resize event is spontaneous or not. From limited testing here (using Qt C++ on Linux), the second resize event is spontaneous, while the first one is not.
You could do your calculations only when the event is spontaneous.
Note: I'm not sure how portable this is, it might depend on your window manager/windowing system.