Morphic の練習(その1)

そういえば、Morphic であんまりプログラミングしたことないなぁ、と唐突に思いました。実用になるものは作ったことがないです。コレではいけないと、勉強 兼 ちょっとした腕だめしに、CheckBox っぽいものを実装してみました。

生成と描画

まず、こんなクラスをば。

Morph subclass: #MNKCheckBoxMorph
    instanceVariableNames: ''
    classVariableNames: ''
    poolDictionaries: ''
    category: 'MorphicLesson'

Morph を 派生させて MNKCheckBoxMorph を作ります。で、描画内容を弄くるには #drawOn: メソッドをオーバライドします。

MNKCheckBoxMorph >> drawOn: aCanvas
    aCanvas frameRectangle: self bounds 
            color: Color black

#drawOn: メソッドは 引数として Canvas オブジェクトを渡してくれるので、そこにお絵かきします。ここで使っている #frameRectangle:color: は渡した Rectangle の外枠をなぞる線を引いてくれるメッセージです。self bounds で自分の描画限界な 方形 を取ってこれるのでこれを渡して、この Morph を黒枠で囲います。

とりあえず Workspace で以下を do it して Morph を表示してみましょう。

MNKCheckBoxMorph new openInHand

こんな感じ。


その他のお絵かきメッセージについては Canvas クラスの drowing-** なプロトコルの物を ブラウズして探せばいろいろあります。これらお絵かきメッセージを使って、self bounds で取れる Morph の 描画範囲を基準にお絵かき、というのが #drawOn: でやることです。

以上でつかみはOK。早速、今回の目標、チェックボックスの外観を作ってしまいましょう。ここら辺は3分間クッキングのノリで実装済みのものをさらっと机の下から、えい(w

MNKCheckBoxMorph >> drawOn: aCanvas
    (self width > self height)
        ifTrue: [self width: self height]
        ifFalse: [self height: self width].

    "外枠の描画"
    self drawBoxOn: aCanvas.

    "チェックの描画"
    self checked ifTrue: [self drawCheckOn: aCanvas]


MNKCheckBoxMorph >> drawBoxOn: aCanvas
    aCanvas frameRectangle: self boxRectangle
            color: Color black


MNKCheckBoxMorph >> drawCheckOn: aCanvas
    aCanvas line: self boxRectangle leftCenter
                                    - (-2@(self height//8))
             to: self boxRectangle bottomCenter
                                   - ((self width//16)@2)
             width: 2
             color: Color black.
    aCanvas line: self boxRectangle bottomCenter
                                    - ((self width//16)@2)
             to: self topRight + (-2@2)
             width: 2
             color: Color black


MNKCheckBoxMorph >> boxRectangle
    | margin |
    margin _ self width // 8 + 1.

    ^self topLeft + (margin@margin)
               corner: self bottomRight - (margin@margin)

MNKCheckBoxMorph >> checked
    "取りあえず、今のところはいつでも true"
    ^true

見た目をちょこちょこ弄りながら拡大しても縮小しても破綻しにくくカッコイイものをめざしてみました。おかげで #drawCheckOn: がゴチャゴチャなコードに...orz。 それと、縦横比を1:1 固定にするためのコードが #drawOn: メソッドの頭に入ってますが、このメソッドにいれるのは良くないかも、と思います。そういう汚いところを除けば、このコードで特筆すべきことはあんまり無いです(^^;

強いて言えば、 self topLeft 等でしょうか。これは、Morph の geometory プロトコルを見ると

Morph >> topLeft

    ^bounds topLeft

な感じで定義されている、いちいち bounds を経由しないで良いようにする ショートカットメソッドです。

出来上がり画像はコチラです。

マウスイベントの処理

ここまででは、ただ見てくれだけなので、クリックするとチェックがついたりつかなかったりするようにします。

その前に、チェックされているか? を保持するインスタンス変数 checked を作りましょう。で、ゲット・セット アクセサをこのように してあげます。

MNKCheckBoxMorph >> checked
    checked ifNil:[ checked _ false ].
    ^checked

MNKCheckBoxMorph >> checked: aBool
    checked _ aBool

さあて、いよいよイベント処理をかきましょうか。イベント処理は、

  1. イベントをハンドリングするか? を答えるメソッド
  2. イベント発生時処理を行うメソッド

の2つを書きます。今回は、マウスボタンのUP でチェック値をフリップさせるので mouseUp イベントだけ何とかすれば良さそうなのですが、実は Morph の規定挙動として マウスでクリック(MouseDown)すると Morph をつかんでしまうのが邪魔になるので、それを止めさせるためにmouseDown もハンドリングします*1

MNKCheckBoxMorph >> handlesMouseDown: anEvent
    ^true


MNKCheckBoxMorph >> handlesMouseUp: anEvent
    ^true


MNKCheckBoxMorph >> mouseUp: anEvent
    self checked: self checked not. 

で、動かしてみると・・・あれれ、表示が変わらない。これはイベントが取れていないわけじゃなくって、例えばサイズ変更なんかで描画更新をかけてあげるとちゃんと「レ」表示が更新されます。要するに変更のタイミングで再描画されてないだけのこと。(なにもやってないのだから当たり前です)

Morph の再描画を行わせるには、自分自身に #changed メッセージを送ってあげれば OK です。

MNKCheckBoxMorph >> checked: aBool
    checked _ aBool.
    self changed

これにてめでたく完成です。

*1:そんなことしなくても、self beSticky でよいそうな