GUIアーキテクチャ
code:python
import Tkinter as tk
class EventBus(object):
"""
Simple pub/sub event bus.
Attributes
----------
_subs : list of function
List of event callbacks.
"""
def __init__(self):
self._subs = []
def subscribe(self, cb):
"""
Register a subscriber callback.
Parameters
----------
cb : function
Callback accepting (event: dict).
"""
self._subs.append(cb)
def publish(self, event):
"""
Publish event to all subscribers.
Parameters
----------
event : dict
Event data.
"""
for cb in self._subs:
cb(event)
class Dog(object):
"""
Dummy Dog entity, knows only its 'bow' message.
"""
def bow(self):
"""
Get 'Woof'.
Returns
-------
str
'Woof'
"""
return 'Woof'
class Cat(object):
"""
Dummy Cat entity, knows only its 'meow' message.
"""
def meow(self):
"""
Get 'Meow'.
Returns
-------
str
'Meow'
"""
return 'Meow'
class Model(object):
"""
Model owns Dog/Cat and the text history (contents).
Attributes
----------
dog : Dog
Dog entity.
cat : Cat
Cat entity.
contents : list of str
History log (for display).
event_bus : EventBus
Event bus for change notification.
"""
def __init__(self, event_bus):
self.dog = Dog()
self.cat = Cat()
self.contents = []
self.event_bus = event_bus
def add_dog(self):
"""
Add dog entry to contents and notify.
Returns
-------
str
The added message.
"""
msg = 'DOG: %s' % self.dog.bow()
self.contents.append(msg)
self.notify_contents()
return msg
def add_cat(self):
"""
Add cat entry to contents and notify.
Returns
-------
str
The added message.
"""
msg = 'CAT: %s' % self.cat.meow()
self.contents.append(msg)
self.notify_contents()
return msg
def undo_dog(self):
"""
Remove the last dog entry and notify.
"""
# remove from the end
for i in range(len(self.contents) - 1, -1, -1):
if self.contentsi == 'DOG: Woof': break
self.notify_contents()
def undo_cat(self):
"""
Remove the last cat entry and notify.
"""
for i in range(len(self.contents) - 1, -1, -1):
if self.contentsi == 'CAT: Meow': break
self.notify_contents()
def notify_contents(self):
"""
Publish the updated contents over the event bus.
"""
self.event_bus.publish({'src': 'model', 'action': 'update_contents', 'contents': list(self.contents)})
class Command(object):
"""
Abstract base class for commands.
"""
def do(self):
pass
def undo(self):
pass
class BowCommand(Command):
"""
Add dog action (do/undo via Model).
Attributes
----------
model : Model
"""
def __init__(self, model):
self.model = model
def do(self):
self.model.add_dog()
def undo(self):
self.model.undo_dog()
class MeowCommand(Command):
"""
Add cat action (do/undo via Model).
Attributes
----------
model : Model
"""
def __init__(self, model):
self.model = model
def do(self):
self.model.add_cat()
def undo(self):
self.model.undo_cat()
class Dispatcher(object):
"""
Undo/redo capable command dispatcher.
Attributes
----------
stack : list of Command
Undo stack.
redo_stack : list of Command
Redo stack.
"""
def __init__(self):
self.stack = []
self.redo_stack = []
def execute(self, cmd):
cmd.do()
self.stack.append(cmd)
def undo(self):
if self.stack:
cmd = self.stack.pop()
cmd.undo()
self.redo_stack.append(cmd)
def redo(self):
if self.redo_stack:
cmd = self.redo_stack.pop()
cmd.do()
self.stack.append(cmd)
class Interaction(object):
"""
Operation mode (DOG or CAT).
Attributes
----------
mode : str
Current operation mode.
"""
DOG_MODE = "dog"
CAT_MODE = "cat"
def __init__(self):
self.mode = self.DOG_MODE
def set_mode(self, mode):
self.mode = mode
def get_command(self, model):
if self.mode == self.DOG_MODE:
return BowCommand(model)
else:
return MeowCommand(model)
class App(object):
"""
Main Tkinter application (VIEW only; no history state).
Attributes
----------
event_bus : EventBus
model : Model
dispatcher : Dispatcher
interaction : Interaction
label : Label
The main label to display contents.
"""
def __init__(self, root):
self.root = root
self.root.title("Dog/Cat Command Sample (Model-contents, Py2)")
self.event_bus = EventBus()
self.model = Model(self.event_bus)
self.dispatcher = Dispatcher()
self.interaction = Interaction()
# Build GUI
self.var = tk.StringVar()
self.var.set(Interaction.DOG_MODE)
tk.Label(root, text="Choose:").pack(anchor='w', padx=5)
frame = tk.Frame(root)
frame.pack(anchor='w', padx=10)
tk.Radiobutton(frame, text="DOG", variable=self.var, value=Interaction.DOG_MODE,
command=self.on_radio_change).pack(side='left')
tk.Radiobutton(frame, text="CAT", variable=self.var, value=Interaction.CAT_MODE,
command=self.on_radio_change).pack(side='left')
tk.Button(root, text="SET", command=self.on_set).pack(fill='x', padx=5, pady=2)
tk.Button(root, text="UNDO", command=self.on_undo).pack(fill='x', padx=5, pady=2)
tk.Button(root, text="REDO", command=self.on_redo).pack(fill='x', padx=5, pady=2)
self.label = tk.Label(root, text="", anchor='w', justify="left")
self.label.pack(fill='x', padx=5, pady=12)
# Subscribe to Model update events
self.event_bus.subscribe(self.on_event)
self.on_radio_change()
# initialize display
self.model.notify_contents()
def on_radio_change(self):
"""
Respond to DOG/CAT radio button.
"""
self.interaction.set_mode(self.var.get())
def on_set(self):
"""
Issue a command depending on the mode.
"""
cmd = self.interaction.get_command(self.model)
self.dispatcher.execute(cmd)
def on_undo(self):
"""
Undo last user action.
"""
self.dispatcher.undo()
def on_redo(self):
"""
Redo last undone user action.
"""
self.dispatcher.redo()
def on_event(self, event):
"""
Update label text when notified from Model.
Parameters
----------
event : dict
Dictionary containing 'contents' (list of str), and possibly other keys.
"""
if event.get('action') == 'update_contents':
contents = event.get('contents', [])
self.show_contents(contents)
def show_contents(self, contents):
"""
Update label with contents.
Parameters
----------
contents : list of str
Text lines to display.
"""
self.label.config(text='\n'.join(contents))
if __name__ == '__main__':
root = tk.Tk()
app = App(root)
root.mainloop()