Python Tkinter Multiple Commands - python

I'm currently working on a GUI using Tkinter and Python. One of the windows I create has two buttons on it: one to restart a separate python script and the other one to shut down the whole program.
When I hit the "restart" button, I'd like it to run the restart code, and then destroy the window that has the two buttons on it. I saw something else on SO that let you run two commands at once through a button click but I can't seem to get it to work. Right now the code for the button is:
buttonRestart = Button(restartWindow, text = "Restart", width = 8,
height=3, command = lambda: self.restartExternal() and
restartWinow.destroy)
When executed, it seems that the restartExternal code is working, but it doesn't destroy the window as well. Any suggestions would be greatly appreciated!

Just create a method that calls the two methods. Tere's no shame in creating an extra function for this. It's a much more maintainable solution that trying to cram a bunch of code into a lambda.
def on_restart(self):
self.restartExternal()
self.restartWinow.destroy()
buttonRestart = Button(..., command = self.on_restart)

Instead of self.restartExternal() and restartWindow.destroy you could do [self.restartExternal(), restartWindow.destroy()]. This way, it will call restartWindow.destroy() whatever self.restartExternal() returns, whereas how it is, if self.restartExternal() returns False, Python doesn't even check to see if restartWindow.destroy is True or False. Besides that, restartWindow.destroy isn't even called in yours, because you left out the parentheses.

The answer proposed by Bryan looks reasonable, but with minimum changes - you can feed the list of functions to lambda function, like this:
buttonRestart = Button(restartWindow, text = "Restart", width = 8,
height=3, command = lambda: [self.restartExternal(),
restartWinow.destroy()] )
In my opinion, it looks better for two functions, at least.

Related

How to add options in OptionMenu without using the add_command function in Tkinter?

Is there any way to add options in an OptionMenu object without using the command flag?
I have seen many posts, such as this one, showing how the add_command will update/remove/add options to an already existing OptionMenu object. For example, this snippet of code removes all options in serialPortOptionMenu and repopulates the option menu with different options:
serialPortOptionMenu["menu"].delete(0, "end")
for serialPort in serialPortsArray:
serialPortOptionMenu["menu"].add_command(label=serialPort[1], command=lambda v=serialPort: serialPortFunc(v))
However, something like this seems to overwrite the original command flag I wrote when creating the OptionMenu object:
serialPortOptionMenuValue = Tkinter.StringVar(optionMenuFrame)
serialPortOptionMenuValue.set(serialPorts[0])
serialPortOptionMenu = Tkinter.OptionMenu(optionMenuFrame, serialPortOptionMenuValue, *serialPorts, command=lambda *args: callbackFuncWhenOptionMenuSelectsAnotherOption(*args))
serialPortOptionMenu.grid(row=3, column=0, columnspan=2, sticky="we")
As I'm sure many people are wondering why I am setting a command within an OptionMenu (weird I know), it is because I want a callback function to be called when the user picks a new option.
"What about the trace option"-Everyone...Yes, I am aware of this as well, but because I am reading/writing new values to the Tkinter variable in code without having the OptionMenu solely change it, using trace within Tkinter would not be an effective substitute for just tracking when the user selects a new option in the OptionMenu.
Why am I changing the Tkinter value in code and not having the OptionMenu do it? Because I thought it would be nice to modify the Tkinter value string with something like a ~ at the end of the string to signify something is happening behind the scenes that isnt completed yet, and only when it is completed will the ~ go away, hence the reading and writing to the value without having the OptionMenu solely change it.
What I am primarily interested in is if anyone knows of other ways to add options to an OptionMenu object without using
myMenu["menu"].add_command(..., command=...)
As it seems to be removing my original callback function.
Note: Having two OptionMenus and performing grid_remove/grid on them to hide/reveal them also crossed my mind, but just seems to messy.
There is no other way. An option menu is nothing more than a menubutton and a menu with a custom binding. You can create your own OptionMenu and add any methods you want.
I don't know if it will be useful to you anymore, but maybe somebody else will be looking for this as I was. You actually just need to put the command as a callback parameter in the command you use to add the item (I found tk._setit):
def refresh_dropdown():
# Reset var and delete all old options
var.set('')
dropdown['menu'].delete(0, 'end')
# Insert list of new options (tk._setit hooks them up to var)
new_choices = ['a', 'b', 'c', '...'] # you can make a dictionary[key_from_previous_dropdown.get()] as I did in my case
for choice in new_choices:
dropdown['menu'].add_command(label=choice, command=_setit(var, choice, new_command))
dropdown = OptionMenu(app, var, command=new_command)

Opening a new Tkinter window while continuing to run a loop in another

I am working on a large program that opens new windows from a desktop widget. The desktop widget has a 'ticker' style label that displays a piece of text representing an iteration through a list. My problem is when I first wrote the program I called mainloop() with each new window I opened. The result was the new window and program would run as designed, but the ticker would freeze. Even upon closing the newly created window, the ticker would not restart. So I removed the mainloop() line. The result of this is the ticker continues to run and I can work within the new window, but everything is soooo laggy. I suspect this has something to do with the after() method?
Attached is a test code that I am using to try to sort this out before applying the correct code to my program. And I'm sure you can tell by reading the code, but I am self taught and an absolute newb, so please dumb down the explanations if possible. Thank so much!
from tkinter import *
def new_window():
nw = Tk()
item = Text(nw)
item.grid()
L = [1, 2, 3, 4, 5]
root = Tk()
Button(root, text = 'Open', command = new_window).grid(row = 1)
while True:
for i in L:
num = Label(root, text = i)
num.grid(row = 0)
root.after(2500)
num.update()
root.mainloop()
A tkinter application should always have exactly one instance ofTk, and you should call mainloop exactly once. If you have more than one instance the program will not likely work the way you expect. It's possible to make it work, but unless you understand exactly what is happening under the hood you should stick to this rule of thumb.
If you need more windows, create instances of Toplevel. You should not call mainloop for each extra window.
Also, you shouldn't have an infinite loop where you call after the way that you do. mainloop is already an infinite loop, you don't need another. There are several examples on this website of using after to call a function at regular intervals without creating a separate loop.

Tkinter buttons and classes. How can i effectively call a function in a class through a button?

So I'm writing this program, to draw disks using turtle. I'm doing a tkinter interface, using buttons etc. but, I don't seem to be able to execute a function inside a class through the buttons. It prompts me with this classic python error, "turtleInput() missing 1 required positional argument: 'numPressed'"
I've tried it a million times, a million ways, I just can't see the problem, maybe one of you can. I'm gonna provide you with the function inside the class and the the button ( in code of course ) hopefully you can help me out. feel free to ask questions if you don't quite understand what I am saying.
def turtleInput(self, numPressed):
self.length = int(numPressed)
self.lstColor = ["maroon","brown","red","orange","yellow",
"green","lightgreen","purple","blue",
"lightblue"]
for i in range(0,self.length):
self.shrink = 220
self.shrinkLst = []
while self.shrink > 0:
self.shrink = self.shrink-20
self.shrinkLst.append(self.shrink)
self.diskCol = self.lstColor[i]
self.turtleDisks(self.diskCol,self.shrinkLst[i])
now the code for the button
num2= Button(root, text="2", width=3)
num2["command"]= lambda: Disk.turtleInput(2)
num2.grid(row=1, column=0, sticky=W, padx=3)
keep in mind that I imported tkinter, turtle and everything else works fine, that is the only problem.
You need to be referencing a Disk object, not the Disk class when referencing a function.
The solution looks like this:
# create instance of Disk
disk = Disk()
...
num2["command"]= lambda: disk.turtleInput(2)

Tkinter: a simple way to avoid race conditions between an event and value update?

The issue
My issue is pretty simple, but I couldn't figure out how to cope with it easily even after some googling.
So I have a checkbutton:
self.but_val = IntVar()
self.but = Checkbutton(frame, text="text", variable=self.but_val)
This checkbutton triggers updates of some file path on the GUI:
self.but.bind('<ButtonRelease-1>',
lambda e: self.update_files_path(e), add='+')
In update_files_path(event), I need to get the value of the checkbutton to select the file paths to be displayed:
if self.but_val.get() == 0:
[...]
else:
[...]
The issue I have is that I get the value of the button before the clic.
And since the processing of file paths depends on different button values, I can't just use the opposite value.
My current work around
At the moment I have a function that is triggered before the clic and that save the state of the GUI:
self.but.bind('<ButtonPress-1>', lambda e,
self.save_design_opts_state(self.buttons_to_backup,
self.before_clic_vars_state), add='+')
Then in update_files_path(event) I call a function that infers the GUI state after the clic:
gui_state = self.get_gui_state(event)
This function is very annoying to implement, because I need to do a lot of things:
1- Check that the clic is really made on a button (to avoid a clic that starts on a button and end elsewhere!)
2- Get the value of the of all required buttons depending of their type
Is there an easier way to deal with this?
Thank you for your help!
Don't set your own bindings. Use the command option of the checkbutton. This option lets you specify a command to be run after the value has changed. There are other ways, but this is by far the simplest, most common way to solve your problem.

Python ttk.Button -command, runs without button being pressed

I'm making a small script in python with ttk and I have a problem where a function runs where it shouldn't. The button code looks as follows:
btReload = ttk.Button(treeBottomUI, text="Reload", width=17, command=loadModelTree(treeModel))
btReload.pack(side="left")
and the function is as this:
def loadModelTree(tree):
print ("Loading models...")
allModels = os.listdir(confModPath)
for chunk in allModels:
...
For some reason, the function runs without the button being pressed. Why?
Markus, yes, that's the right solution, but it is not because you can't use multi-argument commands in widget callouts. Consider, in your original code, ...command=loadModelTree(treeModel)... is an invocation of the method. Lambda allows you to abstract the command so you can have an arbitrary number of arguments without confusing the interpreter by invoking it, e.g., ...command=lambda arg1=myarg1, arg2=myarg2, arg3=myarg3: myCallout(arg1, arg2, arg3)....
I hope that makes what is going on a bit clearer.
Well, as I found the answer, I'll answer my own question.
It appers that ttk.button commands does not support sending arguments to functions so the work around is to do as follows:
btReload = ttk.Button(treeBottomUI, text="Reload", width=17, command=lambda i=treeModel: loadModelTree(i))
btReload.pack(side="left")
Simple as pie!

Categories