Is it possible to make 'dynamically' adjustable widgets in Tkinter/ttk - python

I'm developing very simple GUI for my DB. It shows record's list/tree in DB on left panel and (if user clicks on some record) shows the record on the right panel.
Here some bit of code which creates GUI
from Tkinter import *
import ttk
master = Tk()
reclist = ttk.Treeview(columns=["TIME STAMP","HASH","MESSAGE"])
ysb = ttk.Scrollbar(orient=VERTICAL, command= reclist.yview)
xsb = ttk.Scrollbar(orient=HORIZONTAL, command= reclist.xview)
reclist['yscroll'] = ysb.set
reclist['xscroll'] = xsb.set
reclist.grid(in_=master, row=0, column=0, sticky=NSEW)
ysb.grid(in_=master, row=0, column=1, sticky=NS)
xsb.grid(in_=master, row=1, column=0, sticky=EW)
Comment = Text(master)
Comment.tag_configure("center", justify='center')
ysc = ttk.Scrollbar(orient=VERTICAL, command= Comment.yview)
xsc = ttk.Scrollbar(orient=HORIZONTAL, command= Comment.xview)
Comment.grid(in_=master,row=0,column=2,sticky=W+E+N+S)#, columnspan=5)
ysc.grid(in_=master, row=0, column=3, sticky=NS)
xsc.grid(in_=master, row=1, column=2, sticky=EW)
master.rowconfigure(0, weight=3)
master.columnconfigure(0, weight=3)
master.columnconfigure(2, weight=3)
master.mainloop()
Everything works pretty well, except that two panels are not adjustable. I cannot move border between them to make list of records or record panel bigger or smaller. I'm pretty sure in is possible (for example in gitk you can move the border between the list of commits and a displaied commit). I've search quite a lot with no luck.

What you are looking for is called a "PanedWindow". Both the tkinter and ttk modules have one, and they work almost identically. The general idea is that you create a PanedWindow instance, and then you add two or more widgets to it. The PanedWindow will add a movable slider between each widget. Typically you would use frames, which you can then fill up with other widgets.
Here is an example using the one in Tkinter:
import Tkinter as tk
root = tk.Tk()
pw = tk.PanedWindow()
pw.pack(fill="both", expand=True)
f1 = tk.Frame(width=200, height=200, background="bisque")
f2 = tk.Frame(width=200, height=200, background="pink")
pw.add(f1)
pw.add(f2)
# adding some widgets to the left...
text = tk.Text(f1, height=20, width=20, wrap="none")
ysb = tk.Scrollbar(f1, orient="vertical", command=text.yview)
xsb = tk.Scrollbar(f1, orient="horizontal", command=text.xview)
text.configure(yscrollcommand=ysb.set, xscrollcommand=xsb.set)
f1.grid_rowconfigure(0, weight=1)
f1.grid_columnconfigure(0, weight=1)
xsb.grid(row=1, column=0, sticky="ew")
ysb.grid(row=0, column=1, sticky="ns")
text.grid(row=0, column=0, sticky="nsew")
# and to the right...
b1 = tk.Button(f2, text="Click me!")
s1 = tk.Scale(f2, from_=1, to=20, orient="horizontal")
b1.pack(side="top", fill="x")
s1.pack(side="top", fill="x")
root.mainloop()

Related

Sub Frame Is Not Expanded When Its Parent Is

import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
left_frame_1 = tk.Frame(root, background="#ff0000")
left_frame_1.grid(row=0, column=0)
left_frame_2 = tk.Frame(left_frame_1)
left_frame_2.grid(row=0, column=0)
left_label_1 = tk.Label(left_frame_2, text="HELLO")
left_label_2 = tk.Label(left_frame_2, text="WORLD")
left_label_3 = tk.Label(left_frame_2, text="=D")
left_label_1.grid(row=0, column=0)
left_label_2.grid(row=1, column=0)
left_label_3.grid(row=2, column=0)
right_frame1 = tk.Frame(root, background="#00ff00")
right_frame1.grid(row=0, column=1, sticky="nsew")
right_frame_2 = tk.Frame(right_frame1, background="#0000ff")
right_frame_2.grid(row=0, column=0)
right_label_1 = tk.Label(right_frame_2, text="CENTER ME!")
right_label_1.grid(row=0, column=0)
root.mainloop()
When my parent frame expands to all its free space, the child frame doesn't, instead it just stays on top.
I've been testing if .grid() has something to do with it, but haven't found anything.
Even if I add sticky="nsew" to both the frame and the label, there is still no change.
right_frame1 = tk.Frame(root, background="#00ff00")
right_frame1.grid(row=0, column=1, sticky="nsew")
right_frame_2 = tk.Frame(right_frame1, background="#0000ff")
right_frame_2.grid(row=0, column=0, sticky="nsew")
right_label_1 = tk.Label(right_frame_2, text="CENTER ME!")
right_label_1.grid(row=0, column=0, sticky="nsew")
My goal is for the parent frame (the one with the green color) to expand to all available space (which I've achieved), and for the child frame containing the label to expand.
right_frame_2 looks because it expands.
right_frame_1 is not visible because it is completely covered by right_frame_2.
I hope your help, thank you.
To get the result of the last image in the question, you need to:
change sticky options of .grid() for right_frame_2 and right_label_1
set weight options of .rowconfigure() and .columnconfigure() on root, right_frame1 and right_frame_2
import tkinter as tk
import tkinter.ttk as ttk
root = tk.Tk()
left_frame_1 = tk.Frame(root, background="#ff0000")
left_frame_1.grid(row=0, column=0)
left_frame_2 = tk.Frame(left_frame_1)
left_frame_2.grid(row=0, column=0)
left_label_1 = tk.Label(left_frame_2, text="HELLO")
left_label_2 = tk.Label(left_frame_2, text="WORLD")
left_label_3 = tk.Label(left_frame_2, text="=D")
left_label_1.grid(row=0, column=0)
left_label_2.grid(row=1, column=0)
left_label_3.grid(row=2, column=0)
right_frame1 = tk.Frame(root, background="#00ff00")
right_frame1.grid(row=0, column=1, sticky="nsew")
right_frame_2 = tk.Frame(right_frame1, background="#0000ff")
right_frame_2.grid(row=0, column=0, sticky="nsew") # expand to fill available space
right_label_1 = tk.Label(right_frame_2, text="CENTER ME!")
right_label_1.grid(row=0, column=0, sticky="ew") # expand horizontally
root.rowconfigure(0, weight=1) # make left and right frame expand vertically
root.columnconfigure(1, weight=1) # make right frame expand horizontally
# allocate all space to right_frame_2
right_frame1.rowconfigure(0, weight=1)
right_frame1.columnconfigure(0, weight=1)
# allocate all space of right_frame_2 to right_label_1
right_frame_2.rowconfigure(0, weight=1)
right_frame_2.columnconfigure(0, weight=1)
root.mainloop()
Result:
When the window is resized:

Nested layouts with Tkinter

The Tk documentation says (last section) that nested layouts can be achieved using tk.frame.
The following small example is not working as expected, instead of:
import tkinter as tk
window = tk.Tk()
window.geometry('250x100')
# first level, window as parent
tk.Label(window, text='Choose file:').grid(row=0, column=0, sticky=tk.W)
tk.Button(window, text='Browse ...').grid(row=1, column=0, sticky=tk.W)
fr = tk.Frame(window).grid(row=2, column=0, sticky=tk.W)
# nested, frame as parent
tk.Entry(fr).grid(row=0, column=0, sticky=tk.W)
tk.Entry(fr).grid(row=0, column=1, sticky=tk.W)
tk.mainloop()
it produces:
The real UI is much more complex, so I really want to use nested grids instead of one grid with multiple columns.
AFAIK, tkinter does not produce intuitive results if you create an object and grid at once. This should give you the same result with the documentation:
import tkinter as tk
window = tk.Tk()
window.geometry('250x100')
# first level, window as parent
tk.Button(window, text='Browse ...').grid(row=1, column=0, sticky=tk.W)
tk.Label(window, text='Choose file:').grid(row=0, column=0, sticky=tk.W)
fr = tk.Frame(window)
fr.grid(row=2, column=0, sticky=tk.W)
# nested, frame as parent
entry1 = tk.Entry(fr)
entry1.grid(row=0, column=0, sticky=tk.W)
entry2 = tk.Entry(fr)
entry2.grid(row=0, column=1, sticky=tk.W)
tk.mainloop()

Python Tkinter grid spacing of widgets and LablelFrames not right

I am designing a simple GUI in Python 2.7 Tkinter, but I can't get things to spread out as I want them. I have managed to get my various widgets roughly where I want them, however I can't seem to force spacing out and things are a little bunched up.
I have also tried to draw 3 LabelFrames to separate the window out, but widgets seem to fall over the LabelFrames. I am wondering how I can space this out a little better. The grid system seems to allow things to bunch up and ignores blank rows and columns as far as I can see.
from Tkinter import *
import Tkinter, Tkconstants, tkFileDialog, tkMessageBox
class FileZap():
def __init__(self, root):
root.title("TestGUI")
root.geometry("860x450")
self.topFrame = LabelFrame(root, text="Top Area")
self.topFrame.grid(row=1, column=1, rowspan=6, columnspan=7, padx=5, pady = 5, sticky="NSEW")
self.listbox1 = Listbox(root, width=50, selectmode="multiple")
self.listbox1.grid(row=3, column=2)
self.scrollbar = Scrollbar(orient=VERTICAL, command=self.listbox1.yview)
self.listbox1.config(yscrollcommand=self.scrollbar.set)
self.scrollbar.grid(row=3, column=3, sticky="ns")
self.listbox2 = Listbox(root, width=50)
self.listbox2.grid(row=3, column=4)
self.selectLabel = Label(root, text="Select a folder: ")
self.selectLabel.grid(row=3, column=1)
self.user1 = Entry(root, width="50")
self.user1.grid(row=2, column=2)
self.browse = Button(root, text="Browse")
self.browse.grid(row=2, column=3)
self.addItems = Button(root, text="Add to Selection")
self.addItems.grid(row=4, column=2)
self.clearItems = Button(root, text="Clear Selection")
self.clearItems.grid(row=4, column=4)
self.leftFrame = LabelFrame(root, text="Left Area")
self.leftFrame.grid(row=5, column=1, rowspan=6, columnspan=3, padx=5, pady = 5, sticky="NSEW")
self.replaceInLable = Label(root, text="String to replace: ")
self.replaceOutLable = Label(root, text="New string: ")
self.replaceInLable.grid(row=7, column=1)
self.replaceOutLable.grid(row=7, column=2)
self.replaceIn = Entry(root, width="20")
self.replaceOut = Entry(root, width="20")
self.replaceIn.grid(row=8, column=1)
self.replaceOut.grid(row=8, column=2)
self.replace = Button(root, text="Replace")
self.replace.grid(row=8,column=3)
self.rightFrame = LabelFrame(root, text="Right Area")
self.rightFrame.grid(row=5, column=4, rowspan=6, columnspan=3, padx=5, pady = 5, sticky="NSEW")
self.quit = Button(root, text="Exit", command=root.quit)
self.quit.grid(row=9, column=6)
root = Tkinter.Tk()
file_zap = FileZap(root)
root.mainloop()
I have tried various alterations but can't nail it! Any help would be much appreciated.
First, the columns / row adapt to there content so an empty one as a zero height/width. If you want to put space between your widgets use the padx and pady options in the .grid method. They can take either one number which will give the padding on both sides or a couple of numbers giving the padding on each side.
Secondly, if you want your widgets to be inside a LabelFrame, you need to create them with this LabelFrame as master instead of the main window.
from Tkinter import LabelFrame, Tk, Button, Label
root = Tk()
# make row 0 resize with the window
root.rowconfigure(0, weight=1)
# make column 0 and 1 resize with the window
root.columnconfigure(0, weight=1)
root.columnconfigure(1, weight=1)
# create LabelFrames
top_frame = LabelFrame(root, text="top")
left_frame = LabelFrame(root, text="left")
right_frame = LabelFrame(root, text="right")
top_frame.grid(row=0, column=0, columnspan=2, padx=10, pady=(10,4), sticky="nsew")
left_frame.grid(row=1, column=0, padx=(10,4), pady=4, sticky="nsew")
right_frame.grid(row=1, column=1, padx=(4,10), pady=4, sticky="nsew")
#create widgets inside top_frame
Label(top_frame, text="I'm inside top_frame").pack()
Button(top_frame, text="Top").pack()
#create widgets inside left_frame
Label(left_frame, text="I'm inside left_frame").pack()
Button(left_frame, text="Left").pack()
#create widgets inside top_frame
Label(right_frame, text="I'm inside right_frame").pack()
Button(right_frame, text="Right").pack()
Button(root, text="Quit", command=root.destroy).grid(row=2, column=0,
columnspan=2, pady=10)
root.mainloop()

Tkinter formatting multiple frames next to each other

I am creating a GUI and I essentially want to have two different "toolbars" at the top of the GUI, similar to:
Currently, I have the respective buttons for each toolbar placed into two different respective frames called Toolbar and Selectbar. On each button I call .pack() to format them within the toolbars, and then on each toolbar I call
toolbar.grid(row=0, column=0, sticky='NW')
selectbar.grid(row=0, column=1, sticky='NE')
However, I don't believe this is right because they're two different "grids" that it is trying to place in the respective columns. It still gives me something close to the desired product in this:
.
However, I was wondering how I would "combine" these two frames into a larger respective frame so that I could possibly use .configurecolumn(0, weight=1) to get the first toolbar to stretch out farther along the screen.
In essence, I was wondering how I would be able to have these two "toolbars" next to each other, but have the first one extend with the blank space.
Edit: Here is the code with some parts omitted.
from tkinter import *
from MenuBar import *
from ToolBar import *
import tkinter.ttk
class App(Tk):
def __init__(self):
Tk.__init__(self)
#Creates the MenuBar
menubar = MenuBar(self)
self.config(menu=menubar)
#Creates the ToolBar
toolbar = Frame(bg="#f2f2f2", bd=1, relief=RAISED, width=1000)
newUndIcon = itk.PhotoImage(file="Icons/newUndirected.png")
newDirIcon = itk.PhotoImage(file="Icons/newDirected.png")
b0 = Button(toolbar, text="Create a new undirected graph", image=newUndIcon, relief=FLAT)
b1 = Button(toolbar, text="Create a new directed graph", image=newDirIcon, relief=FLAT)
b2 = Button(toolbar, text="Open an existing graph", image=openIcon, relief=FLAT)
b3 = Button(toolbar, text="Save graph", image=saveIcon, relief=FLAT)
b0.img = newUndIcon
b1.img = newDirIcon
b2.img = openIcon
b3.img = saveIcon
b0.pack(side=LEFT, padx=2, pady=2)
b1.pack(side=LEFT, padx=2, pady=2)
b2.pack(side=LEFT, padx=2, pady=2)
b3.pack(side=LEFT, padx=2, pady=2)
#toolbar.pack(side=TOP, fill=X)
toolbar.grid(row=0, sticky='NW')
toolbar.columnconfigure(1, weight=1)
selectBar = Frame(bg="#f2f2f2", bd=1, relief=FLAT)
c0 = Button(selectBar, image=newUndIcon, relief=FLAT)
c1 = Button(selectBar, image=newDirIcon, relief=FLAT)
c2 = Button(selectBar, image=vertexIcon, relief=FLAT)
c0.img = newUndIcon
c1.img = newDirIcon
c2.img = vertexIcon
c0.pack(side=LEFT, padx=2, pady=2)
c1.pack(side=LEFT, padx=2, pady=2)
c2.pack(side=LEFT, padx=2, pady=2)
selectBar.grid(row=0, column=1, sticky='NW')
selectBar.columnconfigure(1, weight=1)
app=App()
app.iconbitmap('Icons/titleIcon.ico')
app.title("GMPX")
app.geometry('900x600')
app.config(bg="#FFF")
app.mainloop()
app.destroy()
You can try putting the toolbar and selectBar inside a Frame, and use pack() instead of grid():
topbar = Frame(self)
....
toolbar = Frame(topbar, ...)
toolbar.pack(side=LEFT, fill=X, expand=True)
...
selectBar = Frame(topbar, ...)
selectBar.pack(side=RIGHT)
...
topbar.pack(fill=X)

Python3.5 -configure a single cell to expand instead of the entire row or column

Picture a 4x4 grid in a tkinter window. I want to expand the cell at row 2, column 2 but not everything else on row 2 or column 2. Im designing a text window with selectable options on the left side in rows 1-15. Making row 2 with weight 1 and column 2 with weight 1 allows my Text widget to expand but so does everything else in row 2 and column 2. Any way around this?
from tkinter import *
root = Tk()
lbl1 = Label(root, text="label1")
lbl1.grid(row=0, column=1)
lbl2 = Label(root, text="label2")
lbl2.grid(row=1, column=0)
lbl3 = Label(root, text="label3")
lbl3.grid(row=3, column=0)
lbl4 = Label(root, text="label4")
lbl4.grid(row=5, column=0)
txt = Text(root, state='disabled', bg='#E8E8E8')
txt.grid(row=1, column=1, padx=10, pady=10, sticky="NSEW", columnspan=2, rowspan=2)
root.rowconfigure(2, weight=1)
root.columnconfigure(2, weight=1)
root.mainloop()
Example 2:
from tkinter import *
root = Tk()
frame1 = Frame(root)
frame1.grid(row=0, column=1)
frame2 = Frame(root)
frame2.grid(row=1, column=0)
frame3 = Frame(root)
frame3.grid(row=1, column=1, rowspan=2, columnspan=2)
lbl1 = Label(frame1, text="label1")
lbl2 = Label(frame2, text="label2")
lbl3 = Label(frame2, text="label3")
lbl4 = Label(frame2, text="label4")
lbl1.grid(row=0, column=1, sticky=N)
lbl2.grid(row=3, column=0, sticky=N)
lbl3.grid(row=5, column=0, sticky=N)
lbl4.grid(row=7, column=0, sticky=N)
txt = Text(frame3, state='disabled', bg='#E8E8E8')
txt.grid(row=0, column=0, padx=10, pady=10, sticky="NSEW", columnspan=2, rowspan=2)
root.rowconfigure(2, weight=1)
root.columnconfigure(2, weight=1)
frame3.rowconfigure(0, weight=1)
frame3.columnconfigure(0, weight=1)
root.mainloop()
Example 2 has everything in the position I want it in but the Text widget does not expand. Is it possible to set a frame to expand when using grid?
Your question asks about a 4x4 grid, but your example shows only two columns. That makes it hard to understand what you want. In the comments you say you simply want the text area of the example to grow and shrink and all the labels together, so that's what I'll address.
The simplest solution is to have an extra row and column to the right and below the text area. Have the text widget span into those areas, and give those areas a weight of 1. That means that, as the window changes size, any extra space is allocated to areas not occupied by buttons.
pro tip: I find layout problems much easier to visualize and solve when all of the layout code is together.
It would look something like this:
lbl1.grid(row=0, column=1)
lbl2.grid(row=1, column=0)
lbl3.grid(row=2, column=0)
lbl4.grid(row=3, column=0)
txt.grid(row=1, column=1, padx=10, pady=10, sticky="NSEW", columnspan=2, rowspan=4)
root.rowconfigure(4, weight=1)
root.columnconfigure(2, weight=1)
I think your layout problems might be better solved by using pack instead of grid for part of the layout. For example, you might start with three areas: a toolbar, a side panel, and then main area with the text widget:
toolbar = Frame(root, ...)
side = Frame(root, ...)
main = Frame(root, ...)
toolbar.pack(side="top", fill="x")
side.pack(side="left", fill="y")
main.pack(side="right", fill="both", expand=True)
With that you now have three relatively independent areas. You can use pack or grid in each of these frames independently, making it much easier to keep track of rows and columns.
One way around it would be to make your grid twice as large, setting the things you want to be expandable to span two columns/rows.
I.e. you use exclusively odd numbered rows/columns for griding things ([1,1][1,3],[3,1][3,3]...) and you set even-numbered rows/columns to have weight. Anything you want to expand in one or both directions gets a columnspan or rowspan of 2, pushing it into a row/column which may expand as needed.
With the information everyone has provided I was able to come up with a solution. I left the Text widget on the main window instead of in a frame and put my labels/tools in frames. Basically using the fact that a frame will not expand to lock down the labels. Now when the window is expanded only the widget grows.
from tkinter import *
root = Tk()
frame1 = Frame(root)
frame1.grid(row=0, column=1)
frame2 = Frame(root)
frame2.grid(row=1, column=0)
lbl1 = Label(frame1, text="label1")
lbl2 = Label(frame2, text="label2")
lbl3 = Label(frame2, text="label3")
lbl4 = Label(frame2, text="label4")
lbl1.grid(row=0, column=1, sticky=N)
lbl2.grid(row=3, column=0, sticky=N)
lbl3.grid(row=5, column=0, sticky=N)
lbl4.grid(row=7, column=0, sticky=N)
txt = Text(root, state='disabled', bg='#E8E8E8')
txt.grid(row=1, column=1, padx=10, pady=10, sticky="NSEW", columnspan=2, rowspan=2)
root.rowconfigure(2, weight=1)
root.columnconfigure(2, weight=1)
root.mainloop()
Thanks for all the help.

Categories