QScrollArea, how do I make my central widget scrollable? [duplicate] - python

This question already has an answer here:
How to use QScrollArea to make scrollbars appear
(1 answer)
Closed 2 years ago.
I'm trying to turn a widget into a scrollable window. However, all I've accomplished is opening a QScrollarea that's completely independent from my central widget.
class Ui_MainWindow(object):
def mainscreen(self, MainWindow):
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.scrollArea = QtWidgets.QScrollArea()
self.scrollArea.setWidget(self.centralwidget)
MainWindow.setCentralWidget(self.centralwidget)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.mainscreen(MainWindow)
MainWindow.show()
app.exec_()
This just opens up my MainWindow on its own, only way I get an scrollarea open is if I do self.scrollarea.show . I cant figure out how QScrollArea works, how am I supposed to do it?

Simple explanation: the scroll area has no relation with the main window, and then it's not shown when the main window is.
Never, ever modify (or imitate) files generated by pyuic
You are making some confusion around the object structure, Qt parent/ownership and how a scroll area is set up.
This is also caused by the fact that you're clearly trying to mimic the structure and behavior of a file generated by pyuic, which should never be done. Those files are intended to be directly used as they are, without any modification, and imported in the main script of your program.
Their peculiar structure is intended only for that purpose, and the way they're built is not to be emulated, as creation of UI by code should only be done by subclassing a QWidget subclass (QMainWindow, QDialog, etc); trying to imitate what they do is not only unnecessary, but most times leads to confusion, bugs and unexpected behavior, exactly like in your case.
The only valid reason to open (and just read) a file generated by pyuic is to learn how widgets are created in the setupUi, keeping in mind that the setupUi argument (usually, MainWindow for QMainWindows, Form for QWidgets, Dialog for QDialogs, unless the name of the top level widget is changed in the object inspector of designer) is the main widget on which the ui is built upon.
Manually editing those files should only be done if one really knows what she/he is doing. There are few, very rare cases for which this is considered necessary, and it's usually to workaround very specific known bugs in the uic module, that normally rise on very specific conditions and situations.
What is happening?
Then, when a QWidget instance is created with an existing widget as argument, the result is that the new widget becomes a child of the existing one.
Widget can be then reparented in various ways: by setting a widget for a scroll area using setWidget(), or by setting the widget as a central widget to a QMainWindow.
What happens with your code is that you created a new QWidget that becomes a child of the QMainWindow with the following line:
self.centralwidget = QtWidgets.QWidget(MainWindow)
Then you create a scroll area (without any parent):
self.scrollArea = QtWidgets.QScrollArea()
Widgets that are not created with a parent are normally considered top level windows, unless they are added to a layout (or manually reparented using setParent(), but that's another story), which causes the parent (the widget on which the layout is set) to take ownership of the new child widget. Otherwise, if you try to show the "parent-less" widget, it will be shown as a top level widget, meaning it will have its own window, and as any top level widget, they can only be shown by manually calling show() or setVisible(True) (and that's your case).
Then you try to set the centralwidget of the scroll area, which will result in reparenting it:
self.scrollArea.setWidget(self.centralwidget)
Finally, you're setting that widget as the central widget, which will reparent it back again to the main window, with the result that the scroll area will not have a widget anymore (QObjects, from which QWidget inherits, can only have a single parent).
MainWindow.setCentralWidget(self.centralwidget)
The correct approach
The solution is to correctly create a QWidget (or QMainWindow in your case) subclass, set the scroll area as the central widget (if you want it as that) and create a new widget for its contents:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.scrollArea = QtWidgets.QScrollArea()
self.setCentralWidget(self.scrollArea)
self.scrollArea.setWidgetResizable(True)
self.contents = QtWidgets.QWidget()
self.scrollArea.setWidget(self.contents)
# create a layout for the scroll area contents; using the target widget
# as argument of the layout constructor automatically sets the layout on
# the specified widget
layout = QtWidgets.QVBoxLayout(self.contents)
# the same as:
# layout = QtWidgets.QVBoxLayout()
# self.contents.setLayout(layout)
for row in range(20):
button = QtWidgets.QPushButton('button {}'.format(row + 1))
layout.addWidget(button)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())

Related

PySide2 main window not working after pop-up

I am building a graphical interface for an application using PySide2. My main window is a QMainWindow and I am trying to open a pop-up window, which is a QDialog, whenever a specific action is performed on the main window.
The pop-up opens perfectly fine. However, after it is open, the main window is no longer responsive. I believe the problem is that my application is overwriting the main window with the popup window.
The error message whenever I try to change the main window's stackedWidget index is:
AttributeError: 'Ui_popupHideSuccess' object has no attribute 'stackedWidget'
The code I am using to open the main window is the following:
if __name__ == '__main__':
app = QApplication(sys.argv)
myWindow = MainWindow()
myWindow.show()
sys.exit(app.exec_())
And the code I am using to open the pop-up window is the following:
def showPopupSuccessHide(self):
self.window = QDialog()
self.ui = Ui_popupHideSuccess()
self.ui.setupUi(self.window)
self.window.show()
The code for the windows themselves are on other files (as I am using QtDesigner for developing them). I believe it to be unnecessary for resolving this issue, but I can provide it if needed. What am I doing wrong here? I need to open pop-ups and still interact with the main window after.
I have no idea how to actually resolve this. I believe my error to be in the code I am using to open the pop-up window, but I'm not sure how to tweak it for it to work properly.
TL;DR
Do not overwrite self.ui.
Explanation
How uic composition works
One of the common ways of properly using pyuic generated files is to use composition (as opposed to multiple inheritance):
from PyQt5.QtWidgets import QApplication, QMainWindow, QDialog
from ui_mainWindow import Ui_MainWindow
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.myLineEdit.setText('some text')
This is perfectly fine, and makes sense: the concept is that an instance of the pyuic class (sometimes called "form class") is created and then the actual window is "set up" using that instance, with the self.ui object containing references to all widgets.
Note that making the ui persistent (using an instance attribute) is actually not a strict requirement, but it is usually necessary in order to be able to directly access the widgets, which is normally important to create signal connections or read properties.
But, if that's not required, it will work anyway: the widgets are automatically "reparented" to the main window (or their direct parents), and the garbage collection is not an issue as Qt will keep its own references internally (in Qt terms, "the window takes ownership").
Technically speaking, this is completely valid:
class MyWindow(QMainWindow):
def __init__(self):
super().__init__()
Ui_MainWindow().setupUi(self)
Then, we can still access the widgets using findChild and their object names (those set in Designer):
self.findChild(QLineEdit, 'myLineEdit').setText('some text')
Obviously, it is not very practical.
Creating "child" windows
When there is the need to create a child window (usually, a dialog), it's normally suggested to use an instance attribute to avoid garbage collection:
def createWindow(self):
self.window = QDialog()
self.window.show()
If that dialog also has a Designer file, we need to do something similar to what done at the beginning. Unfortunately, a very common mistake is to create the ui instance by using the same name:
def createWindow(self):
self.window = QDialog()
self.ui = Ui_Dialog()
self.ui.setupUi(self.window)
self.ui.anotherLineEdit.setText('another text')
self.window.show()
This is theoretically fine: all works as expected. But there's a huge problem: the above overwrites self.ui, meaning that we lose all references to the widgets of the main window.
Suppose that you want to set the text of the line edit in the dialog based on the text written in that of the main window; the following will probably crash:
def createWindow(self):
self.window = QDialog()
self.ui = Ui_Dialog()
self.ui.setupUi(self.window)
self.ui.anotherLineEdit.setText(self.ui.myLineEdit.text())
self.window.show()
This clearly shows an important aspect: it's mandatory to always think before assigning attributes that may already exist.
In the code here above, this was actually done twice: not only we overwrote the self.ui we created before, but we also did it for window(), which is an existing function of all Qt widgets (it returns the top level ancestor window of the widget on which it was called).
As a rule of thumb, always take your time to decide how to name objects, use smart names, and consider that most common names are probably already taken: remember to check the "List of all members, including inherited members" link in the documentation of the widget type you're using, until you're experienced enough to remember them.
Solutions
The obvious solution is to use a different name for the ui of the dialog:
def createWindow(self):
self.dialog = QDialog()
self.dialog_ui = Ui_Dialog()
self.dialog_ui.setupUi(self.dialog)
self.dialog_ui.anotherLineEdit.setText(self.ui.myLineEdit.text())
self.dialog.show()
A better solution is to create a subclass for your dialog instead:
class MyDialog(QDialog):
def __init__(self, parent=None)
super().__init__(parent)
self.ui = Ui_Dialog()
self.ui.setupUi(self)
class MyWindow(QMainWindow):
# ...
def createWindow(self):
self.dialog = MyDialog()
self.dialog.ui.anotherLineEdit.setText(self.ui.myLineEdit.text())
self.dialog.show()
Also remember that another common (and, to my experience, simpler and more intuitive) method is to use multiple inheritance instead of composition:
class MyDialog(QDialog, Ui_Dialog):
def __init__(self, parent=None)
super().__init__(parent)
self.setupUi(self)
class MyWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.myLineEdit.setText('some text')
def createWindow(self):
self.dialog = MyDialog()
self.dialog.anotherLineEdit.setText(self.myLineEdit.text())
self.dialog.show()
The only issue of this approach is that it may inadvertently overwrite names of functions of the "main" widget: for instance, if you created a child widget in Designer and renamed it "window". As said above, if you always think thoroughly about the names you assign to objects, this will probably never happen (it doesn't make a lot of sense to name a widget "window").

In PyQT why somes widgets needs the "self" parameter before calling them, while others don't

I'm a little bit confused about the use of the "self" parameter with some widgets like (QLineEdit), indeed when learning to use the QLabel widget, I used to call the class without the self paramater, or when using the QLineEdit widget, the widget wouldn't work without the "self" parameter, here's the code I'm working on :
# Import necessary modules
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton
from PyQt5.QtCore import Qt
class EntryWindow(QWidget): # Inherits QWidget
def __init__(self): # Constructor
super().__init__() # Initializer which calls constructor for QWidget
self.initializeUI() # Call function used to set up window
def initializeUI(self):
"""
Initialize the window and display its contents to the screen
"""
self.setGeometry(400, 300, 400, 200)
self.setWindowTitle('QLineEdit Widget')
self.displayWidgets()
self.show() # Show everything
def displayWidgets(self):
'''
Setup the QLineEdit and other widgets.
'''
# Create name label and line edit widgets
QLabel("Please enter your name below.", self).move(100, 20)
name_label = QLabel("Name:", self)
name_label.move(55, 70)
self.name_entry = QLineEdit(self)
self.name_entry.move(120, 68)
self.name_entry.resize(200, 25) # Change size of entry field
self.name_entry.setAlignment(Qt.AlignLeft) # The default alignment
text_font = self.name_entry.font() # Get font option from the Qlineedit
text_font.setPointSize(12) # Modify font size
#text_font.setBold(True) # Bold
self.name_entry.setFont(text_font) # Apply font
self.clear_button = QPushButton('Clear text', self)
self.clear_button.clicked.connect(self.clearEntries)
self.clear_button.move(120, 130)
self.exit_button = QPushButton("Exit", self)
self.exit_button.clicked.connect(self.exitApplication)
self.exit_button.move(240, 130)
def clearEntries(self):
sender = self.sender()
if sender.text() == 'Clear text':
self.name_entry.clear()
def exitApplication(self):
sender = self.sender()
if sender.text() == "Exit":
self.close() # Close the window
# Run program
if __name__ == '__main__':
app = QApplication(sys.argv)
window = EntryWindow()
sys.exit(app.exec_())
So here is where I'm confused, when using QLabel, I didn't have to put the "self" parameter before, or when using QLineEdit, I had to put "self" otherwise my code wouldn't work :
QLabel("Please enter your name below.", self).move(100, 20)
self.name_entry = QLineEdit(self)
First of all, the problem or difference has nothing to do with the "self", but what it is for, pre-established rules in Qt design.
In Qt there is a hierarchy tree between the QObjects where it is established that the parent QObject manages the memory (the life cycle of its children) so if the parent deleted the children, they will also be deleted. This allows avoiding memory leaks since many QObjects are generally used in many applications.
On the other hand, that concept of kinship is also passed to QWidgets since they are also QObjects, but there is also another characteristic: a QWidget in general will be drawn on top of its parent. So if you want the QLineEdit and QLabel to be part of the window then they must be children of the window so it is necessary that you pass the window object which is "self" as parent.
So when you go to the window (in this case "self") you avoid 2 problems:
That the object has a longer life cycle (the same as the window).
And you make the widget (either QLabel or QLineEdit) be placed on top of the window.
The self name is a Python convention to indicate the instance of an object. When set as argument in an object constructor, it's normally used to give the new object a reference to the one that created it, and that's normally called a "parent". Note that while self is usually used as parent argument, you can set any QWidget instance as parent.
In Qt terms, not only it indicates the roles in a "parentship" of the object structure, but also results in two important aspects:
In the world of QObjects (from which QWidget inherits) this helps for memory management: when a parent is deleted, all its children objects are deleted as well. It's also useful to find child objects (using findChild() or findChildren()) which are direct children or [great, ...] grandchildren of another.
When creating a widget with a QWidget parent, that widget is also only "drawn inside" it: it can only be visible within the boundaries of the parent, not outside it; note that this is usually optional, as most times you'll add a widget to a layout manager (but you didn't), which automatically reparents the widget to the widget that is managed by that layout: if it was a child of another widget, it's removed from that and added to the new one. Also consider that if a widget has no parent and is not added to a layout, it's considered a "top level widget" (a standalone window), and if show() or setVisible(True) is called it will have its own window.
This means that if the widget has a parent and the parent becomes visible, the child will be automatically visible too (unless hide() or setVisible() was explicitly called).
Note that while a QWidget that has no parent is always a top level window, a widger that has a parent could be a top level window, and that happens if it has the QtCore.Qt.Window flag set (using the f keyword in the constructor, or with setWindowFlags()). This is useful when the widget has to be a top level window, but the parentship must still be preserved, not only for memory management (when the parent window is closed and deleted, any child should be deleted as well) but for interaction purposes: for example, with QDialogs (which are standalone windows) it's important to set the parent, so that they can be modal to that parent, meaning that no keyboard/mouse interaction can happen on the parent until the dialog is closed.
Finally, it's important to remember that creating widgets "on the fly" in python has a (sometimes perceived incoherent) result: if no python reference is given to the widget but the widget has a parent, it won't garbage collected.
Consider the following:
def test1(self):
someDialog = QtWidgets.QDialog()
someDialog.show()
This will probably show a window just for an instant, and then it will disappear instantly.
def test2(self):
someDialog = QtWidgets.QDialog(self)
someDialog.show()
In this case, the dialog will not disappear, that's because it has a parent, PyQt knows it, and it will not try to delete it.
Even this is actually valid (but, no, don't do it):
def test2b(self):
QtWidgets.QDialog(self).show()
Similar result, but only apparently:
def test3(self):
self.someDialog = QtWidgets.QDialog()
self.someDialog.show()
In the case above, the visual result will be probably the same as test2, but if test3 is called again while the dialog is still visible, it will delete the current one and create a new one: that's because the dialog has no parent, and overwriting self.someDialog with a new instance will cause python to release the previous one, which will tell Qt that it can safely erase it, since it has no parent.
Finally, it's not necessary to set the parent to widgets, as long as they are added to a layout (which is the preferred approach, as using fixed geometries is highly discouraged), since the layout will automatically set the parent anyway as soon as it's [going to be] set on the actual parent.
Note that QDialog is normally shown with exec_() in order to correctly display it as modal of the parent and use it as it should.
Note the import statements at the top
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QLineEdit, QPushButton
QLabel and QLineEdit as can be seen in the documentations
https://doc.qt.io/qtforpython/PySide2/QtWidgets/QLabel.html
https://doc.qt.io/qtforpython/PySide2/QtWidgets/QLineEdit.html
both take a QWidget as a parameter. The self passed as the second parameter indicates the current QWidget type. For other methods inside the class, self is used to invoke the instance methods.

Move pyqt button out of list of buttons

I have a list of pyqt4 push button and want to move the position. Since it is troublesome it make lots of buttons variable I create them through a list. The code below
import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget, QVBoxLayout
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout(self)
self.buttons = []
for i in range(3):
self.buttons.append(QPushButton('',self))
self.buttons[-1].setFixedWidth(50)
self.buttons[-1].setFixedHeight(50)
self.buttons[-1].move(70*i+50,300)
layout.addWidget(self.buttons[-1])
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.resize(500,500)
window.show()
sys.exit(app.exec_())
don't work for specify the position
but
class Window(QWidget):
def __init__(self):
QWidget.__init__(self)
layout = QVBoxLayout(self)
self.button1 = QPushButton('',self)
self.button1.setFixedWidth(50)
self.button1.setFixedHeight(50)
self.button1.move(50,300)
self.button2 = QPushButton('',self)
self.button2.setFixedWidth(50)
self.button2.setFixedHeight(50)
self.button2.move(120,300)
self.button3 = QPushButton('',self)
self.button3.setFixedWidth(50)
self.button3.setFixedHeight(50)
self.button3.move(190,300)
layout = QVBoxLayout(self)
layout.addWidget(self.button1)
layout.addWidget(self.button2)
layout.addWidget(self.button3)
works fine.
What's the reason behind?
If you want to manually specify the geometry (position and size) of widgets, you should not add them to a layout.
Your second example "works" just because you already created and set a layout to the top-level widget (the Window class), and since a layout already exists the second one is not "installed". Running it from the console will shows this error:
StdErr: QLayout: Attempting to add QLayout "" to Window "", which already has a layout
When a widget is added to a layout, the ownership of the "layout item" (an abstract item used by layouts to manage its widgets) is transferred to the layout, and the widget is reparented to the widget that uses that layout.
Since the second layout cannot be set, it will not manage the geometries of the items you tried to add, and the result is that they will keep the geometries you set before.
The same result can be obtained if you remove all the last lines referencing the other layout, which is exactly what you need.
Also, note that in order to add a widget that is not managed by a layout, a parent is required. Your last example also works because you specified the window as the parent while instantiating them; if you don't do that the buttons will not be shown, but if you do show them (with show() or setVisible(True)) they will each appear in separate windows, as they will become their own top level windows.
If you don't have other widgets that should be managed by a layout, you can also avoid creating the first layout at all (but, still, the parent is required).
Finally, let me tell you that using manual geometries is generally discouraged, and there are very few and specific cases for which it's a good idea to go with.
The main reason behind this is that widgets tend to show very differently from device to device, and that depends on various aspects:
different OS and OS versions draw widgets differently (sometimes dramatically), and this involves varying the widget size and behavior; while this might not be a major issue for simple widgets, it can be a problem for things like item views, spinboxes, etc;
different OS and systems use different styles, which also includes things like internal widget content margins and minimum size;
specific system settings (most importantly, the default font) could make widgets mostly unreadable, specifically with text being clipped within the widget margins if the font size is too big;
depending on the OS, you might face issues if the system uses High DPI displays, and you could end up with very tiny widgets that are almost impossible to interact with;
fixed geometries force you (and the user) to have a fixed widget/window size, and (along with the DPI issue above) this can be a problem: the same widget could look too big or too small;

QFileDialog opens in new window while adding it to QHBoxLayout

My issue is that when I am adding QFileDialog to QVBoxLayout it opens in new window. Below is the code which produces my problem.
from PyQt5.QtWidgets import QVBoxLayout, QFileDialog, QPushButton, QWidget
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My own MainWindow")
self.fileDialog = QFileDialog()
self.confirmAction = QPushButton("Press me", self)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.fileDialog)
mainLayout.addWidget(self.confirmAction)
self.setLayout(mainLayout)
According to the docs:
Window flags are a combination of a type (e.g. Qt::Dialog) and zero or
more hints to the window system (e.g. Qt::FramelessWindowHint).
If the widget had type Qt::Widget or Qt::SubWindow and becomes a
window (Qt::Window, Qt::Dialog, etc.), it is put at position (0, 0) on
the desktop. If the widget is a window and becomes a Qt::Widget or
Qt::SubWindow, it is put at position (0, 0) relative to its parent
widget.
So these flags are used to vary the behavior of the widget, for example to convert it into a window, a dialog, a tooltip, and so on.
In the docs gives the following list:
Qt::Widget: This is the default
type for QWidget. Widgets of this type are child widgets if they have
a parent, and independent windows if they have no parent. See also
Qt::Window and Qt::SubWindow.
Qt::Window: Indicates that the
widget is a window, usually with a window system frame and a title
bar, irrespective of whether the widget has a parent or not. Note that
it is not possible to unset this flag if the widget does not have a
parent.
Qt::Dialog :Window Indicates that the widget is a
window that should be decorated as a dialog (i.e., typically no
maximize or minimize buttons in the title bar). This is the default
type for QDialog. If you want to use it as a modal dialog, it should
be launched from another window, or have a parent and used with the
QWidget::windowModality property. If you make it modal, the dialog
will prevent other top-level windows in the application from getting
any input. We refer to a top-level window that has a parent as a
secondary window.
Qt::Sheet: Window Indicates that the
window is a Macintosh sheet. Since using a sheet implies window
modality, the recommended way is to use QWidget::setWindowModality(),
or QDialog::open(), instead.
Qt::Drawer: Window Indicates
that the widget is a Macintosh drawer.
Qt::Popup : Window Indicates that the widget is a pop-up top-level window, i.e.
that it is modal, but has a window system frame appropriate for pop-up
menus.
Qt::Tool: Window Indicates that the widget is a
tool window. A tool window is often a small window with a smaller than
usual title bar and decoration, typically used for collections of tool
buttons. If there is a parent, the tool window will always be kept on
top of it. If there isn't a parent, you may consider using
Qt::WindowStaysOnTopHint as well. If the window system supports it, a
tool window can be decorated with a somewhat lighter frame. It can
also be combined with Qt::FramelessWindowHint.
On Mac OS X, tool windows correspond to the Floating class of windows.
This means that the window lives on a level above normal windows; it
impossible to put a normal window on top of it. By default, tool
windows will disappear when the application is inactive. This can be
controlled by the Qt::WA_MacAlwaysShowToolWindow attribute.
Qt::ToolTip:Window Indicates that the widget is a
tooltip. This is used internally to implement tooltips.
Qt::SplashScreen: Window Indicates that the window is a
splash screen. This is the default type for QSplashScreen.
Qt::Desktop:Window Indicates that this widget is the
desktop. This is the type for QDesktopWidget.
Qt::SubWindow: Indicates that this widget is a sub-window,
such as a QMdiSubWindow widget.
In your case we must change the behavior of Qt::Dialog to Qt::Widget, in the following code I show the code that does it:
class MainWindow(QWidget):
def __init__(self):
super(MainWindow, self).__init__()
self.setWindowTitle("My own MainWindow")
self.fileDialog = QFileDialog(self)
self.fileDialog.setOption(QFileDialog.DontUseNativeDialog)
self.fileDialog.setWindowFlags(Qt.Widget)
self.confirmAction = QPushButton("Press me", self)
mainLayout = QVBoxLayout()
mainLayout.addWidget(self.fileDialog)
mainLayout.addWidget(self.confirmAction)
self.setLayout(mainLayout)
Screenshot:
I've been looking into this myself, dissatisfied with the "just use non-native dialogs" bit. I've been hacking around in the KDE platform file dialog implementation and have gotten (stuck) pretty close to what I'd want.
The only point of attach I've found is just before the dialog will actually be shown; before that there seems to be no way to know the actual parent widget. But there we can:
find the parent QWidget (from the parent QWindow)
from that, obtain the (first) (user-side) QFileDialog instance
if the parent QWidget has a layout, replace the found QFileDialog instance with our own dialog
save the original user-side QFileDialog instance
in the dtor, either restore the original QFD in the layout, or call deleteLater() on it (and set it to NULL in case the action causes recursive calling of the dtor).
Glitches:
- dialogs may end up with 2 sets of OK/Cancel/etc. buttons
- if not, these buttons may actually close only the embedded QFD and not the enclosing dialog (seen with the python example linked above)
- resizing works but the saveSize/restoreSize mechanism doesn't
- AFAICT all signals aren't connected properly (the preview in the Scribus open-file dialog doesn't react to selecting a file as it should). File opening does work though.
Full patch here on this BKO ticket:
https://bugs.kde.org/show_bug.cgi?id=404833#c15
Evidently this is only useful for hackers and software that can ship its own platform theme plugin (which is where the KDE platform file dialog comes from). Fortunately those plugins tend to be relatively small.

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