Search code examples
pythongtkgtk3pygobject

How to handle a "notify::active" signal from a Gtk.Switch? (MVC architecture)


I cannot figure out how to handle the notify::active signal of a Gtk.Switch. I'm working with the MVC architecture (pattern) suggested here.

The error I got is this:

TypeError: _on_switch_serial_toggled() missing 1 required positional argument: 'state'

Here's my minimal working example (without a model):

import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio, GObject


class Application(Gtk.Application):
    def __init__(self):
        app_id = "org.iea.etc"
        flags = Gio.ApplicationFlags.FLAGS_NONE

        super(Application, self).__init__(application_id=app_id, flags=flags)

    def do_activate(self):
        # c.Controller(m.Model(), v.View(application=self))
        Controller(None, View(application=self))

    def do_startup(self):
        Gtk.Application.do_startup(self)


class Controller(object):
    def __init__(self, model, view):
        self._model = model
        self._view = view

        self._view.connect('switch_serial_toggled',
                           self._on_switch_serial_toggled)

        self._view.show_all()

    def _on_switch_serial_toggled(self, switch, state):
            if switch.get_active():
                print('Switch ON')
            else:
                print('Switch OFF')


class View(Gtk.ApplicationWindow):
    __gsignals__ = {
        'switch_serial_toggled': (GObject.SIGNAL_RUN_FIRST, None, ())
    }

    def __init__(self, **kw):
        super(View, self).__init__(**kw)

        self._switch_serial = Gtk.Switch()
        self._switch_serial.connect("notify::active",
                                    self.on_switch_serial_toggled)

        self.add(self._switch_serial)

    def on_switch_serial_toggled(self, switch, state):
        self.emit('switch_serial_toggled')


if __name__ == '__main__':
    app = Application()
    exit_status = app.run(sys.argv)
    sys.exit(exit_status)

Solution

  • First of all, you are handling the notify signal for the active property of Gtk.Switch almost properly. The problem resides in the handling of the custom signal, that you've added to your View.

    It's important to understand what you are doing: You have created a View class that has a property, which is a Gtk.Switch. You've also created a signal, called switch_serial_toggled associated with this class. Inside the class, you want that, when the internal Gtk.Switch changes state, the View triggers its own "toggled" signal.

    This being said, lets fix your code:

    1 - Lets add the toggle state to the View switch_serial_toggled as a boolean

    class View(Gtk.ApplicationWindow):
        __gsignals__ = {
            'switch_serial_toggled': (GObject.SIGNAL_RUN_FIRST, None, (bool,))
        }
    

    2 - Lets give proper names to the arguments of the View internal signal handler and add the state to the emitted signal:

    def on_switch_serial_toggled(self, obj, pspec):
        self.emit('switch_serial_toggled', self._switch_serial.get_active ())
    

    GObject.Object notify signal handler is described here

    3 - Now, let's go to the Controller and handle the View signal correctly:

    def _on_switch_serial_toggled(self, widget, active):
            if active is True:
                print('Switch ON')
            else:
                print('Switch OFF')
    

    widgetargument is the View instance inside the Controller instance and state is the boolean that gets passed with the signal emission.

    The full code, with the fixes above, should look like this:

    import sys
    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk, Gio, GObject
    
    
    class Application(Gtk.Application):
        def __init__(self):
            app_id = "org.iea.etc"
            flags = Gio.ApplicationFlags.FLAGS_NONE
    
            super(Application, self).__init__(application_id=app_id, flags=flags)
    
        def do_activate(self):
            # c.Controller(m.Model(), v.View(application=self))
            Controller(None, View(application=self))
    
        def do_startup(self):
            Gtk.Application.do_startup(self)
    
    
    class Controller(object):
        def __init__(self, model, view):
            self._model = model
            self._view = view
    
            self._view.connect('switch_serial_toggled',
                               self._on_switch_serial_toggled)
    
            self._view.show_all()
    
        def _on_switch_serial_toggled(self, widget, active):
                if active is True:
                    print('Switch ON')
                else:
                    print('Switch OFF')
    
    
    class View(Gtk.ApplicationWindow):
        __gsignals__ = {
            'switch_serial_toggled': (GObject.SIGNAL_RUN_FIRST, None, (bool,))
        }
    
        def __init__(self, **kw):
            super(View, self).__init__(**kw)
    
            self._switch_serial = Gtk.Switch()
            self._switch_serial.connect("notify::active", self.on_switch_serial_toggled)
    
            self.add(self._switch_serial)
    
        def on_switch_serial_toggled(self, obj, pspec):
            self.emit('switch_serial_toggled', self._switch_serial.get_active ())
    
    
    if __name__ == '__main__':
        app = Application()
        exit_status = app.run(sys.argv)
        sys.exit(exit_status)
    

    Resulting in something like this:

    enter image description here

    PS: Notice that in step 2, obj is the object to which you have connected the signal handler which is similar to self._switch_serial. This means that you could use obj.get_active () instead:

    def on_switch_serial_toggled(self, obj, pspec):
        self.emit('switch_serial_toggled', obj.get_active ())