Tkinter window resize to fit content without touching - python

I have created an application that required to use the place() manager. I tried using pack() and grid() but after many tries and lots of effort it did not work for my goals. Since I use relx= and rely= almost all the time and I want to put my app on multiple OS's I need to have the window resize to fit all the widgets without them touching.
Is there a way to do this? Since many OS's and their updates change rendering the sizes of widgets changes greatly and I don't want the user to have to resize the window all the time. I want it to just fit as tightly as possible or get the minimum width and height to I can add some buffers. Is this possible? If not, is there a way to fix my app without having to rewrite everything?
Note:
I found something similar: Get tkinter widget size in pixels, but I couldn't properly get it to work.

I figured it out. You can use the widget.winfo_height()/widget.winfo_width() functions to get the minimum pixel sizes. Since the window.geometry() function requires pixel sizes you can make two lists: one for width, and one for height. By adding the minimum amount of widgets you can get the desired size.
Code:
height_widget_list = [main_label, main_notebook, the_terminal, quit_button, settings_button]
width_height_list = [commands_label, main_notebook, quit_button]
widget_list = [main_label, main_notebook, the_terminal, quit_button, settings_button, commands_label]
# Widgets must be updated for height and width to be taken
for widget in widget_list:
widget.update()
height_required_list = []
width_required_list = []
# Collects data
for widget in height_widget_list:
height_required_list.append(int(widget.winfo_height()))
for widget in width_height_list:
width_required_list.append(int(widget.winfo_width()))
# Get height requirement
minimum_height = 0
for height in height_required_list:
minimum_height += height
# Get width requirement
minimum_width = 0
for width in width_required_list:
minimum_width += width
# Make window correct size make window require the correct sizing
window.geometry("{}x{}".format(minimum_width, minimum_height))
window.resizable(False, False)

Related

Remove empty space at bottom of QTableWidget

I'm using PySide6 6.4.1 to build a table widget that automatically resizes to the number of rows. Here's a minimal example:
from PySide6.QtWidgets import *
class MW(QWidget):
def __init__(self):
super().__init__()
self.button = QPushButton("Test")
self.table = QTableWidget(self)
self.table.setColumnCount(1)
self.table.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
self.setLayout(QVBoxLayout(self))
self.layout().addWidget(self.button)
self.layout().addWidget(self.table)
self.button.clicked.connect(self.test)
return
def test(self):
self.table.insertRow(0)
self.table.setItem(0, 0, QTableWidgetItem("new item"))
self.table.adjustSize()
self.adjustSize()
return
app = QApplication()
mw = MW()
mw.show()
app.exec()
Somehow this always leaves a bit of empty space at the bottom of the table. How do I get rid of this space without doing manual resizing?
(Nevermind the weird font size, it's a known bug when using UI scaling. I've adjusted the font size manually as well and it doesn't get rid of this problem.)
Qt item views inherit from QAbstractScrollArea, which has some peculiar size related aspects:
it has an Expanding size policy that tells the parent layout it can use as much space as possible, possibly increasing the available space at initialization;
it has a minimumSizeHint() that always includes a minimum reasonable size allowing showing the scroll bars (even if they are not visible);
if the sizeAdjustPolicy is AdjustToContents it's also based on the viewport size hint;
It's also mandatory to consider a fundamental aspect about scroll areas: size management is a tricky subject, and some level of compromise is necessary most of the times. This is the case whenever the scroll bars potentially change the available size of the viewport (the part of the widget that is able to scroll), which is the default behavior of Qt in most systems, unless the scroll bars are always hidden/visible or they are transient (they "overlay" above the viewport without affecting its available visible size).
To clarify this aspect, consider a scroll area with content that has a minimum size of 100x100 and scroll bars that have a default extent (width for the vertical one, height for the horizontal) of 20: if the height hint of the content is changed to 110, then you'd theoretically need an area of 100x110. But Qt needs to know the hints before laying out widgets and setting their geometries. This means that you cannot know if the scroll bars have to be shown before the widget is finally laid out, but that hint is required to lay out the widget itself. So, recursion.
Qt layout management is a system that is far from perfect, but I doubt that there is one, at least considering normal UI management (don't consider web layouts: their concept is based on different assumption, most importantly the fact that the whole "window" has potentially infinite dimensions). This is an aspect that must be always considered, especially if the shown contents are set to adapt their size based on the contents; it's the case of fitInView() of QGraphicsView or the known issues of QLayout with rich text based widgets.
Qt doesn't provide "foolproof" solutions for these aspects, because its layout management doesn't allow it as it has been implemented primarily considering performance and usability: the UI has to work and be responsive before being "fancy".
It's one of the reasons for which it's almost impossible to have real fixed-aspect-ratio widgets or windows. You can work around it, but at some point you'll have some inconsistencies, and you have to live with that. Also consider that this kind of behavior is generally not very UX-friendly. UI elements that resize themselves (and, consequentially, alter the whole layout) at anytime are usually annoying and very user-unfriendly, especially if they displace their or other contents: it's like having a car that constantly moves the driving controls depending on the amount of passengers.
That said, it's not impossible to have a partially working solution.
The requirements are to:
override minimumSizeHint(), so that a minimal reasonable size is always returned;
override sizeHint() that is used to adjust the widget (and parents) based on the contents of the view;
change the vertical size policy of the table to Preferred, which will tell the layout manager that the height of the size hint will be considered as default, still allowing it to expand in case other items in the layout don't use the remaining space, and eventually shrink it if required;
eventually do the same for the horizontal policy in order to adapt it to the actual horizontal header size, otherwise use self.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch), but be aware that this might complicate things (see the note after the code);
class TableWidget(QTableWidget):
def sizeHint(self):
hHeader = self.horizontalHeader()
vHeader = self.verticalHeader()
f = self.frameWidth() * 2
# the simple solution is to get the length, but this might be a problem
# whenever *any* section of the header is set to Stretch
targetWidth = width = f + hHeader.length()
# a possible alternative (but still far from perfect):
width = f
for c in range(self.columnCount()):
if hHeader.isSectionHidden(c):
continue
width += self.sizeHintForColumn(c)
targetWidth = width
if not vHeader.isHidden():
width += vHeader.width()
hpol = self.horizontalScrollBarPolicy()
height = f + vHeader.length() + hHeader.height()
if (
hpol != Qt.ScrollBarAlwaysOff
and not self.horizontalScrollBar().isHidden()
and (
hpol == Qt.ScrollBarAlwaysOn
and hHeader.length() + f < targetWidth
)
):
height += self.horizontalScrollBar().sizeHint().height()
return QSize(width, height)
def minimumSizeHint(self):
hint = self.sizeHint()
minHint = super().minimumSizeHint()
return QSize(
min(minHint.width(), hint.width()),
min(super().minimumSizeHint().height(), hint.height())
)
class MW(QWidget):
def __init__(self):
# ...
pol = self.table.sizePolicy()
pol.setVerticalPolicy(QSizePolicy.Preferred)
self.table.setSizePolicy(pol)
Be aware that the above doesn't solve all problems. It might work fine for a QTableView having just one column or when using the default interactive (or fixed) section resize mode, but whenever you set different resize modes for each column the result may be wrong.
In order to provide a finer resize, you'll need to do much complex computations that take into account each section resize mode for the horizontal header, the default/minimum/maximum and eventually the hint based on the content.
Further notes: 1. calling adjustSize() on the parent is normally enough, it's not necessary to do it on the children; 2. self.setLayout(QVBoxLayout(self)) is pointless, the self argument already sets the layout; just use layout = QVBoxLayout(self) and use that as a local variable to add widgets; 3. in Python the return at the end of a function is always implicit, you shall not add it as it's useless, redundant and distracting.

How can I set default screen tkinter not full screen?

I want to set default tkinter screen and not full screen in tkinter.Thank you
root.geometry("500x100") #Width x Height
Use this code to change your Tkinter screen size. Here root is the main window, geometry() is the function used to change screen size. 500 is the width and 100 is the height
its very easy you just need to set the with and heigth of the window by
root.geometry("400x900") # this .geometry() takes a string in the form of width x heigth
You have various arguments for your Tkinter window :
window.attributes("-fullscreen", True)
Changing it to False will disable fullscreen mode, True will activate it
window.geometry("1400x700")
You can set the default size of your GUI with this, but the user will be able to resize it classically
window.minsize(200, 200)
This one will set the minimum size of your GUI, past it, the user will not be able to reduce the window any further. If you set it to the size of your window, you disable the resizing.

Tkinter geometry method to only set width or height

The following will set a tkinter window width and height.
root.geometry("500x500")
Is it possible to only set width or height?
Where can I find a full list of geometry method variations, for setting only select parameters?
I would like to be able to set select parameters, of the window size and/or position, and let the rest be under tkinter dynamic control.
When looking at the tcl/tk documentation, we can see that we do not have to provide a full "widthxheight±x±y" string to the .geometry() method:
with only "widthxheight", the size of the window will be changed but not its position on the screen.
with only "±x±y", the position will be changed but not the size.
However, it is not possible to set separately the width and height of the window with this method. Nevertheless, you can retrieve the dimension you don't want to change and use it in .geometry() with something like
def set_height(window, height):
window.geometry("{}x{}".format(window.winfo_width(), height))
def set_width(window, height):
window.geometry("{}x{}".format(width, window.winfo_height()))
Note: Using root.config(width/height=...) only works for me if the window has never been resized with the mouse or using .geometry()
Use root.config(width=100) or root.config(height=100)
If you define a frame within your window you can set width and height of this frame separately like this:
myframe = tk.Frame(width=200)
This may create the appearance you wish to achieve.
An example program demonstrates how to use this trick:
import tkinter as tk
window = tk.Tk()
window.title('Frame Demo')
frame = tk.Frame(width=300)
frame_2 = tk.Frame()
label = tk.Label(master=frame_2, text="Frame_2")
label.pack()
frame.pack()
frame_2.pack()
window.mainloop()
The frame by the name frame is used only to widen the window and is invisible. No GUI elements should be placed in it. Its only function is to give the main window the needed size of 300, in this example. frame_2 is the canvas, so to say, for the GUI elements.

Tkinter Widget color does not change

When I put a widget inside the frame, the color of the frame vanishes. If it was 'black' before, then after putting a widget(label) inside the frame, the color again becomes white.
Here's my code:
from tkinter import *
root = Tk()
root.geometry("700x600")
f = Frame(root, height = 400, width = 400, bg = 'black')
f.pack()
id = Label(f, text = "Email:", fg = 'blue', font = ('Kristen ITC', 18))
id.pack()
Your Frame is resizing itself to the Label. You need to set...:
...
f.pack_propagate(False)
f.pack()
...
In order for the Frame to maintain its own dimension without affected by the its children widgets.
By default, widgets shrink or expand to fit their contents. When you add the button, the frame shrinks down to fit.
It appears you want the frame to take up just a part of the root window. Instead of explicitly giving the frame a width and height, it's usually better to let tkinter do that for you. Fill the frame with whatever widgets you want, and let tkinter decide how big the frame should be. Then, use appropriate options for grid or pack to arrange them logically. When tkinter is allowed to make widgets the right size, you'll end up with a much more responsive UI.
For example, if you set the fill and expand options when you call pack on the frame, it will not shrink to fit. If you later need to add more widgets, you won't have to modify other parts of your code to make them fit.
f.pack(fill="both", expad=True)
You can also turn off this "shrink to fit" feature by calling f.pack_propagate(False), but that is rarely the right solution because it forces you to calculate sizes, and your calculations may be wrong if you run the program on a system with different fonts or different resolutions.

Set minimal reasonable width for a QScrollArea

I am relatively new to Qt, which I access through PySide.
I have a longish list of content that I want to make vertically scrollable.
The horizontal size is not an issue. I tried using QScrollArea for that. Here is a minimal example:
import sys
import PySide.QtGui as gui
application = gui.QApplication(sys.argv)
window = gui.QScrollArea()
list = gui.QWidget()
layout = gui.QVBoxLayout(list)
for i in range(100):
layout.addWidget(gui.QLabel(
"A something longish, slightly convoluted example text."))
window.setWidget(list)
window.show()
sys.exit(application.exec_())
What happens:
The scroll-area sets its horizontal size to the size needed for the labels
It notices that the vertical space is insufficient, so it adds a vertical scrollbar.
Due to the vertical scrollbar, the horizontal space is now insufficient as well, and so the horizontal scrollbar is also shown.
I can make the horizontal scrollbar go away with setHorizontalScrollBarPolicy, but the main problem persists: the vertical scrollbar obscures part of the labels.
How can I set the width of the scroll-area to the minimal value which does not need a horizontal scrollbar?
Your example is somewhat unrealistic, because the scroll-area is made the top-level window. More typically, it would be a child widget, and its initial size would be determined by a layout, and would be indirectly constrained by the sizes of other widgets and/or layouts. As a top-level window, the constraints are different, and partly under the influence of the window-manager (the exact behaviour of which can vary between platforms).
To ensure that the scroll-area has the correct size, you must set a minimum width for it, based on its contents, and also allowing for the vertical scrollbar and the frame. It is also probably best to set the widgetResizable property to True and add an expandable spacer to the end of the contents layout.
In the example below, I have changed the background colour of the labels to make it easier to see what is going on. I have also allowed resizing the window smaller than its initial size, by resetting the minimum width after it is shown - but that is optional.
import sys
import PySide.QtGui as gui
application = gui.QApplication(sys.argv)
window = gui.QScrollArea()
window.setWidgetResizable(True)
list = gui.QWidget()
layout = gui.QVBoxLayout(list)
# just for testing
window.setStyleSheet('QLabel {background-color: red}')
for i in range(30):
layout.addWidget(gui.QLabel(
"A something longish, slightly convoluted example text."))
layout.addStretch()
window.setWidget(list)
window.setMinimumWidth(
list.sizeHint().width() +
2 * window.frameWidth() +
window.verticalScrollBar().sizeHint().width())
window.show()
# allow resizing smaller
window.setMinimumWidth(1)
sys.exit(application.exec_())
Try to use sizeHint() in the widgets and use the result to set the minimum width of the QScrollArea.
Some extra info here: http://doc.qt.io/qt-5/layout.html

Categories