Forcing TKinter button output to bottom of window? - python-3.x

I am very new to using TKinter. I am making a TKinter window that displays the descriptive statistics of the wine quality data sets. The problem I am having is with the positioning. Even using pack(side=BOTTOM), the button for the histogram shows up next to the column option buttons I have, like so:
Ideally, what I would like the window to look like, is similar to this:
I tried making the button in the same place I made the label "Descriptive statistics", and then configuring it later, but while that keeps the button where I would like it to be, the histogram ends up in the same place.
Edit: I was originally using grid() to manually place everything, but, for aesthetic reasons, I didn't like how the spaces in between the buttons would adjust as more objects were added to the window. I was also getting a "can't use pack() and grid()" warning even though I had changed all pack()s to grid()s, specifically only because of my plot function, and I couldn't figure it out. So in the end I just made the switch from grid() to pack() to avoid continually getting that error.
My code:
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
#the main window
root = tk.Tk()
root.title('Descriptive statistics for vinho verde datasets')
#generate some labels
lbl1 = tk.Label(root, text = "Wine choice:")
lbl1.pack(side=TOP)
lbl2 = tk.Label(root, text = "Descriptive statistics:")
lbl2.pack(side=BOTTOM)
def wine_choice(opt):
#functions determining for which columns to output descriptive statistics
def describe(colm):
if opt == 'white':
res = white[colm].describe()
else:
res = red[colm].describe()
txt = "\nDescriptive statistics for {0} wine, {1}:\n\n{2}"
lbl2.config(text = txt.format(opt,colm,res))
def b_plot():
#figure that will contain the plot
fig = Figure(figsize = (5, 5), dpi = 75)
p1 = fig.add_subplot()
if opt == 'white':
p1.hist(white[colm])
else:
p1.hist(red[colm])
#creating the canvas containing figure and placing on the window
canvas = FigureCanvasTkAgg(fig, root)
canvas.draw()
canvas.get_tk_widget().pack(side=BOTTOM)
btn_p = tk.Button(root, command = b_plot, width=10, height=3, text = "Histogram").pack(side=BOTTOM)
lbl3 = tk.Label(root, text = "Pick an attribute to investigate:")
lbl3.pack(side=TOP)
#spawn attribute buttons after user chooses a wine
#generate buttons
btn3 = tk.Button(root, text='fixed acidity', width=10, height=3)
btn3.pack(side=LEFT)
btn3.bind('<Button-1>', lambda e: describe('fixed acidity'))
btn4 = tk.Button(root, text='volatile\nacidity', width=10, height=3)
btn4.pack(side=LEFT)
btn4.bind('<Button-1>', lambda e: describe('volatile acidity'))
btn5 = tk.Button(root, text='citric\nacid', width=10, height=3)
btn5.pack(side=LEFT)
btn5.bind('<Button-1>', lambda e: describe('citric acid'))
btn6 = tk.Button(root, text='residual\nsugar', width=10, height=3)
btn6.pack(side=LEFT)
btn6.bind('<Button-1>', lambda e: describe('residual sugar'))
btn7 = tk.Button(root, text='chlorides', width=10, height=3)
btn7.pack(side=LEFT)
btn7.bind('<Button-1>', lambda e: describe('chlorides'))
btn8 = tk.Button(root, text='free\nsulfur\ndioxide', width=10, height=3)
btn8.pack(side=LEFT)
btn8.bind('<Button-1>', lambda e: describe('free sulfur dioxide'))
btn9 = tk.Button(root, text='total\nsulfur\ndioxide', width=10, height=3)
btn9.pack(side=LEFT)
btn9.bind('<Button-1>', lambda e: describe('total sulfur dioxide'))
btn10 = tk.Button(root, text='density', width=10, height=3)
btn10.pack(side=LEFT)
btn10.bind('<Button-1>', lambda e: describe('density'))
btn11 = tk.Button(root, text='pH', width=10, height=3)
btn11.pack(side=LEFT)
btn11.bind('<Button-1>', lambda e: describe('pH'))
btn12 = tk.Button(root, text='sulphates', width=10, height=3)
btn12.pack(side=LEFT)
btn12.bind('<Button-1>', lambda e: describe('sulphates'))
btn13 = tk.Button(root, text='alcohol', width=10, height=3)
btn13.pack(side=LEFT)
btn13.bind('<Button-1>', lambda e: describe('alcohol'))
btn14 = tk.Button(root, text='quality', width=10, height=3)
btn14.pack(side=LEFT)
btn14.bind('<Button-1>', lambda e: describe('quality'))
#buttons for wine choices
btn1 = tk.Button(root, text = "white", width=10, height=2)
btn1.pack(side=TOP)
#remember which button user picks
btn1.bind('<Button-1>', lambda e: wine_choice('white'))
btn2 = tk.Button(root, text = "red", width=10, height=2)
btn2.pack(side=TOP)
btn2.bind('<Button-1>', lambda e: wine_choice('red'))
#must be called for window to be drawn and events to be processed
root.mainloop()

The solution is to break your UI into logical groups, and use frames to organize the logical groups. You can make what you have work, but it's much easier to use frames to organize your widgets.
I see perhaps four logical groups:
a set of two buttons stacked vertically
a dozen or so buttons aligned vertically
a block of statistics with a "histogram" button
a histogram
So, start by creating four frames, one for each of those sections. Stacking vertically is best done with pack.
Once you've done that, put the various widgets inside one of those frames. Each frame is independent from a layout perspective, so you can use grid or pack in each. Though, since each group seems to be a vertical or horizontal grouping, pack will probably work best in all cases since it excels at left-to-right and top-to-bottom layouts with the fewest lines of code.

Related

Make multiple tk.Toplevel windows embedded/unified in main tk window

So I'm trying to create a program which uses multiple tk.Toplevel windows. The problem with this is, that all windows show up seperated as their "own App", so when you alt tab, you switch between the toplevel windows.
The pseudocode would look something like this:
import tkinter as tk
top_levels = {}
def open_toplevel():
top_level = tk.Toplevel(root)
top_level.geometry("300x200+0+0")
top_levels.update({f"toplevel{len(top_levels.keys())}" : top_level})
root = tk.Tk()
button = tk.Button(root, command= open_toplevel)
button.place(x=0, y=0)
root.mainloop()
So my question, is: is there a way to unify them into "one window"?
If you want all of them to unify into one window then tk.Frame is a better widget to use instead of tk.Toplevel
The purpose of tk.Toplevel is to create a new temporary window, not an extra part of the window. But frames are a really good way to organise stuff.
This code below creates new frame every time you click the button. This is just a simple example. You can also use grid for widgets in a frame. I also put a border so you can see where the frames are located.
from tkinter import *
def open_frame():
frame = Frame(root, highlightbackground="black", highlightthickness=2)
lbl1 = Label(frame, text=f"Frame {len(frames) + 1} label 1")
lbl2 = Label(frame, text=f"Frame {len(frames) + 1} label 2")
lbl1.pack()
lbl2.pack()
frame.pack(padx=5, pady=5)
frames.append(frame)
root = Tk()
frames = []
btn = Button(root, text="Open Frame", command=open_frame)
btn.pack()
root.mainloop()
I hope this solution is helpful
EDIT
Use this code here to move the frames:
from tkinter import *
def open_frame():
global frame, frames
frame = Frame(root, highlightbackground="black", highlightthickness=2)
lbl1 = Label(frame, text=f"Frame {len(frames) + 1} label 1")
lbl2 = Label(frame, text=f"Frame {len(frames) + 1} label 2")
lbl1.pack()
lbl2.pack()
frame.pack(padx=5, pady=5)
frame_number = len(frames)
lbl1.bind('<B1-Motion>', lambda event: MoveWindow(event, frame_number))
lbl2.bind('<B1-Motion>', lambda event: MoveWindow(event, frame_number))
frame.bind('<B1-Motion>', lambda event: MoveWindow(event, frame_number))
frames.append(frame)
labels.append(lbl1)
labels.append(lbl2)
def MoveWindow(event, frame_number):
global root, frames
root.update_idletasks()
f = frames[frame_number]
x = f.winfo_width()/2
y = f.winfo_height()*1.5
f.place(x=event.x_root-x, y=event.y_root-y)
root = Tk()
root.geometry("500x500")
frames = []
labels = []
btn = Button(root, text="Open Frame", command=open_frame)
btn.pack()
root.mainloop()

Tkinter continuously loop main event

I'm making a trivia game where data is from www.opentdb.com. I want it to continually loop through the main program after it tells you whether your answer is correct or not. (clicking an answer and shows green label but will stay at that screen). I would like it to wait a few seconds and have tried using .after(3000, main()) but do not know how to reset the screen and repeat.Im sorry if this sounds very confusing. Thank you for your assistance :)
from tkinter import *
import trivia
import html
t = trivia.Trivia()
root = Tk()
root.geometry("1366x768")
root.configure(bg='#4dc4ff')
root.iconbitmap("icon.ico")
root.title("Trivia Party!")
frame = Frame(root, bg='#4dc4ff')
class Buttons:
def __init__(self, text):
self.text = html.unescape(text) # remove html entities
self.correct_label = Label(root, text="Correct!", font=("Arial", 30), background="#98FB98", width=20, height=2) # green correct label
def reveal_answer(self):
if self.text == t.return_correct_answer(): # if its the right answer
self.correct_label.pack(pady=175)
frame.destroy()
else:
self.correct_label.config(text="Incorrect", background="#ff6347")
self.correct_label.pack(pady=175)
Label(root, background="#4dc4ff", text=("The correct answer was: " + t.return_correct_answer()),
font=("Arial", 40)).pack()
frame.destroy()
def create(self):
Button(frame, text=self.text, borderwidth=0, highlightbackground="#00abff", font=("Helvetica", 30),
command=self.reveal_answer).pack(side=LEFT, expand=YES)
frame.pack(fill=BOTH, expand=YES)
buttons = []
question = Label(frame, text=html.unescape(t.return_question()), font=("Arial", 25), height=3, bg="#00abff").pack(fill=X)
for i in t.all_answers: # t.all_answers is pulling all of the multiple choice answers ie "coke", "pepsi" "sprite"
buttons.append(Buttons(i).create()) # i is the individual answer eg "pepsi" and is making a button
root.mainloop()

Adding padding query insert to Listbox

I have read a number of threads and other resources to try to find the correct way to handle this but I have not found anything that works with my application.
Here is what I am trying to accomplish.
When a query is completed and the insert of the data to a Listbox is done I cannot seem to get it to margin the data insert by 1 character space.
I am using pack() and I have read the tkinter manual for this and have tried each example available along with others found on various threads here.
The widget:
output = tkinter.Listbox(window_2, height = 20, font='Times 10',
width=42, bd=1, bg = '#FFD599', fg = '#9A0615', selectmode=SINGLE)
output.pack()
output.place(x=210, y=195)
I have tried padx and pady with pack() without success, although this works successfully with the Text widget. I have also attempted to use a few alternatives that I have found here on the site but all without success in margining the Listbox when the data is inserted.
Any advice?
pack's padx/pady and ipadx/ipady options don't affect the data that is inside the listbox. The listbox itself doesn't have any options to add an internal margin.
To get a margin around the inside of the listbox, what I normally do is give it a zero borderwidth and highlightthickness, and then place it in a frame with the same background color and let the frame be the border. You can then add any padding you want between the border and the listbox.
This is also convenient because you can put a scrollbar inside the frame, giving it the appearance that it is inside the listbox without actually being inside the listbox.
Example:
import tkinter as tk
root = tk.Tk()
root.configure(background="gray")
listbox_border = tk.Frame(root, bd=1, relief="sunken", background="white")
listbox_border.pack(padx=10, pady=10, fill=None, expand=False)
listbox = tk.Listbox(listbox_border, width=20, height=10,
borderwidth=0, highlightthickness=0,
background=listbox_border.cget("background"),
)
vsb = tk.Scrollbar(listbox_border, orient="vertical", command=listbox.yview)
listbox.configure(yscrollcommand=vsb)
vsb.pack(side="right", fill="y")
listbox.pack(padx=10, pady=10, fill="both", expand=True)
for i in range(100):
listbox.insert("end", "Item #{}".format(i))
root.mainloop()
here is a variation on the much appreciated answer by Bryan Oakley.
it uses ttk widgets instead of tk widgets
the scrollbar tracks your position in the list box when you scroll with the mouse
uses the oStyle.theme_use("clam") because it may look more modern...this can be commented out
'
import tkinter as tk
from tkinter import ttk
try: # allows the text to be more crisp on a high dpi display
from ctypes import windll
windll.shcore.SetProcessDpiAwareness(1)
except:
pass
root = tk.Tk()
oStyle = ttk.Style()
oStyle.theme_use("clam")
oStyle.configure('LB.TFrame', bd=1, relief="sunken", background="white")
listbox_border = ttk.Frame(root, style='LB.TFrame')
listbox_border.pack(padx=4, pady=4, fill=None, expand=False)
vsb = ttk.Scrollbar(listbox_border)
vsb.pack(side="right", fill="y")
listbox = tk.Listbox(listbox_border, width=20, height=10, borderwidth=0,
highlightthickness=0, selectmode=tk.SINGLE,
activestyle=tk.NONE)
listbox.pack(padx=6, pady=6, fill="y", expand=True)
listbox.config(yscrollcommand=vsb.set)
vsb.config(command=listbox.yview)
for i in range(100):
listbox.insert("end", "Item #{}".format(i))
root.mainloop()
'
first of all to format chars in a tkinter listbox you need to use a fixed font and .format python funcion....;
So you can do something this
Press Load to load data in the listbox and pay attention to this line code
s = '{0:>8}{1:5}'.format(i[0],i[1])
self.list.insert(tk.END, s)
import tkinter as tk
RS = (('Apple',10),
('Banana',20),
('Peack',8),
('Lemon',6),)
class App(tk.Frame):
def __init__(self,):
super().__init__()
self.master.title("Hello World")
self.init_ui()
def init_ui(self):
self.pack(fill=tk.BOTH, expand=1,)
f = tk.Frame()
sb = tk.Scrollbar(f,orient=tk.VERTICAL)
self.list = tk.Listbox(f,
relief=tk.GROOVE,
selectmode=tk.BROWSE,
exportselection=0,
background = 'white',
font='TkFixedFont',
yscrollcommand=sb.set,)
sb.config(command=self.list.yview)
self.list.pack(side=tk.LEFT,fill=tk.BOTH, expand =1)
sb.pack(fill=tk.Y, expand=1)
w = tk.Frame()
tk.Button(w, text="Load", command=self.on_callback).pack()
tk.Button(w, text="Close", command=self.on_close).pack()
f.pack(side=tk.LEFT, fill=tk.BOTH, expand=0)
w.pack(side=tk.RIGHT, fill=tk.BOTH, expand=0)
def on_callback(self,):
for i in RS:
s = '{0:>8}{1:5}'.format(i[0],i[1])
self.list.insert(tk.END, s)
def on_close(self):
self.master.destroy()
if __name__ == '__main__':
app = App()
app.mainloop()

Tkinter - How to center a Widget (Python)

I started with the GUI in Python and have a problem.
I've added widgets to my frame, but they're always on the left side.
I have tried some examples from the internet, but I did not manage it .
I tried .place, but it does not work for me. Can one show me how to place the widgets in the middle?
Code:
import tkinter as tk
def site_open(frame):
frame.tkraise()
window = tk.Tk()
window.title('Test')
window.geometry('500x300')
StartPage = tk.Frame(window)
FirstPage = tk.Frame(window)
for frame in (StartPage, FirstPage):
frame.grid(row=0, column=0, sticky='news')
lab = tk.Label(StartPage, text='Welcome to the Assistant').pack()
lab1 = tk.Label(StartPage, text='\n We show you helpful information about you').pack()
lab2 = tk.Label(StartPage, text='\n \n Name:').pack()
ent = tk.Entry(StartPage).pack()
but = tk.Button(StartPage, text='Press', command=lambda:site_open(FirstPage)).pack()
lab1 = tk.Label(FirstPage, text='1Page').pack()
but1 = tk.Button(FirstPage, text='Press', command=lambda:site_open(StartPage)).pack()
site_open(StartPage)
window.mainloop()
After you have created window, add:
window.columnconfigure(0, weight=1)
More at The Grid Geometry Manager
You are mixing two different Layout Managers. I suggest you either use The Grid Geometry Manager or The Pack Geometry Manager.
Once you have decided which one you would like to use, it is easier to help you :)
For example you could use the Grid Geometry Manager with two rows and two columns and place the widgets like so:
label1 = Label(start_page, text='Welcome to the Assistant')
# we place the label in the page as the fist element on the very left
# and allow it to span over two columns
label1.grid(row=0, column=0, sticky='w', columnspan=2)
button1 = Button(start_page, text='Button1', command=self.button_clicked)
button1.grid(row=1, column=0)
button2 = Button(start_page, text='Button2', command=self.button_clicked)
button2.grid(row=1, column=1)
This will lead to having the label in the first row and below the two buttons next to each other.

How to create a sub frames with a specific layout?

I'm aiming to make a login program but the only part that confuses me is how to make the frames.I need 3 different frames but I neither know how to make a frame other the then like this:
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
and I can only make labels and widgets using that single mainframe. As far as making another one, it is beyond me. I need to know exactly place widets inside of each frame and even after creating frames I don't know how to place stuff on the grid. Would I go for the overall grid, or does something change after making the grid. I'm using the following layout for making the frame. Basically i'm hoping for a crash course in frames. Any information i've gathered doesn't make sense to me, even after I tried to put it into code.
I've got the coding part down just not the frame part.
#Import tkinter to make gui
from tkinter import *
from tkinter import ttk
import codecs
def login(*args
):
file = open("rot13.txt", "r")
lines = file.readlines()
uname = user.get()
pword = pw.get()
for i in lines:
x = i.split()
if codecs.encode(uname,'rot13') == x[0] and codecs.encode(pword,'rot13') == x[1]:
result.set("Successful")
break;
else:
result.set("Access Denied")
root = Tk()
root.title("Login")
#Configures column and row settings and sets padding
mainframe = ttk.Frame(root, padding="3 3 12 12")
mainframe.grid(column=0, row=0, sticky=(N, W, E, S))
mainframe.columnconfigure(0, weight=1)
mainframe.rowconfigure(0, weight=1)
user = StringVar()
pw = StringVar()
result = StringVar()
user_entry = ttk.Entry(mainframe, width=20, textvariable=user)
user_entry.grid(column=2, row=1, sticky=(W, E))
pw_entry = ttk.Entry(mainframe, width=20, textvariable=pw)
pw_entry.grid(column=2, row=2, sticky=(W, E))
ttk.Label(mainframe, text="Username ").grid(column=1, row=1, sticky=W)
ttk.Label(mainframe, text="Password ").grid(column=1, row=2, sticky=W)
ttk.Label(mainframe, text="").grid(column=1, row=3, sticky=W)
ttk.Label(mainframe, text="Result").grid(column=1, row=4, sticky=W)
ttk.Label(mainframe, text="").grid(column=1, row=5, sticky=W)
ttk.Button(mainframe, text="Login", command=login).grid(column=3, row=6, sticky=W)
#Makes a spot to put in result
ttk.Label(mainframe, textvariable=result).grid(column=2, row=4, sticky=(W, E))
#Opens up with item selected and allows you to enter username without having to click it
user_entry.focus()
#Runs calculate if click enter
root.bind('<Return>', login)
root.mainloop()
I believe the key point that you are missing is that subframes of mainframe use mainframe as the parent and that widgets within subframes use the subframe as parent. Furthermore, you can then place the subframe within the mainframe and the subframe widgets within the subframe. You do not have to pass parents to .grid because each widget knows its parent. A simplified example:
from tkinter import *
root = Tk()
mainframe = Frame(root)
login = Frame(mainframe)
label = Label(login, text='label')
entry = Entry(login)
display = Frame(mainframe)
result = Label(display, text='display result')
mainframe.grid() # within root
login.grid(row=0, column=0) # within mainframe
label.grid(row=0, column=0) # within login
entry.grid(row=0, column=1) # within login
display.grid() # within mainfram
result.grid(row=2, column=0) # within display

Resources