Python Tkinter open multiple windows within a function - python

I have an inbuilt calculator within my program. My main program was designed to be somewhat similar to Microsoft Bob. Now when the user presses the 'Calculator' Button in the main focus, it opens up the part of the calculator where you define your 2 numbers and the operator. When you press 'Results' the answer does not appear in any shape of form until my main program is closed. Any help? Attached is the calculator code sample, and more than happy to change the second window to something else. BTW this is for a school project.
def Calculator():
calculator = Tk()
Number1 = DoubleVar()
Number2 = DoubleVar()
Operator = IntVar()
Operator.set(1)
Entry(calculator,textvariable=Number1,justify="c").grid()
Entry(calculator,textvariable=Number2,justify="c").grid()
Radiobutton(calculator,text="Add",variable=Operator,value=1).grid()
Radiobutton(calculator,text="Subtract",variable=Operator,value=2).grid()
Radiobutton(calculator,text="Multiply",variable=Operator,value=3).grid()
Radiobutton(calculator,text="Divide",variable=Operator,value=4).grid()
Radiobutton(calculator,text="Square",variable=Operator,value=5).grid()
Radiobutton(calculator,text="Square root",variable=Operator,value=6).grid()
Button(calculator,text="results",command=calculator.destroy,width=16).grid()
Number1=Number1.get()
Number2=Number2.get()
Operator=Operator.get()
if Operator==1:Results=Number1+Number2
if Operator==2:Results=Number1-Number2
if Operator==3:Results=Number1*Number2
if Operator==4:Results=Number1/Number2
if Operator==5:Results=math.pow(Number1,Number2)
if Operator==6:Results=Number1*(1/Number2)
Results = "The answer is "+str(Results)
Answer = Tk()
Answer.geometry("150x150")
Label(Answer, text=Results).place(relx=.5,rely=.5,anchor="center")
Answer.mainloop()

You can't have multiple Tk objects with their own mainloops.
Or, rather, you can, but whichever one is currently running mainloop, none of the others (and none of their children) gets to do anything until that mainloop finishes.
What you want is to have multiple Toplevel widgets, with the same Tk as their master. (If you just have one Tk, as you usually do, you can leave that as the default.)
But you have another, equally serious problem here.
You're creating a Calculator with a bunch of Tk vars attached, then immediately trying to read those vars and do something with them. You can't do that.
What you need to do is to put all of that in the callback to some kind of user event, like clicking the Results button.
You have one more problem in your code that makes things a bit harder:
Number1=Number1.get()
What you really want here is two separate variables, one the Tk var, and the other an int. And then you need to make the Tk var accessible in the results callback in some way. The obvious way is to move all of this to a class, and store all your Tk vars as instance attributes. If you don't know how to do that, you can always use globals. (Not ideal, but until you learn classes, it's fine.) Then the actual numbers are just local to the callback function.
Putting it all together:
def Calculator(root):
global Number1, Number2, Operator
calculator = Toplevel()
Number1 = DoubleVar()
Number2 = DoubleVar()
Operator = IntVar()
Operator.set(1)
Entry(calculator,textvariable=Number1,justify="c").grid()
Entry(calculator,textvariable=Number2,justify="c").grid()
Radiobutton(calculator,text="Add",variable=Operator,value=1).grid()
Radiobutton(calculator,text="Subtract",variable=Operator,value=2).grid()
Radiobutton(calculator,text="Multiply",variable=Operator,value=3).grid()
Radiobutton(calculator,text="Divide",variable=Operator,value=4).grid()
Radiobutton(calculator,text="Square",variable=Operator,value=5).grid()
Radiobutton(calculator,text="Square root",variable=Operator,value=6).grid()
Button(calculator,text="results", command=lambda event: calculate(calculator), width=16).grid()
def calculate(calculator):
n1=Number1.get()
n2=Number2.get()
op=Operator.get()
if op==1:Results=n1+n2
if op==2:Results=n1-n2
if op==3:Results=n1*n2
if op==4:Results=n1/n2
if op==5:Results=math.pow(n1,n2)
if op==6:Results=n1*(1/n2)
Results = "The answer is "+str(Results)
calculator.destroy()
Answer = Toplevel()
Answer.geometry("150x150")
Label(Answer, text=Results).place(relx=.5,rely=.5,anchor="center")
root = Tk()
# Presumably your real code has some top-level stuff, where the
# user can ask you to open a calculator, like a button whose
# command calls the Calculator function? But here, we'll just:
Calculator(root)
root.mainloop()

Related

There is a way to wait on a user's answer in tkinter?

I'm developing an application what have its functions set in different files.
The main file have a tkinter interface and the buttons, entrys and labels are in other file, like this:
Mainfile.py
from tkinter import *
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
import Buttons
self.branch = Buttons.New_Button(self.main_frame)
#Here i wuold like to verify the hipotetic variable after the main_frame were destroyed
if self.branch.hipotetic_variable:
root.mainloop()
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame):
self.button_1 = Button(using_frame, text = 'Button 1', command=functools.partial(self.Func, using_frame))
self.button_1.pack()
def Func(self, to_destroy):
to_destroy.destroy()
#Here is the hipotetic variable what i would like to verify with if statment
self.hipotetic_variable = True
The problem is that I want to keep managing the program in the main file calling the other functions and implementing it, but I cannot verify if it's time to update the screen because mainloop makes impossible to verify it using a while loop and an hipotetic variable that's created after user pressed button.
I wold like to know if there is an way to update an variable contained in the Buttons.py file on Mainfile.py to keep implementing all other canvas in this file.
Your if self.branch.hipotetic_variable: check in the Program.__init__() method is only going to be executed when the Program class instance gets created initially, which is before the button that could change the value of the variable could have been pressed. You also don't want to make the hipotetic_variable an attribute of the Button because that will be destroyed along with the Frame it is in when that's destroyed in the button callback function.
Tkinter applications are user-event driven, meaning that they're "run" by responding to events (that's what mainloop is all about). This type of programming paradigm is different from the procedural or imperative one you're probably used to.
Therefore to do what you want requires setting things up so an event that the program can respond to will be generated, which in this case to when the frame is destroyed. One way to do that is by taking advantage of tkinter Variable classes to hold this hipotetic variable you're interested in. It looks like a boolean, so I used a tkinter BooleanVar to hold its value. One interesting thing about Variables is that you can have changes to their values "traced" by defining functions to be called whenever that happens. That's what I have done in the code below, and the callback function in this case — check_hipotetic_variable() — updates a Label to display the new value of the variable when it's called.
Below is your code with the modifications necessary to use a tkinter BooleanVar and trace changes to its value.
Mainfile.py
from tkinter import *
import Buttons
class Program:
def __init__(self, root):
root.geometry('200x200')
self.main_frame = Frame(root)
self.main_frame.pack()
self.notice_lbl = Label(root, text='')
self.notice_lbl.pack(side=BOTTOM)
self.hipotetic_variable = BooleanVar(value=False)
# Set up a trace "write" callback for whenever its contents are changed.
self.hipotetic_variable.trace('w', self.check_hipotetic_variable)
self.branch = Buttons.New_Button(self.main_frame, self.hipotetic_variable)
root.mainloop()
def check_hipotetic_variable(self, *args):
"""Display value of the hipotetic variable."""
value = self.hipotetic_variable.get()
self.notice_lbl.config(text=f'hipotetic variable is: {value}')
app = Program(Tk())
Buttons.py
from tkinter import *
import functools
class New_Button:
def __init__(self, using_frame, variable):
self.button_1 = Button(using_frame, text = 'Button 1',
command=functools.partial(self.Func, using_frame))
self.button_1.pack()
self.variable = variable # Save for use in callback.
def Func(self, to_destroy):
to_destroy.destroy()
self.variable.set(True) # # Change value of the variable.
P.S. I noticed you're not following the PEP 8 - Style Guide for Python Code, which makes reading your code harder to read and follow that if you're were following them — for that reason I strongly suggest you read the guide and start following the suggestions, especially the Naming Conventions which apply to functions and variable names, as well as the names of script files.

Tkinter control scale and entry field with each other

I have a scale and an input field which both control the same variable to give the user choice of which one they'd like to use. I've coded it a bit like this:
def scale_has_moved(value):
entry_field.delete(0, END)
entry_field.insert(0, str(float(value)))
# Other functions I want the code to do
def entry_field_has_been_written(*args):
value = float( entry_field.get() )
scale.set(value)
This works, when I move the scale the entry_field gets written in and vice versa, and the other functions I want the code to do all happen. The obvious problem is the functions call each other in a loop, so moving the scale calls scale_has_moved() which calls the additional functions within and writes in the entry field, then because the entry field has been written in entry_field_has_been_written() gets called which in turn calls scale_has_moved() again, it doesn't go in an endless loop but it does everything at least twice everytime which affects performance.
Any clue how I'd fix this? Thank you
If you use the same variable for both widgets, they will automatically stay in sync. You don't need your two functions at all. The following code illustrates the technique.
import tkinter as tk
root = tk.Tk()
var = tk.IntVar(value=0)
scale = tk.Scale(root, variable=var, orient="horizontal")
entry = tk.Entry(root, textvariable=var)
scale.pack(side="top", fill="x")
entry.pack(side="top", fill="x")
root.mainloop()

Create a function that ends mainloop and starts new one in tkinter

I'm writing my first GUI program today using Tkinter and I have stumbled onto a problem. I am trying to make a game that starts with an introduction window that closes after you press a button, then opens a new window where you can choose one of two modes. Unfortunately, I just can't get it running. It looks a little something like this.
#These are the functions that I defined to make it work
def start():
root.destroy()
def Rules_mode_1():
root.destroy
rules1 = Tk()
understood1 = Button(rules1, text="I understood", command="Start_game_mode_1")
understood.pack()
rules1.mainloop
# I haven't added rules 2 yet cause I couldn't get it to work with rules 1 so I haven't even #bothered but it would be the same code just switching the 1 for a 2. But really it isn't even
#necessary to have 2 different rule functions because the rules are the same but I couldn't think
#of another way to go about it. if you have an idea let me know
def Start_game_mode_1():
rules1.destroy #<----- THIS IS WHERE THE PROBLEM LIES. JUST DOESN'T RUN
gamemode1 = Tk()
#Here I would have the game
gamemode1.mainloop()
#now same here don't have gamemode 2 yet cause it just doesn't work yet
#This is where it really starts
root = Tk()
startbutton = Button(root, text="Start", command=start)
startbutton.pack
root.mainloop
root = Tk()
def mode():
mode1 = Button(root, command=Rules_mode_1)
mode1.pack
mode2 = #Buttonblablabla
mode()
root.mainloop()
Now I've been trying around for hours, trying to give the mainloops different names. For example giving the
rules1.mainloop
#the name
root.mainloop
but that obviously didn't work. I tried it with dozens of helper function and with the lambda expression and did hours of research but just can't seem to fix it. Does anybody have any ideas? Please be respectful and keep in mind it's my first time using Tkinter.
Thank you for your help!
After the comments didn't really help me I just tried things out for hours and in case anybody ever is having a a similar problem and reads this: The rules1 variable is inside a function, and therefore only local, which means it can't be destroyed in another function. I fixed it by making it a global, like:
def Rules_mode_1():
root.destroy
global rules1
rules1 = Tk()
understood1 = Button(rules1, text="I understood", command="Start_game_mode_1")
understood.pack()
rules1.mainloop
After that I could destroy the mainloop in the next function.

VarString.get() return an empty string

I am a beginner and I am making a login system (just for practicing).
I'm using tkinter to develop a simple UI. The thing is that when I call a second root (sign_in root) with a button from another root (main_screen), and I try to get some values typed in entry with StringVars assigned to them, they return just an empty string ""
def main_screen():
root=Tk()
user=StringVar()
pas=StringVar()
btn2=Button(root,text='Sign-In',command=sign_in_screen)
btn2.place(x=125,y=160)
root.mainloop()
def sign_in_screen():
root1=Tk()
newuser=StringVar()
newpas=StringVar()
ent3=Entry(root1,width=28,textvariable=newuser)
ent3.place(x=100,y=50)
ent4=Entry(root1,width=28,textvariable=newpas,show="*")
ent4.place(x=100,y=100)
btn3=Button(root1,text='Sign-In',command=lambda:register(newuser.get(), newpas.get()))
btn3.place(x=50,y=160)
root1.mainloop()
main_screen()
Having multiple instances of Tk become hideously complicated because each one creates a separate tcl interpreter. This causes weird effects like what you see here. It's you almost always want to use the Toplevel widget.

Stopping label replacement

I am creating a revision program for myself however whenever the fun1 function is called it prints out underneath the previously executed function. e.g the label will print out underneath the last one instead of replacing it, any ideas? Any help would be appreciated!!
#Imports moduals used
from tkinter import *
import time
import random
#Sets GUI
gui = Tk()
gui.geometry("500x500")
gui.maxsize(width=500, height=500)
gui.minsize(width=500, height=500)
#Sets list of facts
def t():
print("hi")
facts = ['fact one','true', 'fact two','abc']
#Defines random fact generator
def fun1():
r = random.randrange(len(facts))
lbl = Label(gui,text=facts[r]).pack()
btnt = Button(text="True", command=t).pack()
btnf = Button(text="False", command=t).pack()
gui.after(5000, fun1)
gui.after(5000, fun1)
mainloop()
Overview
The best way to write this sort of program is to create the label or button only once, and then use the configure method to change the text of the the label.
Using a procedural style
Here's an example based off of your original code:
#Imports moduals used
from tkinter import *
import time
import random
#Sets GUI
gui = Tk()
gui.geometry("500x500")
gui.maxsize(width=500, height=500)
gui.minsize(width=500, height=500)
def t():
print("hi")
#Defines random fact generator
def fun1():
r = random.randrange(len(facts))
lbl.configure(text=facts[r])
gui.after(5000, fun1)
#Sets list of facts
facts = ['fact one','true', 'fact two','abc']
# create the widgets
lbl = Label(gui,text="")
btnt = Button(text="True", command=t)
btnf = Button(text="False", command=t)
# lay out the widgets
lbl.pack(side="top", fill="x", expand=True)
btnt.pack(side="left", fill="x")
btnf.pack(side="right", fill="y")
# show the first fact; it will cause other
# facts to show up every N seconds
fun1()
mainloop()
Using an object-oriented style
Since you're just getting started, let me suggest a better way to organize your code. Python is object-oriented in nature, so it makes sense to make your application an object. Even though you may not be familiar with classes an objects, if you start with this pattern and follow a couple of simple rules, you can get all the benefits of object orientation without much effort.
The only thing you need to remember is that in your main class, all functions need to have self as the first parameter, and when calling a function you use self.function and omit self as an argument (python does that for you). Other than that, you can pretty much code as normal inside a class.
Here's an example:
import tkinter as tk
import random
class Example(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.facts = ['fact one','true', 'fact two','abc', 'a long fact\non two lines']
self.label = tk.Label(self,text="", width = 40, height=4)
self.true_button = tk.Button(self, text="True", command=self.t)
self.false_button = tk.Button(self, text="False", command=self.t)
self.label.pack(side="top", fill="both", expand=True, pady=40)
self.true_button.pack(side="left", fill="x")
self.false_button.pack(side="right", fill="x")
self.fun1()
def t(self):
print("hi")
def fun1(self):
r = random.randrange(len(self.facts))
self.label.configure(text=self.facts[r])
self.after(5000, self.fun1)
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Here are a few things to notice:
import tkinter as tk this requires a tiny bit more typing, but it makes your program more robust. The side effect of this (which is a good side effect IMO) is that all tkinter commands now need to be prefixed with tk (eg: tk.Button). In my opinion this is a much better way than just blindly importing everything from tkinter as a bunch of global variables and functions. I know must tutorials show from tkinter import *, but they are misguided.
The main part of your logic is a class. This makes it easy to avoid using global variables. As a rule of thumb when programming, you should avoid global variables.
Notice how self is an argument to t, fun1 and __init__ -- this is a requirement for python classes. It's just how you do it. Also notice we call them like self.fun1 rather than just fun1. This lets python know that you want to call the function associated with the current object. Once your program gets bigger, this makes it easier to know where fun1 is defined.
I removed the code that forces the size of the GUI. Tkinter is really good at calculating what the size should be, so let it do it's job. Also, if you take care when laying out your widgets, they will grow and shrink properly when the window is resized. This means you don't need to force a min or max size to a window. Forcing a size gives a bad user experience -- users should always be able to resize windows to fit their needs.
I separated the creation of the widgets from the layout of the widgets. For one, you have to separate them if you want to keep references to widgets. This is because this: lbl=label(...).pack() sets lbl to None. That's just how python works -- the last function is what gets saved to a variable, and both pack and grid always return none. The second reason is simply that it makes your code easier to write and maintain. All of the code that organizes your widgets is in one place, making it easier to see the big picture.

Categories

Resources