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.
Related
I am writing a chess program and I can't get the pieces to output since I changed the text from being defined with text to text variable and now I can't get string working to output the pieces.
I define the buttons in a for loop and it outputs nothing in the squares. The code is
def drawboard(self):
x=0
y=0
for column in range(self.n):
self.changecolours()
x=x+1
y=0
for row in range(self.n):
y=y+1
colour = self.colours[self.colourindex]
pos=(x,9-y)
buttons=(tk.Button(self.boardframe, padx=10, textvariable=lambda position=pos: self.placepieces(position), borderwidth=0, bg=colour, relief="solid", font=self.piecefont, command=lambda position=pos: self.movepiece(position) ))
buttons.grid(column=(x-1), row=(y-1), sticky="W"+"E"+"N"+"S" )
self.changecolours()
#classmethod
def placepieces(cls, position):
black=Black()
white=White()
squareposition=position
icon=tk.Stringvar("")
if squareposition in white.position.values():
for key in white.position:
value=white.position.get(key)
if value==squareposition:
if key.endswith("pawn"):
icon.set(white.pawntype[key])
elif key=="king":
icon.set(white.PIECES.get("KING"))
elif key=="queen":
icon.set(white.PIECES.get("QUEEN"))
elif key.endswith("bishop"):
icon.set(white.PIECES.get("BISHOP"))
elif key.endswith("knight"):
icon.set(white.PIECES.get("KNIGHT"))
else:
icon.set(white.PIECES.get("ROOK"))
else:
pass
elif squareposition in black.position.values():
for key in black.position:
value=black.position.get(key)
if value==squareposition:
if key.endswith("pawn"):
icon.set(black.pawntype.get(key))
elif key=="king":
icon.set(black.PIECES.get("KING"))
elif key=="queen":
icon.set(black.PIECES.get("QUEEN"))
elif key.endswith("bishop"):
icon.set(black.PIECES.get("BISHOP"))
elif key.endswith("knight"):
icon.set(black.PIECES.get("KNIGHT"))
else:
icon.set(black.PIECES.get("ROOK"))
break
else:
pass
else:
icon.set("")
return icon
How does stringvar work with textvariable?
In the case of a Button, you can configure the text to be displayed in one of two ways: with a hard-coded string (eg: text='click me') or with the textvariable attribute.
The textvariable attribute must be set to an instance of one of the special tkinter variable objects StringVar, IntVar, DoubleVar or BooleanVar (eg: var=tk.StringVar(); Button(..., textvariable=var)). When configured with an instance of one of these variables, the text on the button will display whatever the value of the variable is.
Internally, when you specify a textvariable, tkinter will take the string representation of that object and create a tcl variable in the embedded interpreter. So, while it may see to accept a standard variable, command, or even a lambda, it ultimately ends up creating an internal tcl variable with that name, and associates that internal variable with the underlying tcl widget. If you do not use one of the special variables, it is going to be difficult for you to get or set the value of that variable.
For example, consider this set of commands:
var = tk.StringVar(value="hello")
tk.Button(root, textvariable=var)
When that code is run, the text that appears on the button will be hello. If you change the variable at any time (eg: var.set("goodbye")), the button will automatically be changed to show the new value.
I am trying to create option menus in a loop, and the number of option menus is dependent on a variable. So I'm trying to use exec in my code.
I used the following to pass the value of 'i' to connect to which variable is changing the value.
But once I call the trace, the option I select in the Option menu, does not get updated in the Option menu box. If I do not call the trace funtion, it is getting updated in the display.
trackProcessMenu is the callback function.
Please let me know, where I am making the mistake.
Adding my code:
for i in range(0,numOfLibFiles):
exec('self.processOptionMenuVar_%d = StringVar()'%i)
process_menu = ("ff","ss","tt","fff","sss","ttt")
exec('self.processOptionMenu_%d = OptionMenu(self, self.processOptionMenuVar_%d, *process_menu )'%(i,i))
exec('self.processOptionMenu_%d.config(indicatoron=0,compound=RIGHT,image= self.downArrowImage, anchor = CENTER , direction = RIGHT)'%i)
exec('self.processOptionMenuVar_%d.set("--")'%i)
exec('self.processOptionMenu_%d.grid(row = i, column =1, sticky = N ,padx=30, pady =7 )'%i)
def trackProcessMenu(self,*args):
i = args[0]
exec('process = self.processOptionMenuVar_%d.get()'%i)
You should not use exec this way. A good rule of thumb is that you should never use exec until you can answer the question "why should I never use exec?" :-) exec has it's uses, but this isn't one of them.
Instead of trying to automagically generate variable names, keep your widgets in a list or dictionary.
For example:
option_vars = []
option_menus = []
for i in range(0,numOfLibFiles):
process_menu = ("ff","ss","tt","fff","sss","ttt")
var = StringVar()
om = OptionMenu(self, var, *process_menu)
om.config(indicatoron=0,compound=RIGHT,image= self.downArrowImage, anchor = CENTER , direction = RIGHT)
var.set("--")
om.grid(row = i, column =1, sticky = N ,padx=30, pady =7)
option_vars.append(var)
option_menus.append(om)
With the above, you can now reference the variables and menus with a simple index:
print("option 1 value is:", option_vars[1].get())
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.
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
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.