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

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

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.

Calling different functions inside a class

I'm having a question related to functions in a class. My goal is to write a program with a Visualization class, which handles all the GUI related stuff with tkinter and another class called SensorHandling, which will mainly test sensors whether they work or not and return the result, which will then be displayed in Visualization. I'm having problems with my GUI class.
I have two different functions inside the class Visualization, namely StartPage and TestingHead_Page. I pass root in order for them to be able to modify my application directly. Now, I want to display the TestingHead_Page with my button bTestingHead, but I get an error that TestingHead_Page is not defined.
How do I go about this problem?
Criticism is always appreciated as I want to learn.
import tkinter as tk
from tkinter import ttk
import serial
import platform
import time
LARGEFONT = ("Verdana", 35)
class Visualization:
def StartPage(self, root):
lTitle= ttk.Label(root, text ="Title", font = LARGEFONT)
lTitle.grid(row = 0, column = 1, sticky = "N", padx = 10, pady = 10)
lTitle.config(font = ('Calibri', 32, 'bold'))
lTesting = ttk.Label(root, text = "Testing", font = ('calibri', 20))
lTesting.grid(row = 1, column = 0, padx = 10, pady = 10)
bTestingHead = ttk.Button(root, text = "Testing Head", command = self.TestingHead_Page(root))
bTestingHead.grid(row = 2 , column = 0, padx = 20, pady = 20)
exit = tk.PhotoImage(file = "exit_icon.png")
bExit = ttk.Button(root, text = "exit", image = exit, command = root.destroy)
bExit.image = exit
bExit.place(relx = 1.0, rely = 0.0, anchor = 'ne')
def TestingHead_Page(self, root):
lTitle = ttk.Label(root, text = "Testing Head")
lTitle.grid(row = 0, column = 0, sticky = "W", padx = 10, pady = 10)
bStartPage = ttk.Button(root, text = "Start Page", command = "")
bStartPage.grid(row = 0, column = 1, sticky = "E", padx = 10, pady = 10)
exit = tk.PhotoImage(file = "exit_icon.png")
bExit = ttk.Button(root, text = "exit", image = exit, command = root.destroy)
bExit.image = exit
bExit.place(relx = 1.0, rely = 0.0, anchor = 'ne')
if __name__ == "__main__":
root = tk.Tk()
guiref = StartPage(self, root)
tk.mainloop()
#class SensorHandling():
In python, you need to pass the current instance, referenced through the self keyword, explicitly in the class's methods:
def StartPage(self, root):
...
def TestingHead_Page(self, root):
...
While inside the class, you would call them like this:
self.StartPage(root)
self.TestingHead_Page(root)
Read up on classes in Python.
Also, from the code, it seems like TestingHead_Page should return some value since you try to capture that in command = TestingHead_Page(root), so you need to fix that as well.

Change tkinter entry widget multiple times after pressing a button

I have a tkinter Entry widget and when a user presses a button the contents update:
from tkinter import *
window = Tk()
def abcdef(num):
ent.config(state=NORMAL)
ent.delete(0, 'end')
ent.insert(0, num)
ent.config(state = "readonly")
print(num) #Just to check the code is being run
def changeEntry():
for j in range(3):
ent.after(1000, abcdef(j))
ent = Entry(widow, text="", state = "readonly", readonlybackground="white", font = "20")
ent.grid(row = 0, column = 0, columnspan = 3, sticky = "E")
btn = Button(window, text="Button", command=changeEntry)
btn.grid(row = 1, column = 0, sticky = "NESW", pady = 10, padx = 10)
window.mainloop()
When I press the button the window freezes for 3 seconds and then just displays the final number. How can I make it so when the user presses the button, the entry changes every second instead of just freezing for 3 seconds and only displaying the final one?
Thanks in advance
You have two problems with that .after call. The .after method tells Tkinter to call the function you pass it after the time interval has passed. But you're telling Tkinter to do 3 things after 1000 milliseconds have passed, so they'll all happen on top of each other. So you need to stagger the delays.
Secondly, you need to give .after a function to call when its time to call it. But your code calls the function and gives .after the return value of your function. We can fix that by wrapping the function call inside another function. A convenient way to do that is using lambda, giving the lambda a default argument it can pass to abcdef
import tkinter as tk
window = tk.Tk()
def abcdef(num):
ent.config(state=tk.NORMAL)
ent.delete(0, 'end')
ent.insert(0, num)
ent.config(state = "readonly")
print(num) #Just to check the code is being run
def changeEntry():
for j in range(3):
ent.after(1000 * j, lambda num=j: abcdef(num))
ent = tk.Entry(window, text="", state = "readonly", readonlybackground="white", font = "20")
ent.grid(row = 0, column = 0, columnspan = 3, sticky = "E")
btn = tk.Button(window, text="Button", command=changeEntry)
btn.grid(row = 1, column = 0, sticky = "NESW", pady = 10, padx = 10)
window.mainloop()
I've also replaced that "star" import with the neater import tkinter as tk. That makes it obvious which names come from Tkinter and which names are local to your program.
Bryan Oakley points out that we don't need that lambda, we can pass in arguments after the function name. See the Basic Widget Methods in the Tkinter docs for details. So we can re-write changeEntry like this:
def changeEntry():
for j in range(3):
ent.after(1000 * j, abcdef, j)
Thanks, Bryan!

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.

window label is not printing : Python

I want to create two windows.
Behaviour of windows:
Window1 has a label and a button. When I click on that button, 2nd window has to open. 2nd window have a label.
Problem:
Label in 2nd window is not appearing.
Code:
def window1():
root = tkinter.Tk()
root.geometry("200x200")
root.title("Window1")
var = tkinter.StringVar()
tkinter.Label(root, textvariable = var, bg = "red").grid(row = 0, column = 0)
var.set("This is window1")
tkinter.Button(root, text = "Button1", command = OnBut).grid(row = 0, column = 1)
root.mainloop()
def OnBut():
window2()
def window2():
root = tkinter.Tk()
root.title("Window2")
root.geometry("250x250")
var = tkinter.StringVar()
tkinter.Label(root, textvariable = var, bg = "blue").grid(row = 1, column = 0, padx = 3, pady = 3)
tkinter.Button(root, text = "Button", command = OnBut).grid(row = 0, column = 1, padx =3, pady = 3)
var.set("This is window2") #not appearing <-- problem
root.mainloop()
window1()
when I call window2 seperately, its working fine. Why label not printing in 2nd window, by clicking on button?
You don't really need a real function for your command in this case. This is what lambdas are made for -- callbacks!
Remove your onBut function (which is the problem anyway, since root isn't defined there) and replace your command in each button with:
command = lambda: window2(root)
Currently, when you call onBut, it tries to do:
window2(root)
# HELP I DON'T KNOW WHAT root IS!!
This throws a NameError on my copy. Your code may vary.
Since you're editing willy nilly, let me just write you some working code.
import tkinter
def run():
root = tkinter.Tk()
root.title("Window1")
s_var = tkinter.StringVar()
tkinter.Label(root, textvariable = s_var).pack()
tkinter.Button(root, text = "Button", command = lambda: makewindow(root)).pack()
s_var.set("Window #1")
def makewindow(root):
top = tkinter.Toplevel(root)
top.title("Window2")
s_var = tkinter.StringVar()
tkinter.Label(top, textvariable = s_var).pack()
tkinter.Button(top, text = "Button", command = lambda: makewindow(root)).pack()
s_var.set("Window #2")
if __name__ == "__main__":
run()

Categories

Resources