Encountered error; Tkinter GUI using python - python

I'm creating a simple Tkinter gui. However, something appears to be going haywire. Nothing is actually being 'pack'ed to the frame. Can anyone spot what I've done wrong? (Other than the issues caused by using 'from Tkinter import *', and the apparently un-useful 'do_nothing()' function.
#/usr/bin/python
from Tkinter import *
class gui:
def __init__(self, parent):
f = Frame(parent, width=300, height=500)
f.pack(padx=30, pady=15)
self.label = Label(f, text="Japanese Trainer")
self.label.pack(side=TOP, padx=10, pady=12)
self.txtlbl = Entry(f, justify=CENTER, text="", font=("Calibri", 15, "bold"), width=37)
self.txtlbl.pack()
self.txtlbl.grid(row=1, rowspan=2, sticky=E, pady=10, padx=40)
self.button0 = Button(f, text="Kana Trainer", width=20, command=self.do_nothing)
self.button0.pack()
self.button0.grid(row=3, rowspan=2, sticky=W, pady=10, padx=40)
self.button1 = Button(f, text="Vocab Trainer", width=20, command=self.do_nothing)
self.button1.pack()
self.button1.grid(row=3, rowspan=2, sticky=E, pady=10, padx=40)
def do_nothing(self):
self.txtlbl.delete(0, END)
self.txtlbl.insert(END, "Command did nothing...")
root = Tk()
root.title('Eg.')
app = gui(root)
root.mainloop()

You are mixing grid and pack in the same master window. You can't do that. Each one will potentially resize the widgets they manage, and each will respond to resizes in the widgets they manage. So, pack will resize the widgets to fit, grid will recognize the change and try to resize widgets to fit, pack will recognize the change and try to resize widgets to fit, ... resulting in an endless loop.
You can use pack and grid together in the same program, but you cannot use them to manage the same container.

Related

How to set default value of radio button using TKinter in a class?

I'm trying to set the default value of a radio button using TKinter and Python. It's my first time using it so I'm pretty new. My understanding is that the default value should be set to the second radio button in my example (value=1).
from tkinter import *
from tkinter import ttk
class RadioButtons:
def __init__(self, root):
self.root = root
self.jobNum = IntVar(value=1)
self.create()
def create(self):
content = ttk.Frame(self.root)
radioButtons = ttk.LabelFrame(content, borderwidth=5, relief="ridge", width=400, height=400, text="Radio Buttons")
radioButtonsLbl=ttk.Label(radioButtons, text="Buttons")
# radio buttons
jobType1 = ttk.Radiobutton(radioButtons, text="Button 0", variable= self.jobNum, value=0)
jobType2 = ttk.Radiobutton(radioButtons, text="Button 1", variable= self.jobNum, value=1)
jobType3 = ttk.Radiobutton(radioButtons, text="Button 2", variable= self.jobNum, value=2)
content.grid(column=0, row=0)
# add to grid
radioButtons.grid(column=0, row=0, columnspan=3, rowspan=3)
radioButtonsLbl.grid(column=0, row=5, padx=20, pady=5, sticky=W)
jobType1.grid(column=1, row=5, padx=20, pady=0, sticky=W)
jobType2.grid(column=1, row=6, padx=20, pady=0, sticky=W)
jobType3.grid(column=1, row=7, padx=20, pady=0, sticky=W)
root = Tk()
RadioButtons(root)
root.mainloop()
However no radio button is selected when running the program. (screenshot of program)
The debugger confirms that the value of self.jobNum is set correctly.(screenshot of debugger)
How do I set the default value? I've tried a number of things including self.jobNum.set() before and after creating and adding the radio buttons but to no avail.
What am I missing here? Is this some kind of scope issue?
I suspect this has something to do with python's garbage collector. I can make the problem go away by saving a reference to RadioButtons(root):
root = Tk()
rb = RadioButtons(root)
root.mainloop()

How can i attach my scroll bar to my text box on tkinter?

Im trying to use a vertical scrollbar for my text box but am coming across some problems:
I cant get the scroll bar to be directly touching the right side of the text box (so they are connected)
It seems the scroll bar wont affect my text box
I looked through some solutions but none seemed to work.
Heres my code:
from tkinter import *
writtenQ = Tk()
writtenQ.title("Written Response Question")
writtenQ.resizable(0,0)
header = LabelFrame(writtenQ, bg="white")
content = LabelFrame(writtenQ, bg="white")
header.columnconfigure(0, weight=1) # Forces column to expand to fill all available space
homeButton=Button(content,width=50,height=50)
try:
homeIcon=PhotoImage(file="yes.png")
homeButton.config(image=homeIcon)
homeButton.image = homeIcon
except TclError:
print("Home")
homeButton.grid(row=1, sticky="w", padx=15, pady=2)
#the image of the question will be put here
titleHeader = Label(content, text="Question Image here",pady=15, padx=20, bg="white", font=("Ariel",20, "bold"), anchor="w", relief="solid", borderwidth=1)
titleHeader.grid(row=2, column=0, columnspan=3, padx=15, pady=5, ipadx=370, ipady=150)
#this will allow the user to input their written response
answerInput = Text(content, width = 60, borderwidth=5, font=("HelvLight", 18))
answerInput.grid(row=3, column=0, ipady = 10, sticky="w", padx=(20,0), pady=20)
answerScrollBar= Scrollbar(content, command=answerInput.yview, orient="vertical")
answerScrollBar.grid(row=3, column=1, sticky="w")
submitButton = Button(content, borderwidth=1, font=("Ariel", 22), text="Submit", bg="#12a8e3", fg="black", activebackground="#12a8e3", relief="solid")
submitButton.grid(row=3, column=2, ipady=50, ipadx=70, sticky="nw", pady=20)
header.grid(row=0, sticky='NSEW')
content.grid(row=1, sticky='NSEW')
Configuring a scrollbar requires a two-way connection: the scrollbar needs to call the yview or xview method of the widget, and the widget needs to call the set method of the scrollbar.
Usually, this is done in three steps like in the following example:
answerInput = Text(...)
answerScrollBar= Scrollbar(..., command=answerInput.yview)
answerInput.configure(yscrollcommand=answerScrollBar.set)
You are forgetting the final step.
Unrelated to an actual functioning scrollbar, you're going to want to be able to see the scrollbar. You need to use sticky="ns" for the scrollbar so that it stretches in the Y direction. Otherwise it will only be a couple dozen pixels tall.
answerScrollBar.grid(row=3, column=1, sticky="ns")
Have you tried the solution here?
Let's say that the text widget is called text. Your code could be (excluding the setup of the window):
import tkinter
import tkinter.ttk as ttk
scrollb = ttk.Scrollbar(self, command=text.yview)
scrollb.grid(row=0, column=1, sticky='nsew')
text['yscrollcommand'] = scrollb.set
I have picked out what I think will be ueful for you from Honest Abe's answer. Hope it helped. Remember to set up your window before using the code...

StringVar().set() Not Adjusting StringVar

I'm a beginner learning Python and mucking around with the tkinter GUI stuff. I'm trying to make a very basic beginner project that allows a user to type something into a text box and click a button, whereupon that input is added to a label in another part of the window.
However, I'm running into an issue where the StringVar that I'm using as an output isn't being updated by the .set() command.
def __init__(self):
self.window = Tk()
self.window.title("Terminal Writer 9000!")
self.terminalString = StringVar()
self.terminalString.set("This is an example message.")
self.allcontent = ttk.Frame(self.window)
self.allcontent.grid(row=0, column=0, sticky="nwse")
self.mainframe = ttk.Frame(self.allcontent)
self.mainframe.grid(row=0, column=0, sticky = "nwse", columnspan=4, rowspan=5)
self.terminal = ttk.Label(self.mainframe, textvariable=self.terminalString, padding=10, relief="sunken")
self.terminal.grid(row=0, column=0, rowspan=5, columnspan=2, sticky="nwse")
# GUI setup for Buttons and Entry box omitted...
play = TerminalWriterApp()
play.window.mainloop()
However, the area used by the terminal Label is blank, even though it should display "This is an example message." While troubleshooting, I made this, which is basically a complete copy/paste of the functional elements of my original code:
from tkinter import *
from tkinter import ttk
window = Tk()
strvar = StringVar()
strvar.set("Test 2")
allcontent = ttk.Frame(window)
allcontent.grid(row=0, column=0, sticky="nwse")
mainframe = ttk.Frame(allcontent)
mainframe.grid(row=0, column=0, sticky="nwse", columnspan=4, rowspan=5)
text = Label(mainframe, text="Test 1")
text.grid(row=0, column=0, sticky="nwse")
text2 = Label(mainframe, textvariable=strvar)
text2.grid(row=1, column=0, sticky="nwse")
window.mainloop()
This code functions as intended, displaying a window with "Test 1" and "Test 2" on separate lines.
Does anyone know why the set() method wouldn't work in this context? (Also, feel free to get mad at my horrible code - I need to learn good habits somehow!)
For some reasons, the label appears when the app takes focus (when you click on it); maybe it is because of the stack nested frames, IDK.
You could use focus_force to constrain the OS to give focus to your app immediately.
from tkinter import *
from tkinter import ttk
class TerminalWriterApp:
def __init__(self):
self.window = Tk()
self.window.title("Terminal Writer 9000!")
self.terminalString = StringVar()
self.terminalString.set("This is an example message.")
self.allcontent = ttk.Frame(self.window)
self.allcontent.grid(row=0, column=0, sticky="nwse")
self.mainframe = ttk.Frame(self.allcontent)
self.mainframe.grid(row=0, column=0, sticky = "nwse", columnspan=4, rowspan=5)
self.terminal = ttk.Label(self.mainframe, textvariable=self.terminalString, padding=10, relief="sunken")
self.terminal.grid(row=0, column=0, rowspan=5, columnspan=2, sticky="nwse")
self.terminal.focus_force()
play = TerminalWriterApp()
play.window.mainloop()

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

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()

frames in frames layout using tkinter

I want to create tkinter grid layout 5 frames. It looked the way I wanted before replacing Frame content.
self.Frame1 = tk.Frame(self.parent, bg="grey")
with
self.Frame1 = LeftUpperWindow(self.parent)
When I maximize the window I see everything in LeftUpperWindow frame in messed up, layout incorrectly. A picture is worth a thousand words so I have this
and would like to have this
import sys
import os
import Tkinter as tk
import ttk as ttk
class LeftUpperWindow(tk.Frame):
def __init__(self, master=None):
self.parent = master
tk.Frame.__init__(self, self.parent, bg='#ffffff', borderwidth=1, relief="sunken")
self.__create_layout()
def __create_layout(self):
self.parent.grid()
for r in range(3):
self.parent.rowconfigure(r, weight=1)
for c in range(2):
self.parent.columnconfigure(c, weight=1)
self.editArea = tk.Text(self.parent, wrap=tk.NONE, undo=True,
relief=tk.SUNKEN, borderwidth=5, highlightthickness=0, insertbackground="white")
self.editArea.config(font=('courier', 10, 'normal'))
self.editArea.configure(bg="black", fg="white")
self.editArea.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.W, tk.E))
self.scrollbarY = tk.Scrollbar(self.parent, orient=tk.VERTICAL)
self.scrollbarY.grid(row=0, column=1, sticky=(tk.N, tk.S), rowspan=2)
self.scrollbarX = tk.Scrollbar(self.parent, orient=tk.HORIZONTAL)
self.scrollbarX.grid(row=1, column=0, sticky=(tk.W, tk.E))
self.status = tk.Label(self.parent, text="Status label", bd=1, relief=tk.SUNKEN, anchor=tk.W, bg='lightgray')
self.status.grid(row=2, column=0, sticky=(tk.N, tk.S, tk.W, tk.E), columnspan=2)
def __config_window(self):
pass
def close_quit(self, event=None):
self.parent.destroy()
def dummy_func(self):
print("dummy")
pass
class MainWindow(tk.Frame):
def __init__(self, master=None):
self.parent = master
tk.Frame.__init__(self, self.parent, bg='#ffffff', borderwidth=1, relief="sunken")
self.__create_layout()
self.__create_menu()
self.__config_mainWindow()
def __create_layout(self):
self.parent.grid()
for r in range(6):
self.parent.rowconfigure(r, weight=1)
for c in range(10):
self.parent.columnconfigure(c, weight=1)
self.Frame1 = LeftUpperWindow(self.parent) #tk.Frame(self.parent, bg="grey")
self.Frame1.grid(row=0, column=0, rowspan=4, columnspan=8, sticky=(tk.N, tk.S, tk.W, tk.E))
self.Frame2 = tk.Frame(self.parent, bg="blue")
self.Frame2.grid(row=0, column=8, rowspan=4, columnspan=2, sticky=(tk.N, tk.S, tk.W, tk.E))
self.Frame3 = tk.Frame(self.parent, bg="green")
self.Frame3.grid(row=4, column=0, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))
self.Frame4 = tk.Frame(self.parent, bg="brown")
self.Frame4.grid(row=4, column=5, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))
self.Frame5 = tk.Frame(self.parent, bg="pink")
self.Frame5.grid(row=5, column=0, rowspan=1, columnspan=10, sticky=(tk.N, tk.S, tk.W, tk.E))
def close_quit(self, event=None):
self.parent.destroy()
def __config_mainWindow(self):
self.parent.config(menu=self.menubar)
def __create_menu(self):
self.menubar = tk.Menu(self.parent)
self.filemenu = tk.Menu(self.menubar, tearoff=0)
self.filemenu.add_command(label="Open", command=self.dummy_func)
self.filemenu.add_separator()
self.filemenu.add_command(label="Exit", command=self.close_quit)
self.menubar.add_cascade(label="File", menu=self.filemenu)
helpmenu = tk.Menu(self.menubar, tearoff=0)
helpmenu.add_command(label="About...", command=self.dummy_func)
self.menubar.add_cascade(label="Help", menu=helpmenu)
def dummy_func(self):
print("dummy")
pass
#
# MAIN
#
def main():
root = tk.Tk()
root.title("Frames")
root.geometry("550x300+525+300")
root.configure(background="#808080")
root.option_add("*font", ("Courier New", 9, "normal"))
window = MainWindow(master=root)
root.protocol("WM_DELETE_WINDOW", window.close_quit)
root.mainloop()
if __name__ == '__main__':
main()
Overview
There is no quick fix for your problem. You are doing several things in your code that stack the odds against you, and which makes it really hard to debug your code. A rewrite is the best solution.
Here are things I've found that make the problem more difficult than they need to be, and which can be solved by a rewrite.
First, don't have a widget be responsible for placing itself in its parent. In your case you're going one step further by having a widget place it's parent into its grandparent. That's simply the wrong way to do it. A containing window should be responsible for layout out it's children, and nothing more
Second, put as much of your layout code together as possible. By "all" I mean "all for a given container". So, for example, instead of this:
x=Label(...)
x.grid(...)
y =Label(...)
y.grid(...)
... do it like this:
x = Label(...)
y = Label(...)
x.grid(...)
y.grid(...)
This makes it much easier to visualize your layout in the code.
Third, don't try to solve multiple layout problems at once. Solve one problem at a time. Since you posted your whole program, I assume you have a problem with the whole program and not just the upper-left widgets. When I ran your code I observed resize behavior that seemed off to me, reinforcing that belief.
Fourth, if you create a class like MainWindow that is designed to contain other widgets, all of these other widgets shild be children of the window, not children of the parent.
Fifth, as a general rule of thumb you want only a single row and single column with a container to have a non-zero weight. This is only a guideline, because there are often times when you want more than one widget to expand or contract. In the case of scrollbars and statuslabels, you typically don't want them to be given extra space. Part of the problem in your code is that you give everything a weight, so extra space is allocated to every widget.
The solution
The solution is work on one section at a time. Get it working, then move on to the next section. In your case I see the effort breaking down into four phases. First, get the main window created and laid out. Next, add the widgets that are immediate children of the main window. After that, create the container for the upper-left window. Finally, add the widgets to the upper left window. After each phase, be sure to run the code and verify that everything looks right.
The main window
The first step is to start with the main window. Get it to look right, and behave properly when you resize the main window. When I ran your code the bottom and right windows would vanish when the window became small, and the bottom frames grew when the window was large. I'm guessing that's not the correct behavior. Regardless, start with what you think is the correct behavior and make it work before adding any more widgets.
Start with the following code. Verify that when you resize the window, everything expands and contracts the way you expect. Notice that I made a couple of simple changes. The main thing to notice is that the function that creates the main window is responsible for making that window visible. I use pack since it's the only widget in the root window, and it means I can do it all in one line without having to worry about row and column weights with grid.
import sys
import os
import Tkinter as tk
import ttk as ttk
class MainWindow(tk.Frame):
def __init__(self, master=None):
self.parent = master
tk.Frame.__init__(self, self.parent, bg='bisque', borderwidth=1, relief="sunken")
self.__create_layout()
def __create_layout(self):
pass
#
# MAIN
#
def main():
root = tk.Tk()
root.title("Frames")
root.geometry("550x300+525+300")
root.configure(background="#808080")
root.option_add("*font", ("Courier New", 9, "normal"))
window = MainWindow(master=root)
window.pack(side="top", fill="both", expand=True)
root.mainloop()
if __name__ == '__main__':
main()
The widgets inside of MainWindow
Once you have the MainWindow shell behaving properly, it's time to add more widgets. It looks like you have five frames, though one of them is a custom class. For now we'll use a regular frame to keep things simple. It's good that you give each one a color, that's a great way to visualize the layout as you build up the window.
Modify __create_layout to look like the following. Note two important changes: every widget is a child of self rather than self.parent, and I've grouped all of the layout code together.
The resize behavior looks off to me, but I don't know exactly what you intend. For me it seems odd to have the green, red and pink frames resize the way they do, though maybe that's what you want. Regardless, now is the time to get it right.
def __create_layout(self):
self.Frame1 = tk.Frame(self, bg="grey")
self.Frame2 = tk.Frame(self, bg="blue")
self.Frame3 = tk.Frame(self, bg="green")
self.Frame4 = tk.Frame(self, bg="brown")
self.Frame5 = tk.Frame(self, bg="pink")
self.Frame1.grid(row=0, column=0, rowspan=4, columnspan=8, sticky=(tk.N, tk.S, tk.W, tk.E))
self.Frame2.grid(row=0, column=8, rowspan=4, columnspan=2, sticky=(tk.N, tk.S, tk.W, tk.E))
self.Frame3.grid(row=4, column=0, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))
self.Frame4.grid(row=4, column=5, rowspan=2, columnspan=5, sticky=(tk.N, tk.S, tk.W, tk.E))
self.Frame5.grid(row=5, column=0, rowspan=1, columnspan=10, sticky=(tk.N, tk.S, tk.W, tk.E))
for r in range(6):
self.rowconfigure(r, weight=1)
for c in range(10):
self.columnconfigure(c, weight=1)
The LeftUpperWindow widget
Next, let's define the LeftUpperWindow class, and make sure we haven't broken anything. Add a stub for the class, give the window a distinct color, and make sure the window still looks ok when it appears and when it is resized.
class LeftUpperWindow(tk.Frame):
def __init__(self, master=None):
self.parent = master
tk.Frame.__init__(self, self.parent, bg='bisque', borderwidth=1, relief="sunken")
self.__create_layout()
def __create_layout(self):
pass
Remember to modify MainWindow.__create_layout to create an instance of this class rather than frame:
self.frame1 = LeftUpperWindow(self)
The widgets in LeftUpperWindow
Finally, it's time to add the widgets in LeftUpperWindow. Like we did in MainWindow, the widgets are all children of self, and the widget creation and widget layout are done in separate groups. Also, it's usually best to only give a single row and a single column a non-zero weight. This is usually the row and column that a text or canvas widget is in.
You can do what you want, of course. Just be mindful of the fact that because you gave each row and column a weight in your original code, that contributed to all of the space between widgets.
Also, because you're giving the GUI an explicit size (via root.geometry(...)), you want to give the text widget a small size. Otherwise, the default width and height will push out the other widgets since tkinter tries to give each widget its default size. Since you're constraining the overall window size you want the text widget to be as small as possible, and then let grid expand it to fill the area it's been given.
Here's what __create_layout should look like:
def __create_layout(self):
self.editArea = tk.Text(self, wrap=tk.NONE, undo=True,
width=1, height=1,
relief=tk.SUNKEN, borderwidth=5,
highlightthickness=0, insertbackground="white")
self.editArea.config(font=('courier', 10, 'normal'))
self.editArea.configure(bg="black", fg="white")
self.scrollbarY = tk.Scrollbar(self, orient=tk.VERTICAL)
self.scrollbarX = tk.Scrollbar(self, orient=tk.HORIZONTAL)
self.status = tk.Label(self, text="Status label", bd=1, relief=tk.SUNKEN,
anchor=tk.W, bg='lightgray')
self.editArea.grid(row=0, column=0, sticky=(tk.N, tk.S, tk.W, tk.E))
self.scrollbarY.grid(row=0, column=1, sticky=(tk.N, tk.S), rowspan=2)
self.scrollbarX.grid(row=1, column=0, sticky=(tk.W, tk.E))
self.status.grid(row=2, column=0, sticky=(tk.N, tk.S, tk.W, tk.E), columnspan=2)
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)

Categories