RealBasic University

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

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

OOP University:パート 7

我々は前回、インスタンス(instance)とインスタンスへの参照の小さな、しかし重要な違いとともに、オブジェクトのインスタンスとオブジェクトのクラス(class)の違いについて学びました。今回は、REALbasicにおけるオブジェクトの使用法に関する実際の行いを細かくみることによって、その違いについてのレッスンを続けていきましょう。具体的に言えば、今回はそのコピーの方法についてです。

オブジェクトのコピー

前回のレッスンで、どうしてオブジェクトを他の変数にただ代入するだけでは、複製することができないのか学んだことを覚えているでしょう。つまり次の例は機能しません:

  
dim d1, d2 as date

d1 = new date
d2 = new date
d1 = d2

それでは、ただ等号を使うだけではオブジェクトを他にコピーできないのだとしたら、コピーするにはどうすればいいのでしょうか?ええ、それには古典的な方法しかないのです。それぞれの属性を一つ一つコピーしていかなければなりません。例を挙げましょう:

  
anObject2.theContents = anObject1.theContents

10の属性があるとすれば、10の等号が存在します。逆にコピーする場合もまた同様です!

そう、それはとても面倒ですね。けれどもオブジェクトにメソッドを追加して、自分自身をコピーするようにすれば、少しだけ簡単にすることができますよ。

例えば、実際のコピーのメソッドを含めるために、前回お見せした簡単なデモを拡張しました(ここからダウンロードしてください):

"Real Copy"(実際のコピー)ボタンを押すと、一方のオブジェクトの内容が他方のオブジェクトにコピーされ、"Fake Copy"(にせのコピー)ボタンを押すと、ただオブジェクトの参照ポインターがコピーされることに注意しましょう。にせのコピーボタンを押すと、何もそれを参照しないように、ある一つのインスタンスが破棄されます。しかし実際のコピーでは、どちらのオブジェクトもまだ存在しています。それは同じ内容ですが、二つの独立したオブジェクトとして存在します。これを理解するために、実際のコピーをして、テキスト欄の中のテキストを変更し、オブジェクトを更新してみましょう。それはそれぞれ独立に更新されることが分るでしょう。

さらによく理解するために、このコピーをどのように実行するか調べてみましょう。私は、myClasscopyメソッドを追加しました。それはオブジェクトについて何も知らなくてもmyClassオブジェクト(これがキーポイントです)の複製を可能にします。

ここに、そのcopyメソッドがあります:

  
sub copy(toObject as myClass)
if toObject <> nil then
toObject.name = me.name
toObject.theAmount = me.theAmount
toObject.aProperty = me.aProperty
end if
end sub

これはmyClass型の前もって初期化されたオブジェクトのパラメータ(toObjectが"new"で初期化され、nilではないことを意味します:それはオブジェクトのインスタンスを参照しています)を持つことに注意しましょう。そうして、そのオブジェクトにその属性をコピーします。

実際にコピーをするコードは簡単です:ボタンが押されるときに、次のように命令します:

  
anObject1.copy(anObject2)

これだけです!外側のオブジェクト(この場合はanObject1)が、渡されたオブジェクト(anObject2)に自身をコピーします。

これで、どれほど柔軟性が出たか分かりますか?コピー・コマンドをオブジェクト自身に埋め込むことで(オブジェクトは自身をコピーする方法を知っていますので)、メイン・コードからコピーの込み入った詳細を切り離すことができます。我々が新しい属性をmyClassに追加したり、既にあるその属性を変更したりしても、ただ一箇所のcopyメソッドを更新するだけです。それ以外では、他のどの部分も全く変更する必要はありません!

これはカプセル化(encapsulation)と呼ばれています。これについては次回詳しく学習しましょう。

パラメータとしてオブジェクトを渡す

我々のcopyメソッドでは、オブジェクトを渡したと思ったでしょう。果たして本当でしょうか?

鋭い読者は、実際にはそこへオブジェクトを渡していないことにすぐに気が付いたことでしょう!つまり、オブジェクトの参照を渡したのです!(それが、使用前にnilをテストしなければならなかった理由です)

さて、通常はメソッドに変数を渡すと、ルーチン内では、最終的には新しい変数のコピーを処理します。そのルーチンが終了すると、そのコピーは破棄されます。元の変数はルーチンによって変更されることはありません。

次の例を見てみましょう:

  
function addOneByValue(n as integer) As integer
return n + 1
end function

これは数に一つ加えるものです。しかし、このメソッドで参照されたnは、決して変更されません。

一方、byrefコマンドを使うと、変数のコピーは作成されずに、オリジナル変数への参照が使われます。よって、そのメソッド内でnに起きることは、我々のオリジナルの変数にも実際に反映されます。

  
sub addOneByRef(byref n as integer)
n = n + 1
end sub

パラメータとして情報を渡すこれら二つのメソッドは、値渡し(passing by value)と参照渡し(passing by reference)と呼ばれています。REALbasicの初期設定は値渡しです:参照渡しをしたい場合は、ルーチンの定義でそれを指定する必要があります。

詳細

変数を変更するどちらの方法(byrefまたは関数によって返された結果)も、メリットとデメリットがあります。関数の主なデメリットは、ただ一つの結果しか返すことができないことです。

ユーザにテキスト入力欄にあるテキストを入力させるプログラムをあなたが書いたとしましょう。そのプログラムは、ユーザにテキストを正確に入力することを要求します(余分なスペースは無し、大文字小文字は正確、等々)。テキストのフォーマットは重要で、正確でなければなりません。

さて、あなたがテキストを受け取って、ミスをチェックし、テキストの正しさに応じてtruefalseを返す関数を書いたとしましょう。しかし渡されたテキストを修正し、単純なミスを正してくれるルーチンもまた欲しいとしましょう。すべてのエラーは修正することができないかもしれませんが、ユーザのためにも単純なエラーは修正したいものです。しかし、テキストが正しいか間違っているかを知る必要があるので、ユーザに最終的な判断してもらうため、テキストを再表示する必要があるでしょう。どうすればこれができるでしょうか?

それは簡単です:両方の方法を使いましょう!ルーチンにbyrefとしてテキストを渡し、論理型(boolean)の結果を返します。それには次のようにします:

  
function checkText(byref theText as string) as boolean

// byrefのパラメータは修正しても良いので
// これは適正です。
theText = trim(theText)

// ここで解析を行ってください。
...

// 必要に応じて、falseを返します(検証が失敗したときなど)

return true
end function

これによって、関数のtrue/falseの結果を返す一方で、theTextに修正を加えることができます。これで一挙両得です。(お気づきかもしれませんが、REALbasicのparseDate関数もこれと同じように動作します!)

しかし、オブジェクトについてはどうなのでしょう?オブジェクトではbyrefとして指定しませんでしたが、copyメソッドでは元のオブジェクトを修正することができました!

その通りです!パラメータとしてオブジェクトを渡すキーポイントを、たった今学びました:オブジェクトは常にbyrefとして渡されます!

ある意味で、object1 = object2の状況に立ち戻ったことになります:REALbasicがメソッドでオブジェクトを"コピー"するとき、それは偽のコピーです。オブジェクト自身ではなく、元のオブジェクトへの参照(ポインター)だけしかコピーしていません。だから、渡された参照を通した変更はすべて、実際には元のオブジェクトに起こります。それはちょうど"Fake Copy"のボタンを押すと、二つのポインターが同じオブジェクトを指し示すのと同じです。

この振る舞いにあなたが気づいている限りは、これは問題ではありません。あなたが通常の値渡しパラメータに慣れていると、オブジェクトを渡すときに、思いがけない振る舞いに戸惑うでしょう。私の初期の頃のプログラムでは、これには混乱させられたことを、よく覚えています。

上記の"addOne"のREALbasicプロジェクトを手に入れたい場合は、ここからダウンロードしてください。

次週

オブジェクトの機能について十分できませんでしたが、次週に行うことを約束します!

News

REALbasic Developerの第4号は、現在印刷所にまわされていて、2月初旬〔2003年〕には手にはいるでしょう。まだ定期購読に間に合います――今すぐ定期購読をして、まず始めにRBD 1.4を受け取りましょう!

また、既刊のRBDを買い損ねた人には、バックナンバーも購入可能です。

ここで、2月/3月号の内容を少しだけお見せしましょう。

ソフトウェアの販売戦略
始めの特集記事は、Electric Butterfly社のDave Wooldridge氏(UniHelpで有名)によるソフトウェアの販売戦略についてです。ソフトウェアを販売するために必要な数多くの作業に嫌気がさした経験をお持ちならば、これこそあなたの読むべき記事です。ブランドとして確立する手法、サーチ・エンジンの登録方法、プレス・リリースの書き方、広告、報酬の扱いなどについて、Dave氏が解説します。

Aquaのための設計
次に、Mac OS Xの"Aqua表示"に適したプログラムを設計することに奮闘しているならば、Mike Benonis氏が役に立つ情報を与えてくれます。さらに、mactester.comのDoug Grinbergs氏によって編集された便利な"Aquaチェックリスト"を入手しました。

TouchCADの制作者へのインタビュー
グラフィックスに興味があるならば、Claes Lundster氏へのインタビューに惹かれることでしょう。彼はボートのデザイナーで、REALbasicで驚くほどの機能を備えたTouchCAD($795)を開発しました。

ポストモーテム(事後分析)
この号のポストモーテムでは、Thomas Reed氏が既存のプログラム(Coffee Break Pro)を、REALbasicで書き換えることについて詳述します。

さらに
そしてもちろん、すべてのレギュラー・コラムも忘れてはなりません:Matt Neuburg氏のコラムはハッシュ・コーディングについてです。Thomas Cunningham氏のコラムは、ビギナーの視点に立った分かりやすいグラフィック入門についてです(これはThomas Reed氏の上級グラフィック講座の補足になります)。そして、今話題のトピックに興味があるならば、Didier氏の"Beyond the Limits"を忘れずに読みましょう。ここでは、REALbasicでステガノグラフィー(電子透かし技術:画像の中に隠れた情報を埋め込む技法)をどのように行うか説明しています。

今すぐ申し込みましょう!

Letters

今週は、フランスのNicolas Jullienさんからちょっとした難問をいただきました:

リストボックスのある特定の行に、何かをドラッグして挿入するよいアイデアはあるでしょうか。要するに、マウスのボタンを放す前に、ドラッグ動作を横取りするにはどうすればよいのでしょうか?

もう一つ質問(又は思いつき)があります。ドラッグされたオブジェクトを受け付ける対象にドラッグしている時に、それをドラッグしていることを知らせる囲い(正確な用語がわかりません)がありません。しかし、ウィンドウの外側でドラッグを始めれば、囲いが現れます!このようにすることなく、囲いを付けるにはどうすればよいのでしょうか?

Nicolas Jullien

Nicolasさん、あなたは不幸にも、REALbasicの一つの限界にたどり着いてしまいました。RBがあなたに代わってたくさんの作業をして、ドラッグ&ドロップのサポートを簡単にしているのですが、その代償は、ドラッグのイベント中にあなたがコントロールできることがほとんどないということです。例えば、ドラッグをしている間は、プログラムはいかなるマウス・イベントも受け付けません:mouseEntermouseMove等を受け付けないのです(私の例題プログラムで、ドラッグ中にはマウスの座標が更新されないことを確認してみてください)。この制限によって、あなたのしようとしていることが困難になります。

しかし、技術的な制限はありますが、不可能なことでもありません。

なすべきことは、ユーザがどの行にオブジェクトをドロップするのかを、計算して割り出すことです。現行の行のテキストを、新しくドロップされたテキストで置き換えたいとしましょう。ここに、動作中の私の例題プログラムを示します:

これをするには、二つの事項を知る必要があります:ピクセル単位で各行の高さを知ることと、ドロップされるy(縦)座標を知ることです。(マルチコラム・リストボックスで、どのセルにドロップされたかを知るには、私の例題の範疇を越えています。それをするには、x座標を割り出し、各セルの幅を知る必要があります。)

各行の高さは、REALbasicにとっては既知の事柄ですが、それは動的に変化するので入手は困難です。リストボックスにはdefaultRowHeight属性がありますが、通常は、あなたのリストボックスで使用するフォントサイズに応じて高さを調整することを意味する-1に設定されています。我々のケースでは、固定されたサイズにする必要があるので、これを特定のサイズに指定します(私は12ピクセルとしました)。プログラムがリストボックスのフォントサイズを変更する場合は、この値の変更もまた忘れずに行ってください。

二番目の情報――ドロップするy座標――は、コントロールのmouseY関数を使って入手できます。これはウィンドウ座標が返されるので、ローカル座標に変換する必要があることに注意しましょう。

私は次のようにしました。始めに、myLBClassと呼ばれるlistBoxの新しいサブクラスを作成しました。それから、次のような新しいイベントであるdroppedObjectを追加しました:

それからmyLBClassDropObjectイベントへ行き、次のコードを追加しました:

  
dim y, row as integer

// Window coordinates
y = me.mouseY

// Convert to local
y = y - me.top

row = floor(y \ me.defaultRowHeight)
if me.hasHeading then
row = row - 1
end if

// Call new event
droppedObject obj, row

ここでは、我々がどこの行へドロップしたかを割り出し、その情報を新しいイベントであるdroppedObjectに渡します。droppedObjectには、objrowの二つのパラメータがあるので、必要な情報はこれで与えられます。

次に、ウィンドウにmyLBClassのインスタンスを作成します。そのインスタンスのdroppedObjectイベントに、ある特定の行に何かドロップされたときに、我々のしたいことができるようにコードを追加します。あるポイントにドロップしたものを挿入するか、既存のテキストを新しいテキストに置き換えるかです(私の例では、どちらも示してあります)。

そのコードは次のようになります:

  
dim s as string

if obj.TextAvailable then
s = "Line " + str(row) + ": " + obj.text

// 新しいテキストで、指定した行のテキストと
// 置き換えることもできます。
if row > -1 and row < me.listCount then
me.cell(row, 0) = s
else
beep
end if

// あるいは、その行にテキストを挿入することもできます。
'me.insertRow(row, s)

end if

私の例では、テキストのドロップしか扱っていないことに注意しましょう。そして、挿入動作の方が好みであれば、置き換え部のコードをコメントアウトして、挿入部のコードのコメントを解除してください。

これをクラスとして作成すれば、どのプロジェクトのどのlistBoxにでも、このクラスを追加してlistBoxの型をmyLBClassにするだけで、簡単にこの動作を追加できます。

また、listBoxの様々な設定は、物事を混乱させることに注意しましょう。例えば、私の例ではlistBoxのヘッダー使用時の調整を試みました。しかし、ヘッダーの正確な高さを知らなければ、私の計算は少しずれた結果を与えるでしょう。完全にうまく機能させるためには、あなたは何度もテストを繰り返して、様々な状況に合わせた調整をする必要があります(つまり私の例題は、階層的なリストボックスでは機能しないでしょう)。

基本的に、myLBClassはあなたの望むことを行います。しかし、一つ重大な注意点があります:REALbasicはドラッグ中にどのようなイベントも受け付けないので、ユーザに"この行へドロップ"しようとしていることを示すための表示を行うことはできません。ユーザは目隠しでドロップすることになります。それは、あまりよいユーザ・インターフェースではありません。不幸にも、あなた自身のドラッグ&ドロップのシステム(REALbasicの機能を使用しない)を導入しない限りは、これを回避する方法がありません。そしてさらに、REALbasicはテキスト入力欄から自動的にテキストをドラッグさせる問題があり、これを回避する方法もありません。

多くのRBプログラマーがしていることは、ドロップしたときはある種の決まった動作で行うことです。例えば、ドロップした時には常にlistBoxの最終行に新たな行を作成することです。あるいは、ユーザが行を選択した場合には、現行の行のすぐ後にドロップしたものを挿入します(私のZ-Writeワープロは、テキストをセクション・リストへドラッグする時に、これを行っています)。大事な点は、それを一貫して行うことで、ユーザが次に何が起こるのか予測できることです。

二番目の質問のドラッグの強調表示については、REALbasicの抱えるもう一つの問題です。私はこの回避策を知りませんが、私の知らない回避策があるかもしれません(ご存じの方がいらっしゃれば、遠慮なくお知らせください)。

DragExampleプロジェクトのファイルを希望される場合は、こちらからダウンロードできます。


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

INDEXに戻る