PyQt4: Window shows up at another position after hide() and show() - python

Using PyQt4, when I hide a window and show it afterwards, it appears at another position (at least here on Linux). Example code:
#!/usr/bin/python3
from PyQt4.QtGui import *
app = QApplication([])
widget = QWidget()
widget.setLayout(QVBoxLayout())
label = QLabel()
widget.layout().addWidget(label)
def hideShow():
widget.hide()
widget.show()
widget.layout().addWidget(QPushButton('Hide/Show', clicked = hideShow))
widget.show()
app.exec_()
The window disappears and appears, but a bit below and to the right of the original position. I think it's displaced by the size of the window manager's frame around the actual widget.
How can I place the window at the exact position where it was? And why does it move at all? Shouldn't it stay where it is?

On Linux, window placement can be very unpredictable. See this section in the Qt documentation for a break-down of the issues.
There's probably no general solution to the problem, but for me, setting the geometry before the initial show() seems to work:
...
widget.setGeometry(200, 200, 100, 50)
widget.show()
app.exec_()
UPDATE
After some testing with the KDE window manager, I may have discovered a potential solution.
It seems that calling show() immediately after hide() does not give the window manager enough time to calculate the correct window position. So a simple workaround is to explicitly set the geometry after a small delay:
from PyQt4.QtGui import *
from PyQt4.QtCore import QTimer
app = QApplication([])
widget = QWidget()
widget.setLayout(QVBoxLayout())
label = QLabel()
widget.layout().addWidget(label)
def hideShow():
widget.hide()
QTimer.singleShot(25, showWidget)
def showWidget():
widget.setGeometry(widget.geometry())
widget.show()
widget.layout().addWidget(QPushButton('Hide/Show', clicked = hideShow))
widget.show()
app.exec_()
This works for me using KDE-4.8 and OpenBox, but of course YMMV.

I have had similar problem with xfce. Perhaps you could get the position before hiding it (or when displaying it, depending on what you want), store it away, and then set it when it is displayed again with setGeometry()? A bit hacky perhaps..

Related

Fonts in PyQt5 not showing up correctly [duplicate]

I am learning how to use PyQt5 and I came across this issue where "my first label" does not complete display on my screen.
Display after running the code:
Code:
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) #enable highdpi scaling
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) #use highdpi icons
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win = QMainWindow()
win.setGeometry(200, 200, 400, 400)
win.setWindowTitle("Tech with Aeijan")
label = QtWidgets.QLabel(win)
label.setText("my first label!")
label.move(50,50)
win.show()
sys.exit(app.exec_())
window()
QLabel adapts its contents based on the (possible) parent layout manager, but you didn't use any, so it doesn't know how to correctly display itself or adapt its size to do that.
The simplest solution is to call label.adjustSize(), which will cause the label to resize itself so that it will be able to display its contents.
That wouldn't be a very good idea, though: you are trying to use a fixed position for a widget (which is normally considered a bad thing to do, for plenty of reasons); the result will be that if the label text is too big and the user resizes the window, the text won't be completely visible as it should be, nor the label would know how to resize or eventually wrap its contents to do ensure that all its text is shown.
The better approach is to use a layout manager, but that is a solution reserved for simpler widgets (like a QWidget or a QDialog); a QMainWindow doesn't work like that, and it requires a central widget to be set to ensure that its contents are correctly displayed and managed.
In your case, you could simply use self.setCentralWidget(label), but that would prevent you to add any other widget to your window.
A "container" widget should be used instead, and that widget would be set as the central one for the main window; then you can set a layout for that widget and add the label to it:
def window():
app = QApplication(sys.argv)
win = QMainWindow()
central = QWidget()
win.setCentralWidget(central)
layout = QVBoxLayout()
central.setLayout(layout)
# alternatively, the above is the same as this:
# layout = QVBoxLayout(central)
label = QtWidgets.QLabel(win)
label.setText("my first label!")
layout.addWidget(label)
# ...

Why does the label not fully display?

I am learning how to use PyQt5 and I came across this issue where "my first label" does not complete display on my screen.
Display after running the code:
Code:
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import QApplication, QMainWindow
import sys
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True) #enable highdpi scaling
QtWidgets.QApplication.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True) #use highdpi icons
def window():
app = QApplication(sys.argv)
win = QMainWindow()
win = QMainWindow()
win.setGeometry(200, 200, 400, 400)
win.setWindowTitle("Tech with Aeijan")
label = QtWidgets.QLabel(win)
label.setText("my first label!")
label.move(50,50)
win.show()
sys.exit(app.exec_())
window()
QLabel adapts its contents based on the (possible) parent layout manager, but you didn't use any, so it doesn't know how to correctly display itself or adapt its size to do that.
The simplest solution is to call label.adjustSize(), which will cause the label to resize itself so that it will be able to display its contents.
That wouldn't be a very good idea, though: you are trying to use a fixed position for a widget (which is normally considered a bad thing to do, for plenty of reasons); the result will be that if the label text is too big and the user resizes the window, the text won't be completely visible as it should be, nor the label would know how to resize or eventually wrap its contents to do ensure that all its text is shown.
The better approach is to use a layout manager, but that is a solution reserved for simpler widgets (like a QWidget or a QDialog); a QMainWindow doesn't work like that, and it requires a central widget to be set to ensure that its contents are correctly displayed and managed.
In your case, you could simply use self.setCentralWidget(label), but that would prevent you to add any other widget to your window.
A "container" widget should be used instead, and that widget would be set as the central one for the main window; then you can set a layout for that widget and add the label to it:
def window():
app = QApplication(sys.argv)
win = QMainWindow()
central = QWidget()
win.setCentralWidget(central)
layout = QVBoxLayout()
central.setLayout(layout)
# alternatively, the above is the same as this:
# layout = QVBoxLayout(central)
label = QtWidgets.QLabel(win)
label.setText("my first label!")
layout.addWidget(label)
# ...

How to change current color group for QPalette

I'm trying to change the current color group fora QPalette, but it seems that the setCurrentColorGroup method of QPalette simply does not work.
I'm running this code:
app = QtGui.QApplication(sys.argv)
button = QPushButton()
svgWidget = QSvgWidget(resources_paths.getPathToIconFile("_playableLabels/42-labelPlay-disabled-c.svg"))
button.setLayout(QHBoxLayout())
button.layout().addWidget(svgWidget)
button.setFixedSize(QSize(300, 300))
print button.palette().currentColorGroup()
button.setEnabled(False)
print button.palette().currentColorGroup()
button.palette().setCurrentColorGroup(QPalette.ColorGroup.Normal)
print button.palette().currentColorGroup()
button.show()
print button.palette().currentColorGroup()
app.exec_()
This is the output I get:
PySide.QtGui.QPalette.ColorGroup.Normal
PySide.QtGui.QPalette.ColorGroup.Disabled
PySide.QtGui.QPalette.ColorGroup.Disabled
PySide.QtGui.QPalette.ColorGroup.Disabled
Process finished with exit code -1
So... It seems that setCurrentColorGroup does exactly nothing. Any ideas on how could I change the current color group?
Thanks in advance!
(BTW, I'm running PySide 1.2.4 with Qt 4.8 on a Windows 7 system)
It seems that you are trying to change the way icons are rendered, rather than the way widgets are painted, so the palette is not the right API to use. Instead, you should use a QIcon, which allows different images to be used for various modes and states.
To use the same image for both Normal and Disabled modes, you would use code like this:
icon = QtGui.QIcon()
icon.addPixmap(QtGui.QPixmap('image.svg'), QtGui.QIcon.Normal)
icon.addPixmap(QtGui.QPixmap('image.svg'), QtGui.QIcon.Disabled)
button = QtGui.QPushButton()
button.setIcon(icon)
However, you should take careful note of this warning from the Qt docs:
Custom icon engines are free to ignore additionally added pixmaps.
So there is no guarantee that this will work with all widget styles on all platforms.
UPDATE:
If the above method doesn't work, it probably means the widget style is controlling how disabled icons are rendered. The relevant QStyle API is generatedIconPixmap, which returns a copy of the pixmap modified according to the icon mode and style options. It seems that this method may sometimes also take the palette into account (somewhat contrary to what I stated above) - but when I tested this, it did not have any affect. I reset the palette like this:
palette = self.button.palette()
palette.setCurrentColorGroup(QtGui.QPalette.Normal)
palette.setColorGroup(QtGui.QPalette.Disabled,
palette.windowText(), palette.button(),
palette.light(), palette.dark(), palette.mid(),
palette.text(), palette.brightText(),
palette.base(), palette.window(),
)
button.setPalette(palette)
which made the colours look normal when the button was disabled - but the icon was still greyed out. Still, you might want to try it in case things work differently on your platform (don't be surprised if they don't).
It seems that the correct way to control the disabling of icons is to create a QProxyStyle and override the generatedIconPixmap method. Unfortunately, this class is not available in PyQt4, but I have tested it in PyQt5, and it works. So the only working solution I have at the moment is to upgrade to PyQt5, and use QProxyStyle. Here is a demo script that shows how to implement it:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class ProxyStyle(QtWidgets.QProxyStyle):
def generatedIconPixmap(self, mode, pixmap, option):
if mode == QtGui.QIcon.Disabled:
mode = QtGui.QIcon.Normal
return super(ProxyStyle, self).generatedIconPixmap(
mode, pixmap, option)
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtWidgets.QPushButton(self)
self.button.setIcon(QtGui.QIcon('image.svg'))
self.button2 = QtWidgets.QPushButton('Test', self)
self.button2.setCheckable(True)
self.button2.clicked.connect(self.button.setDisabled)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.button2)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
app.setStyle(ProxyStyle())
window = Window()
window.setGeometry(600, 100, 300, 200)
window.show()
sys.exit(app.exec_())

How to automatically focus QDialog with a Qt.Popup flag set?

Consider the following code snippet using Python 3 and PyQt 5.5:
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QWidget()
d = QDialog()
l = QLineEdit(d)
w.show()
# Comment the following line to gain focus.
d.setWindowFlags(Qt.Popup)
d.show()
app.exec_()
After d.show() is invoked, the dialog is shown but the QLineEdit inside it doesn't have focus. No amount of raise_(), activateWindow() or setFocus() seems to be working. How can I make the dialog automatically gain focus when it's shown? I would like to keep the dialog as Qt.Popup, because I need it to close when I click outside of it.
There is
QWidget::raise();
QWidget::activateWindow();
From the docs:
Sets the top-level widget containing this widget to be the active window.
An active window is a visible top-level window that has the keyboard input focus.
This function performs the same operation as clicking the mouse on the title bar of a top-level window. On X11, the result depends on the Window Manager. If you want to ensure that the window is stacked on top as well you should also call raise(). Note that the window must be visible, otherwise activateWindow() has no effect.
On Windows, if you are calling this when the application is not currently the active one then it will not make it the active window. It will change the color of the taskbar entry to indicate that the window has changed in some way. This is because Microsoft does not allow an application to interrupt what the user is currently doing in another application.
It seems that you need to set focus on the line-edit after the dialog is shown:
l = QLineEdit(d)
w.show()
d.setWindowFlags(Qt.Popup)
d.show()
l.setFocus()
app.exec_()
If that doesn't work, try it with a timer:
QTimer.singleShot(1, l.setFocus)

PyQt: getting widgets to resize automatically in a QDialog

I'm having difficulty getting widgets in a QDialog resized automatically when the dialog itself is resized.
In the following program, the textarea resizes automatically if you resize the main window. However, the textarea within the dialog stays the same size when the dialog is resized.
Is there any way of making the textarea in the dialog resize automatically? I've tried using setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) on the dialog itself and the two widgets within, but that seems to have no effect.
I'm using Qt version 3.3.7 and PyQt version 3.5.5-29 on openSuSE 10.2, if that's relevant.
import sys
from qt import *
# The numbers 1 to 1000 as a string.
NUMBERS = ("%d " * 1000) % (tuple(range(1,1001)))
# Add a textarea containing the numbers 1 to 1000 to the given
# QWidget.
def addTextArea(parent, size):
textbox = QTextEdit(parent)
textbox.setReadOnly(True)
textbox.setMinimumSize(QSize(size, size*0.75))
textbox.setText(NUMBERS)
class TestDialog(QDialog):
def __init__(self,parent=None):
QDialog.__init__(self,parent)
self.setCaption("Dialog")
everything = QVBox(self)
addTextArea(everything, 400)
everything.resize(everything.sizeHint())
class TestMainWindow(QMainWindow):
def __init__(self,parent=None):
QMainWindow.__init__(self,parent)
self.setCaption("Main Window")
everything = QVBox(self)
addTextArea(everything, 800)
button = QPushButton("Open dialog", everything)
self.connect(button, SIGNAL('clicked()'), self.openDialog)
self.setCentralWidget(everything)
self.resize(self.sizeHint())
self.dialog = TestDialog(self)
def openDialog(self):
self.dialog.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
mainwin = TestMainWindow(None)
app.setMainWidget(mainwin)
mainwin.show()
app.exec_loop()
QMainWindow has special behavior for the central widget that a QDialog does not. To achieve the desired behavior you need to create a layout, add the text area to the layout and assign the layout to the dialog.
Just to add a little note about this - I was trying to have a child window spawned from an application, which is a QDialog, containing a single QTextEdit as a child/content - and I wanted the QTextEdit to resize automatically whenever the QDialog window size changes. This seems to have done the trick for me with PyQt4:
def showTextWindow(self):
#QVBox, QHBox # don't exist in Qt4
dialog = QDialog(self)
#dialog.setGeometry(QRect(100, 100, 400, 200))
dialog.setWindowTitle("Title")
dialog.setAttribute(QtCore.Qt.WA_DeleteOnClose)
textbox = QTextEdit(dialog)
textbox.setReadOnly(True)
textbox.setMinimumSize(QSize(400, 400*0.75))
textbox.setText("AHAAA!")
# this seems enough to have the QTextEdit
# autoresize to window size changes of dialog!
layout = QHBoxLayout(dialog)
layout.addWidget(textbox)
dialog.setLayout(layout)
dialog.exec_()
I had looked at using a QLayout before but had no luck. I was trying to do something like
dialog.setLayout(some_layout)
but I couldn't get that approach to work so I gave up.
My mistake was that I was trying to pass the layout to the dialog when I should have been passing the dialog to the layout.
Adding the lines
layout = QVBoxLayout(self)
layout.add(everything)
to the end of TestDialog.__init__ fixes the problem.
Thanks to Monjardin for prompting me to reconsider layouts.
Check out Python QT Automatic Widget Resizer It's suppose to work well.

Categories