How can I give a widget a fixed position? Like so I can "attach"/put it at the bottom of the window and it will always be there; even when the window is expanded. I couldn't find anything useful on how to do it and, I suppose obviously, none of the obvious things work (resize(), setGeometry(), etc.). Any help?
I assume by "fixed position" you mean a position relative to one of the window edges. That's what your second sentence implies. So that's the question I will answer.
Use a layout manager with stretches and spacings. Here's a simple example to attach a widget "w" to the bottom of a window "win". This code typically gets called by (or goes inside) your window's constructor.
lay = QVBoxLayout(win)
lay.addStretch(1)
lay.addWidget(w)
The BoxLayout makes "w" stick to the bottom of the window and stay in that position as the window is resized.
you must reimplement the parent windows resizeEvent function, here is the code to make a widget "attached" to the bottom of the window:
def resizeEvent(self, event):
#widget.move(x, y)
self.bottom_widget.move(0, self.height() - self.bottom_widget.height())
#if you want the widgets width equal to window width:
self.bottom_widget.setWidth(self.width())
whenever the window is resized, this function will be called and it will move the widget to the bottom of the window. this is an absolute positioning approach, but you can always use QSpacerItem to push your widget to the bottom.
Related
So, I'm using the place method to have a widget overlap other widgets, but its position is relative (with winfo) to a widget that uses pack. When the parent frame is resized, the pack position will change, but the place position will not.
This is my code:
from tkinter import *
root = Tk()
root.geometry("200x300")
search = Entry(root)
search.pack()
search.update()
x = search.winfo_x()
y = search.winfo_y()
width = search.winfo_width()
height = search.winfo_height()
frame = LabelFrame(root, width=width, height=200)
frame.place(x=x, y=y+height)
root.mainloop()
The LabelFrame stays in its x and y position when the window is resized. The Entry widget will be used as a search bar and I want autocompletion under it. There will be widgets under the entry widget and the autocompletion will only appear when you are typing (That's not what I'm looking for though. Its just more exposition if you need it). So, is there a way to have the place widget always be relative to the pack widget. If you have any answers, thank you:)
If your goal is to put one widget relative to another, place lets you do that. It's great for things like tooltips or other transient widgets that don't otherwise fit into a normal layout.
The easiest way to do that is to make the widget a child of the controlling widget. For example, for your frame to be placed relative to the search box you can make it a child of the search box. If it's inconvenient to do that, you can use the in_ parameter to tell place which widget is the other widget.
For example, to place your labelframe immediately below the search box and with the same width as the search box you might do it something like this:
frame.place(
in_=search,
bordermode="outside",
anchor="nw",
relx=0,
rely=1.0,
y=5,
relwidth=1.0
)
This is what the options mean:
in_=search: place the frame relative to the search box
bordermode="outside": relative measurements are from the outside of the border (default is "inside")
anchor="nw": place the widget so that the northwest corner of the frame is at the computed coordinate
relx=0: place the anchor point 0% from the left edge of the search box
rely=1.0: place the frame at 100% of the height of the search box
y=5: add 5 pixels to the computed position so it floats just a little below the window
relwidth=1.0: make the width of the frame 100% the width of the search box.
Obviously you don't have to use y=5, I just added it to illustrate the additive behavior of using rely and y.
I am working in Maya with pyside2 (basically the same as PyQt5).
I have a gui with a scroll area in it... The scroll area has rows of some buttons and stuff that I want to expand horizontally if the user drags the window and expands it larger. Extra rows of these items can be added to it which makes the vertical scroll bar appear (obviously).
I'm trying to determine a way to prevent the slight resize of my rows of items when the scroll bar appears/disappears. Visually, I want the width of the items in each row to say the same if new rows get added and the scroll bar appears, or if the user expands the window some and the scroll bar disappears...
Is there a signal or something that gets sent if the scroll bar's visibility changes so I can adjust the margins of the layout for the items in the scroll area?
I know I can set the scroll bar to be visible or not, but I can't find anything that I can hook into for when this inherently changes as the size of the gui or contents in the scroll area is modified... If there's not already a signal, how could I go about creating one?
Ok, after searching around about the hideEvent like Aaron commented about, I've found something that seems to work...
I created an event filter for the vertical scroll bar in my scroll area... So, in the part of my code where the scroll area is defined, I have something like this:
scroll_area_widget = QtWidgets.QWidget()
self.scroll_area_layout = QtWidgets.QVBoxLayout()
scroll_area_widget.setLayout(self.scroll_area_layout)
scroll_area = QtWidgets.QScrollArea()
scroll_area.setWidget(scroll_area_widget)
self.scrollbar = scroll_area.verticalScrollBar()
self.scrollbar.installEventFilter(self)
Then, I define an 'eventFilter' method like this:
def eventFilter(self, o, e):
if o is self.scrollbar:
if e.type() == QtCore.QEvent.Show:
self.scroll_area_layout.setContentsMargins(5, 0, 3, 0)
elif e.type() == QtCore.QEvent.Hide:
self.scroll_area_layout.setContentsMargins(5, 0, 15, 0)
QtWidgets.QApplication.processEvents()
return False
I put the 'processEvents' in there because I'm getting a flickering when the scrollbar disappears where I think the widgets in the layout stretch out to fill the space before the contents margins gets updated... It doesn't help prevent the flickering every time, but it seems to be less than if I don't have it...
The question now is how do I avoid the flicker? Of course, another question is whether or not this is even a good way to solve this particular issue since I don't know enough about event filters... Is this something that will get triggered only when the scroll bar is shown/hidden, or does it apply to anything in the gui that gets shown/hidden? I ask that since the 'eventFilter' method doesn't seem specific to the scroll bar, and placing the if statement checking if it's the scrollbar seems like it might be a bit of overhead if every event in the whole gui is passing through....
This attached image is the screenshot of an application developed using PyQt5.
The image clearly has an invisible line running in the middle of the boxes enclosing the contents.
What code should I add in my program to draw an invisible line overlaying all other objects created earlier. I couldn't find any documentation regarding this but as the image suggests, it has somehow been implemented.
A code snippet is not needed to be provided by me since this is a question about adding/developing a feature rather than debugging or changing any existing code.
Premise: what you provided as an example doesn't seem a very good thing to do. It also seems more a glich than a "feature", and adding "invisible" lines like that might result in an annoying GUI for the user. The only scenario in which I'd use it would be a purely graphical/fancy one, for which you actually want to create a "glitch" for some reason. Also, note that the following solutions are not easy, and their usage requires you an advanced skill level and experience with Qt, because if you don't really understand what's happening, you'll most certainly encounter bugs or unexpected results that will be very difficult to fix.
Now. You can't actually "paint an invisible line", but there are certain work arounds that can get you a similar result, depending on the situation.
The main problem is that painting (at least on Qt) happens from the "bottom" of each widget, and each child widget is painted over the previous painting process, in reverse stacking order: if you have widgets that overlap, the topmost one will paint over the other. This is more clear if you have a container widget (such as a QFrame or a QGroupBox) with a background color and its children use another one: the background of the children will be painted over the parent's.
The (theoretically) most simple solution is to have a child widget that is not added to the main widget layout manager.
Two important notes:
The following will only work if applied to the topmost widget on which the "invisible line" must be applied.
If the widget on which you apply this is not the top level window, the line will probably not be really invisible.
class TestWithChildLine(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
for row in range(3):
for col in range(6):
layout.addWidget(QtWidgets.QDial(), row, col)
# create a widget child of this one, but *do not add* it to the layout
self.invisibleWidget = QtWidgets.QWidget(self)
# ensure that the widget background is painted
self.invisibleWidget.setAutoFillBackground(True)
# and that it doesn't receive mouse events
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
def resizeEvent(self, event):
super().resizeEvent(event)
# create a rectangle that will be used for the "invisible" line, wide
# as the main widget but with 10 pixel height, then center it
rect = QtCore.QRect(0, 0, self.width(), 10)
rect.moveCenter(self.rect().center())
# set the geometry of the "invisible" widget to that rectangle
self.invisibleWidget.setGeometry(rect)
Unfortunately, this approach has a big issue: if the background color has an alpha component or uses a pixmap (like many styles do, and you have NO control nor access to it), the result will not be an invisible line.
Here is a screenshot taken using the "Oxygen" style (I set a 20 pixel spacing for the layout); as you can see, the Oxygen style draws a custom gradient for window backgrounds, which will result in a "not invisible line":
The only easy workaround for that is to set the background using stylesheets (changing the palette is not enough, as the style will still use its own way of painting using a gradient derived from the QPalette.Window role):
self.invisibleWidget = QtWidgets.QWidget(self)
self.invisibleWidget.setObjectName('InvisibleLine')
self.invisibleWidget.setAutoFillBackground(True)
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
self.setStyleSheet('''
TestWithChildFull, #InvisibleLine {
background: lightGray;
}
''')
The selectors are required to avoid stylesheet propagation to child widgets; I used the '#' selector to identify the object name of the "invisible" widget.
As you can see, now we've lost the gradient, but the result works as expected:
Now. There's another, more complicated solution, but that should work with any situation, assuming that you're still using it on a top level window.
This approach still uses the child widget technique, but uses QWidget.render() to paint the current background of the top level window on a QPixmap, and then set that pixmap to the child widget (which now is a QLabel).
The trick is to use the DrawWindowBackground render flag, which allows us to paint the widget without any children. Note that in this case I used a black background, which shows a "lighter" gradient on the borders that better demonstrate the effect:
class TestWithChildLabel(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QGridLayout(self)
layout.setSpacing(40)
for row in range(3):
for col in range(6):
layout.addWidget(QtWidgets.QDial(), row, col)
self.invisibleWidget = QtWidgets.QLabel(self)
self.invisibleWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
palette = self.palette()
palette.setColor(palette.Window, QtGui.QColor('black'))
self.setPalette(palette)
def resizeEvent(self, event):
super().resizeEvent(event)
pm = QtGui.QPixmap(self.size())
pm.fill(QtCore.Qt.transparent)
qp = QtGui.QPainter(pm)
maskRect = QtCore.QRect(0, 0, self.width(), 50)
maskRect.moveTop(50)
region = QtGui.QRegion(maskRect)
self.render(qp, maskRect.topLeft(), flags=self.DrawWindowBackground,
sourceRegion=region)
qp.end()
self.invisibleWidget.setPixmap(pm)
self.invisibleWidget.setGeometry(self.rect())
And here is the result:
Finally, an further alternative would be to manually apply a mask to each child widget, according to their position. But that could become really difficult (and possibly hard to manage/debug) if you have complex layouts or a high child count, since you'd need to set (or unset) the mask for all direct children each time a resize event occurs. I won't demonstrate this scenario, as I believe it's too complex and unnecessary.
I’m trying to make an application consisting of a QMainWindow, the central widget of which is a QToolBar (it may not be usual, but for my purpose the toolbar’s well suited). Docks are allowed below only. I added a QDockWidget to it, and a QAction on the QToolBar toggles the QDockWidget on and off with removeDockWidget() and restoreDockWidget().
The default size of the QMainWindow is 800 by 24, QToolBar’s maximumHeight is set to 24 too. Right after the removeDockWidget() is called, QMainWindow’s geometry is set back to (0,0,800,24) with setGeometry().
What I want to achieve is to resize the QMainWindow’s height to 24 when the DockWidget’s removed. The setGeometry() seems to work since width and position change accordingly, but funnily enough, the height doesn’t budge. And that’s my problem really :)
What’s the matter you think?
Here is a screen-cast illustrating the issue at hand.
NB: if i create the same scenario using a QWidget rather than QMainWindow, and using a show() or hide() on the child widget, then I can resize the parent with adjustSize() without problem: it seems the problem here above is QMainWindow specific.
Options
a) You can overload sizeHint() a virtual function. Let it return the size you want for your main window.
b) In the main window's constructor you can call setMinimumSize() and setMaximumSize() one after another, both with the the desired main window size. If you keep both same you get a fixed size.
c) Take a look at layout()->setResizeMode(Fixed).
It looks like you misunderstood the meaning of the QMainWindow.sizeHint() method.
According to QWidget.sizeHint() documentation (from which QMainWindow inherits):
This property holds the recommended size for the widget.
If the value of this property is an invalid size, no size is recommended.
The default implementation of sizeHint() returns an invalid size if there is no layout for this widget, and returns the layout's preferred size otherwise.
To get the actual size of your window, you should use the QMainWindow.geometry() method instead, which gives all the informations about the widget size and position:
win_geo = self.geometry()
win_top = win_geo.top()
win_bottom = win_geo.bottom()
win_left = win_geo.left()
win_right = win_geo.right()
win_width = win_geo.width()
win_height = win_geo.height()
I have a glade GUI and i'm using dome gtk.MessageDialog widgets created with pygtk for user interaction. My problem is that whenever I throw a dialog message on the screen, they show up all over the place. One might show up on the top right corner, the next on the bottom left, top left, mid left etc...
Is there a way to force these things to show up in the center of the screen or at the position where the parent window is at?
Never mind. Found the solution.
For others who might wander about the same thing, the solution to this problem lies in specifying a parent value to the gtk.MessageDialog construct.
If you are using a glade gui, in your class, and your glade xml is loaded in to a variable named 'gui', it would look like this:
#!/usr/bin/env/python
par = self.gui.get_widget('your_parent_window')
msg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK, parent=par)
if msg.run():
msg.destroy()
return None
Check out the reference material at PyGTK 2.0 Reference Manual
I have not had a chance to try this but MessageDialog seems to be derived from Window which has a set_position method.
This method accepts one of the following:
# No influence is made on placement.
gtk.WIN_POS_NONE
# Windows should be placed in the center of the screen.
gtk.WIN_POS_CENTER
# Windows should be placed at the current mouse position.
gtk.WIN_POS_MOUSE
# Keep window centered as it changes size, etc.
gtk.WIN_POS_CENTER_ALWAYS
# Center the window on its transient parent
# (see the gtk.Window.set_transient_for()) method.
gtk.WIN_POS_CENTER_ON_PARENT
None of the provided solutions will work if your parent window is not yet shown, that is if the messagedialog is to be shown during the instantiation of a class (your class, not the "parent" window class). During this time Gtk has not yet placed the window, even if code for messagedialog is after the code that shows the window. Which means your dialog box will be somehow "parentless" and the message dialog will appear wherever it likes...
My naive solution for that problem...
GObject.timeout_add(interval=50, function=self.stupid_dialog_1)
and
def stupid_dialog_1(self):
par = self.gui.get_widget('your_parent_window')
msg = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons = gtk.BUTTONS_OK, parent=par)
# do anything here...
return False #stop the timer...