Tkinter ttk Combobox Default Value - python

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.

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()

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.

Making a class remove itself

I am making a program using Tkinter that will create a list of events. I used a class to make an event. If I click the Add Event button (second script shown), it creates a new instance of the class, lengthening the list. However, I also want to be able to remove events from the list. I am trying to attach a remove button to each class that, when clicked, will delete the class. This is my code from the class script (classes.py):
from Tkinter import *
class agendaEvent:
def __init__(self, master):
self.frame = Frame(master, padx=10, pady=10)
self.frame.pack(side=TOP)
self.name = Entry(self.frame)
self.name.grid(row=1, column=0)
self.time = Entry(self.frame, width=10)
self.time.grid(row=1, column=1, padx=5)
self.label1 = Label(self.frame, text="Event Name")
self.label1.grid(row=0, column=0)
self.label2 = Label(self.frame, text="Minutes")
self.label2.grid(row=0, column=1)
self.remove = Button(self.frame, text="Remove", command=agendaEvent.remove)
self.remove.grid(row=1, column=3)
def remove(agendaEvent):
del agendaEvent
When I press the remove button, I get the error
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python27\lib\lib-tk\Tkinter.py", line 1532, in __call__
return self.func(*args)
TypeError: unbound method remove() must be called with agendaEvent instance as first argument (got nothing instead)
How can I call the instance of agendaEvent? Or is there a better way of going about this? This is the code for the main script (main.py):
from Tkinter import *
import classes
def addEvent():
classes.agendaEvent(root)
root = Tk()
addEventButton = Button(root, text="Add Event", command=addEvent)
addEventButton.pack(side=BOTTOM)
root.mainloop()
You can remove labels and other widgets (not sure all) with .destroy(). If you store the instances of the class in some way you can forget or destroy them, but if the instance is just an instance then I don't see your problem (read, I can't help you). If you want to learn how to build GUIs with Tkinter you should check out sentdex's videos on the subject on youtube.
This is how I make tkinter widgets with buttons like I think you are trying to do:
I make a counter variable that counts how many objects my list contains.
I make a button that point to a method or a function that makes a label that contains the info I want it to contain, e.g. .get()
something from an entry box.
The function then adds one entry to the counter variable.
Then I make a button that points to a method or function that destroys the last label, I use the counter variable as index to know
what label to destroy.
I then subtract one from the counter variable
I use a dictionary to keep my labels and other widgets in order, e.g. by using a counter variable as key.
Example:
from tkinter import *
class MyEvent("something tkinter"):
def __init__(self, parent):
"here you need some tkinter code to make a frame or something to put your widgets in"
self.mycounter = 0
self.myEventLabel = {}
addButton = Button(parent, text="add event", command=addEvent).pack()
destroyButton = Button(parent, text="remove last event", command=removeEvent).pack()
def addEvent(self):
self.myEventLabel[self.mycounter] = Label("Here goes the options you want)
self.mycounter+=1
def removeEvent(self):
self.mycounter-=1
self.myEventLabel[self.mycounter].destroy()
Hope this helped, if I missed the point, then in my defense I'm not a programmer, just someone who needs to use programming as a means to an end. Again, Sentdex's videos covers alot of tkinter, check them out.
You have to save the class instance to remove it. Below, addEvent() has been changed to catch the return from calling the class, and that instance is attached to the button so the button knows which instance to destroy. This would better if all of the code was wrapped in classes but I am following the code you posted above which is likely just a simple example.
from Tkinter import *
from functools import partial
class agendaEvent:
def __init__(self, master):
self.frame = Frame(master, padx=10, pady=10)
self.frame.grid()
self.name = Entry(self.frame)
self.name.grid(row=1, column=0)
self.time = Entry(self.frame, width=10)
self.time.grid(row=1, column=1, padx=5)
self.label1 = Label(self.frame, text="Event Name")
self.label1.grid(row=0, column=0)
self.label2 = Label(self.frame, text="Minutes")
self.label2.grid(row=0, column=1)
def addEvent(master):
this_instance=agendaEvent(master)
## add the button to the frame created by this function call
## and not to the root, so it also is destroyed
rem = Button(this_instance.frame, text="Remove", bg="lightblue",
command=partial(remove_class, this_instance))
rem.grid(row=6, column=0)
def remove_class(instance):
## destroys the frame created by the instance
## i.e. self.frame in agendaEvent
instance.frame.destroy()
instance="" ## reassigns "instance" so the class is garbage collected
root = Tk()
addEventButton = Button(root, text="Add Event", command=partial(addEvent, root))
addEventButton.grid(row=5, column=0)
Button(root, text="Exit", bg="orange", command=root.quit).grid(row=99, column=0)
root.mainloop()
Changing your class as shown below will fix the error you're currently getting. It's caused by the fact that the handler specified with a command keyword argument passed to the Button constructor is always called without any arguments. Fixing that with a lambda function then exposes another problem, which is that you've named both the Button instance and the method you want to associated it with the same thing – remove – so I renamed it to remove_button.
from Tkinter import *
class agendaEvent:
def __init__(self, master):
self.frame = Frame(master, padx=10, pady=10)
self.frame.pack(side=TOP)
self.name = Entry(self.frame)
self.name.grid(row=1, column=0)
self.time = Entry(self.frame, width=10)
self.time.grid(row=1, column=1, padx=5)
self.label1 = Label(self.frame, text="Event Name")
self.label1.grid(row=0, column=0)
self.label2 = Label(self.frame, text="Minutes")
self.label2.grid(row=0, column=1)
self.remove_button = Button(self.frame, text="Remove",
command=lambda: self.remove())
self.remove_button.grid(row=1, column=3)
def remove(self): # by convention the first argument to a class
del self # method is usually named "self"

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

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()

tkinter script to print entry text

I'm just starting to learn to use tkinter with python and it seems counterintuitive that this attempt at a simple script to print whatever is entered into the entry box, doesn't work:
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
text_write = Entry(frame)
text_write.pack()
self.button = Button(frame, text="quit", fg="red", command=frame.quit)
self.button.pack(side=LEFT)
self.hi_there = Button(frame, text='hello', fg='black', command=self.say_hi(text_write.get()))
self.hi_there.pack(side=RIGHT)
def say_hi(self, text):
print(text)
root = Tk()
app = App(root)
root.mainloop()
This does nothing and outputs no errors, but if I change it to this:
class App:
def __init__(self, master):
frame = Frame(master)
frame.pack()
self.text_write = Entry(frame)
self.text_write.pack()
self.button = Button(frame, text="quit", fg="red", command=frame.quit)
self.button.pack(side=LEFT)
self.hi_there = Button(frame, text='hello', fg='black', command=self.say_hi)
self.hi_there.pack(side=RIGHT)
def say_hi(self):
print(self.text_write.get())
then it calls the function and prints the value. Why does the 'self' need to be declared there? And why can't you pass the value of text_write as an argument to say_hi (as in the first example) and have it display? Or can you and I'm just doing it wrong?
When you do this:
self.button = Button(..., command=func())
then python will call func(), and the result of that will be assigned to the command attribute. Since your func doesn't return anything, command will be None. That's why, when you push the button, nothing happens.
Your second version seems fine. The reason self is required is that without it, text_write is a local variable only visible to to the init function. By using self, it becomes an attribute of the object and thus accessible to all of the methods of the object.
If you want to learn how to pass arguments similar to your first attempt, search this site for uses of lambda and functools.partial. This sort of question has been asked and answered several times.

Categories