Why are my tkinter window objects (OOP tkinter) not BOTH showing? - python

I am trying to learn about tkinter from an OOP point of view so that I can create multiple windows.
I have created two files (main.py and Humanclass.py).
Why are both windows not being created? I thought that I had created a Class and in the main program created 2 instance of that class with different data?
Main.py:
import humanclass
from tkinter import *
window = Tk()
human1 = humanclass.Human(window, "Jim", "78", "British")
human2 = humanclass.Human(window, "Bob", "18", "Welsh")
window.mainloop()
humanclass.py:
from tkinter import *
class Human():
def __init__(self, window, name, age, nation):
self.window=window
self.window.geometry("500x200+100+200")
self.window.title(name)
self.label1 = Label(self.window, text=age).grid(row=0, column=0, sticky=W)
self.label2 = Label(self.window, text=nation).grid(row=1, column=0, sticky=W)
self.button = Button(self.window, text="Close", width=5, command=self.clicked).grid(row=3, column=0, sticky=W)
def clicked(self):
self.window.destroy()
Any help to show me the errors in my limited understanding would be gratefully received.

It's because window is only one active window, i.e. the root window. If you want to create multiple windows you will need to spawn them off of that root window. Simply assigning things to that window would overwrite whatever was previously there. That's why only your bottom instance is showing. While technically you could get away with implementing threading and running two root windows with two mainloops, it is highly advised not to do that.
What you should do is create Toplevel instances off of the root window. Think of these as like popup windows that are independent. You can make them independent of the root window or have them anchored to it. That way if you close the root window all the Toplevels off of it will close. I suggest you look more into Toplevels and you'll find what you're looking for. You probably want something like this:
Main.py
import humanclass
from Tkinter import *
window = Tk()
# Hides the root window since you will no longer see it
window.withdraw()
human1 = humanclass.Human(window, "Jim", "78", "British")
human2 = humanclass.Human(window, "Bob", "18", "Welsh")
window.mainloop()
humanclass.py
from Tkinter import *
class Human():
def __init__(self, window, name, age, nation):
# Creates a toplevel instance instead of using the root window
self.window=Toplevel(window)
self.window.geometry("500x200+100+200")
self.window.title(name)
self.label1 = Label(self.window, text=age).grid(row=0, column=0, sticky=W)
self.label2 = Label(self.window, text=nation).grid(row=1, column=0, sticky=W)
self.button = Button(self.window, text="Close", width=5, command=self.clicked).grid(row=3, column=0, sticky=W)
def clicked(self):
self.window.destroy()

Related

Python: Tkinter open custom widget in new window

My aim is to generate a window with a button "NewWindow" on it. If I press the button, the program should open a new window. This window I stored in a class "NewWindow" to quickly reproduce it.
In this "NewWindow" I have another button. If I press it the label of basic window should be updated and the window "NewWindow" should be closed automatically.
Here is my code:
from tkinter import *
class NewWindow(Toplevel):
def __init__(self, master = None):
super().__init__(master = master)
self.title('NewWindow')
self.lb = Label(self, text='Hello')
self.lb.grid(column=0, row=0, columnspan=1)
self.bt1 = Button(self, text="apply Hello", command= self.bt_press)
self.bt1.grid(column=0, row=1)
def bt_press(self):
window.basic_lb.text = "Hello"
window = Tk()
def new_Editor():
a = NewWindow(window)
window.title("BasicWindow")
window.basic_lb = Label(window, text='None')
window.basic_lb.grid(column=0, row=0, columnspan=1)
window.basic_bt = Button(window, text="NewWindow", command=new_Editor)
window.basic_bt.grid(column=0, row=1)
window.mainloop()
Problems:
At start both windows NewWindow and BasicWindow are displayd. I only want to open BasicWindow and NewWindow should be opened after button basic_bt is clicked. How can I solve it? (already solved by commed below)
Why the label text in basic_lb did not get some update after pressing self.bt1?
How is it possible to close NewWindow with use of bt_press method?
You have a few typos/errors in your code that are casuing some of your problems. As #Tim said, when you pass a function to a command like command=function(), it will be called on runtime, not when the button is pressed. You need to pass the function handle to the command, command=function. You got around this by using a lambda function in your button command, but it is easier to just have command=self.bt_press
Answering your second question, window.basic_lb.text = "Hello" is not how you change the text in a tkinter Label, use <Label>.config(text="Hello"). You also should use self.master and define self.master = master in __init__ instead of just using window, because while you can access window due to it not being defined in local scope, it's better to explicitly define it.
You can close a window using window.destroy().
Your working code is now:
from tkinter import *
class NewWindow(Toplevel):
def __init__(self, master = None):
super().__init__(master = master)
self.title('NewWindow')
self.master = master
self.lb = Label(self, text='Hello')
self.lb.grid(column=0, row=0, columnspan=1)
self.bt1 = Button(self, text="apply Hello", command=self.bt_press)
self.bt1.grid(column=0, row=1)
def bt_press(self):
self.master.basic_lb.config(text="Hello")
self.destroy()
window = Tk()
def new_Editor():
a = NewWindow(window)
window.title("BasicWindow")
window.basic_lb = Label(window, text='None')
window.basic_lb.grid(column=0, row=0, columnspan=1)
window.basic_bt = Button(window, text="NewWindow", command=new_Editor)
window.basic_bt.grid(column=0, row=1)
window.mainloop()

Is there a way to display a class in python tkinter as a Frame, so other things can be added?

I want to have a tkinter window that displays both a cronometer and a sudoku. The cronometer is a class, so how can I add it to the window that displays the sudoku?
I already managed to get two separate windows, but I couldn't make one with both things.
def GUI4x4(dif): #This function gets just called from other place
# What I want is to be able to display this class
# Cronometer in the main window that's created below
class Cronometer():
...
def __init__(self):
self.crono=Tk()
self.tiempo = StringVar()
self.tiempo.set("00:00:00")
self.label = Label(self.crono,textvariable=self.tiempo, bg="white")
self.label.grid(column=0,row=0)
self.label.configure(font=("Times 13 bold"))
self.btnI = Button(self.crono, bg="white", text="Start",command=self.iniciarT,font=("Times 11"))
self.btnI.grid(pady=3,column=0,row=1)
self.btnP = Button(self.crono, bg="white", text="Pause",command=self.pausarT,font=("Times 11"))
self.btnP.grid(pady=3,column=0,row=2)
self.btnR = Button(self.crono, bg="white", text="Restart",command=self.reiniciarT,font=("Times 11"))
self.btnR.grid(pady=3,column=0,row=3)
GUI = Tk() # This creates the main window, and places
# 34 buttons in it
...
# Defining the Buttons
btn00 = Button(GUI, text=Tablero[0][0], width=5, height=3, activebackground="lime")
btn01 = Button(GUI, text=Tablero[0][1], width=5, height=3, activebackground="lime")
btn02 = Button(GUI, text=Tablero[0][2], width=5, height=3, activebackground="lime")
...
btn33 = Button(GUI, text=Tablero[3][3], width=5, height=3, activebackground="lime")
#Placing the 34 buttons
btn00.grid(row=0, column=0)
btn01.grid(row=0, column=1)
btn02.grid(row=0, column=2)
...
btn33.grid(row=3, column=3)
The standard way to deal with this with tkinter is that each "widget" in the application is its own class based on the tkinter Frame widget, one class for the chrono, another for the sudoko game. There might even be a main app class.
Advantage of this method is that each widget frame can be created independently and then joined together later. These classes might also be split up in to separate code files.
A fairly simple example below
import tkinter as tk
class Chromometer(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.tiempo = tk.StringVar()
self.tiempo.set("00:00:00")
self.label = tk.Label(self,textvariable=self.tiempo, bg="white")
self.label.grid(column=0,row=0)
class Sudoko(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.label = tk.Label(self,text="Sudoko", bg="white")
self.label.grid(column=0,row=0)
class MainApp(tk.Frame):
def __init__(self,master=None,**kw):
tk.Frame.__init__(self,master=master,**kw)
self.chrono = Chromometer(master=self)
self.chrono.grid()
self.sudoko = Sudoko(master=self)
self.sudoko.grid()
if __name__ == '__main__':
root = tk.Tk()
app = MainApp(master=root)
app.grid()
root.mainloop()
Each class will have their own methods to perform the functionality needed by each. The chromo/chrono class will have a method to update the timer.

Tkinter : Button in frame not visible

I'm trying to implement a TicTacToe program. I am an absolute beginner in python. After viewing many tutorials and reading a few books, I have understood the basics of Python. I'm trying to get the buttons to display in a frame, but all I get is a blank window.
link for image of the resultant window
This is the code I have so far:
from Tkinter import *
class Buttons(object):
def __init__(self,master):
frame = Frame(master)
frame.pack()
self.button1= Button(frame,text="1",height=4,width=8,command=self.move)
self.button1.pack(side=LEFT)
self.button2= Button(frame,text="2",height=4,width=8,command=self.move)
self.button2.pack(side=LEFT)
self.button3= Button(frame,text="3",height=4,width=8,command=self.move)
self.button3.pack(side=LEFT)
root = Tk()
root=mainloop()
You defined your Buttons class but you didn't create an instance of that class, so no buttons were actually constructed. Also, you had a typo / syntax error:
root=mainloop()
should be
root.mainloop()
Also, you didn't define the move callback method.
Here's a repaired version of your code:
from Tkinter import *
class Buttons(object):
def __init__(self,master):
frame = Frame(master)
frame.pack()
self.button1 = Button(frame, text="1", height=4, width=8, command=self.move)
self.button1.pack(side=LEFT)
self.button2 = Button(frame, text="2", height=4, width=8, command=self.move)
self.button2.pack(side=LEFT)
self.button3 = Button(frame, text="3", height=4, width=8, command=self.move)
self.button3.pack(side=LEFT)
def move(self):
print "click!"
root = Tk()
Buttons(root)
root.mainloop()
However, this still has a problem: The move method has no way of knowing which button called it. Here's one way to fix that. I've also changed
from Tkinter import *
to
import tkinter as tk
It's not a good idea to use "star" imports. They make code harder to read and they pollute your namespace with all the names defined in the imported module (that's 175 names in the case of Tkinter), which can lead to name collisions.
import Tkinter as tk
class Buttons(object):
def __init__(self,master):
frame = tk.Frame(master)
frame.pack()
self.buttons = []
for i in range(1, 4):
button = tk.Button(
frame, text=i, height=4, width=8,
command=lambda n=i:self.move(n)
)
button.pack(side=tk.LEFT)
self.buttons.append(button)
def move(self, n):
print "click", n
root = tk.Tk()
Buttons(root)
root.mainloop()
Okay the problem was I needed to add a variable at the end of the code. Something like b=Buttons(root). It's working now.

Tkinter ttk Combobox Default Value

I'm building a Tkinter application and I came across an issue with setting a default value to a combobox. I managed to fix the problem, but I am curious to know why it worked and I would like to know if there is a better way to do it.
I have a tk.Toplevel() window pop up with a combobox using the fowling code:
class add_equation():
def __init__(self):
self.add_window = tk.Toplevel()
self.add_window.title("Add Equation Set")
self.add_window.resizable(width=False, height=False)
self.name_entry_var = tk.StringVar()
self.name_entry = ttk.Entry(self.add_window, textvariable=self.name_entry_var, width=30)
self.name_entry.grid(row=1, columnspan=2, stick="w")
self.equation_type_var = tk.StringVar()
self.equation_type = ttk.Combobox(self.add_window, textvariable=self.equation_type_var, values=("System", "Exact", "Import Point List..."), state="readonly", width=28, postcommand =lambda: self.add_window.update())
self.equation_type.current(0)
self.equation_type.grid(row=2, columnspan=2, sticky="w")
self.add_window.update()
The class add_quation() is called in the following bit of code:
import tkinter as tk
from tkinter import ttk
class Solver_App(tk.Tk, ttk.Frame):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
list_frame = ttk.Frame(self, height=50)
list_frame.pack(side="top", fill="y", expand=True, anchor="w")
# Button that will call the class add_equation that creates a new window.
add_button = ttk.Button(list_frame, text="Add Equation Set", command=add_equation)
add_button.pack(side="top", expand=False, fill="x", padx=10, pady=5, anchor="n")
app = Solver_App()
app.mainloop()
Looking back at the add_equation() class, if you remove self.equation_type.current(0) or postcommand =lambda: self.add_window.update(), the default value will no longer show, but with both, it works just fine. Why is it working like this instead of only having self.equation_type.current(0)?
I've tried to find a more elegant way of doing this, and I found something related over here, but I had no luck implementing that method and I assume calling add_equation() from a button command may have something to do with that.
*I'm using Python 3.4 on Mac OS X Yosemite.
I think this is probably because you're creating a window by calling the add_equation constructor, and that window is immediately garbage collected (or atleast the python handle to it is) so never gets properly refreshed.
I'd rewrite it as something like this:
class Equation_Window(tk.Toplevel):
def __init__(self):
tk.Toplevel.__init__(self)
self.title("Add Equation Set")
self.resizable(width=False, height=False)
self.name_entry_var = tk.StringVar()
self.name_entry = ttk.Entry(self, textvariable=self.name_entry_var, width=30)
self.name_entry.grid(row=1, columnspan=2, stick="w")
self.equation_type_var = tk.StringVar()
self.equation_type = ttk.Combobox(self, textvariable=self.equation_type_var, values=("System", "Exact", "Import Point List..."), state="readonly", width=28)
self.equation_type.current(0)
self.equation_type.grid(row=2, columnspan=2, sticky="w")
def add_equation():
w = Equation_Window()
w.wait_window()
(everything else remains the same)
I've changed the your add_equation class to something derived from a tk.Toplevel (and renamed it), which I think makes more sense. I've then made add_equation a function, and called wait_window (which acts like mainloop but just for one window). wait_window will keep w alive until the window is closed, and so everything gets refreshed properly.

How can I create a non-unique browse button in python?

I am using; Python 3.4, Windows 8, tkinter. I am trying to create a generic browse button that will get a file name and assign it to a variable.
I have created the following code to do this.
from tkinter import *
from tkinter import filedialog
from tkinter import ttk
class Application(Frame):
# A GUI Application.
# Initialize the Frame
def __init__(self, master):
Frame.__init__(self, master)
nbook = ttk.Notebook(root)
nbook.pack(fill='both', expand='yes')
f1 = ttk.Frame(nbook)
nbook.add(f1, text='QC1')
self.qc1_tab(f1)
# create QC1 tab contents
def qc1_tab(self, tab_loc):
# Set up file name entry.
Label(tab_loc, text="Select file:").grid(pady=v_pad, row=0, column=0, sticky=W)
self.flnm = ttk.Entry(tab_loc, width=60)
self.flnm.focus_set()
self.flnm.grid(pady=v_pad, row=0, column=1, columnspan=2, sticky=W)
ttk.Button(tab_loc, text="Browse...", width=10, command=self.browse).grid(row=0, column=3)
def browse(self):
temp = filedialog.askopenfilename()
self.flnm.delete(0, END)
self.flnm.insert(0, temp)
root = Tk()
app = Application(root)
root.mainloop()
The only problem with this is that the browse button is tied to self.flnm and cannot be used for anything else. I plan to use the browse button several times to acquire the file name of several different files and would rather not have multiple browse commands.
I need to call it from a button and somehow assign it to a variable afterwards.
I was thinking of something like
ttk.Button(..., command=lambda: self.flnm = self.browse)
...
def browse(self):
filename = filedialog.askopenfilename()
return filename
but that failed terribly.
How can I make a general purpose browse button?
You can write:
def browse(self, target):
temp = filedialog.askopenfilename()
target.delete(0, END)
target.insert(0, temp)
ttk.Button(..., command=lambda: self.browse(self.flnm))

Categories