Python : How can two GTK widgets interact with each other?
Question: What is the proper way to interact with a button without actually clicking on
it?

I have a button "button", that can, upon click :

  * Call the method "the_method" that will print what argument (here "filename") has been passed to it
  * toggle its own attributes, here its icon.

And I have a treeview, whose rows must, upon double click :

  * Call the method "the_method" that will print what argument (here "filename") has been passed to it
  * toggle "button"'s attributes, here its icon.

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()
    


Answer: 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):

  * using a `GtkToggleButton` instead of tracking the button state yourself
  * using gtk_button_set_use_stock to tell the button to interpret the label you will set in the button as the stock id for the button (this may print the associated text too, not sure about this)
  * switching to GTK 3 (uses pyGObject), as this is GTK 2 code (uses pyGTK), unless you want Windows compatibility

See you on linuxfr :-)

