2

My problem with the following code is passing 'i' (just a simple range of numbers but changes according to number_boxes) through lambda to callback in order to have seperate functionality of each box created.

I have tried reading tutorials and attempted various things in my code but it either doesn't work or I get errors 'lambda() requires only 2 arguments, 3 given' etc. I believe I would need to make 'i' a list but I still get this particular error..

I have commented on the code where the problems arise. I need to return the values inside each box as well as overwrite the text.

Thank you.

self.clicked = [] # In my __init__ definition
self.numbers = [StringVar() for i in xrange(self.number_boxes) ]   # Create Stringvar for each box

for i in xrange(self.number_boxes): # For each number, create a box

        self.clicked.append(False) # Not clicked in yet
        self.box.append(Entry(self.frame_table,bg='white',borderwidth=0, width=10, justify="center", textvariable=self.numbers[i], fg='grey')) # Textvariable where I enter a value to get after, need to do for each box (use box[i] but cannot for append)
        self.box[i].grid(row=row_list,column=column+i, sticky='nsew', padx=1, pady=1) 
        self.box[i].insert(0, "Value %g" % float(i+1))
        self.box[i].bind("<Button-1>", lambda event, index=i : self.callback(event, index)) # Need to pass the 'i's' to callback but having lambda difficulties

for i in self.numbers: 
        i.trace('w',lambda index=i: self.numberwritten(index) ) # Need for each box again here

def numberwritten(self, index): # A way of combining numberwritten and callback?
    po = self.box[index].get() # Get the values in each box
    print po

def callback(self, event, index):
        if (self.clicked[index] == False): # When clicked in box, overwrite default text with value and change colour to black, not grey
            self.box[index].delete(0, END)
            self.box[index].config(fg='black')
                self.clicked[index] = True

UPDATE: Current problem: Need to pass all values of 'i' to callback and not just one but how to put list into lambda?

Error:

Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python26\lib\lib-tk\Tkinter.py", line 1410, in __call__
return self.func(*args)
TypeError: lambda() takes at most 1 argument (3 given)
8
  • I don't understand line po = box[i].get(): shouldn't it be po = self.box[i].get() instead ? I don't understand lambda i: self.numberwritten(n) either: where does n come from ? Commented Jul 5, 2012 at 14:14
  • Yep sorry, I used the old version of code. Code edited. Commented Jul 5, 2012 at 14:16
  • paste the full traceback Commented Jul 5, 2012 at 14:16
  • I'm not sure what you mean, I've included all that linked to the numberwritten function. Commented Jul 5, 2012 at 14:24
  • 1
    What is it you are really trying to accomplish? Do you really need a callback every time each entry is changed? For what purpose? Is it for input validation? If so, there are better ways to do input validations on entry widgets. Commented Jul 6, 2012 at 13:38

2 Answers 2

2

Your code is very close, however this line:

self.box[i].bind("<Button-1>", lambda self, event,i : self.callback(event, data))

should be

self.box[i].bind("<Button-1>", lambda event, index=i: self.callback(event, index))

The object index is just an arbitrary label to which we assign the value of i. Note that Python throws an error if we pass i, rather than a variable name. And we don't need to pass self; it is passed implicitly by its use in the function call: self.callback().

My only other comment is that you should turn clicked into a list, so that you can keep track of which objects the user selects. You can form this exactly the way box is formed. Good luck.


Here are some tips on turning clicked into a list, since I think this is current issue you are having. Edit: Changed several lines per J.F. Sebastian's comments.

# Create an empty list based on the number of boxes created 
# above. This could go in your class's __init__ function, 
# or it could be declared globally, depending on the scope
# of self.box and the loop which determines its length.
self.clicked = ([False] * len(self.box))

# Finally, in your callback, check one member of the list,
# depending on which box was clicked.
def cb(self, event, index):
    if not self.clicked[index]:
        self.box[index].delete(0, END)
        self.box[index].config(fg='black')
        self.clicked[index] = True
    # Print the value of each of the widgets stored in the box list.
    for i, j in enumerate(self.box):
        print("%ith element: %s" % (i, j.get()))  
Sign up to request clarification or add additional context in comments.

11 Comments

This work but the problem is my loop as index would have to be a list to contain all values of i no? I also don't understand how I could make clicked a list since it's not a numerical one. Sorry to be such a beginner in these things!
Clicked is a bool object and cannot use append. I'm struggling on getting the final points to my callback function and generally sorting my code out.
Have updated code as it stands currently. Only able to pass index=0 to callback and still wondering whether it's possible to merge numberwritten and callback to write in and read the values in boxes?
Regarding lists - they can have any type you want in them, so just use clicked = [] to make an empty list, then clicked.append(False).
you could use: self.clicked = [False] * len(self.box) and if not self.clicked[index]: syntax instead.
|
0

updated: it seems your lambda is completly superflous. Remove it altogether.

try:

self.box[i].bind("<Button-1>",self.callback)

instead of:

lambda self, event, i: self.callback(event, i)

update:

While i was typing my answer you seemd to have worsened the situation, maybe a little clarification of the lambda-idiom would help you more then my solution above. ;-)

lambda-functions are also known as anonymous-function, for they don't need to have a name.

compare the following examples:

>>>ulist = 'a1 s3 d2 f4 k6 w9'.split()

def normal_sort(L,R):
    result = int(L[1]) - int(R[1])
    return result

>>> sorted(ulist, normal_sort)
['a1', 'd2', 's3', 'f4', 'k6', 'w9']

the same as a lambda-function:

sorted(ulist, lambda L, R: int(L[1])-int(R[1]))
['a1', 'd2', 's3', 'f4', 'k6', 'w9']

The lambda-function has

  1. no name
  2. no argument parenthesis
  3. no return statement, because the evaluation of it's single statement gets passed back as the return value.

You may name a lambda-function by assigning it to a name:

lambda_sort = lambda L, R: int(L[1])-int(R[1])

but then it may be already useful to switch to a normal function.

5 Comments

This doesn't work, it still gives me an error with the number of arguments.
Thanks for the update (I was using an example I found to try and make my code clear, obviously not!). It still however doesn't seem to work and it's really confusing me! I have changed my code back..!
i dont know which and how many arguments StringVar.trace is supposed to have, but my guess is: ONE and this one should be a string or at least somethig convertible to a string, therefore kill your lambda construct there also
@DonQuestion: the trace callback takes 3 arguments:, name1, name2, and op. the first is the name of the variable, the second is an index into the variable if it is an array (which I don't think tkinter supports), and the third will be "read", "write", or "unset", depending on which event caused the callback to be called.
ahh, ok - i'm not a vivid tcl/tk user - this whole "everything is a string" just doesn't appeal to me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.