SwapOff

Python hacking, redux. 
Filed under

signals

 

Simplifying PyGTK signals in Python

This is a followup to my previous post.

A fairly common pattern in GTK code seems to be updating a widget whenever a value changes in your controller. That is, something like this:

class Counter(gobject.Gobject):
  __gsignals__ = {
    'counter-changed': (gobject.SIGNAL_RUN_FIRST,
                        gobject.TYPE_NONE, (int,)),
    }

  def __init__(self):
    self.counter = 0

  def inc(self):
    self.counter += 1
    self.emit('counter-changed', self.counter)

  def dec(self):
    self.counter -= 1
    self.emit('counter-changed', self.counter)


Needless to say, this can get a bit tedious. With a bit of stack frame hackery we can reduce that boilerplate to the following:

class Counter(gobject.GObject):
  counter = SignalProperty('counter-changed', 0)

  def inc(self):
    self.counter += 1

  def dec(self):
    self.counter -= 1


Here's the code:

class SignalProperty(object):
  def __init__(self, name, default=None):
    self.name =  name
    self.default = default
    frame = sys._getframe(1)
    locals_ = frame.f_locals
    signals = locals_.setdefault('__gsignals__', {})
    signals[name] = (gobject.SIGNAL_RUN_FIRST,
                     gobject.TYPE_NONE, (object,))

  def __get__(self, instance, owner):
    if instance is None:
      return self
    return getattr(instance, '_signal_' + self.name, self.default)

  def __set__(self, instance, value):
    setattr(instance, '_signal_' + self.name, value)
    instance.emit(self.name, value)



Loading mentions Retweet
Filed under  //   gtk   python   signals  

Comments [0]

Decoupling UI from logic with PyGTK

Last week I set out to learn PyGTK by doing. Quickly. As can be imagined, the resulting code was less than stellar, but I did learn a lot.

By far the most glaring design error I made was in not completely decoupling my UI objects from "business" logic, ala MVC. This failure was the result of not totally comprehending the role signals should play in GTK applications. I made the first step correctly, with distinct objects for the UI panel and logic, but sadly that was the last smart choice I made. My next was deciding how to update the UI when the state of the system changed. "Hmm", I thought. "If I just pass the appropriate UI control through to the logic layer, I can update the view as necessary! Brilliant!". BZzzzzzzzzzzzt! Wrong.

The right approach in GTK is to use signals to notify the UI of changes to your objects. To do this, inherit from gobject.Gobject and add a __gsignals__ attribute. Here's a mocked up example of a simulation that emits a "simulation-step" signal every time the simulation is updated. To update a hypothetical counter widget we simply connect the signal to a callback that updates the widget.

class Controller(gobject.GObject):
  __gsignals__ = {
    'simulation-step': (gobject.SIGNAL_RUN_FIRST,
                        gobject.TYPE_NONE, (int,)),
  }

  def step_simulation(self, step):
    self.emit('simulation-step', step)

class View(object):
  def __init__(self, builder, controller):
    # "builder" is a Glade gtk.Builder object.
    builder.connect_signals(self)
    self.step_counter = builder.get_object('step_counter')
    controller.connect('simulation-step', self.on_simulation_step)

  def on_simulation_step(self, simulation, step):
    self.step_counter.set_value(step)

controller = Controller()
view = View(controller)


The fundamental mistake I made was assuming that GTK signals were purely for notifying my application of UI changes. In reality, signals are equally important in notifying the UI of application state changes.

Loading mentions Retweet
Filed under  //   design   gtk   mvc   python   signals  

Comments [0]