Tkinter - Text widget shrinks when Scrollbar is added - python

I'm working on the GUI for a simple quiz app using Tkinter in Python 2.7.
Thus far, I have begun to set up my frame. I've put a scrollbar inside of a Text widget named results_txtbx to scroll up and down a list noting the player's performance on each question. I've been using grid since it's easier for me to manage.
from Tkinter import *
class Q_and_A:
def __init__(self, master):
frame = Frame(master)
Label(master).grid(row = 4)
results_txtbx = Text(master)
results_scrbr = Scrollbar(results_txtbx)
results_scrbr.grid(sticky = NS + E)
results_txtbx.config(width = 20, height = 4, wrap = NONE, yscrollcommand = results_scrbr.set)
results_txtbx.grid(row = 3, column = 1, padx = 12, sticky = W)
root = Tk()
root.wm_title("Question and Answer")
root.resizable(0, 0)
app = Q_and_A(root)
root.mainloop()
What happens is that when it runs, results_txtbx resizes to fit the scrollbar. Is there any way to make it keep its original size using grid?

You don't want to use a text widget as the master for a scrollbar. Like any other widget, if you pack or grid the scrollbar in the text widget, the text widget will shrink or expand to fit the scrollbar. That is the crux of your problem.
Instead, create a separate frame (which you're already doing), and use that frame as the parent for both the text widget and the scrollbars. If you want the appearance that the scrollbars are inside, set the borderwidth of the text widget to zero, and then give the containing frame a small border.
As a final usability hint, I recommend not making the window non-resizable. Your users probably know better what size of window they want than you do. Don't take that control away from your users.
Here's (roughly) how I would implement your code:
I would use import Tkinter as tk rather than from Tkinter import * since global imports are generally a bad idea.
I would make Q_and_A a subclass of tk.Frame so that it can be treated as a widget.
I would make the whole window resizable
I would separate widget creation from widget layout, so all my layout options are in one place. This makes it easier to write and maintain, IMO.
As mentioned in my answer, I would put the text and scrollbar widgets inside a frame
Here's the final result:
import Tkinter as tk
class Q_and_A(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master, borderwidth=1, relief="sunken")
self.label = tk.Label(self)
self.results_txtbx = tk.Text(self, width=20, height=4, wrap="none",
borderwidth=0, highlightthickness=0)
self.results_scrbr = tk.Scrollbar(self, orient="vertical",
command=self.results_txtbx.yview)
self.results_txtbx.configure(yscrollcommand=self.results_scrbr.set)
self.label.grid(row=1, columnspan=2)
self.results_scrbr.grid(row=0, column=1, sticky="ns")
self.results_txtbx.grid(row=0, column=0, sticky="nsew")
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
root = tk.Tk()
root.wm_title("Question And Answer")
app = Q_and_A(root)
app.pack(side="top", fill="both", expand=True)
root.mainloop()

Set results_scrbr.grid(row = 3, column = 2) next to results_txtbx.grid(row = 3,column = 1, padx = 4), sticky is not needed because window is not resizable, and i lowered the padx so scrollbar is closer to text.
Also to make the results_txtbx vertically scrollable, add results_scrbr.config(command=results_txtbx.yview)
Here is a working code...
from Tkinter import *
class Q_and_A:
def __init__(self, master):
frame = Frame(master)
Label(master).grid(row = 4)
results_txtbx = Text(master)
results_scrbr = Scrollbar(master)
results_scrbr.grid(row = 3, column = 2)
results_scrbr.config(command=results_txtbx.yview)
results_txtbx.config(width = 20, height = 4,
wrap = NONE, yscrollcommand = results_scrbr.set)
results_txtbx.grid(row = 3, column = 1, padx = 4)
root = Tk()
root.wm_title("Question and Answer")
root.resizable(0, 0)
app = Q_and_A(root)
root.mainloop()

My implemented solution:
I needed to add more widgets to the app, so I bound the Scrollbar and Text widgets to another label and put that in the proper column the code (trimmed for readability) is below:
import Tkinter as tk
class Q_and_A(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.label = tk.Label(self)
#Set up menu strip
self.main_menu = tk.Menu(self)
self.file_menu = tk.Menu(self.main_menu, tearoff = 0)
self.file_menu.add_command(label = "Exit", command = self.quit)
self.main_menu.add_cascade(label = "File", menu = self.file_menu)
self.master.config(menu = self.main_menu)
#Set up labels
self.question_lbl = tk.Label(self, text = "Question #: ", padx = 12, pady = 6)
self.question_lbl.grid(row = 0, sticky = "w")
tk.Label(self, text = "Hint: ").grid(row = 1, sticky = "w", padx = 12, pady = 6)
tk.Label(self, text = "Answer: ").grid(row = 2, sticky = "w", padx = 12, pady = 6)
tk.Label(self, text = "Results: ").grid(row = 3, sticky = "nw", padx = 12, pady = 6)
tk.Label(self).grid(row = 4)
#Set up textboxes
self.question_txtbx = tk.Entry(self)
self.question_txtbx.config(width = 60)
self.question_txtbx.grid(row = 0, column = 1, padx = 12, columnspan = 3, sticky = "w")
self.help_txtbx = tk.Entry(self)
self.help_txtbx.config(width = 40)
self.help_txtbx.grid(row = 1, column = 1, columnspan = 2, padx = 12, sticky = "w")
self.answer_txtbx = tk.Entry(self)
self.answer_txtbx.config(width = 40)
self.answer_txtbx.grid(row = 2, column = 1, columnspan = 2, padx = 12, sticky = "w")
self.results_label = tk.Label(self)
self.results_txtbx = tk.Text(self.results_label, width = 10, height = 4, wrap = "none", borderwidth = 1, highlightthickness = 1)
self.results_scrbr = tk.Scrollbar(self.results_label, orient = "vertical", command = self.results_txtbx.yview)
self.results_txtbx.configure(yscrollcommand = self.results_scrbr.set)
self.label.grid(row = 1)
self.results_label.grid(row = 3, column = 1, padx = 11, sticky = "w")
self.results_scrbr.grid(row = 0, column = 1, sticky = "nse")
self.results_txtbx.grid(row = 0, column = 0, sticky = "w")
root = tk.Tk()
root.wm_title("Question and Answer")
#A note: The window is non-resizable due to project specifications.
root.resizable(0, 0)
app = Q_and_A(root)
app.pack(side = "top", fill = "both")
root.mainloop()
I'll keep storage in nested labels as a reference for myself for when I need to group things close together, unless there's some reason it should be avoided. Worked very well here. Thanks to Bryan for the advice.

Related

How to scrolldown a textbox using scrollbar widget tkinter

Application: Building a notes application (as an intro to GUI development in Python) which includes feature of a scrollbar to scroll through a textbox
Problem: I can't actually seem to scroll down through the textbox. I don't seem to get the grayed rectangle which lets me control the scrollbar and scroll up/down through the textbox
#importing necessary packages
from tkinter import *
from tkinter import font
from tkinter import ttk
#set up main window
root = Tk()
root.title("Notes")
root.geometry("400x650")
#functions
#functions to change all widget button's backgrounds when user hovers over it and leaves it
def enter_button(e):
e.widget.config(background = "#D4D4D4")
#SystemButtonFace is default colour
def leave_button(e):
e.widget.config(background = "SystemButtonFace")
#clear text in text-box
def clear():
#delete all text from text_box
text_box.delete(1.0,END)
def bold_it():
#create font
try:
bold_font = font.Font(text_box, text_box.cget("font"))
bold_font.configure(weight = "bold")
#creating tag called "bold" which bolds textll upon condition
text_box.tag_configure("bold", font = bold_font)
#creating a bold tag which highlights first character
bold_tag = text_box.tag_names("sel.first")
#condition for checking to see if tag is applied or not
#in the first highlighted character
#if tag is applied, remove the bold from first-highlighted text
#- last highlighted text
#"bold" needs to be matched in the tag
if "bold" in bold_tag:
text_box.tag_remove("bold","sel.first","sel.last")
else:
text_box.tag_add("bold","sel.first", "sel.last")
except TclError:
pass
def italics_it():
try:
#create a font
italics_font = font.Font(text_box, text_box.cget("font"))
italics_font.configure(slant = "italic")
#create a tag called "italic"
text_box.tag_configure("italics", font = italics_font)
italics_tag = text_box.tag_names("sel.first")
#condition to see whether tag has been applies or not
if "italics" in italics_tag:
text_box.tag_remove("italics", "sel.first","sel.last")
else:
text_box.tag_add("italics", "sel.first", "sel.last")
except TclError:
pass
#frames
top_frame = LabelFrame(root, padx = 30, pady = 10)
button_frame = LabelFrame(root, padx = 30, pady = 10)
text_frame = LabelFrame(root, padx = 10, pady = 10)
bottom_frame = LabelFrame(root, borderwidth = 0, highlightthickness = 5)
top_frame.grid(row = 0 , column = 0)
button_frame.grid(row = 1, column = 0, pady = 10)
text_frame.grid(row = 2, column = 0, pady = 1)
bottom_frame.grid(row = 3, column = 0, pady = 3)
#labels, textboxes, buttons
#top_frame content
Notes_label = Label(top_frame, text = "Notes", fg = "black", font = 1, padx = 141)
Notes_label.grid(row = 0 , column = 0)
save_button = Button(top_frame, text = "save")
#padx increases distance between buttons
#button_frame content
#bold button
#the ideal is that if u press ctrl + b, the bold_button is pressed by itself
#rn, it's gonna be a highlight technique
bold_button = Button(button_frame, text = "B", padx = 4, pady = 2, command = bold_it)
bold_button.grid(row = 0, column = 0)
#italicsize button
italics_button = Button(button_frame, text = "I", padx = 4, pady = 2, command = italics_it)
italics_button.grid(row = 0, column = 2, padx = 15)
#text_box frame button
text_box = Text(text_frame, width = 45, height = 27)
text_box.grid(row = 0, column = 0)
#text_box frame content
main_scrollbar = ttk.Scrollbar(text_frame, orient = "vertical", command = text_box.yview)
main_scrollbar.grid(row = 0, column = 1)
text_box["yscrollcommand"] = main_scrollbar.set
clear_button = Button(bottom_frame, text = "clear", padx = 2, pady = 2, command = clear)
clear_button.grid(row = 0, column = 0, padx = 15, pady = 10)
save_button = Button(bottom_frame, text = "save note", padx = 2, pady = 2)
save_button.grid(row = 0, column =1, padx = 15, pady = 10)
#binding all buttons for changing colours when user hovers over it and leaves it
bold_button.bind("<Enter>", enter_button)
bold_button.bind("<Leave>", leave_button)
italics_button.bind("<Enter>", enter_button)
italics_button.bind("<Leave>", leave_button)
clear_button.bind("<Enter>", enter_button)
clear_button.bind("<Leave>", leave_button)
save_button.bind("<Enter>", enter_button)
save_button.bind("<Leave>", leave_button)
# main program loop
root.mainloop()
here's an image of the problem image of problem
I would also be very grateful if one could explain the concept of scrollbar.set and like yview and why they are both needed for the scrollbar to work. Tutorials and videos don't seem to explain the concept, but just implement it
In line 145. You're missing sticky
main_scrollbar.grid(row = 0, column = 1, sticky=NS)
Output:

Can't get Tkinter GUI to show using classes

I am trying to make a Tkinter GUI that takes Excel documents, reads them, and exports them to the window. The issue is when I changed the code to below to incorporate class structure, I cannot make the GUI load properly anymore.
import tkinter as tk
from tkinter.filedialog import askopenfilename
def NewFile():
print("New File!")
def OpenFile():
name = askopenfilename()
print(name)
def About():
print("About works")
def deletelist():
listbox.delete(0, END)
class MainApplication(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.frame = tk.Frame(self.master)
self.load = tk.Button(self.frame, text = "Load XLSX File", command = OpenFile)
self.load.grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'w')
self.ckframe = tk.LabelFrame(self.frame, text="Currency Selections")
self.ckframe.grid(row = 1, column = 0, padx = 5, pady = 5, stick = 'nesw')
self.prochk = tk.Checkbutton(self.ckframe, text = '1 Currency').grid(row = 1, column = 0, columnspan = 2, sticky = 'w')
self.msnchk = tk.Checkbutton(self.ckframe, text = '2 Currency').grid(row = 1, column = 2, columnspan = 2, sticky = 'w')
self.nightschk = tk.Checkbutton(self.ckframe, text = '3 Currency').grid(row = 1, column = 4, columnspan = 2, sticky = 'w')
self.semichk = tk.Checkbutton(self.ckframe, text = '4 Currency').grid(row = 2, column = 0, columnspan = 2, sticky = 'w')
self.instqualCRchk = tk.Checkbutton(self.ckframe, text = '5 Currency').grid(row = 2, column = 2, columnspan = 2, sticky = 'w')
self.msnCRchk = tk.Checkbutton(self.ckframe, text = '6 Currency').grid(row = 2, column = 4, columnspan = 2, sticky = 'w')
self.listbox = tk.Listbox(self.frame, width = 83)
self.listbox.grid(row = 3, column = 0, columnspan = 1, sticky = 'w') # Fix width size function
self.listbox.insert(1, 'Test1')
self.listbox.insert(0, 'Test2')
self.save = tk.Button(self.frame, text = "Save").grid(row = 8, column = 0, padx = 5, pady = 5, stick = 'e')
self.delete = tk.Button(self.frame, text = "Delete", command = deletelist).grid(row = 8, column = 0, padx = 45, pady = 5, stick = 'e')
if __name__ == '__main__':
root = tk.Tk()
MainApplication(root)
root.mainloop()
I searched a lot trying to find a solution to using classes with tkinter GUI and the grid system but I mostly found ways to make GUIs with pack() solution.
The instance of MainApplication is a frame. You never call pack or place or grid on that instance so that frame and all of its children will be invisible.
This frame creates another frame that contains all of the other widgets, self.frame. You never call pack, place, or grid on it either, so it and all of its children will be invisible.
Since the widgets are in a frame that is invisible, and that frame itself is invisible, none of the other widgets will be visible.
The first thing to do is to get rid of self.frame. Remove the line that creates it, and everywhere you reference self.frame, replace it with self. Since self in this context is already a frame it makes no sense to create another frame inside.
Next, you need to add the instance of MainApplication to the window. Since it's the only widget directly in the root window, pack is the simplest choice.
root = tk.Tk()
app = = MainApplication(root)
app.pack(fill="both", expand=True)

Tkinter grid method

I'm using Tkinter to create a GUI for my computer science coursework based on steganography. I'm using the .grid() function on the widgets in my window to lay them out, however I can't get this particular part to look how I want it to.
Here's what my GUI currently looks like: http://imgur.com/LNEZtEL
(or just the part with the error).
I want the remaining characters label to sit directly underneath the text entry box, but for some reason row 4 starts a large way down underneath the box. If I label the GUI with columns and rows anchored north west it looks like this: http://imgur.com/a/V7dTW.
If I shrink the image box on the left, it looks how I want, however I don't want the image this small: http://imgur.com/a/0Dudu.
The image box has a rowspan of 2, so what is causing the 4th row to start so low down from the text entry box? Here's roughly what I want the GUI to look like: http://imgur.com/a/ck04A.
Full code:
imageButton = Button(root, text="Add Image", command = add_image)
imageButton.grid(row = 2, columnspan = 2, sticky = W, padx = 30, pady = 20)
steg_widgets.append(imageButton)
image = Image.open("square.jpg")
image = image.resize((250,250))
photo = ImageTk.PhotoImage(image)
pictureLabel = Label(root, image = photo)
pictureLabel.image = photo
pictureLabel.grid(column = 0, row = 3, columnspan = 2, rowspan = 2, padx = 20, pady = (0, 20), sticky = NW)
steg_widgets.append(pictureLabel)
nameLabel = Label(root, text = "Brandon Edwards - OCR Computer Science Coursework 2016/2017")
nameLabel.grid(row = 0, column = 2, columnspan = 2, padx = (0, 20), pady = 10)
steg_widgets.append(nameLabel)
inputTextLabel = Label(root, text = "Enter text:")
inputTextLabel.grid(row = 2, column = 2, sticky = W)
steg_widgets.append(inputTextLabel)
startButton = Button(root, text="Go!", command = start_stega)
startButton.grid(row = 2, column = 2, sticky = E)
steg_widgets.append(startButton)
inputTextBox = Text(root, height = 10, width = 30)
inputTextBox.grid(row = 3, column = 2, sticky = NW)
steg_widgets.append(inputTextBox)
maxCharLabel = Label(root, text = "Remaining characters:")
maxCharLabel.grid(row = 4, column = 2, sticky = NW)
steg_widgets.append(maxCharLabel)
saveButton = Button(root, text="Save Image", command = save_image)
saveButton.grid(row = 2, column = 3, sticky = W)
steg_widgets.append(saveButton)
I recommend breaking your UI down into logical sections, and laying out each section separately.
For example, you clearly have two distinct sections: the image and button on the left, and the other widgets on the right. Start by creating containers for those two groups:
import Tkinter as tk
...
left_side = tk.Frame(root)
right_side = tk.Frame(root)
Since they are side-by-side, pack is the simplest way to lay them out:
left_side.pack(side="left", fill="y", expand=False)
right_side.pack(side="right", fill="both", expand=True)
Next, you can focus on just one side. You can use pack or grid. This uses grid for illustrative purposes:
image = tk.Canvas(left_side, ...)
button = tk.Button(left_side, ...)
left_side.grid_rowconfigure(0, weight=1)
left_side.grid_columnconfigure(0, weight=1)
image.grid(row=0, column=0, sticky="nw")
button.grid(row=1, column=0, sticky="n")
Finally, work on the right side. Since widgets are stacked top-to-bottom, pack is the natural choice:
l1 = tk.Label(right_side, text="Enter text:")
l2 = tk.Label(right_side, text="Remaining characters")
text = tk.Text(right_side)
l1.pack(side="top", fill="x")
text.pack(side="top", fill="both", expand=True)
l2.pack(side="top", fill="x")

tkinter gui fine tune widget layout

This is a follow-on to an answered post - I assume it's poor form to add to an answered thread even if it's related.
Overall I'm pretty happy with the layout (heavy lifting done by Bryan O. here).
Now I'm trying to fine-tune some widgets, and I can't seem to nudge things. In order to shift widgets I seem to need to layer additional frames to do so. This seems like using a shotgun to kill a fly, but what do I know?
I would like to nudge the button 'Add Edges" over so it has some space between it and the number entry widget to the left.
I would also love to have some space between the ok and cancel buttons on the bottom. Have tried adding padding via padx, and implementing layered frames whacks things up bad layout truncated central region pretty badly.I guess that geometry propagation means using padx isn't the right approach.
I cannot seem to nudge the widgets where I want them. My question specifically: using the code base I have, how do you recommend I make these fine-tune adjustments??
Thx
current gui layout
code:
from Tkinter import *
root2 = Tk()
root2.title('Model Definition')
root2.geometry('{}x{}'.format(460, 350))
# functions/commands
def get_list(event):
global seltext
"""
read the listbox selection and put the result somewhere
"""
# get selected line index
index = data_list.curselection()[0]
# get the line's text
seltext = data_list.get(index)
root2.update_idletasks()
# create all of the main containers
top_frame = Frame(root2, bg='cyan', width = 450, height=50, pady=6)
center = Frame(root2, bg='gray2', width=50, height=40, padx=3, pady=3)
btm_frame = Frame(root2, bg='plum4', width = 450, height = 45, pady=3)
btm_frame2_outer = Frame(root2, bg='lavender', width = 450, height = 60, pady=3)
btm_frame2 = Frame(btm_frame2_outer, bg='green', width = 350, height = 60, pady=3)
btm_frame2_cntr = Frame(btm_frame2_outer, bg='gray', width = 50, padx=7)
# layout all of the main containers
root2.grid_rowconfigure(1, weight=1)
root2.grid_columnconfigure(0, weight=1)
top_frame.grid(row=0, sticky="ew")
center.grid(row=1, sticky="nsew")
btm_frame.grid(row = 3, sticky="ew")
btm_frame2_outer.grid(row = 4, sticky="ew")
btm_frame2.grid(row = 1, columnspan = 2, sticky="ew")
btm_frame2_cntr.grid(row = 1, column = 4, sticky='ew')
# create the widgets for the top frame
model_label = Label(top_frame, text = 'Model Dimensions')
width_label = Label(top_frame, text = 'Width:')
length_label = Label(top_frame, text = 'Length:')
entry_W = Entry(top_frame, background="pink")
entry_L = Entry(top_frame, background="orange")
# layout the widgets in the top frame
model_label.grid(row = 0, column = 0, pady=5)
width_label.grid(row = 1, column = 0, sticky = 'e')
length_label.grid(row = 1, column = 2)
entry_W.grid(row = 1, column = 1)
entry_L.grid(row = 1, column = 3)
# create the center widgets
center.grid_rowconfigure(0, weight=1)
center.grid_columnconfigure(1, weight=1)
ctr_left = Frame(center, bg='blue', width=100, height=190)
ctr_mid = Frame(center, bg='yellow', width=250, height=190, padx=3, pady=3)
ctr_right = Frame(center, width=100, height=190, padx=3, pady=3)
ctr_left.grid(row=0, column = 0, sticky="ns")
ctr_mid.grid(row=0, column = 1, sticky="nsew")
ctr_right.grid(row=0, column = 2, sticky="ns")
# decorate the center frame widgets
# left
shift_up_label = Label(ctr_left, text = 'Shift Up')
shift_down_label = Label(ctr_left, text = 'Shift Down')
cut_label = Label(ctr_left, text = 'Cut')
copy_label = Label(ctr_left, text = 'Copy')
paste_label = Label(ctr_left, text = 'Paste')
# center
data_list = Listbox(ctr_mid, bg='snow2', width='55')
yscroll = Scrollbar(ctr_mid, command=data_list.yview, orient=VERTICAL)
# right
status_label = Label(ctr_right, text = 'Status', bg = 'green', height = 11)
#####################################################################
# layout the center widgets
#####################################################################
#left
shift_up_label.grid(row = 0, column = 0, pady = '7', sticky = 'nsew')
shift_down_label.grid(row = 1, column = 0, pady = '7', sticky = 'nsew')
cut_label.grid(row = 2, column = 0, pady = '7', sticky = 'nsew')
copy_label.grid(row = 3, column = 0, pady = '7', sticky = 'nsew')
paste_label.grid(row = 4, column = 0, pady = '7', sticky = 'nsew')
# center
data_list.grid(row=0, column=0, sticky='ns')
yscroll.grid(row=0, column=0, sticky='ens')
# right
status_label.grid(row = 2, column = 0, rowspan = 4, sticky = 'nsew')
# create the bottom widgets
# layout the bottom widgets
#####################################################################
# create bottom widgets
#####################################################################
label_label = Label(btm_frame, text = 'Label:', padx = '4')
entry_label = Entry(btm_frame, background="orange")
entry_number = Entry(btm_frame, background="cyan")
number_label = Label(btm_frame, text = 'Number:', padx = '4')
add_btn = Button(btm_frame, text='Add Edges', padx = '12')
ok_btn = Button(btm_frame2_cntr, text='OK', padx = '5')
cancel_btn = Button(btm_frame2_cntr, text='Cancel', padx = '12')
#####################################################################
# layout the bottom widgets
#####################################################################
label_label.grid(row = 1, column = 1, sticky = 'ew')
entry_label.grid(row = 1, column = 2, sticky = 'w')
number_label.grid(row = 1, column = 3, sticky = 'w')
entry_number.grid(row = 1, column = 4, sticky = 'e')
add_btn.grid(row = 1, column = 6, sticky = 'e')
ok_btn.grid(row = 0, column = 3, sticky = 'ew')
cancel_btn.grid(row = 0, column = 4, sticky = 'e')
# commands/bindings
data_list.configure(yscrollcommand=yscroll.set)
data_list.bind('<ButtonRelease-1>', get_list)
root2.mainloop()
I would like to nudge the button 'Add Edges" over so it has some space
between it and the number entry widget to the left.
When you call grid on a widget, there are two options that control spacing on either side of the widget: padx and pady. You can specify a single value that applies to both sides (eg: padx=20 will add 20 pixels on both the left and right side of the widget), or you can provide a two-tuple (eg: padx=(0,20) will only add 20 pixels on the right).
For example, to "nudge" the "Add Edges" button over, just add some padding:
add_btn.grid(row = 1, column = 6, sticky = 'e', padx=(20, 0))
I would also love to have some space between the ok and cancel buttons
on the bottom.
Again, padx is the solution:
ok_btn.grid(row = 0, column = 3, sticky = 'ew', padx=(0, 8))

Duplicate Frames Created When Calling a Function in a Tkinter Application

So this is my first Python GUI project utilizing tkinter. I come from a background in R.
I decided after a review of the documentation to create a class to handle the bulk of the work. The problem appears with my incrementer functions fwd() and bck(). If I do not call these functions in the following chunk of code:
class App:
def __init__(self, master):
....
self.total = 2
self.fwd()
self.bck()
The output of the entire code is an empty tkinter frame.
On the other hand, if I do call them, the fwd() function works as one would expect, but every time I click the back button (command = bck()), a new and identical GUI will be attached directly to the bottom of my current GUI. If I click the back button again, another GUI will pop up behind the current GUI.
from tkinter import *
from tkinter import font
from tkinter import filedialog
class App: #I'm not typing what goes in this class, this way I can avoid issues with App(Frame), etc. DUCKTYPE!
def __init__(self, master):
self.frame = Frame(master)
self.frame.pack()
self.master = master
master.title("PyCCI Caste")
self.total = 2
self.fwd() #Need to call these at the beginning otherwise the window is minimized??? No idea why.
self.bck() #The back button creates a duplicate window...
## +Incrementer
def fwd(self):
self.total += 1
print(self.total)
## -Incrementer THIS CREATES A SECOND PANED WINDOW, WHY?!
def bck(self):
self.total += -1
if self.total < 3:
self.total = 2
print(self.total)
#Body
self.k1 = PanedWindow(self.frame, #Note: if this is not self.frame, the error: 'App' object has no attribute 'tk' is thrown
height=500,
width=750,
orient = VERTICAL)
self.k1.pack(fill=BOTH, expand = 1)
self.titlefont = font.Font(size = 12,
weight = 'bold')
self.boldfont = font.Font(size=8,
weight = 'bold')
self.textfont = font.Font(family = 'Arial',
size = 10)
#Title
self.title = PanedWindow(self.k1)
self.k1.add(self.title, padx = 10, pady = 10)
Label(self.title, text = "Chronic Critically Ill Patient GUI",
font = self.titlefont,
fg="darkslateblue").pack()
#Top row open csv window & button
self.k2 = PanedWindow(self.k1)
self.k1.add(self.k2)
self.openbutton = Button(self.k2,
text = "Open CSV")#, command = openfile())
self.openbutton.pack(side = LEFT,
padx = 30)
#Panes below buttons
self.k3 = PanedWindow(self.k1)
self.k1.add(self.k3)
self.leftpane = PanedWindow(self.k3)
self.k3.add(self.leftpane,
width = 400,
padx = 30,
pady = 25,
stretch = "first")
self.separator = PanedWindow(self.k3,
relief = SUNKEN)
self.k3.add(self.separator,
width=2,
padx=1,
pady=20)
self.rightpane = PanedWindow(self.k3)
self.k3.add(self.rightpane,
width = 220,
padx = 10,
pady = 25,
stretch = "never")
#Left pane patient note text frame doo-diddly
self.ptframe = LabelFrame(self.leftpane,
text = "Medical Record",
font = self.boldfont,
padx = 0,
pady=0,
borderwidth = 0)
self.ptframe.pack()
Label(self.ptframe,
text = "patient # of ##").pack()
#Incrementer buttons
self.buttonframe = Frame(self.ptframe)
self.buttonframe.pack()
self.buttonframe.place(relx=0.97, anchor = NE)
#Back Button
self.button1 = Button(self.buttonframe, text = 'Back', width = 6, command = self.bck)
self.button1.grid(row = 0, column = 0, padx = 2, pady = 2)
#Next Button
self.button2 = Button(self.buttonframe, text = 'Next', width = 6, command = self.fwd)
self.button2.grid(row = 0, column = 2, padx = 2, pady = 2)
#Scrollbar!
self.ptscroll = Scrollbar(self.ptframe)
self.ptscroll.pack(side = RIGHT, fill = Y)
self.pttext = Text(self.ptframe,
height=300,
width=400,
wrap=WORD,
font=self.textfont,
spacing1=2,
spacing2=2,
spacing3=3,
padx=15,
pady=15)
self.pttext.pack()
self.ptscroll.config(command=self.pttext.yview)
self.pttext.config(yscrollcommand=self.ptscroll.set)
#Checkbuttons
self.checkframe = LabelFrame(self.rightpane, text="Indicators",
font=self.boldfont,
padx = 10,
pady = 10,
borderwidth=0)
self.checkframe.pack()
self.check1 = Checkbutton(self.checkframe, text="Non-Adherence")
self.check1.grid(row = 1,
column = 0,
sticky = W)
root = Tk()
app = App(root) ## apply the class "App" to Tk()
### Menu stuff does not need to be part of the class
menubar = Menu(root)
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Open CSV")#, command=openfile)
menubar.add_cascade(label="File", menu=filemenu)
helpmenu = Menu(menubar, tearoff=0)
helpmenu.add_command(label="About")#, command=about)
menubar.add_cascade(label="Help", menu=helpmenu)
root.config(menu=menubar)
root.mainloop()
What do you folks think? If I'm missing any pertinent information here, please let me know. The difficulty I'm having is that I don't know what I don't know about Python/Tkinter yet.
Thanks, I really appreciate any insight and direction.
Solved (thanks Bryan Oakley & TigerhawkT3): Due to Python's use of indentation as part of its syntax, I had created a function bck() which, when called, includes the code for the entirety of the rest of the GUI. To solve this problem after it was pointed out, I drew heavily from:
Python def function: How do you specify the end of the function?
You appear you have a simple indentation error. It seems like you intend for bck to have four lines of code, but because almost all of the remaining code is indented the same, it is all considered to be part of bck.

Categories

Resources