Python 3 How do I temporarily disable tracing variables - python

I am using Python 3.4.1 on windows, if that should help.
Q1: How do I temporarily disable tracing variables
I have a variable field with at trace, and I would like to temporarily disable the trace so that I can change the value of the field without triggering the call to the trace function.
Does it make sense?
And it might be that I am doing it all wrong (and I join a snippet of code):
I have a drop down list that shows a list of items I can choose from.
I have a second drop down list that shows, for each of the items in the first drop down menu, a list of "sub items", which of course must be updated when I change the first drop down menu.
Q2: The question is, how do I "repack" the second drop down menu when the first one is changed?
Here is the code:
import tkinter as tk
WORKINGWINDOWWIDTH = 800 # Width for the working window
WORKINGWINDOWHEIGHT = 800 # Height for the working window
root = tk.Tk()
w = tk.Canvas(root, width=WORKINGWINDOWWIDTH - 10, height=WORKINGWINDOWHEIGHT - 10, bg="darkred")
def display_parameters(*args):
print("args: {0}, and I have the following option: {1}".format(args, functionChoiceVar.get()))
if functionChoiceVar.get() == "Option 1":
print("I picked the first one...")
print("How do I repack the presets?")
elif functionChoiceVar.get() == "Option 2":
print("I picked the second one...")
return
def display_options(*args):
print("args: {0}, and I have the following suboption: {1}".format(args, presetChoiceVar.get()))
return
functionChoiceVar = tk.StringVar(root)
functionChoices = ['Option 1', 'Option 2']
functionOption = tk.OptionMenu(root, functionChoiceVar, *functionChoices)
functionOption.pack(side='left', padx=10, pady=10)
functionOption.place(x= 10, y=10)
functionChoiceVar.set('Option 1')
functionChoiceVar.trace("w", display_parameters)
presetChoiceVar = tk.StringVar(root)
presetChoices11 = ['Suboption 11', 'Suboption 12', 'Suboption 13', 'Suboption 14','Suboption 15']
presetChoices12 = ['Suboption 21', 'Suboption 22', 'Suboption 23', 'Suboption 24','Suboption 25']
presetOption = tk.OptionMenu(root, presetChoiceVar, *presetChoices11)
presetOption.pack(side='left', padx=10, pady=10)
presetOption.place(x= 100, y=10)
presetChoiceVar.set('Suboption 11')
presetChoiceVar.trace("w", display_options)

When you set a trace, tkinter will return an id which you can use to later remove the trace with the .trace_vdelete() method. To restart the trace, simply do what you did the first time.
An easy way to keep track of the trace id is to store it as an attribute right to the instance of a StringVar.
For example:
functionChoiceVar.trace_id = functionChoiceVar.trace("w", display_parameters)
...
functionChoiceVar.trace_vdelete("w", functionChoiceVar.trace_id)
(by the way, unrelated to the question that was asked, calling .pack() and then immediately calling .place() serves no purpose. You can remove the call to .pack() because it gets negated by the call to .place() )

A1: how to remove tracing temporarily & return it afterwards
aWriteTracerID = presetChoiceVar.trace( "w", aWriteHANDLER ) # SAVE <<aTracer>> ID#
# setup code continues
presetChoiceVar.trace_vdelete( "w", aWriteTracerID ) # REMOVE <<aTracer>>
# code
# that needs the write-tracer
# to have been removed
# ...
aWriteTracerID = presetChoiceVar.trace( "w", aWriteHANDLER ) # SET AGAIN

I ran into this myself while creating a new class which inherits from a base class taking a variable as an input keyword argument. So I developed the below routine which attempts to recover a named callback function from the globals() based on the fact that the Trace ID is nothing more than some numerical digits prepended to the original callback function name. I expect that those digits have some meaning, but I was not able to ascertain them. Here's the routine:
def get_observer_callback(id):
func = None
funcname = id.lstrip('0123456789')
if funcname != '<lambda>' and funcname in globals():
func = globals().get(funcname)
if type(func) is not type(lambda:()):
func = None
return func
While testing, a trace_id was 50360848my_callback and the callback function was identified as
<function my_callback at 0x03064370>
Interestingly, 50360848 from the wrapper is hex 0x03007210, which is in the same ballpark as hex 0x03064370 from the function description. I couldn't find a hard relationship between the two beyond that...
I would describe the above routine as a kludge, but maybe, given the Tk implementation, the above kludge is sufficient for named functions. Clearly it is not helpful for lambda functions. I expect that within Tk there's a registration table which holds all the desired information, including references to the function objects. Ideally a call which returns the function object from within the Tk internal table would be best. Any input is welcome.

The easiest way to remove a trace would be to say:
idx = 0 # This is the index of the first trace on tk_variable.
tk_variable.trace_remove(*tk_variable.trace_info()[idx])
To add the trace back, you can say:
tk_variable.trace_add('write', your_function)
To check if a variable is traced, you can say:
if tk_variable.trace_info():
# do something
pass

Related

Pausing from within a tkinter function

I'm trying to create a couple of functions which do things in a sequential order. First they need to open a new window and display a label, then they need to wait for some seconds, then they need to call another function. However, I'm struggling to get the functions to wait, all the methods I've tried (.after, .sleep, .wait_visibility) seem to be ignored and it just skips to the next function call without pausing.
Here's what I have (sorry if it's messy, I'm new to python):
from tkinter import *
import time
root =Tk()
root.geometry('600x600')
def scale_screen(event = None):
global s_screen
s_screen = Toplevel(root)
s_screen.title('Residual Inhibition Tester')
s_screen.geometry('600x600')
s_screen.transient(root)
s_screen.bind('<Return>', sel)
global var
var = IntVar()
scale = Scale(s_screen, variable = var, orient = HORIZONTAL, length = 1000)
scale.focus_set()
scale.pack(anchor=CENTER)
button = Button(s_screen, text="Select", command=sel)
button.pack(anchor=CENTER)
def sel(event = None):
label = Label(s_screen)
selection = "Value = " + str(var.get())
label.config(text = selection)
interval_screen()
def interval_screen():
global i_screen
i_screen = Toplevel(root)
i_screen.geometry('600x600')
i_screen.transient(root)
i_label = Label(i_screen, text = "Please Wait")
i_label.pack(anchor = CENTER)
s_screen.destroy()
i_screen.after(3000, masker_screen)
#time.sleep(3)
#i_screen.after(300,i_label.configure(text="Playing New Masker Noise"))
#root.wait_visibility(window = i_screen)
def masker_screen():
global m_screen
m_screen = Toplevel(root)
m_screen.geometry('600x600')
m_screen.transient(root)
m_label = Label(m_screen, text = "Playing New Masker Noise").pack(anchor = CENTER)
m_screen.after(3000, lambda: scale_screen(event = None))
i_screen.destroy()
b1 = Button(root, command = scale_screen).pack(anchor=CENTER)
root.bind('<Return>', scale_screen)
root.mainloop()
In this example, the program will run but just skip the interval_screen entirely and just do the masker_screen. I'm also not averse to just using one screen and using the .configure methods to change the label text if that's easier.
Thanks!
Without seeing all the ways you tried it, it's impossible to know what you did wrong. In general you should never call time.sleep and you should never call after with just a single argument. Also, when you use after with two arguments, the second argument must be a reference to a function.
The proper way to do this is to have your first function call your second function via after:
def interval_screen():
...
i_screen.after(3000, maker_screen)
def masker_screen():
...
m_screen.after(3000, lambda: scale_screen(event = None))
Note that in your updated question you're using after incorrectly:
m_screen.after(3000, scale_screen(event = None))
You're calling the function scale_screen(...) immediately, and giving the result of that to the after function. If you need to pass arguments to your function you must create another function that does not require arguments. The simplest way to do this is with lambda, though you can also use functools.partial or you can create your own function.

Tkinter "entry" object not updating textvariable variable (Python 3.4)

I am trying to write a simple Python program that will allow a user to input an IP address in decimal, or dotted-decimal format, then convert it to the opposite format and display it in the same entry box (ie, if they enter a decimal IP address, they can click a button and their input will be replaced with the dotted-decimal equivalent).
The problem I'm having is with pulling the data out of the entry box, then putting the new data back into the entry box. I've written an example with just the GUI code, and none of my other conversion logic, to simplify the problem:
import tkinter as tk
root = tk.Tk()
root.title("Test")
win1 = tk.Frame(root)
win1.grid()
x = tk.StringVar()
y = tk.StringVar()
xBox = tk.Entry(win1)
xBox.grid(row = 0, column = 0)
xBox.textvariable = x
yBox = tk.Entry(win1)
yBox.grid(row = 1, column = 0)
yBox.textvariable = y
button = tk.Button(win1,text = "Calculate", command = lambda: copyVal())
button.grid(row = 2, column = 0)
def copyVal():
print("x: " + x.get())
print("y: " + y.get())
xVal = x.get()
print("xval: " + xVal)
y.set(xVal)
root.update_idletasks()
root.mainloop()
Here's what I expect to happen with this code:
The value entered in the top box should be stored in StringVar x.
Clicking the "Calculate" button should run the copyVal() function:
copyVal() gets the value of StringVar x and stores it as xVal.
copyVal() sets the value of StringVar y to match xVal.
The text in the bottom box should now match the text in the top box.
Instead, it does not retrieve the value of StringVar x, so there's nothing to set StringVar y to.
I've tried the following variations:
Using xVal = xBox.get() instead of xVal = x.get(): this retrieves the contents of the top entry box, and sets the value of StringVar y to match it, but the bottom entry box does not change.
Using command = copyVal() instead of command = lambda: copyVal(): the copyVal function executes immediately upon program execution, rather than when the button is pressed.
Moving the copyVal function outside the root mainloop: raises a NameError exception when the button is pressed (copyVal is seen as not defined).
Moving root.update_idletasks() outside the copyVal function has no effect.
I've looked around for solutions to this issue, but no matter how many people I find who are experiencing similar problems, none of their fixes seem to resolve the issue for me (I usually see them told to use StringVar() to get/set values). I am completely new to working with Tkinter, so I'm sure this is something really basic that I'm overlooking, and I appreciate any advice anyone can offer.
Python objects often allow you to add attributes to them arbitrarily:
>>> class Foo:
... pass
...
>>> foo = Foo()
>>> foo.a = 1 # No error. It makes a new attribute.
>>> foo.a
1
>>>
>>> def foo():
... pass
...
>>> foo.a = 1 # Works with function objects too.
>>> foo.a
1
>>>
So, when you do:
xBox.textvariable = x
...
yBox.textvariable = y
you are not actually setting the Entrys' textvariable options to x and y. Instead, you are creating new attributes named textvariable on each of those objects.
To fix the problem, either set each Entry's textvariable option when you create the widgets:
xBox = tk.Entry(win1, textvariable=x)
...
yBox = tk.Entry(win1, textvariable=y)
or use the .config method to change them later:
xBox.config(textvariable=x)
...
yBox.config(textvariable=y)

How to recognize a button from its command?

I have created a list of entries in a for-loop. All entries are stored in a list so I can just obtain all of the inputs later:
inputs = [e.get() for e in self.entries]
However, I have also created a button next to each entry in the for-loop (so they each call the same function). How can I make it so that it recognizes which button belongs to which row/entry? Is there something I can do with event?
row = 0
self.entries = []
self.comments = []
for n in names:
e = Entry(self.top, bd = 5)
e.insert(0, n)
e.grid(column = 1, row = self.row, sticky = 'NSWE', padx = 5, pady = 5)
self.entries.append(e)
self.comments += [""]
commentButton = Button(self.top, text = "comment", command = self.commentSelected)
commentButton.grid(column = 3, row = self.row, sticky = 'NSWE', padx = 5, pady = 5)
self.row = self.row + 1
Yes -- use Callback Shims ( Currying Functions )
( courtesy Russell Owen )
I find I often wish to pass extra data to a callback function, in addition that that normally given. For instance the Button widget sends no arguments to its command callback, but I may want to use one callback function to handle multiple buttons, in which case I need to know which button was pressed.
The way to handle this is to define the callback function just before you pass it to the widget and include any extra information that you require. Unfortunately, like most languages, Python doesn't handle the mixing of early binding (information known when the function is defined) and late binding (informtation known when the function is called) particularly well. I personally find the easiest and cleanest solution is:
Write my callback function to take all desired data as arguments.
Use a callback shim class to create a callable object that stores my function and the extra arguments and does the right thing when called. In other words, it calls my function with the saved data plus the data that the caller supplies.
I hope the example given below makes this clearer.
The callback shim I use is RO.Alg.GenericCallback, which is available in my RO package. A simplified version that does not handle keyword arguments is given in the example below. All shim code is based on a python recipe by Scott David Daniels, who calls this "currying a function" (a term that is probably more common than "callback shim").
#!/usr/local/bin/Python
""" Example showing use of a callback shim"""
import Tkinter
def doButton(buttonName):
""" My desired callback.
I'll need a callback shim
because Button command callbacks receive no arguments.
"""
print buttonName, "pressed"
class SimpleCallback:
""" Create a callback shim.
Based on code by Scott David Daniels
(which also handles keyword arguments).
"""
def __init__(self, callback, *firstArgs):
self.__callback = callback
self.__firstArgs = firstArgs
def __call__(self, *args):
return self.__callback (*(self.__firstArgs + args))
root = Tkinter.Tk()
buttonNames = ( "Button 1", "Button 2", "Button 3" )
for name in buttonNames:
callback = SimpleCallback( doButton, name )
Tkinter.Button( root, text = name, command = callback ).pack()
root.mainloop()
You can also use lambda:
from tkinter import *
def bla(b):
...
root = Tk()
buttons = []
for i in range(...):
button = Button(root)
button.configure(command=lambda b=button: bla(b)) # Make sure the Button object already exists
buttons.append(button)
button.pack()
root.mainloop()
As far as I see, you can't create the buttons in a single list comprehension now, but it is simpler and more readable than a class imho.

Python Execution Order with GUI

I'm having some issues with the following code. This is the first time that I'm working with a GUI and it's been a while since I've worked with python as well. When I try to execute the solfield function with the button, it yields no output.
from Tkinter import *
import math
master = Tk()
n = float()
I = float()
def solfield():
pass
label_coils = Label(text='Number of Coils Per Meter', textvariable=n)
label_coils.grid()
coils = Entry(master)
coils.grid()
label_current = Label(text='Current in Amps', textvariable=I)
label_current.grid()
current = Entry(master)
current.grid()
calculate_button = Button(text='Calculate', command=solfield())
calculate_button.grid()
label_bfield = Label(text='B Field in +z Direction')
label_bfield.grid()
label_result = Label(text='solfield')
label_result.grid()
master.title('Coil Gun Simulation')
master.mainloop()
def solfield():
mu0 = math.pi*4e-7
solfield = mu0*n*I
print solfield
Any other tips would be appreciated as well, as there will eventually be much more coding for me to do.
This has been solved. If anyone is interested, here is the code after several fixes were made:
from Tkinter import *
import math
master = Tk()
label_coils = Label(text='Number of Coils Per Meter')
label_coils.grid()
coils = Entry(master)
coils.grid()
label_current = Label(text='Current in Amps')
label_current.grid()
current = Entry(master)
current.grid()
def solfield():
mu0 = math.pi*4e-7
n = float(coils.get())
I = float(current.get())
fieldmag = mu0*n*I
print fieldmag
calculate_button = Button(text='Calculate', command=solfield)
calculate_button.grid()
label_bfield = Label(text='B Field in +z Direction')
label_bfield.grid()
label_result = Label(text='solfield')
label_result.grid()
master.title('Coil Gun Simulation')
master.mainloop()
The problem is here:
calculate_button = Button(text='Calculate', command=solfield())
To pass the function solfield itself as the command, just use its name:
calculate_button = Button(text='Calculate', command=solfield)
What you're doing is calling the function, and then passing the return value of that function as the command.
Since you defined solfield above as do-nothing function, that return value is None, so you're telling calculate_button that its command=None, and it's properly doing nothing.
Meanwhile, as SethMMorton pointed out (but then deleted):
You have two functions named solfield, and you are naming a variable solfield in one of your solfield functions. Remove the empty function (the one with pass), and using a different variable name in the remaining function.
This isn't causing your actual problem, but it's certainly adding to the confusion that makes it harder for you to find the problem. (For example, if you hadn't included the excess empty definition of solfield at all, you would have gotten a NameError in the incorrect line, which would have made things easier to debug.)
Putting it all together, what you should do is:
Get rid of the empty (pass-only) definition of solfield.
Move the real implementation of solfield up above the point where you build the GUI.
Don't name a local variable solfield within the function.
Pass just solfield, not solfield() as the command for calculate_button.

Combined Event Handling of widgets in TKinter

I am making a GUI Program in Tkinter and am running into problems.What I want to do is draw 2 checkboxes and a button. According to the user input next steps should take place. A part of my code has been shown below :-
CheckVar1 = IntVar()
CheckVar2 = IntVar()
self.C1 = Checkbutton(root, text = "C Classifier", variable = CheckVar1, onvalue = 1, offvalue = 0, height=5,width = 20).grid(row=4)
self.C2 = Checkbutton(root, text = "GClassifier", variable = CheckVar2, onvalue = 1, offvalue = 0, height=5, width = 20).grid(row=5)
self.proceed1 = Button(root,text = "\n Proceed",command = self.proceed(CheckVar1.get(),CheckVar2.get())).grid(row=6)
# where proceed prints the combined values of 2 checkboxes
The error that I am getting is typical ie a default value of both the selected checkboxes gets printed up and then there is no further input. The error that I get is NullType Object is not callable.
I searched on the net and I think the answer is related to lambda events or curry.
Please help ..
You're passing the value of self.proceed(CheckVar1.get(),CheckVar2.get()) to the Button constructor, but presumably what you want is for command to be set to a function which will call self.proceed(CheckVar1.get(),CheckVar2.get()) and return a new, possibly different value every time the button is pressed. You can fix that with a lambda, or by wrapping the call in a short callback function. For example, replace the last line with:
def callback():
return self.proceed(CheckVar1.get(), CheckVar2.get())
self.proceed1 = Button(root, text="\n Proceed", command=callback).grid(row=6)
This is pretty typical Tkinter. Remember: when you see a variable called command in Tkinter, it's looking for a function, not a value.
EDIT: to be clear: you're getting 'NullType Object is not callable' because you've set command to equal the return value of a single call to self.proceed (that's the NullType Object). self.proceed is a function, but its return value is not. What you need is to set command to be a function which calls self.proceed.
Like Peter Milley said, the command option needs a reference to a function (ie: give it a function name (ie: no parenthesis). Don't try to "inline" something, create a special function. Your code will be easier to understand and to maintain.

Categories

Resources