I am considering how to lay out code for a graphical UI. I'm using Python and WX: it not really the focus of the question here, but let's use it as an example.
I understand the point of MVC, and it's something I have tried to implement before, but I often seem to get in a tangle, interfaces leak and everything ends up looking a bit unhappy, if somewhat working. This time, I want to Get It Right (TM).
The problem I (think I) usually have is the inherent statefulness of UI controls, such as a text entry. In the WX wiki's MVC example, the MVC consists of a "Money" model, and an "add" and "remove" action in a separate controller widget, and uses a non-editable text entry to present it in the view.
However, what I would like to achieve is a UI which has an editable text entry for a string (let's say it's a "name"). The user can type in the box, which will then cause a list of suggestions to be generated by some back-end. These will be presented in a list in the UI. If the user selects one of these, the entry will be updated with the that value, or the user can keep typing to get more refined suggestions. Confirming (e.g. "enter" or some button) will perform some action on the current value of the entry box. This is basically the same as many web-browsers' autocompletion address bars.
However, this seems to imply that the input text box is part of both the View and the Controller, since it both presents the current "name" (either typed or selected from the back-end suggestions) and provides events from the user (a text change event in this case). If nothing else, a naive implementation will end up in an infinite recursion since a modification to the entry will cause an update, which will modify the entry...
This is a cut down example, without the suggestion back-end. The entry box lives in the View, and the Controller can reach in to get the value from the View. the if (self.get_name() != new_name):
avoids recursion, and in this case, since the text comes from this box in the first place, the control never gets updated in the view.
import wx
from wx.lib.pubsub import pub
class NameModel(object):
def __init__(self):
self.name = ''
def set_name(self, new_name):
self.name = new_name
print("Model name is now: %s" % new_name)
# name has changed - tell anyone who cares
# (maybe bailiffs?)
pub.sendMessage("NAME CHANGED", name=self.name)
class NameView(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="Name View")
sizer = wx.BoxSizer(wx.VERTICAL)
self.name_ctrl = wx.TextCtrl(self)
sizer.Add(self.name_ctrl, 0, wx.EXPAND | wx.ALL)
self.name_ctrl.SetEditable(True)
self.SetSizer(sizer)
def get_name(self):
'''
Why does the View have a getter?
'''
return self.name_ctrl.GetValue()
def set_name(self, new_name):
# check for infinite recursion, but this may need to be aware of
# more state so we don't throw away useful updates
# Or perhaps disconnect the event?
if (self.get_name() != new_name):
print("Setting name in view: %s" % new_name)
self.name_ctrl.SetValue(new_name)
class NameController(object):
def __init__(self, app):
self.model = NameModel()
self.view = NameView(None)
# bind events to wire everything up
# hmm, this feels a bit wierd - the text entry is in the View,
# but it's driving the Controller here?
self.view.name_ctrl.Bind(wx.EVT_TEXT, self.update_name)
# subscribe the the model messages
pub.subscribe(self.name_changed, "NAME CHANGED")
self.view.Show()
def update_name(self, evt):
'''
Called when the user has set the name somehow
'''
# ??? getting the data _from_ the view ???
new_name = self.view.get_name()
self.model.set_name(new_name)
def name_changed(self, name):
'''
The model has changed, update the view(s) as needed
'''
self.view.set_name(name)
if __name__ == "__main__":
app = wx.App(False)
controller = NameController(app)
app.MainLoop()
Is this the correct way to integrate a stateful UI widget into an MVC-style program? It feels a bit untidy and I worry that the checks for "should update" are going to get onerous, code-maintenance-wise.
Aucun commentaire:
Enregistrer un commentaire