第3章 Command パターン 2/4, IronPython

前の記事記事一覧次の記事

IronPython で学ぶ WPF プログラミングの世界《IronPython2.6》

Command パターン

《著》森こねこ・後藤いるか・小粒ちゃん《監修》小泉ひよ子とタマゴ倶楽部
第1版♪1995/07/02 ● 第2版♪2003/01/29 ● 第3版♪2008/04/28

■ 概要

WPF の特徴を活かして、デザインパターン Command を導入します。

イベントに呼応するアクションを規定したいときには〈GoF〉Command パターンを導入します。すると、密接に関係する「イベント/アクション」対を、再利用可能な「部品」として扱えます。

《Note》IronPython1.x 用に作成したセミナー課題を、IronPython2.6 で再構成しました。

■ 関連記事


2a)機能の要求:Command

Command::Command では、対象となる要素に共通するプロトコルを規定します。

class Command:                 # Command::Command
    def execute(self, sender, e):
        raise NotImplementedError("%s.execute"
            %self.__class__.__name__)

ここで規定したメソッド execute を、子孫クラスで再定義しないと、実行時に例外 NotImplementedError を生成するとともに、そのクラス名を出力します。たとえば、実行時に次のようなメッセージ

NotImplementedError: PaintColor.execute

が出力されるので、子孫クラス PaintColor でメソッド execute を再定義していないのが分かります。

2b)機能の実現:ConcreteCommand

Command::ConcreteCommand では、Command::Command で規定されたプロトコルに従って、各機能を実現します。

■ クラス PaintColor
class PaintColor(Button, Command):         # Command::ConcreteCommand
    def __init__(self, Content, panel):         
        self.panel = panel
        self.Content = Content
        self.Click += self.execute
    def execute(self, sender, e):
        e = sender.Content
        self.panel.Background = getattr(Brushes, e)

メソッド execute では、パネル panel の背景色を設定します。選択した項目(ボタン).Content に設定された値をもとに、背景 Background となるブラシ Brushes の色を設定します。

■ クラス FileOpen
class FileOpen(Button, Command):         # Command::ConcreteCommand
    def __init__(self, panel):
        self.panel = panel
        self.Content = "Open..."
        self.Click += self.execute
    def execute(self, sender, e):
        dialog = OpenFileDialog()
        dialog.ShowDialog()
        e = dialog.FileName.split("\\")[-1]
        brush = ImageBrush(
            ImageSource=BitmapImage(Uri("Flags/%s"%e, UriKind.Relative))
            )
        self.panel.Children.Add(Rectangle(
            Width=50,
            Height=50,
            Fill=brush,
            ))

メソッド execute では、選択したファイル内の画像を、パネル panel に追加します。

■ クラス FileClose
class FileClose(Button, Command):         # Command::ConcreteCommand
    def __init__(self, window):
        self.window = window
        self.Content = "Close"
        self.Click += self.execute
    def execute(self, sender, e):
        self.window.Close()

メソッド execute では、ウィンドウを閉じて、アプリケーションを終了させます。


何が問題か:Command パターンを概観する

モジュール CommandToolBar.py は〈GoF〉Command パターンを適用したものです。


WPF コントロールに割当てられたプロバティー(ここでは Click)を介して、イベントハンドラーを起動していると、コマンドとしての独立性が損なわれます。既存のフレームワークに依存するため、柔軟性に優れた拡張が困難になります。そこで、この問題を解消するために、コマンドバインディングを導入します。

Last updated♪09/06/20

《付録》CommandToolBar.py

# -*- coding: utf-8 -*-
#===============================================================================
#    Copyright (C) 2000-2008, 小泉ひよ子とタマゴ倶楽部
#
# History: WPF examples
#    2008/01/25, IronPython 1.1.1 (download)
#    2008/08/22, IronPython 1.1.2 (download)
#    2008/03/16, ver.2.0, WPF
#    2008/00/00, ver.2.1, IronPython 1.1.2 
#===============================================================================
from _ant import *
from System import *
from System.Windows import *
from System.Windows.Controls import *
from System.Windows.Media import *
from System.Windows.Media.Imaging import *
from System.Windows.Shapes import *

clr.AddReference("System.Windows.Forms")
from              System.Windows.Forms import OpenFileDialog

## --------------------                         # Command::Command
class Command:
    def execute(self):
        raise NotImplementedError("%s.execute"
            %self.__class__.__name__)

## --------------------                         # Command::ConcreteCommand
class FileOpen(Button, Command):
    def __init__(self, panel):
        self.panel = panel
        self.Content = "Open..."
        self.Click += self.execute
    def execute(self, sender, e):
        dialog = OpenFileDialog()
        dialog.ShowDialog()
        e = dialog.FileName.split("\\")[-1]
        brush = ImageBrush(
            ImageSource=BitmapImage(Uri("Flags/%s"%e, UriKind.Relative))
            )
        self.panel.Children.Add(Rectangle(
            Width=50,
            Height=50,
            Fill=brush,
            ))

## --------------------                         # Command::ConcreteCommand
class FileClose(Button, Command):
    def __init__(self, window):
        self.window = window
        self.Content = "Close"
        self.Click += self.execute
    def execute(self, sender, e):
        self.window.Close()

## --------------------                         # Command::ConcreteCommand
class PaintColor(Button, Command):
    def __init__(self, Content, panel):
        self.panel = panel
        self.Content = Content
        self.Click += self.execute
    def execute(self, sender, e):
        e = sender.Content
        self.panel.Background = getattr(Brushes, e)

## --------------------
class ExWindow(Window):                         # Command::Client
    def __init__(self, Content=None, **args):
        self.InitializeComponent(Content)
        self.init()

    def InitializeComponent(self, Content):
        self.Content = LoadXaml(Content)
        
    def init(self):
        target = "toolBarTray", "panel",
        self._Controls(target)
        target = "fileToolBar", "colorToolBar",
        for e in self.toolBarTray.ToolBars:
            if e.Name in target:
                setattr(self, e.Name, e)

        for e in FileOpen(self.panel), FileClose(self):
            self.fileToolBar.Items.Add(e)
        for e in "Red", "Green", "Blue":
            e = PaintColor(
                Content=e, panel=self.panel)
            self.colorToolBar.Items.Add(e)

    def _Controls(self, target):
        controls = xaml_controls(self)
        for e in target:
            setattr(self, e, controls[e])

## --------------------               
if __name__ == "__main__":
    xaml = __file__.replace(".py",".xaml")
    win = ExWindow(
        Title=__file__,
        Width=260, Height=120,
        Content=xaml,
        )
    Application().Run(win)