Ok so I'm trying to use menu bars to grey out different widgets in my gui

il show you all the lines that i think are important and the error messages for each command i have tried.

root = Tk()  
 
input_text = Text(root, height = 10, width = 25).grid(row = 1, column = 1, sticky = N+S+E+W)

thats assigning the main window as root, and creating a text entry box assigned to input_text with root as its master. The rest of the options there are not important.

These are the commands i tried to get the menu button to grey out the input_text widget.

The first command and error

input_menu.add_radiobutton(label='Input From File', variable = input_option, command = root.input_text(state = DISABLED))

input_menu.add_radiobutton(label='Input From File', variable = input_option, command = root.input_text(state = DISABLED))
File "C:\Python25\lib\lib-tk\Tkinter.py", line 1721, in __getattr__
return getattr(self.tk, attr)
AttributeError: input_text

The next command and error was

input_menu.add_radiobutton(label='Input From File', variable = input_option, command = input_text(state = DISABLED))

input_menu.add_radiobutton(label='Input From File', variable = input_option, command = input_text(state = DISABLED))
TypeError: 'NoneType' object is not callable


Any help with this would be great, I'm sorry if the code isn't all on the right lines.

Oh... this is a confusing feature of GUI programming, and especially with Tkinter.

Making root the parent of input_text does *not* automatically make input_text the child of root.

Weird, huh?

Think of it like this: Tkinter is a Python interface to Tk. Thus, there are two systems going on at once -- Tk and Python. The line

input_text = Text(root, height = 10, width = 25).grid(row = 1, column = 1, sticky = N+S+E+W)

tells Tk that you want a Text widget that is the child of root.

But Python doesn't know it! To inform Python, you have to do so explicitly:

root.input_text = Text(root, ...) # rest of options go in ...

Now, Python will make input_text a data member of root, which is what you wanted anyways.

Now, the second command just doesn't make sense.

input_menu.add_radiobutton(label='Input From File', variable = input_option, command = [b]input_text(state = DISABLED)[/b])

input_text is a Text widget. The ( ) operator says "call this function." And command requires a function name -- NOT a called function, just the name.

So poor Python is nobly trying to call the Text widget and pass the return value as a function name to the command parameter of input_menu.add_radiobutton().

Not surprisingly, it complains. :lol:

What did you intend here?

Hope it helps,
Jeff

root.input_text = Text(root, ...) # rest of options go in ...

Now, Python will make input_text a data member of root, which is what you wanted anyways.

Would this not mean that your just changing the variable to root.input.text should it not be like this

input_text = root.Text(root, ...) # rest of options go in

Now, the second command just doesn't make sense.

input_menu.add_radiobutton(label='Input From File', variable = input_option, command = [b]input_text(state = DISABLED)[/b])

What did you intend here?

The command part was intended to mean that when the button was selected it would disable the input_text widget. Should i create a function that does this then put that as the command?

thanks for the help.

root.input_text = Text(root, ...) # rest of options go in ...

Now, Python will make input_text a data member of root, which is what you wanted anyways.

Would this not mean that your just changing the variable to root.input.text should it not be like this

input_text = root.Text(root, ...) # rest of options go in

Well, here's what the first one means:

* Create a Text widget.
* Assign the return value (the Text widget object itself) to the variable root.input_text.

So yes, it does change the value of root.input_text, by setting it equal to the Text widget. Previously, root.input_text did not exist, so that's not a problem; you haven't clobbered anything.

Here's what the second one means:

* call the function root.Text() (which doesn't exist, since Tk objects don't a function called Text())
* Assign the return value to the local variable input_text.

You don't want that, since input_text will go out of scope when your function ends, and then you lose ability to access it.

I hope that's clear...

The command part was intended to mean that when the button was selected it would disable the input_text widget. Should i create a function that does this then put that as the command?

Ah... yes, you should create a new function and supply the name of that function as the command.

Jeff

i these are the two variations of the function i use.

def disable(widget):
     widget(state = disabled)

def disable(widget):
     widget(state = DISABLED)

i then changed the command to look like this

root.input_menu.add_radiobutton(label='Input From File', variable = input_option, command = disable(root.input_text))

this command with the first functions gives this error

root.input_menu.add_radiobutton(label='Input From File', variable = input_option, command = disable(root.input_text))
line 4, in disable
widget(state = disabled)
NameError: global name 'disabled' is not defined


the second function gives this error

root.input_menu.add_radiobutton(label='Input From File', variable = input_option, command = disable(root.input_text))
line 4, in disable
widget(state = DISABLED)
TypeError: 'NoneType' object is not callable

i also tried using a simple function with no variables but this gave the same errors as the last 2 depending on if i used caps to write disabled.

Right, you want to pass the NAME of the function:

def disable():
   root.input_text["state"] = DISABLED

root.input_menu.add_radiobutton(label='Input From File', variable = input_option, command = disable)

What you're doing is calling the function *when* you add the radiobutton, then setting command = return value.

Remember that the () operator means "call the function now."

Jeff

def disable():
   root.input_text["state"] = DISABLED

root.input_menu.add_radiobutton(label='Input From File', variable = input_option, command = disable)

Ok so i have made a function and changed the command

def disable():
     root.input_text(state = DISABLED) #way you wrote it doesn't work

root.input_menu.add_radiobutton(label='Input From File', variable = input_option, command = disable)

the program loads correctly but as soon as i press that button in the GUI i get this error

File "C:\Python25\lib\lib-tk\Tkinter.py", line 1403, in __call__
return self.func(*args)
root.input_text(state = DISABLED)
TypeError: 'NoneType' object is not callable

also even if this way does work it seems like the wrong way to do it because i would have to create a new function for every widget i wish to disable, surely there is a way to pass variables into functions using the command.

Posted by jrcagle:

def disable():
root.input_text["state"] = DISABLED

Posted by Haze:

def disable():
root.input_text(state = DISABLED) #way you wrote it doesn't work

Do you see the difference...?

posted by jrcagle:

Remember that the () operator means "call the function now."

you understand this or ...?

Ok, root.input_text is an object NOT function. So in your case you want to set property of this object. So you either call member function or set its object's property in the way,
Posted by jrcagle:

def disable():
root.input_text["state"] = DISABLED

or root.input_text.config(state = 'DISABLED') kath.

Here's a working small version:

from Tkinter import *
def disable():
    root.button["state"] = DISABLED

root = Tk()
root.button = Radiobutton(root, text="My Button")
root.button2 = Button(root, text="Disable the Radiobutton", command=disable)
root.button.grid()
root.button2.grid()
root.mainloop()

Hope it helps,

Jeff

from Tkinter import *

def disable():# temp
    root.input_text['state'] = DISABLED

root = Tk()
root.input_text = Text(root, height = 10, width = 25).grid(row = 1, column = 1, sticky = N+S+E+W)
root.mainbar_menu = Menu(root)
root.input_menu = Menu(root.mainbar_menu)
root.mainbar_menu.add_cascade(label='Input Options', menu = root.input_menu)
root.input_menu.add_radiobutton(label='Input From File', command = disable)
root.config(menu=root.mainbar_menu)

root.mainloop()

This code is taken out of part of the larger program im writing so some of the code may seem pointless, im only keeping it their in case i dont realise how much of a effect it has.

error -
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Python25\lib\lib-tk\Tkinter.py", line 1403, in __call__
return self.func(*args)
File "C:/Documents and Settings/Owner/Desktop/ex.py", line 4, in disable
root.input_text = DISABLED
TypeError: 'NoneType' object does not support item assignment

The error occurs when i click the menu button, not when the program loads. What am i doing wrong when im making this menu?

also your example worked fine, although i tried editing it so i would not have to write a new function for every time i want to disable a diffrent widget by changing these lines.

def disable(widget):
    widget["state"] = DISABLED

root.button2 = Button(root, text="Disable the Radiobutton", command=disable(root.button))

this doesnt work as expected because as soon as the gui has loaded the button is allready disabled. sorry i took over a week to get back to you but getting this program to work is not exactly high proiority right now. thanks.

bump, anyone know or im i missing the point completely? and even if you dont know is there anywhere else i could post this so i could get a response? thanks.

Sorry Haze; I mistook "not exactly high priority" to mean "not worth investing in." :)

This line

root.input_text = Text(root, height = 10, width = 25).grid(row = 1, column = 1, sticky = N+S+E+W)

does the following:

* creates a new Text widget
* calls its .grid() method, which returns None
* then assigns it to root.input_text

So no matter what, root.input_text == None, NOT a Text widget.

Then later, when you call disable(), Surprise! 'None' doesn't have any methods.

BTW, in debugging things like this, I *always* print values of variables just to make sure that they are what I think they are...

What you really want is this:

root.input_text = Text(root, height = 10, width = 25)
root.input_text.grid(row = 1, column = 1, sticky = N+S+E+W)

Now with regard to the second problem,

def disable(widget):
    widget["state"] = DISABLED

root.button2 = Button(root, text="Disable the Radiobutton", command=disable(root.button))

You can probably predict why the root.button is disabled from the beginning -- when you create root.button2, you *call* disable(root.button)!

So in your code, the act of creating root.button2 causes root.button to be disabled. Definitely not what you wanted.

I would solve your problem by using classes. What you want, if I read correctly, is for a bunch of buttons to have their own special "targets" that they can disable. Yes?

So what you really want is a type of Button() with a disable function attached to it. That calls for a new class that inherits from Button:

from Tkinter import *

class DisablerButton(Button):

    def __init__(self, master, target, cnf={}, **kw):
        Button.__init__(self, master, cnf, **kw)
        self.target = target
        self['command'] = self.disable

    def disable(self):
        self.target["state"] = DISABLED

mainw = Tk()
mainw.b1 = Button(mainw, height=5,width=10, text="My Button")
mainw.b2 = DisablerButton(mainw, mainw.b1, height=5, width=10, text="Press Me!")
mainw.b3 = DisablerButton(mainw, mainw.b2, height=5,width=10, text="Me Too!")
mainw.b1.grid()
mainw.b2.grid()
mainw.b3.grid()
mainw.mainloop()

There's a couple things that need explaining. First, the __init__ method for Button can take a lot of arguments. The **kw refers to all of the configuration arguments like width= and height=. cnf is a less commonly used way to package those configuration options into a dictionary. Rather than spell all those arguments out, I just package them up and pass them upstairs to Button.__init__.

Second, notice that this way of doing things allows you to specify a different target for each individual DisablerButton widget. I think this is what you wanted.

Can you see why a class is required to do this?

Jeff

ok i understand the first problem now. The second problem i also understand (a bit) but for your example you used a button and what i was planning to use it with was a radiobutton so i tried it.

from Tkinter import *

class abler_radiobutton(Radiobutton):
    def __init__(self, master, target, cnf={}, **kw):
        Radiobutton.__init__(self, master, cnf, **kw)
        self.target = target
        self['command'] = self.disable

    def disable(self):
        self.disable_target['state'] = DISABLED

root = Tk()
root.input_text = Text(root, height = 10, width = 25)
root.input_text.grid(row = 1, column = 1, sticky = N+S+E+W)
root.mainbar_menu = Menu(root)
root.input_menu = Menu(root.mainbar_menu)
root.mainbar_menu.add_cascade(label='Input Options', menu = root.input_menu)
root.input_menu.add_abler_radiobutton(label='Input From File')

root.config(menu=root.mainbar_menu)
root.mainloop()

Obviously this doesnt work because i havent put enough in the .add_abler_radiobutton part. but i dont know exactly what to put in because the creation of this radiobutton was a lot more complicated compared to the creation of a button.

Also another question, why does the disable function have to be indented so that its created along with the class? and why is self.disable not disable(self)? and what exactly does the "button.__init__" line do.

sorry if this is to many questions i just dont like using code when i have no idea what said code actully does.

ok i understand the first problem now. The second problem i also understand (a bit) but for your example you used a button and what i was planning to use it with was a radiobutton so i tried it.

from Tkinter import *

class abler_radiobutton(Radiobutton):
    def __init__(self, master, target, cnf={}, **kw):
        Radiobutton.__init__(self, master, cnf, **kw)
        self.target = target
        self['command'] = self.disable

    def disable(self):
        self.disable_target['state'] = DISABLED

root = Tk()
root.input_text = Text(root, height = 10, width = 25)
root.input_text.grid(row = 1, column = 1, sticky = N+S+E+W)
root.mainbar_menu = Menu(root)
root.input_menu = Menu(root.mainbar_menu)
root.mainbar_menu.add_cascade(label='Input Options', menu = root.input_menu)
root.input_menu.add_abler_radiobutton(label='Input From File')

root.config(menu=root.mainbar_menu)
root.mainloop()

Obviously this doesnt work because i havent put enough in the .add_abler_radiobutton part. but i dont know exactly what to put in because the creation of this radiobutton was a lot more complicated compared to the creation of a button.

Also another question, why does the disable function have to be indented so that its created along with the class? and why is self.disable not disable(self)? and what exactly does the "button.__init__" line do.

sorry if this is to many questions i just dont like using code when i have no idea what said code actully does.

No, it's perfectly fine to insist on complete clarity. That's what good coding is all about.

The disable function needs to be a part of the class because you want it to be something that only DisablerButtons can do. AND, you want each DisablerButton to have its own copy of disable(), so that it can disable its own target.

If you made disable() a public function, outside the class scope, then there would be no easy way to tell disable which target to disable.

But since each DisablerButton has its own copy of the disable() method, the problem is solved: disable operates on self.target.

The reason for the syntax

self.disable

instead of

disable(self)

is that a method is always written in terms of its parent. Try this:

'hi there'.reverse()

Semantically, Python turns 'self.method(arg1, arg2)'

into 'method(self,arg1,arg2)'. But the distinction is made syntactically between the parent (self) and the other arguments so that humans can read it more clearly.

The Button.__init__() line is needed because DisablerButton inherits its properties and methods from Button. Normally, there's a lot that happens behind the scenes when a Button widget is created. All of this occurs in the __init__ method of Button. So DisablerButton calls Button.__init__ to make sure that all of those things happen, like construction of the widget, attachment to mainw, things like that that you don't want to worry about.

Bottom line: it's usually a good idea for children to call the parent's __init__ method.

Hope it helps, and I'll think about the radiobutton problem later.

Jeff

thanks for clarifying that, i will try to figure out the radiobutton thing aswel and sorry about the double post, i was having trouble with this website yesterday i kept getting database errors.

just making sure my "i will try to figure out the radiobutton thing aswel" isnt being mistook for "dont bother posting" :)

This is a first draft of what you might want. The radiobutton has a 'command' feature that allows you to supply a callback when it is activated, so I eliminated the need for a separate class. The only problem with this so far is that you can't supply a target, but I know how to fix that. See if you understand this first and whether it's along the lines of what you are trying for:

from Tkinter import *

def disable():
    print "disabling"
    root.button['state'] = DISABLED

def enable():
    print "enabling"
    root.button['state'] = ACTIVE

def button_com():
    print "hi"
    
root = Tk()
root.input_text = Text(root, height = 10, width = 25)
root.input_text.grid(row = 1, column = 1, sticky = N+S+E+W)
root.mainbar_menu = Menu(root)
root.input_menu = Menu(root.mainbar_menu)
root.mainbar_menu.add_cascade(label='Input Options', menu = root.input_menu)
root.button = Button(root, text="Swill", command = button_com)
root.button.grid()
root.input_menu.add_radiobutton(label='Input From File', command=disable)
root.input_menu.add_radiobutton(label='Input from Pipe', command=enable)
root.input_menu.add_radiobutton(label='Input from Stream', command=enable)
root.config(menu=root.mainbar_menu)
root.mainloop()

I think this'll work for you, if the above was heading in the right direction:

from Tkinter import *

def disable_factory(target):
    def f():
        target['state'] = DISABLED
    return f

def enable_factory(target):
    def f():
        target['state'] = ACTIVE
    return f

def button_com():
    print "hi"
    
root = Tk()
root.input_text = Text(root, height = 10, width = 25)
root.input_text.grid(row = 1, column = 1, sticky = N+S+E+W)
root.mainbar_menu = Menu(root)
root.input_menu = Menu(root.mainbar_menu)
root.mainbar_menu.add_cascade(label='Input Options', menu = root.input_menu)
root.button = Button(root, text="Swill", command = button_com)
root.button.grid()
root.input_menu.add_radiobutton(label='Input From File',
                                command=disable_factory(root.button))
root.input_menu.add_radiobutton(label='Input from Pipe',
                                command=enable_factory(root.button))
root.input_menu.add_radiobutton(label='Input from Stream',
                                command=enable_factory(root.button))
root.config(menu=root.mainbar_menu)
root.mainloop()

It needs some explanation...

The function 'disable_factory' creates a new function to disable the target every time it is called. That new function is then returned.

At creation of the radiobutton, Tkinter *calls* the disable_factory(root.button). disable_factory then returns a function f that can disable root.button. It is that function f that becomes the command for the radiobutton.

Here's the timeline:

Widget Creation Time:
disable_factory(root.button) --> f = "root.button = DISABLED"
.add_radiobutton(..., command = f)

Widget RunTime:
user clicks radiobutton --> f is called --> Swill button disabled.

Likewise for enable_factory()

Does that make sense?

Note: doing it all within a class is conceptually cleaner. But because of the limitations of the Menu widget -- it can only add certain prespecified widgets, like the radiobutton -- we had to go this route.

Jeff

This article has been dead for over six months. Start a new discussion instead.