Adaptive resizing for a tkinter Text widget - python

I have been attempting to create a application that contains two Text() widgets, both of which can dynamically resize when the window size is changed. Before I have always used the root.pack() manager, with fill='both' and expand=True.
While this works for LabelFrames and most other widgets, it does not work when a Text widget is resized smaller then its original dimensions. Is there a way to have dynamically resizing Text widgets?
Ex.
import tkinter as tk
window = tk.Tk()
editor = tk.Text(bg='red')
editor.pack(side='top',fill='both',expand=True)
output = tk.Text(bg='green')
output.pack(side='top',fill='both',expand=True)
window.mainloop()

Tkinter will try to honor the requested size of a text widget. Since you didn't specify a size, the text widget will request a size of 80x24. When you resize the window smaller, pack tries to make room for everything at its requested size, and it does so in stacking order.
As the window shrinks, there's room for all of the first text widget but not enough for both. Because there's not enough room, it has to subtract space from the remaining widgets. Thus, it starts chopping off the last text widget.
To combat this, you can set the requested size of the text widgets to a small value that will fit in almost any window size, and then force them to grow by setting the size of the window as a whole. This way, pack will first allocate enough space for each small window, and then expand them equally when there's extra space.
For example:
import tkinter as tk
window = tk.Tk()
window.geometry("400x400")
editor = tk.Text(bg='red', width=1, height=1)
output = tk.Text(bg='green', width=1, height=1)
editor.pack(side='top',fill='both',expand=True)
output.pack(side='top',fill='both',expand=True)
window.mainloop()
The other solution is to use grid which lets you specify that rows and columns should be of uniform size.
import tkinter as tk
window = tk.Tk()
editor = tk.Text(bg='red')
output = tk.Text(bg='green')
editor.grid(row=0, column=0, sticky="nsew")
output.grid(row=1, column=0, sticky="nsew")
window.grid_columnconfigure(0, weight=1)
window.grid_rowconfigure((0,1), weight=1, uniform=1)
window.mainloop()

Related

Is there a way to change the size of the contents of a GUI when the GUI is resized by the user?

I've made a GUI using Tkinter. For the time being, I set the geometry to '962x652' and made it so the user can't resize it by using .resizable(0, 0). I'm now looking for a way to make it so when the GUI is resized, all the elements change size as well along side it, preferably with the aspect ratio locked.
Is there a way to achieve this whilst making it so only the element change size visibly without changing actually changing size? For example, if my GUI contains a scrolledtext box, if the user was to resize the GUI, the scrolledtext will also increase in size, but so will the text and each line of text in it will stay in the same line. I hope that makes sense.
I've add some code down below to show what I'm working with. This should have a similar effect to what I currently have:
import tkinter as tk
from tkinter import *
from tkinter import scrolledtext
TrialGUI = Tk()
TrialGUI.title('Resize GUI')
TrialGUI.geometry('962x652+0+0')
#Remove the following so you can resize the GUI
TrialGUI.resizable(0, 0)
#These are the two frames. I've changed their colour so they are visible when they are resized.
ABC1b = Frame(TrialGUI, bg='lightpink', bd=20, width=900, height=600)
ABC1b.grid(row=0, column=0)
ABC2 = Frame(TrialGUI, bg='lightblue', bd=20, width=452, height=600)
ABC2.grid(row=0, column=1)
#This is the text box
txtQuestion = scrolledtext.ScrolledText(ABC1b, wrap=tk.WORD, width=42, height=10, font=(14))
txtQuestion.grid(row=0, column=0, columnspan=4, pady=3)
#This inserts text into it. I made it insert which line the text should be in so it is easier to check if it is still in the same line when it is resized.
txtQuestion.insert(tk.INSERT, 'This should be in the first line.----------------------------------- This should be in the second line.------------------------------ This should be in the third line.----------------------------------')
TrialGUI.mainloop()
If anyone decides to play around with this code, remember to remove TrialGUI.resizable(0, 0) so it can be resized.

Python - is there any way to expand the resolution ratio for existing Tkinter Tk() and Toplevel() widget? (zooming-in)

I just made an app using python and tkinter widgets.
There are Labels, Frames, Buttons, etc in the Tk and Toplevel widgets.
However, it includes thousands of codes and its really annoying to resize every widgets when I support multiple resolutions.
Is there any way to expand the resolution ratio for existing Tkinter Tk() and Toplevel() widget and their child widgets? (zooming-in)
If not, what would be the best approach to support multiple resolutions of a python app with the same ratio?
Any help would be much appreciated, sorry for bad English.
Yes, this is possible however it depends on the geometry manager you have used in your program.
For the .pack() method (which is arguably the simplest geometry method for "intelligent" GUI designs) you can use a range of attributes on when you declare .pack() on the widget. These attributes include (but are not limited to) fill, expand, anchor, padx, pady, etc.
The below shows an example of a set of three buttons which will automatically expand to fit the window if it changes or is initialised to a different size than was used during development.
from tkinter import *
root = Tk()
btn1 = Button(root, text="btn1")
btn2 = Button(root, text="btn2")
btn3 = Button(root, text="btn3")
btn1.pack(fill="both", expand=True)
btn2.pack(fill="both", expand=True)
btn3.pack(fill="both", expand=True)
root.mainloop()
For the .grid() method you will need to make use of the functions Grid.columnconfigure() and Grid.rowconfigure. Both of these have the attribute weight which determines which rows and columns should be given priority for assignment of extra space if more becomes available in the window. Setting all rows and columns to have a weight of 1 means they will all be given space equally. You will also need to use the sticky attribute when declaring .grid() on the widgets.
The below shows an example of a set of three buttons which will automatically expand to fit the window if it changes or is initialised to a different size than was used during development.
from tkinter import *
root = Tk()
for column in range(3):
Grid.columnconfigure(root, column, weight=1)
for row in range(1):
Grid.rowconfigure(root, row, weight=1)
btn1 = Button(root, text="btn1")
btn2 = Button(root, text="btn2")
btn3 = Button(root, text="btn3")
btn1.grid(column=0, row=0, sticky=N+S+E+W)
btn2.grid(column=1, row=0, sticky=N+S+E+W)
btn3.grid(column=2, row=0, sticky=N+S+E+W)
root.mainloop()
Using .place() would be a lot more difficult, you would need to have a function setup which would trigger on every window resize event which would calculate the size that the buttons need to expand to.
This would look something like the below:
from tkinter import *
class App:
def __init__(self, root):
self.root = root
self.button = Button(self.root, text="Button")
self.button.place(relx=0.5, rely=0.5, anchor="center")
self.root.bind("<Configure>", self.resize)
def resize(self, *args):
self.button.configure(width=self.root.winfo_width(), height=self.root.winfo_height())
root = Tk()
App(root)
root.mainloop()
Subjectively speaking, .pack() tends to be easier, however this all comes down to how much effort you're willing to put in to implement this with your current program.
Can't comment so I add a short tip to the detailed Ethan answer. You can design most of the GUIs in tkinter with either pack, grid or a combination of both (by placing frames on a window with one of them, and using either grid or pack inside of each frame, to "place" the widgets). You can tune the configurations for proper location and size when the window resizes. Keep placer use for special cases (like overlaying some widget on the top of others)

condensing/reducing vertical spacing between widgets in tkinter (Python) (AKA: setting vertical line/text spacing/padding)

Here's my program:
import tkinter as tk
#Create main window object
root = tk.Tk()
#build GUI
for i in range(5):
tk.Label(root, text="hello", height=0).grid(row=i)
#mainloop
root.mainloop()
It produces the following (running in Xubuntu 16.04 LTS)
Notice all that extra vertical space between the lines of text. I don't want that! How do I decrease it?
If I run this code instead:
import tkinter as tk
#Create main window object
root = tk.Tk()
#build GUI
for i in range(5):
tk.Label(root, text="hello", height=0).grid(row=i)
tk.Grid.rowconfigure(root, i, weight=1) #allow vertical compression/expansion to fit window size after manual resizing
#mainloop
root.mainloop()
...it opens up and initially looks exactly the same as before, but now I can manually drag the box vertically to shrink it, like so:
Notice how much more vertically-compressed it is! But, how do I do this programatically, so I don't have to manually drag it to make it this way? I want to set this tight vertical spacing from the start, but no matter which parameters I change in Label, grid, or rowconfigure I can't seem to make it work without me manually dragging the box with the mouse to resize and vertically compress the text.
There are many ways to affect vertical spacing.
When you use grid or pack there are options for padding (eg: pady, ipady, minsize). Also, the widget itself has many options which control its appearance. For example, in the case of a label you can set the borderwidth, highlightthickness and pady values to zero in order to make the widget less tall.
Different systems have different default values for these various options, and for some of the options the default is something bigger than zero. When trying to configure the visual aspects of your GUI, the first step is to read the documentation, and look for options that affect the visual appearance. Then, you can start experimenting with them to see which ones give you the look that you desire.
In your specific case, this is about the most compact you can get:
label = tk.Label(root, highlightthickness=0, borderwidth=0, pady=0, text="hello")
label.grid(row=i, pady=0, ipady=0)
You can programatically modify the geometry just before starting the main loop instead of manually dragging it (change 0.6 to whatever % reduction you want):
import tkinter as tk
#Create main window object
root = tk.Tk()
#build GUI
for i in range(5):
label = tk.Label(root, text = 'hello')
label.grid(row=i)
tk.Grid.rowconfigure(root, i, weight=1) #allow vertical compression/expansion to fit window size after manual resizing
#mainloop
root.update()
root.geometry("{}x{}".format(root.winfo_width(), int(0.6*root.winfo_height())))
root.mainloop()
Here is a screenshot of the result running on Xubuntu 16.04 LTS with Python 3.5.2:

Setting Tkinter/ttk Frame background color

I'm trying to change the background color of a ttk frame and I've looked up other examples, but none have seemed to work. This is my code so far:
from Tkinter import *
import ttk
p = Tk()
p.geometry('600x350')
p.configure(bg='#334353')
gui_style = ttk.Style()
gui_style.configure('My.TButton', foreground='#334353')
gui_style.configure('My.TFrame', background='#334353')
frame = ttk.Frame(p, style='My.TFrame')
frame.grid(column=1, row=1)
ttk.Button(frame, text='test', style='My.TButton').grid(column=0, row=0)
ttk.Button(frame, text='Test 2', style='My.TButton').grid(column=3, row=3)
p.mainloop()
The window has the background color that I want, but the frame still has the default gray background. Is there something i need to add differently? I want the entire window except for the buttons to be the color #334353. How do I do this?
EDIT: I've attached what my window looks like. I don't want the gray. :/ (Note. I don't have enough rep to post images apparently, so here is a link to imgur with my current window: http://imgur.com/KyhbdMB
Your frame is only sized to the minimum size required to hold the two child windows (the buttons). It seems like you want the frame to fill the main window. When you grid the frame you should add the sticky option to have it expand to fill the available space (eg: frame.grid(column=1,row=1,sticky='news')). Then you need to have the parent allocate all the space space to this grid cell. For that you want to use the grid_rowconfigure and grid_columnconfigure methods for the parent window. In this case:
p.grid_columnconfigure(1, weight=1)
p.grid_rowconfigure(1, weight=1)
which tells the main frame grid geometry manager that spare space should be given to the cell and row 1 column 1. This will lead to your frame expanding to fill the window.
It works on my PC!
Try this:
Update your Python environment(Tested under Py 3.4 Windows 32bit)
Install the lastest TTK package

Tkinter multi-frame resize

I have a device that understands multiple serial protocols. During development I created simple Tkinter UIs to play with the protocols. Each protocol got a new UI. Since the protocols have lots of commands, I implemented the entire UI within a scrollable canvas to permit it to be scrolled when used on smaller displays. The separate UIs worked fine, and I'm now trying to combine the separate UIs into a tabbed UI.
The common elements of each UI are the serial port selector, which I separated out and put into a separate top frame. I then implemented a notebook, and put each protocol UI into a frame for each tab.
But I'm unable to properly control sizing: I want the root window width to be fixed at the maximum width of any of the protocol frames or serial selector frame, with horizontal resizing disabled. I want the serial selector to always be present, and not be affected when the window is vertically resized (only the notebook is resized/scrolled).
Below is what I have so far. All the pieces are present, but the notebook doesn't fill the complete window width, and the notebook doesn't resize when the window is resized (resizing just adds blank space).
def main():
## Main window
root = Tkinter.Tk()
root.title("Multi-Protocol UI")
## Grid sizing behavior in window
root.grid_rowconfigure(0, weight=0)
root.grid_rowconfigure(1, weight=1)
root.grid_columnconfigure(0, weight=1)
root.grid_columnconfigure(1, weight=0)
## Window content
upper = ttk.Frame(root) # Serial port selector
upper.grid(row=0)
upper.grid_rowconfigure(0, weight=0)
upper.grid_columnconfigure(0, weight=1)
upper.grid_columnconfigure(1, weight=0)
lower = ttk.Frame(root) # For protocols
lower.grid(row=1)
lower.grid(row=1, sticky='nswe')
lower.grid_rowconfigure(0, weight=1)
lower.grid_columnconfigure(0, weight=1)
lower.grid_columnconfigure(1, weight=0)
# Setup serial control frame
serial = SerialFrame(master=upper) # Serial port selector widget + Open button
## Protocol GUIs are large: Use a form within a scrollable canvas.
cnv = Tkinter.Canvas(lower)
cnv.grid(row=0, column=0, sticky='nswe')
cnv.grid_rowconfigure(0, weight=1)
cnv.grid_columnconfigure(0, weight=1)
cnv.grid_columnconfigure(1, weight=0)
# Scrollbar for canvas
vScroll = Tkinter.Scrollbar(
lower, orient=Tkinter.VERTICAL, command=cnv.yview)
vScroll.grid(row=0, column=1, sticky='ns')
cnv.configure(yscrollcommand=vScroll.set)
# Frame in canvas
window = Tkinter.Frame(cnv)
window.grid()
# Put the frame in the canvas's scrollable zone
cnv.create_window(0, 0, window=window, anchor='nw')
# Setup the notebook (tabs) within the scrollable window
notebook = ttk.Notebook(window)
frame1 = ttk.Frame(notebook)
frame2 = ttk.Frame(notebook)
notebook.add(frame1, text="ProtoA")
notebook.add(frame2, text="ProtoB")
notebook.grid(row=0, column=0, sticky='nswe')
# Create tab frames
protoA = ProtoAFrame(master=frame1)
protoA.grid()
protoA.update_idletasks()
protoB = ProtoBFrame(master=frame2)
protoB.grid()
protoB.update_idletasks()
## Update display to get correct dimensions
root.update_idletasks()
window.update_idletasks()
## Configure size of canvas's scrollable zone
cnv.configure(scrollregion=(0, 0, window.winfo_width(), window.winfo_height()))
# Not resizable in width:
root.resizable(width=0, height=1)
## Go!
root.mainloop()
How do I lock down the top serial frame, expand the notebook to full width, and force window resizing to only affect the notebook frame? I'm a Tkinter newbie, so please be gentle if I've missed something "obvious".
TIA!
You have widgets nested inside widgets. Even if you have the notebook set to expand properly in it's container, you need to also make sure that every container upwards also expands properly. You haven't done that. For example, ask yourself whether the canvas has been set up to properly grow inside of lower.
Since you are starting out, here is what I recommend. Instead of trying to get everything right at once, choose a "divide and conquer" approach. First, create frames for the major areas of your GUI. Do nothing but those frames, and get their resize behavior to be exactly what you want. It helps at this stage to give each its own color so you can clearly see where the widgets are. You can always change the color later. Also, if you only have a couple widgets, or all your widgets are oriented horizontally or vertically, pack is often easier to use than grid.
For example:
upper.pack(side="top", fill="x", expand=False)
lower.pack(side="bottom", fill="both", expand=True)
Once you have these major pieces resizing appropriately it's time to solve the problem for just one of those areas. Pick an area, and do the same: add in its children widgets, or subdivide into frames if you have areas within an area. Once you have this working, lather, rinse, repeat.

Categories

Resources