Duplicate Frames Created When Calling a Function in a Tkinter Application - python

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.

Related

Clearing a tkinter root.subform from within a separate function

I have several larger tkinter / python program which I would like to incorporate into one program which would clear a frame when another program is called; each program currently being inside a function (I probably should use classes eventually when I understand them) and each of these function being displayed on a form being cleared of widgets from the previous if any do exist.
The code below is just a small trial for me to understand how to do this, but it's not working.
When I invoke the widget.destroy() function, it removes the frame (DisplayFrame) and does not clear the widgets inside it and hence not displaying the new widgets.
here is the current trial code:
#!/usr/bin/env python3
import tkinter as tk
from tkinter import *
from tkinter import ttk
#import pandas as pd
import MultiTempsP3
import datetime, time
from tkinter import messagebox
import sqlite3
from tkinter import colorchooser
from configparser import ConfigParser
import os
import csv
if os.environ.get('DISPLAY','') == "":
print('no display found.Using :0.0')
os.environ.__setitem__('DISPLAY',':0.0')
root = tk.Tk()
root.title("Kombucha Program")
root.geometry("1400x800")
root.minsize(width=900, height=600)
#root.maxsize(width=1400, height = 900)
root.grid_rowconfigure(3, weight=1)
root.grid_columnconfigure(2, weight=1)
root.configure( bg = '#000080' )
DisplayFrame = tk.Frame(root, width=1200, height = 630, bg = 'yellow') #0059b3')
DisplayFrame.grid(column=0,row=1, sticky = N, in_ = root)
rightFrame = tk.Frame(root, width = 120, height = 390, bg = 'white') # #000080
rightFrame.grid(column = 1, row = 0, pady = 10, padx = 10)
lblFrame = tk.Frame(root, height = 70, width = 670, bg = 'black')
lblFrame.grid(column = 0, row = 0, sticky =N, in_ = root)
##'W' stands for West = WrightFrmae (west fframe on the right of screen
#WrightFrame = tk.Frame(rightFrame, width = 70, height = 300, bg = 'green') # #000080
#WrightFrame.grid(column = 0, row = 1)
WidgetFrame = tk.Frame(root, height = 300, width = 120, bg = 'red') # #000080
WidgetFrame.grid(column=0,row=2, pady = 30)
fromTemp = MultiTempsP3.temps("65cd6bd")
lblTemp = Label(rightFrame, text=fromTemp).grid(row=1,column=0,pady=0 )
#lblTemp.pack()
def clearDisplayFrame():
for widgets in DisplayFrame.winfo_children():
widgets.destroy()
###***### - This section is in the right top little frame = rightFrame
state = "yes" ## delete this row and use below state=GPIO when on an RPi
#state = GPIO.input(17)
if state:
state_17="GPIO_17 (HeatPad) is On "
else:
state_17="GPIO_17 (HeatPad) is Off "
lblHeatPad = Label(rightFrame, text=state).grid(row=3,column=0,pady=0 ) #shows as text in the window
#lblHeatPad.pack() #organizes widgets in blocks before placing them in the parent.
###***### End of rightFrame widgets
def func_quit():
root.destroy()
def openData():
clearDisplayFrame()
print("I am inside openData()")
lbltrial=tk.Label(DisplayFrame,text="trial").grid(row=3, column=2)
def func_Temps():
clearDisplayFrame()
print("I am inside func_Temps()")
#DisplayFrame = tk.Frame(root, width=1200, height = 630, bg = 'yellow') #0059b3')
#DisplayFrame.grid(column=0,row=1, sticky = N, in_ = root)
lblSomething = tk.Label(DisplayFrame, text = "Open Temps").grid(row=2,column=2)
###***### This section is top of left = lblFrame
exitButton = tk.Button(lblFrame, text="Quit the Program", width = 12, command=root.destroy, bg= "magenta")
exitButton.grid(row = 0, column = 0, columnspan = 1, pady = 5, padx = 5)
dataButton = Button(lblFrame, text="Open Dates Window", command=openData).grid(row=0, column=1) ## the open refers to the above function
tempsButton= Button(lblFrame, text="Open Temps Info", command=func_Temps).grid(row=0, column=2)
###***### End of top left widget in lblFrame
mainloop()
As an answer, here is an approach that uses 2 frame and switches between them in the click of the switch. This is the way usually switching between frame is implemented in procedural programming, AFAIK:
from tkinter import *
root = Tk()
def change(frame):
frame.tkraise() # Raising the passed frame
window1 = Frame(root)
window2 = Frame(root)
window1.grid(row=0,column=0) # Grid in the same location so one will cover/hide the other
window2.grid(row=0,column=0)
# Contents inside your frame...
Label(window1,text='This is page 1',font=(0,21)).pack()
Label(window2,text='This is page 2',font=(0,21)).pack()
# Buttons to switch between frame by passing the frame as an argument
Button(root,text='Page 1',command=lambda: change(window1)).grid(row=1,column=0,stick='w')
Button(root,text='Page 2',command=lambda: change(window2)).grid(row=1,column=0,stick='e')
root.mainloop()
So instead of destroying all the items inside your frame, you should just raise the other frame, as destroyed widgets cannot be brought back.

How to close a tkinter dialog box after running another function?

I am working with python tkinter and I have a dialog box window that pops up. I have two buttons in the box:
from tksheet import Sheet
from tkinter import *
import os
import sys
import mapMaker2
root=Tk()
root.title('Map Tool')
root.geometry("750x750")
sheetframe = Frame(root)
sheetframe.grid(row = 0, column = 0,)
buttonEditlabel = Button(sheetframe, text='Edit Labels', width=12, command=lambda: [openEditWindow()], bg='#cacccf',fg='black')
buttonEditlabel.grid(row = 0, sticky=W, column = 0, pady = (25,5), padx = (50,0))
def openEditWindow():
top = Toplevel(root)
top.geometry("260x195")
top.title('Edit Axes Labels')
frm = Frame(top, borderwidth=0, relief='ridge')
frm.grid(row = 0, column = 0, pady = (20,0),padx=(20,0))
b_cancel = Button(frm, text='Close', width=10)
b_cancel['command'] = top.destroy
b_cancel.grid(column = 0, row = 6, pady = (15,0),padx=(0,0))
b_save = Button(frm, text='Save', width=10)
b_save['command'] = lambda: editLabels()
b_save.grid(column = 1, row = 6, sticky = E, pady = (15,0),padx=(0,0))
def editLabels():
pass
mainloop()
Cancel button closes the window with top.destroy command. I would like the Save button to also close the window after running the editLabels() function first. I have tried:
b_save['command'] = [lambda: editLabels(), top.destroy]
but this doesn't work.
Here is one way you can do it. Create a function to destroy the top window.
def kill_main():
top.destroy()
top.update()
Then call the function wherever you want. You don't have to add kill_main() to the button itself. Just put it inside the next function you are opening so that it will close the Top windows first and then run the rest of the editlabels() function. Hopefully, it makes sense to you.
def editLabels():
kill_main()
pass

Why is my notebook in tkinter not fully visible within a frame whereas it expands correctly outside?

Here is my problem: I'm writing a GUI in python with tkinter and of course without being an expert the more complex it becomes, the more stange problems I find. So Here I am.
The small script bellow is a part of my main app and still generate my problem. I've several sub-frames in a main containing frame. In the first sub-frame, two buttons can call classes to fill the other both sub-frames.
My problem is when I want to fill using the "local view" button the bottom frame f3 with a notebook (from the pmw package), the latter seems to be cut as if f3 couldn't expand. If I populate the middle sub-frame with a press on "global view" button, it expands f3 without expanding the notebook.
However:
If I replace the notebook by another frame, everything expands correctly.
If I place the notebook under the subframes, it is first cut as if it was in the f3 subframe but expand correctly when "global view" button is used.
Do you have any idea of what happens with my notebook ? Thank !
To test the script, use the IF statements at lines 37 and 39 to switch between inside and under the f4 subframe, and the IF statements at lines 61 and 66 to switch between the notebook and a frame (ask me more details if required).
import sys,os
if sys.version_info[0] < 3:
import Tkinter as tk
else:
import tkinter as tk
import Pmw
class MainApp():
def __init__(self, master):
self.master = master
self.mainf = tk.LabelFrame(self.master, text="main")
self.mainf.grid(row=0, column=0, padx = 10, pady = 10, sticky='nsew')
self.f1 = tk.LabelFrame(self.mainf, text="f1")
self.f1.grid(row=0, column=0, padx = 10, pady = 10, sticky='nsew')
self.f2 = tk.LabelFrame(self.mainf, text="f2")
self.f2.grid(row=1, column=0, padx = 10, pady = 10, sticky='nsew')
self.f3 = tk.LabelFrame(self.mainf, text="f3")
self.f3.grid(row=2, column=0, padx = 10, pady = 10, sticky='nsew')
### Buttons
self.selButton = tk.Button(self.f1, text = 'local view', width=15, command=self.set_local_view)
self.selButton.grid(row=0, column=0, padx = 10, pady = 10)
self.allButton = tk.Button(self.f1, text = "global view", width=15, command=self.set_global_view)
self.allButton.grid(row=0, column=1, padx = 10, pady = 10)
## Close function
self.master.wm_protocol("WM_DELETE_WINDOW", self.exitProgram)
def set_local_view(self):
if 1: # add the notebook in the main frame
DetailMon(self.mainf)
if 0: # add the notebook in a subframe
DetailMon(self.f3)
def set_global_view(self):
PreviewMon(self.f2)
def exitProgram(self):
print("Exit Button pressed")
self.master.destroy()
class PreviewMon():
def __init__(self, master):
self.master = master
self.f1 = tk.LabelFrame(self.master, text="P1", width=700, height=50)
self.f1.grid(row=0, column=0, padx = 5, pady = 5, sticky='nsew')
self.f1.grid_propagate(False)
class DetailMon():
def __init__(self, master):
self.master = master
if 1: # Create, grid and add a page to the notebook
self.notebook = Pmw.NoteBook(self.master)
self.notebook.grid(row=3, column=0, padx = 10, pady = 10, sticky='nsew')
self.page = self.notebook.add('Manual')
if 0: # Create a labelframe instead of a notebook
self.page = tk.LabelFrame(self.master, text="manual")
self.page.grid(row=0, column=0, padx = 5, pady = 5, sticky='nsew')
self.group1 = tk.LabelFrame(self.page, text='f1')
self.group1.grid(row=0, column=0, padx = 10, pady = 1, sticky='nsew')
# Create a scale
tk.Scale(self.group1, from_=10, to=40, length=600, tickinterval=5, orient='horizontal').pack()
def main():
root = tk.Tk()
app = MainApp(root)
root.mainloop()
if __name__ == '__main__':
main()

Empty Tkinter Window

I am doing a bit of basic Tkinter code, and when I launch I get no errors, but my window is empty, even though I have added things to them. I saw this question here, but that does not help me, as I have what it says to do.
from tkinter import *
class App:
def __init__(self,master):
frame = Frame(master)
frame.pack
self.sg = Button(frame, text = "Study Guide", command = self.studyGuide)
self.sg.grid(row = 2, column = 1)
self.sg.pack()
self.quizlet = Button(frame, text = "Quizlet", command = self.quizlet)
self.quizlet.grid(row = 2, column = 2)
self.quizlet.pack()
self.flashcard = Button(frame, text = "Flash Cards", command = self.flashcard)
self.flashcard.grid(row = 2, column = 3)
self.flashcard.pack()
self.quitButton = Button(frame, text = "Quit", command = frame.quit)
self.quitButton.grid(row = 3, column = 2)
self.quitButton.pack()
self.text = Label(frame, text = "Social Studies Study Tool")
self.text.grid(row = 1, column = 2)
self.text.pack()
def studyGuide(self):
print("Study Guide")
def quizlet(self):
print("Quizlet")
def flashcard(self):
print("Flashcards")
root = Tk()
app = App(root)
root.mainloop()
First, for every element that you call grid for, don't call pack. You only need to use one or the other. Second:
frame = Frame(master)
frame.pack
You appear to be missing a parentheses here.
frame = Frame(master)
frame.pack()
Don't mix up layout managers! Use either pack() or grid(), but not both.
If you use pack, add the side where to pack the items:
frame.pack() # note the missing () in your code
...
self.sg.pack(side=TOP)
If you use grid(), add frame.grid() to the top of your code:
frame.grid()
...
self.sg.grid(row = 2, column = 1)

Tkinter - Text widget shrinks when Scrollbar is added

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.

Categories

Resources