RealBasic University

このコラムはStone Table Softwareのオーナーであり、またREALbasic Developerの編集者でもあるMarc Zeedar氏により書かれたものを、著者の許可を得て翻訳したものです。この翻訳はHREM Researchにより提供されています。この日本語版へのご意見はRBU-Jまでご連絡下さい。

URL: http://www.applelinks.com/rbu/088/
INDEXに戻る

OOP University:パート 12

前回のレッスンでは、描画要素を選択・解除できるようにしたオブジェクト指向のお絵かきプログラム、SuperDrawを作成しました。今回は、描画要素の移動やサイズ変更ができるように、プログラムを拡張していきます!

移動機能の追加

今回、我々が計画している2つの機能――オブジェクトの移動とサイズ変更――を追加するためには、異なる2つの状況を考慮する必要があります。例えば、オブジェクトのサイズ変更は、ユーザがコーナー・ハンドルをドラッグするときにだけ起こります。ユーザが描画要素の内側をドラッグするときには、オブジェクトがただ移動します。

我々は両方の機能を追加したいので、計画をうまく立て、同時に2つのコードを作成していきましょう。ただし、最初の段階ではサイズ変更の機能は追加しません。まず始めに、shapeClassにメソッドを追加しましょう。

前回は、クリックがオブジェクトの内側でなされたかどうかを判定して知らせてくれるwithinMeメソッドを、shapeClassに追加したことを覚えているでしょうか?今回は、クリックがコーナー・ハンドルの内側でなされたかどうかを判定してくれるという点以外は、同じことをするメソッドを作成します。

shapeClassを開き、次のような新規メソッド(編集メニューの「新規メソッド」)を追加してください:

  
function withinHandle(x as integer, y as integer) as boolean
dim w, h, w1, h1 as integer

theHandle = -1 // None
w = left - 4
h = top - 4
w1 = left + width + 4
h1 = top + height + 4
draggingHandle = true
if x >= w and x <= left and y >= h and y <= top then
theHandle = 0 // 左上
return true
end if
if x >= w1 - 4 and x <= w1 and y >= h and y <= top then
theHandle = 1 // 右上
return true
end if
if x >= w1 - 4 and x <= w1 and y >= h1 - 4 and y <= h1 then
theHandle = 2 // 右下
return true
end if
if x >= w and x <= left and y >= h1 - 3 and y <= h1 then
theHandle = 3 // 左下
return true
end if

// どのハンドルもクリックしていないとき
draggingHandle = false
end function

このコードはとても簡単です:渡されたxy座標がそれぞれのコーナー・ハンドルの内側にあるかを判定します。それが内側にあれば、trueを返します。さらにdraggingHandletrueに設定すると同時に、どのコーナー・ハンドルがクリックされたかを知らせるtheHandleと呼ばれるプロパティを0〜3に設定します。

詳細

このコードは潜在的に問題を含んでいることに、あなたはすでに気がついたかもしれません:私はハンドルのサイズ(4ピクセル)を、プログラム中で書き込みました。理想的には、これはコンスタントにするべきです。この方法ならば、(どんな理由であれ)ハンドルのサイズを変更する必要が生じたとき、コンスタントの値を変更するだけで、コードに手を加える必要はありません。私が今回書いた方法では、いくつものルーチンにわたって値を変更する必要が出てきます。

残念なことに、REALbasicオブジェクトは、コンスタントをサポートしていません(モジュールだけで可能です)ので、この種の問題を回避することは困難です。一つの解決法は、あなたの欲しい値を返してくれるメソッド――例えば4を返してくれるhandleSizeというメソッド――を追加することです。しかし、それは単純なコンスタントの手法に比べると、あまりにも複雑です。おそらく将来のRBでは、クラスでもコンスタントが使えるようになるでしょう。

今のうちに、次の2つの新規プロパティを追加しておきましょう:

  
draggingHandle as boolean
theHandle as integer

バグ警報

わずかの鋭い読者たちは、withinMeメソッドでは、クリック座標に加えてグラフィック・オブジェクトを渡すのに対して、withinHandleではクリック座標しか渡さないことに気がついたかもしれません。それはどうしてでしょうか?

ええ、それは手落ちです。バグです。それは問題を引き起こす原因となるバグではなく、余分なコードです。余剰コードはリソースの無駄になるだけでなく、コードを分かりづらくする要因ともなります。例えば、渡されたパラメータであるgは、withinMeメソッドでさえ使用されていないことに気づくでしょう!

そういう訳で、今のうちにそれを修正しておきましょう。withinMeのパラメータの箇所を、単純にx as integer, y as integerと変更してください。次に、drawCanvasClassを開き、mouseDownイベントに行ってください。withinMeを呼び出す部分で、me.graphicsパラメータを削除してください。修正後は、次のようになっているでしょう:

  
if objectList(i).withinMe(x, y) then

私が言ったように、この余計なパラメータは何の問題も引き起こさないのですが、存在価値は無いので、削除しておくのがベストでしょう。

さてオブジェクトの移動の問題に話を戻します。drawCanvasClassを開いて、mouseDragイベントに行きましょう。そこへ次のコードを加えてください:

  
dim i, n as integer

if currentObject.draggingHandle and currentObject.selected then



// elseifはx/yが移動されたかを判断します
// それはクリックではなく、ドラッグによる移動の場合です。
elseif (x <> xStart) or (y <> yStart) then
// Is moving object...

// 差分(移動量)の計算
xStart = x - xStart
yStart = y - yStart

// 選択されたかを確認
currentObject.selected = true

n = objectCount
for i = 1 to n
if objectList(i).selected then
objectList(i).left = objectList(i).left + xStart
objectList(i).top = objectList(i).top + yStart
end if
next // i

// 現マウス座標のリセット
xStart = x
yStart = y

me.refresh
end if

見てお分かりのように、ユーザがハンドルをドラッグした時の処理の部分はブランクにしておきました。後ほど、その部分に手を加えようと思います。ドラッグに関する我々の2つの選択肢は、ハンドルをドラッグするのか、あるいは描画オブジェクトをドラッグするのかのいずれかなので、上のelseIf行コードは、ハンドルをドラッグしているのではなく、オブジェクトを移動していることを示しています。実際には、一つ以上のオブジェクトが選択されると考えられるので、グループ選択として複数のオブジェクトを移動させることになるでしょう!

移動に関しては、まず始めに移動量を計算して、この結果をxStartyStartに割り当てます。この量を計算するのに用いた数式では、左あるいは上方向に移動するとこの値はマイナスになります。それから、選択されたオブジェクトを探し出し、それを計算された移動量(xStartyStart)で移動させるために、簡単なfor/nextループをすべてのオブジェクトに対して行います。

最後のステップは、xStartyStartを現在のマウス座標にリセットすることです――これは再びmouseDragが呼ばれた時に、正しいマウス移動量を計算するために重要になります。我々は、初期のクリック座標ではなく直前のマウス座標からの移動量を必要としているのは明確でしょう。最後に、オブジェクトが新しい場所で描画されるようにdrawCanvasClassを再描画して終わります。

さあプロジェクトを試してみましょう:実行してオブジェクトを自由に移動させてみてください。

オブジェクトのサイズ変更

描画オブジェクトのサイズ変更は複雑に見えますが、ほとんどの作業はすでに完了しています!

まず始めに、mouseDragイベントに、ハンドルをドラッグ(サイズ変更)したときのコードを入力する必要があります。よって、次のコードをif currentObject.draggingHandle and currentObject.selected thenの行の後に入力してください。

  
// どのハンドルがドラッグされているかに基づくサイズ変更

// 差分計算
xStart = x - xStart
yStart = y - yStart
select case currentObject.theHandle
case 0 // 左上
adjustObject(xStart, yStart, -xStart, -yStart)
case 1 // 右上
adjustObject(0, yStart, xStart, -yStart)
case 2 // 右下
adjustObject(0, 0, xStart, yStart)
case 3 // 左下
adjustObject(xStart, 0, -xStart, yStart)
end select

me.refresh
// 現在のマウス座標へリセット
xStart = x
yStart = y

ここでも移動量を計算し、その結果を実際にオブジェクトのサイズを変更するadjustObjectという新規のメソッドに渡します。それぞれのコーナー・ハンドルではドラッグする方向が違うので、adjustObjectへ渡す値も変更します。これで、オブジェクトはドラッグと同じ方向で伸びたり(あるいは縮んだり)します。

最後に、再描画をしてから、現在のマウス座標を保存します。

もちろん、これがうまく行くためには、adjustObjectメソッドを追加する必要があります。それは次のようになります:

  
Private sub adjustObject(aLeft as integer, aTop as integer, aWidth as integer, aHeight as integer)
const minSize = 10

currentObject.left = currentObject.left + aLeft
currentObject.top = currentObject.top + aTop
currentObject.width = currentObject.width + aWidth
currentObject.height = currentObject.height + aHeight

// サイズが最小値を下回るので、行ったことをアン・ドゥする
if currentObject.width < minSize then
currentObject.left = currentObject.left - aLeft
currentObject.width = currentObject.width - aWidth
end if

if currentObject.height < minSize then
currentObject.top = currentObject.top - aTop
currentObject.height = currentObject.height - aHeight
end if
end sub

パラメータとして4つの整数値を取り、関数がprivateとして定義されていることに気づくでしょう。privateとは、メソッドはdrawCanvasClassからのみ呼ばれるという意味です。要するに、window1adjustObjectを呼ぶことができません。

どうしてメソッドをprivateに設定したのでしょうか?それは、重要なOOPの概念です。あるルーチンをプライベートにして、あるものをパブリックにすることで、オブジェクトは、他のオブジェクトがそれを参照することのできる範囲を制御することができます。あるオブジェクトだけがアクセスする必要のあるものを扱うルーチンは、そのオブジェクトだけがアクセスできて、その他の外部ルーチンはアクセスできないようにprivateに設定するのが一番良いでしょう。この場合では、window1 (あるいは他のオブジェクト)は、オブジェクトのサイズ変更には何の関わりもありません――それはdrawCanvasClassだけの仕事です。

さてSuperDrawを実行すると、ハンドルをクリックしても動作しないことに気がつくでしょう。それは、そのイベントに関するmouseDownに、確認作業を追加していなかったからです!

drawCanvasClassのmouseDownイベントに行き、n = objectCount行の後に次のコードを入力してください:

  
// それはハンドルの内側か?
for i = n downto 1
if objectList(i).selected and objectList(i).withinHandle(x, y) then
// currentObjectはクリックされたオブジェクトを指定します
currentObject = objectList(i)
xStart = x
yStart = y
return true // ループを継続しても意味がありません
end if
next // i

これはすべてのオブジェクトを一巡して確認します。始めにオブジェクトが選択されているかをチェックします――選択されていないオブジェクトについてハンドルがクリックされているかをチェックしても、何の意味もないからです――それから、そのクリックはハンドルの内側であるかをオブジェクトに尋ねます。withinHandletrueを返すときは、オブジェクトのtheHandleプロパティはクリックされたコーナーを示していることを覚えていますか。それによって、ドラッグルーチンはどのハンドルが現在ドラッグされているかを知ります!

ユーザがハンドルをドラッグする時には、我々はループで該当するオブジェクトにcurrentObjectを設定し、xStartyStartを指定し、そしてmouseDownイベントを終了します(コメントで述べているように、ループを継続しつづけても何の意味もありません)。

次は何でしょう?以上です!これで完成しました!

SuperDrawを実行して、何ができるかを確認してみましょう。キャンバスに追加したどのオブジェクトに対しても、選択、選択解除、移動、そしてサイズ変更ができます:

素晴らしいでしょう?我々は、オブジェクト指向の流儀に沿ってこれをプログラムしたので、今回はいかなる外部コード(window1canvas1)、あるいは以前に制作したサブオブジェクト(circleClassrectClasstriangleClass等)にも手を加えなかったことに注目してください。それでも、この簡単な改良でこれらのオブジェクトすべてがうまく動作します!

それがオブジェクトのすぐれた力です。しかし、あなたはまだ何も経験していません!次回のレッスンでは、即座に、とても簡単にいくつかのまったく新しいオブジェクトを追加する予定です。そして、あることにさえ気づかなかった特徴を、自動的に継承することを知って驚くでしょう!

今週のチュートリアルの完全なREALbasicプロジェクトを手に入れたい場合は、ここからダウンロードしてください。

次週

SuperDrawに新規のオブジェクトを追加します。

RBU裏技

単純なのですが、よく見落とされがちな情報です。REALbasicのTipウィンドウは、メソッドで要求されたパラメータを表示するとても便利なものです。さらに良いことは、あなたが自分で作成したルーチンのパラメータをも表示することです!

しかし、表示されるものは、あなたがメソッドの定義で用いる変数名であることに注意してください。ですからメソッドのパラメータでは、説明的な名前を使用しましょう:それはTipウィンドウに表示され、ルーチンが必要とするものをうまく思い出させてくれます。

始めのTipウィンドウよりも、こちらの方が判りやすくありませんか?

ニュース

今週、REAL Software社はREALbasic 5 for Macintoshがついに発売されることを発表しました。これは今までのREALbasicの中で最も大きな改良が加えられ(新たなコンパイラーも含まれます)、製品の確かな未来を約束するものとなりました。新しい機能の中には、Jaguar(Mac OS X)サポートの向上、新しい通信プロトコル(email、http、等々)、そして新しい言語機能が含まれます。

REAL Software社のウェブサイトでは、REALbasic 5についてのさらに多くの情報を手に入れることができます。アップグレードは$29.95〜です。

REALbasic 5の新機能に興味がありましたら、これから発売予定のREALbasic Developerマガジンの4/5月号版で、この重大なアップグレードについて、2つの徹底解説記事が掲載されます。

Letters

今週は、先週のコラムについてEメールでご意見を頂きました。始めに、先週の「デバッグの難問」に関連した「訂正」について、Georg Christmannさんから次のような意見を頂きました。

Marcさん、こんにちは。

こう申し上げるのも何なのですが、REALbasicのデバッグ機能が変数「s」はすでに使われていると警告してくれました;-)

ちなみに、それは私が予想したとおりの結果です。

けれどもRBUのファンであることに変わりはありません。RB Developerも購読しています。

ご幸運を願って

Georg

まさにあなたの言うとおり、REALbasic 5ではバグを知らせてくれます:しかしREALbasic 4.xではそうはいきません。このよい機会に付言しておきますと、RB5は潜在的な多くの問題を事前にとらえ、問題が発生する以前に我々に警告を与えてくれます。

Lars Jensenさんからは、Douglas Beagleyさんの3Dの問い合わせに関する返事を頂きました。

こんにちは、Marcさん。あなたはDouglasさんのメールアドレスを公表されていないので、これを彼に転送して頂けると幸いです。RBの3D機能を実際に見てみるには、私のウェブサイトに訪問するようDouglasさんに提案したいと思います:

http://ljensen.com

そして、Iron Guestを試してみてください。それは兄と私が作成したとても洗練された3Dタイプのボードゲームです。(Classic, Windows, and X)

SceneBenchもお勧めです。ユーザは、RB3Dスペース、オブジェクト、背景、そして照明のほとんどすべてのプロパティをその場で直接に操作して確認できます。それはとても速くて簡便にAPIのチェックやテストをするためのものです。(Classicのみ)

どちらもフリーの試作版です。

Larsさん、凄いです!このような素晴らしいリソースを共有して頂きありがとうございます!


RBU-Jの通知サービス!コラムが発表されるたびに日本語版REALbasic Universityのお知らせの emailがあなたに届きます。登録・削除は ここ から。

INDEXに戻る