Reputation: 8357
What is the proper way to interact with a button without actually clicking on it?
I have a button "button", that can, upon click :
And I have a treeview, whose rows must, upon double click :
And only the 1st part works. The "foo" function is called (via a callback for the button, directly for the treeview item) and the argument ("filename") is retrieved OK, but how to execute part 2 of the job (changing "button"'s attributes, here its icon)?
import gtk
class Lister(object):
def __init__(self):
self.hbox = gtk.HBox()
liststore = gtk.ListStore(str)
liststore.append(["foo"])
liststore.append(["bar"])
treeview = gtk.TreeView(liststore)
self.hbox.pack_start(treeview, False)
cell = gtk.CellRendererText()
col = gtk.TreeViewColumn("Column 1")
col.pack_start(cell, True)
col.set_attributes(cell,text=0)
treeview.connect('row-activated', self.open_file)
treeview.append_column(col)
def open_file(self, button, *args):
Buttons().the_method(self, "foo")
class Buttons(object):
OPEN_IMAGE = gtk.image_new_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_BUTTON)
CLOSED_IMAGE = gtk.image_new_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_BUTTON)
def __init__(self):
self.button = gtk.Button() # THIS is the button to modify
self.hbox = gtk.HBox()
self.hbox.pack_start(self.button, False)
self.button.set_image(self.OPEN_IMAGE)
self.button.connect('clicked', self.the_method, "plop")
self.toggled = True
def the_method(self, button, filename):
print filename
print vars(self)
if self.toggled:
self.button.set_image(self.CLOSED_IMAGE)
self.toggled = False
else:
self.button.set_image(self.OPEN_IMAGE)
self.toggled = True
class GUI(object):
def delete_event(self, widget, event, data=None):
gtk.main_quit()
return False
def __init__(self):
self.window = gtk.Window()
self.window.set_size_request(100, 150)
self.window.connect("delete_event", self.delete_event)
vbox = gtk.VBox()
vbox.pack_start(Buttons().hbox, False, False, 1)
vbox.pack_start(Lister().hbox)
self.window.add(vbox)
self.window.show_all()
return
def main():
gtk.main()
if __name__ == "__main__":
GUI()
main()
Upvotes: 1
Views: 1450
Reputation: 11454
I strongly disagree with user1146332 answer. This is not a GTK+ issue, nor a strong design issue, just an object oriented programming issue. The cause of your bug is that you call the_method
like this:
Buttons().the_method(self, "foo")
This can't work, because you're mixing up two different fundamental things: a class, and an instance of a class. When you call Buttons()
, you're creating a new instance of the Buttons
class. Thus, as this class is not a singleton, you're in fact creating a new instance, with a new GtkButton, and end up not interacting with the button you previously created.
The solution here is to make the Lister
object aware of what it needs to modify, which means storing around the Buttons
instance you previously created, for example in self.button
, and calling the_method
on it.
self.button.the_method("foo")
Here's a slightly modified version of your code. The important thing is that the Lister
instance is now aware of the Buttons
instance it needs to modify.
import gtk
class Lister(object):
def __init__(self, button):
self.hbox = gtk.HBox()
self.button = button
liststore = gtk.ListStore(str)
liststore.append(["foo"])
liststore.append(["bar"])
treeview = gtk.TreeView(liststore)
self.hbox.pack_start(treeview, False)
cell = gtk.CellRendererText()
col = gtk.TreeViewColumn("Column 1")
col.pack_start(cell, True)
col.set_attributes(cell,text=0)
treeview.connect('row-activated', self.open_file)
treeview.append_column(col)
def open_file(self, button, *args):
self.button.the_method("foo")
class Buttons(object):
OPEN_IMAGE = gtk.image_new_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_BUTTON)
CLOSED_IMAGE = gtk.image_new_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_BUTTON)
def __init__(self):
self.button = gtk.Button() # THIS is the button to modify
self.hbox = gtk.HBox()
self.hbox.pack_start(self.button, False)
self.button.set_image(self.OPEN_IMAGE)
self.button.connect('clicked', self.the_method, "plop")
self.toggled = True
def the_method(self, filename):
print filename
print vars(self)
if self.toggled:
self.button.set_image(self.CLOSED_IMAGE)
self.toggled = False
else:
self.button.set_image(self.OPEN_IMAGE)
self.toggled = True
class GUI(object):
def delete_event(self, widget, event, data=None):
gtk.main_quit()
return False
def __init__(self):
self.window = gtk.Window()
self.window.set_size_request(100, 150)
self.window.connect("delete_event", self.delete_event)
vbox = gtk.VBox()
buttons = Buttons()
vbox.pack_start(buttons.hbox, False, False, 1)
vbox.pack_start(Lister(buttons).hbox)
self.window.add(vbox)
self.window.show_all()
return
def main():
gtk.main()
if __name__ == "__main__":
GUI()
main()
However, there's still lots of room for improvement. I suggest you don't use the __init__
function to create your widgets, but a create
method that will return the toplevel widget of your widget tree. This is because you can't return anything in __init__
, so it's easier to use a different method instead of raising exceptions there.
b = Buttons()
vbox.pack_start(b.create(), False, False, 1)
l = Lister(b)
vbox.pack_start(l.create(), False, False, 1)
Other improvement might be (sorry, i'm using the C naming here for GTK classes/functions, which I know better than the python one):
GtkToggleButton
instead of tracking the button state yourselfSee you on linuxfr :-)
Upvotes: 4
Reputation: 2770
First of all i don't know anything about python but i have some experiences with gtk+
and i'm more or less familiar with its concepts.
The first thing i noticed is that you define a class called GUI
and two separate classes called Buttons
and Lister
. For me such approach makes only sense if you design the two last mentioned classes in a way that they are a kind of (composite) widget theirselves. So that you can instantiate them at a higher level for example in the GUI class. This would be a generic approach and makes perfectly sense if you want to reuse these new widgets.
The way you did it doesn't make sense to me. From what i have gathered so far the actual aim of Buttons
and Lister
is to populate your main application window with widgets, to connect callbacks to signals of those widgets and to define these callbacks as methods.
I think you limit the flexibility of gtk if you make it this way. For example you connect signals to callbacks at a point where in principle you aren't able to access all the widgets of your interface. In contrast, I prefer a common place in the code at which i connect signals to callbacks and at which i can principally pass all widgets of interest to a specific callback.
In fact one often have to act upon several widgets from inside a callback. So you have to consider to implement the callbacks as methods of your GUI class where they can principally access all elements of your user interface.
Also you should consider to design your interface with glade
. That way your code would be much more legible.
Supplement (after some code pushing):
import gtk
class GUI(object):
OPEN_IMAGE = gtk.image_new_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_BUTTON)
CLOSED_IMAGE = gtk.image_new_from_stock(gtk.STOCK_REFRESH, gtk.ICON_SIZE_BUTTON)
toggled = True
def __init__(self):
self.window = gtk.Window()
self.window.set_size_request(100, 150)
self.window.connect("delete_event", gtk.main_quit)
vbox = gtk.VBox()
self.button = gtk.Button() # THIS is the button to modify
self.button.set_image(self.OPEN_IMAGE)
liststore = gtk.ListStore(str)
liststore.append(["foo"])
liststore.append(["bar"])
self.treeview = gtk.TreeView(liststore)
cell = gtk.CellRendererText()
col = gtk.TreeViewColumn("Column 1")
col.pack_start(cell, True)
col.set_attributes(cell,text=0)
self.treeview.append_column(col)
vbox.pack_start(self.button, False, False, 1)
vbox.pack_start(self.treeview, False, False, 1)
self.treeview.connect('row-activated', self.the_method_wrapper, "plop")
self.button.connect('clicked', self.the_method, "plop")
self.window.add(vbox)
self.window.show_all()
return
def the_method_wrapper(self, button, *args):
self.the_method(self, "foo")
def the_method(self, button, filename):
print filename
print vars(self)
if self.toggled:
self.button.set_image(self.CLOSED_IMAGE)
self.toggled = False
else:
self.button.set_image(self.OPEN_IMAGE)
self.toggled = True
def main():
gtk.main()
if __name__ == "__main__":
GUI()
main()
Upvotes: 0