VisualWorks Smalltalk の“アプリケーションモデル”で BMI checker 2


id:sumim:20080919:p1 の続き。umejavaさんにコメントやメールで教えていただいたので、フィールドの色が変わる版も。複数のウインドウが共通のモデルを共有したときにも動作するように細工してみました。

| app1 app2 window1 window2 |
app1 := BMIChecker4 new.
window1 := app1 open window.
app2 := BMIChecker4 new.
app2 height: app1 height.
app2 weight: app1 weight.
app2 bmi: app1 bmi.
window2 := app2 open window.
window1 moveTo: 100@100.
window2 moveTo: window1 displayBox topRight + (20@0)

http://squab.no-ip.com/collab/uploads/61/bmicheker4.png


前の BMIChecker との違い(=教えていただいて学んだこと)は、

  • フィールドの色を変えるために、GUI Painter Tool → BMI値入力欄 → Basics → ID:欄 に #bmi を入力して、あとで #componentAt: でたぐれるようにしたこと
  • #height、#weight、#bmi は、同じく GUI Painter Tool で対応する入力欄を選択した状態で Edit → Define で自動生成させたこと。それに伴い、
    • インスタンス変数アクセスを間接アクセスパターンに切り替えたこと
    • BMI値更新用の #bmiChanged を新たに設けたこと
  • ウィジェットビルド後にコールされる #postBuildWith: をフックして BMI値欄の色づけを伴う初期化を行なっていること

…といったところでしょうか。あと、モデル共有時に期待通りの動作をしなかったため今回は使いませんでしたが、#onChangeSend:to: のコールをハンドコードする代わりに、GUI Painter Tool → 各入力欄 → Notificationタブ → Change:欄で #bmiChanged などと入力して指定することでも似たようなことが可能であることも教えていただきました。

Smalltalk defineClass: #BMIChecker4
    superclass: #{UI.ApplicationModel}
    indexedType: #none
    private: false
    instanceVariableNames: 'height weight bmi '
    classInstanceVariableNames: ''
    imports: ''
    category: '(none)'

BMIChecker4 >> initialize
    super initialize.
    self height value: 1.704.
    self height onChangeSend: #bmiChanged to: self.
    self weight value: 60.4.
    self weight onChangeSend: #bmiChanged to: self

BMIChecker4 >> postBuildWith: aBuilder
    self bmiChanged

BMIChecker4 >> bmi: aValueHolder
    bmi := aValueHolder

BMIChecker4 >> height: aValueHolder
    height := aValueHolder.
    aValueHolder onChangeSend: #bmiChanged to: self

BMIChecker4 >> weight: aValueHolder
    weight := aValueHolder.
    aValueHolder onChangeSend: #bmiChanged to: self

BMIChecker4 >> bmi
    ^bmi isNil ifTrue: [bmi := 0 asValue] ifFalse: [bmi]

BMIChecker4 >> height
    ^height isNil ifTrue: [height := 0 asValue] ifFalse: [height]

BMIChecker4 >> weight
    ^weight isNil ifTrue: [weight := 0 asValue] ifFalse: [weight]

BMIChecker4 >> bmiChanged
    | bmiValue component fieldColor |
    bmiValue := self weight value / (self height value ** 2).
    self bmi value: bmiValue.
    fieldColor :=
        bmiValue < 18.5 ifTrue: [ColorValue white] ifFalse: [
        bmiValue < 20 ifTrue: [ColorValue yellow] ifFalse: [
        bmiValue < 30
            ifTrue: [ColorValue orange]
            ifFalse: [ColorValue red]]].
    component := self builder componentAt: #bmi.
    component lookPreferences: (component lookPreferences setBackgroundColor: fieldColor)

BMIChecker4 class >> windowSpec
    "Tools.UIPainter new openOnClass: self andSelector: #windowSpec"

    <resource: #canvas>
    ^#(#{UI.FullSpec}
        #window:
        #(#{UI.WindowSpec}
            #label: 'BMI checker'
            #bounds: #(#{Graphics.Rectangle} 640 400 840 520 ) )
        #component:
        #(#{UI.SpecCollection}
            #collection: #(
                #(#{UI.LabelSpec}
                    #layout: #(#{Graphics.LayoutSizedOrigin} 15 0 15 0 46 18 )
                    #label: 'Height:' )
                #(#{UI.LabelSpec}
                    #layout: #(#{Graphics.LayoutSizedOrigin} 15 0 50 0 46 18 )
                    #label: 'Weight:' )
                #(#{UI.LabelSpec}
                    #layout: #(#{Graphics.LayoutSizedOrigin} 15 0 85 0 29 18 )
                    #label: 'BMI:' )
                #(#{UI.InputFieldSpec}
                    #layout: #(#{Graphics.LayoutSizedOrigin} 70 0 13 0 100 23 )
                    #model: #height
                    #type: #number )
                #(#{UI.InputFieldSpec}
                    #layout: #(#{Graphics.LayoutSizedOrigin} 70 0 48 0 100 23 )
                    #model: #weight
                    #type: #number )
                #(#{UI.InputFieldSpec}
                    #layout: #(#{Graphics.LayoutSizedOrigin} 70 0 83 0 100 23 )
                    #name: #bmi
                    #model: #bmi
                    #type: #number )
                #(#{UI.LabelSpec}
                    #layout: #(#{Graphics.LayoutSizedOrigin} 175 0 15 0 10 18 )
                    #label: 'm' )
                #(#{UI.LabelSpec}
                    #layout: #(#{Graphics.LayoutSizedOrigin} 175 0 50 0 16 18 )
                    #label: 'kg' ) ) ) )


ファイルイン用のファイルはこちらに。