RealBasic University

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

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

Monotab:パート II

前回のレッスンでは、Monotabのインターフェースの設定を行いましたが、変換コードはまだ書き始めていませんでした。今回はプログラムを完成させることにしましょう。

ファイルのインポート

まず最初のタスクは、変換するためのテキストファイルを取り入れることです。われわれはすでにドロップされたファイルを受け付けるためのlistBox1を設定しました。次にそれを用いて何かしなければなりません。

listBox1のDropObjectイベントへ行き、次のコードを加えてください。

   if obj.folderItemAvailable then 
addText(obj.folderItem)
end if

これは単純にfolderItemが利用可能かを確認しています。もし利用可能ならば、それをaddTextと呼ばれるルーチンに渡します。さてそれではそのメソッドを加えることにしましょう。

新しいメソッドを作成(Editメニューの"New Method")し、addTextと命名してください。始めに、それが有効なファイルであるか確かめて、テキストファイルとしてそれを開くことにします。どちらかの操作が失敗した場合、何もせずにルーチンから出ます。ファイルが無事にテキストファイルとして開ければ、テキストを抜き出してそれをlistBox1に加えます。

次がそのコードです。

   dim t as textInputStream 
dim s as string
dim colNum, i as integer

if f = nil then
return
end if

if f.directory then
return
end if

t = f.openAsTextFile
if t = nil then
return
end if

listBox1.deleteAllRows
s = t.readLine
colNum = countFields(s, chr(9))
listBox1.ColumnCount = colNum
for i = 0 to colNum
listBox1.heading(i) = nthField(s, chr(9), i + 1)
next // i

while not t.EOF
s = t.readLine
listBox1.addRow nthField(s, chr(9), 1)
for i = 2 to colNum
listBox1.cell(listBox1.lastIndex, i - 1) = nthField(s, chr(9), i)
next // i
wend

t.close

まず始めに、listBox1の列をすべて削除することに注意してください。それはユーザがすでにlistBoxにファイルをドロップしていた場合、それを削除しておくためのものです。

次に、テキストファイルの第1行目を読み出します。その第1行目から判断して、そのファイルにはどれだけのフィールドが存在するか設定します。なぜならテキストファイルの第1行目は、他のすべての行と同じだけのフィールドが含まれているので重要なものだからです。実際には、この第1行目はそれぞれの列の名前(ヘッダー)が含まれていると想定します。明らかなことですが、商用やプロ仕様のプログラムではこのような仮定はしたくありません。しかし個人的な使用のために書いているプログラムでは、これは妥当な仮定です。

第1行目の列をカウントするためには、列はタブで区切られているとして、countFieldsコマンドを使用します。そうして第1行目からそれを抜き出すことによって、listBox1のヘッダーをセットします。

いったんヘッダーが設定されれば、残されているのはデータを読み出すことだけです。ファイル中に、読み出すものが残っている限り読み出し続けるwhile-wendループを始めます。ループを一周するたびに、テキストの1行を読み取ります。それからnthFieldコマンド(番号によって個々のフィールドのテキストを抜き出す)でその行を解析し、listBox1のセルにそれぞれのフィールドのテキストを慎重に加えていきます。

(listBox1はいくつかの列があるので、単純にはaddRowコマンドは使えないことを覚えておいてください。そのコマンドはただ列に第1番の列を加えていくだけです。他の列のデータはセルごとに加えていかなければなりません。)

すべて終了したら、ファイルを閉じてメソッドは完了です。ファイルはインポートされていて、現在はプログラムのメモリーに保存されています。

テキストの変換

テキストを変換するためには、いくつかの特別なルーチンが必要となるでしょう。一つ目はシンプルです。それはnスペース(nは整数)の文字列を返す関数です。

新しいメソッドを作成(Editメニューの"New Method")し、padSpacesと命名してください。次のようにパラメータとしてn as integerを与え、return typeをstringとしてください。

次にメソッドのコードを示します。

   dim i as integer 
dim s as string

s = ""
for i = 1 to n
s = s + " "
next // i

return s

お分かりのように、これはかなり単純な関数です。それはただsn個のスペースを加えて、結果としてnスペースの文字列を返しています。

変換するために必要な2つ目のルーチンは、もう少し複雑です。先週はいろいろと変換してみて、列がどれくらいの幅(文字数)であるかを知るためには、その列のデータ内でいちばん幅の広い項目を知る必要があることを発見しました。私が用いた例はMLS(Major League Soccer)チームのリストです。トップチームの"San Jose Earthquakes"は長い名前ですが、"New York/New Jersey Metrostars"はさらに長い名前でした。

この計算を解決するためには、フィールド内の各項目の長さを調べる必要があります。われわれはこの情報を保存する必要があるので、それを格納する場所が必要になってきます。そして各列のために保存する値が必要になるので、各列のための要素と一緒にこの値を配列内に格納しておくのが合理的でしょう。

window1に属性を加えましょう。Editメニューへ行き、"New Property"を選択します。配列をcolMax(0) as integerのように追加してください。

さてそれでは新しいメソッドを作成(Editメニューの"New Method")し、それをCalcMaxと命名してください。このルーチンは、各列の一番大きな(幅の広い)文字列を探し出して、colMax配列に格納します。

始めにルーチンが行うことは、colMaxを現在のデータファイル内のフィールド数に初期化することです。それから2つのネスト(入れ子)構造のループを始めます。外側のループ(col)は各列をステップしていきます。内側のループ(row)はテキストの各行(record)をステップしていき、nにそのフィールドの幅を設定します。それからcolMax(col)に格納されている値より大きいかどうか確認するためにnをチェックします。もしそうであれば、colMax(col)nを格納し、以前からあった値を書き換えます。

次にコードを示します。

   dim row, col, n as integer 

redim colMax(listBox1.columnCount - 1)

for col = 0 to (listBox1.columnCount - 1)
colMax(col) = 0
for row = 0 to (listBox1.listCount - 1)

n = len(listBox1.cell(row, col))
if n > colMax(col) then
colMax(col) = n
end if

next // row
next // col
 

このルーチンの実行後は、colMaxは各列の最大の長さを含むことに注意しましょう。それがルーチンで行っていることのすべてのことです。つまりcolMax配列の構築です。

さてexportTextと呼ばれる新しいメソッドを作成しましょう。これは実際の変換が行われるところになります。われわれは基本的に2つの事項が必要になります。1つ目に変換されたテキストを保存する新しいファイルを作成すること。2つ目にタブをスペースに変換することです。

1つ目は難しいことではありません。ユーザに名前をつけさせて、新しいファイルを保存してもらい、それからそのファイルを作成します(T.createTextFile関数を使用する)。すべてうまくいけば、テキストを変換するための準備が整います。

次にコードを示します。

   dim row, col as integer 
dim s, tcell as string
dim f as folderItem
dim t as textOutputStream

f = getSaveFolderItem("", "Converted")
if f = nil then
beep
return
end if

t = f.CreateTextFile
if t = nil then
beep
return
end if

calcMax

s = ""
for col = 0 to (listBox1.columnCount - 1)
s = s + listBox1.heading(col)
s = s + padSpaces(colMax(col) - len(listBox1.heading(col)) + padSlider.value)
next // col

s = s + chr(13)
for row = 0 to (listBox1.listCount - 1)

for col = 0 to (listBox1.columnCount - 1)
tcell = listBox1.cell(row, col)
s = s + tcell

if col < (listBox1.columnCount - 1) then
s = s + padSpaces(colMax(col) - len(tcell) + padSlider.value)
end if
next // col
s = s + chr(13)

next // row

t.write s
t.close

msgBox "All done!"

変換を始める前に、colMax配列を設定するために、まずはcalcMaxを呼び出します。次に、listBox1のヘッダーを調べ、出力ファイルとなるsの最初の列を構築します。padSpaces(フィールド間のスペース数)に送るスペースの量は、次の計算式を用いることに注意しましょう。

C - L - P

ここでCはそのフィールド内でいちばん大きな項目のサイズです。Lは現在のフィールドの値の長さです。そしてPpadSliderの値です。

sにヘッダーを追加した後に、改行コード(chr(13))を加えます。それからいくつかのfor-nextループを開始します。外側のループはlistBox1の各行を数え上げていくrowです。内側のループはデータの各列をステップしていくcolです。各フィールドに、列を伸ばすために適当な数のスペースを追加します。各列の後ろに、その行を終わるためにsに改行コードを追加します。

ループを終了したときに、それを保存するためにファイルにsを書き込んでから、それを閉じます。"Done"(終了)メッセージを表示するので、ユーザはファイルが保存され、すべて完了したかを知ることができます。

しかしながら、このプログラムが動作するには、われわれがしなければならないことがもう一つあります:exportButtonからexportTextを呼び出さなければなりません。exportButtonのActionイベントに次のコードを追加してください。

  exportText 

やった! これで全部おわりました。どのように動くでしょうか?私が先週配布したサンプルファイルを使ってテストしましょう。次に結果を示します。

 Date       Match                                         Time             Channel     
May 31 France vs. Senegal 7:25 a.m. ET ESPN2
June 1 Uruguay vs. Denmark 4:55 a.m. ET ESPN2
June 1 Germany vs. Saudi Arabia 7:25 a.m. ET ESPN
June 1 Rep. of Ireland vs. Cameroon (tape) 3:30 p.m. ET ABC
June 2 Argentina vs. Nigeria 1:25 a.m. ET ESPN2
June 2 Paraguay vs. South Africa 3:25 a.m. ET ESPN
June 2 Spain vs. Slovenia 7:25 a.m. ET ESPN
June 2 England vs. Sweden (tape) 3:30 p.m. ET ABC
June 3 Croatia vs. Mexico 2:25 a.m. ET ESPN2
June 3 Brazil vs. Turkey 4:55 a.m. ET ESPN2
June 3 Italy vs. Ecuador 7:25 a.m. ET ESPN2
June 4 China vs. Costa Rica 2:25 a.m. ET ESPN2
June 4 Japan vs. Belgium 4:55 a.m. ET ESPN2
June 4 Korea Republic vs. Poland 7:25 a.m. ET ESPN2
June 4 Brazil vs. Turkey (replay) 1:00 p.m. ET Classic
June 4 Italy vs. Ecuador (replay) 3:00 p.m. ET Classic
June 5 Russia vs. Tunisia 2:25 a.m. ET ESPN2
June 5 United States vs. Portugal 4:55 a.m. ET ESPN2
June 5 Germany vs. Rep. of Ireland 7:25 a.m. ET ESPN2
June 5 United States vs. Portugal (replay) 3:00 p.m. ET ESPN2
June 5 Korea Republic vs. Poland (replay) 3:00 p.m. ET Classic
June 6 Denmark vs. Senegal 2:25 a.m. ET ESPN2
June 6 Cameroon vs. Saudi Arabia 4:55 a.m. ET ESPN2
June 6 France vs. Uruguay 7:25 a.m. ET ESPN2
June 6 United States vs. Portugal (replay) 1:00 p.m. ET Classic
June 6 Germany vs. Rep. of Ireland (replay) 3:00 p.m. ET Classic
June 7 Sweden vs. Nigeria 2:25 a.m. ET ESPN2
June 7 Spain vs. Paraguay 4:55 a.m. ET ESPN2
June 7 Argentina vs. England 7:25 a.m. ET ESPN2
June 7 France vs. Uruguay (replay) 12:00 p.m. ET ESPN2
June 8 South Africa vs. Slovenia 2:25 a.m. ET ESPN2
June 8 Italy vs. Croatia 4:55 a.m. ET ESPN2
June 8 Brazil vs. China 7:25 a.m. ET ESPN
June 8 Argentina vs. England (replay) 1:00 p.m. ET ABC
June 9 Mexico vs. Ecuador 2:25 a.m. ET ESPN2
June 9 Costa Rica vs. Turkey 4:55 a.m. ET ESPN2
June 9 Japan vs. Russia 7:25 a.m. ET ESPN
June 10 Korea Republic vs. United States 2:25 a.m. ET ESPN2
June 10 Tunisia vs. Belgium 4:55 a.m. ET ESPN2
June 10 Portugal vs. Poland 7:25 a.m. ET ESPN2
June 10 Korea Republic vs. United States (replay) 2:00 p.m. ET ESPN2
June 11 Denmark vs. France 2:25 a.m. ET ESPN
June 11 Senegal vs. Uruguay 2:25 a.m. ET ESPN2
June 11 Cameroon vs. Germany 7:25 a.m. ET ESPN
June 11 Saudi Arabia vs. Rep. of Ireland 7:25 a.m. ET ESPN2
June 11 Korea Republic. vs. United States (replay) 1:00 p.m. ET Classic
June 11 Portugal vs. Poland (replay) 3:00 p.m. ET Classic
June 12 Sweden vs. Argentina 2:25 a.m. ET ESPN
June 12 Nigeria vs. England 2:25 a.m. ET ESPN2
June 12 South Africa vs. Spain 7:25 a.m. ET ESPN
June 12 Slovenia vs. Paraguay 7:25 a.m. ET ESPN2
June 12 Denmark vs. France (replay) 1:00 p.m. ET Classic
June 13 Costa Rica vs. Brazil 2:25 a.m. ET ESPN
June 13 Turkey vs. China 2:25 a.m. ET ESPN2
June 13 Mexico vs. Italy 7:25 a.m. ET ESPN
June 13 Ecuador vs. Croatia 7:25 a.m. ET ESPN2
June 13 Sweden vs. Argentina (replay) 1:00 p.m. ET Classic
June 13 Mexico vs. Italy (replay) 3:00 p.m. ET ESPN
June 13 Costa Rica vs. Brazil (replay) 7:00 p.m. ET ESPN2
June 14 Tunisia vs. Japan 2:25 a.m. ET ESPN
June 14 Belgium vs. Russia 2:25 a.m. ET ESPN2
June 14 Portugal vs. Korea Repbulic 7:25 a.m. ET ESPN2
June 14 Poland vs. United States 7:25 a.m. ET ESPN
June 14 Mexico vs. Italy (replay) 1:00 p.m. ET Classic
June 15 1st E vs. 2nd B (1) 2:25 a.m. ET ESPN2
June 15 1st A vs. 2nd F (5) 7:25 a.m. ET ESPN
June 15 Poland vs. United States (replay) 1:00 p.m. ET ABC
June 15 Round of 16 match (tape) 3:30 p.m. ET ABC
June 16 1st F vs. 2nd A (6) 2:25 a.m. ET ESPN2
June 16 1st B vs. 2nd E (2) 7:25 a.m. ET ESPN
June 16 Round of 16 match (tape) 1:30 p.m. ET ABC
June 17 1st G vs. 2nd D (3) 2:25 a.m. ET ESPN2
June 17 1st C vs. 2nd H (7) 7:25 a.m. ET ESPN2
June 18 1st H vs. 2nd C (8) 2:25 a.m. ET ESPN2
June 18 1st D vs. 2nd G (4) 7:25 a.m. ET ESPN2
June 18 Round of 16 (replay) 12:00 p.m. ET Classic
June 18 1st D vs. 2nd G (replay) 2:00 p.m. ET ESPN2
June 19 Round of 16 (replay) 1:00 p.m. ET Classic
June 19 Round of 16 (replay) 3:00 p.m. ET Classic
June 21 Winner (5) vs. Winner (7) (C) 2:25 a.m. ET ESPN2
June 21 Winner (1) vs. Winner (3) (A) 7:25 a.m. ET ESPN2
June 22 Winner (2) vs. Winner (4) (B) 2:25 a.m. ET ESPN2
June 22 Winner (6) vs. Winner (8) (D) 7:25 a.m. ET ESPN
June 22 Quarterfinal match (tape) 1:30 p.m. ET ABC
June 22 Quarterfinal match (replay) 9:30 p.m. ET ESPN2
June 25 Winner (A) vs. Winner (B) 7:25 a.m. ET ESPN2
June 25 Semifinal #1 (replay) 3:00 p.m. ET ESPN2
June 26 Winner (C) vs. Winner (D) 7:25 a.m. ET ESPN2
June 26 Semifinal #1 (replay) 1:00 p.m. ET Classic
June 26 Semifinal #2 (replay) 3:00 p.m. ET ESPN2
June 27 Semifinal #2 (replay) 1:00 p.m. ET Classic
June 29 Semifinal #1 (replay) 2:30 a.m. ET ESPN2
June 29 Semifinal #2 (replay) 4:30 a.m. ET ESPN2
June 29 Third place match (tape) 1:30 p.m. ET ABC
June 30 World Cup final 6:30 a.m. ET ABC
June 30 World Cup final (replay) 12:30 p.m. ET ABC
July 3 World Cup final (replay) 2:30 p.m. ET ESPN2

とてもよい感じですね。ここで各行の全体的な長さは考慮に入れていないので、Eメール(ほとんどのメールプログラムはテキストが65あるいは70文字を超えると自動的に改行します)には長すぎる行を生成する可能性もあることに注意してください。改良されたMonotabは、セル内で自動改行して全体の行の長さをユーザの設定した最大文字数に収めるでしょう。それは将来のRBUで行うかもしれません――現在のところは、私はそのバージョンを作ろうと試してみましたが、驚くほど複雑でまだ完成していません。

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

ボーナスプログラム:Convert Times

私がどれほどREALbasicにとりつかれているユーザか示すために、Monotabと平衡して書いてきた別のプログラムのソースをお見せしましょう。それはConvert Timesと呼ばれます。

Convert Timesは上記のワールドカップのTVスケジュールを見るため、東部標準時間から太平洋標準時間(私はカルフォルニアに住んでいます)に時間を変換するために特別に書かれました。明らかに、他の目的には役に立ちませんが、コードに目を通すことは興味あることで、さらにプログラムはいくつかの面白い課題を提示してくれました。例えば、東部から太平洋時間に変換するには3時間差し引くだけの"簡単"なものですが、東部時間で午前2:30にテレビが始まるとしたらどうしたらいいのでしょうか?

Convert Timesのプロジェクトファイルを手に入れたい場合は、ここからダウンロードすることができます。

次週

先週ニューヨークで開かれたMacworld Expoで発表されたばかりの最新バージョン、REALbasic 4.5のミニ・レビューを行います。

News

REALbasic Developerマガジン創刊!

ニューヨークでのMacworld Expoについて言いますと、私自らチラシやREALbasic Developerの創刊号を配りながら挨拶してまわりました。結果は際立ったものでした。誰もが創刊号を見て興奮して、記事のバランスの良さは潜在的な読者にも訴えかけていたように思えます。

すでにREALbasic Developerを購読申込みしている人々については、雑誌は現在配送中です(海外からの注文は米国郵政省のお役所仕事のために少し時間がかかると思います)。デジタル(PDF)購読の方は、ダウンロード方法が記載されたEメールを近いうちに受け取ることでしょう。

もし定期購読をしたい方がいらっしゃいましたら、まだ遅くはありません。遅れて購読された読者のために8月の終わりに創刊号(これは8月/9月合併号です)の第2回目の発送をする予定でいます。

Letters

今週はJoeさんから次のようなお手紙をいただきました。

こんにちは Marcさん!

素晴らしいコラムです。私がREALbasicを学習している間、それはとても役に立つことを発見しました。ところで私の知りたいことで本当に単純なことがあります。それは、どのように画像を入力すればよいのでしょうか?レファレンスに目を通しましたが、それはPicture tool(私が思うには)を使うよう言っています。しかしそれがどこにあるのか分かりません。ばかばかしく思えるかもしれませんが、ImageWellやCanvasのようなものはすべて試してみましたが、うまく機能しませんでした!

どうぞよろしくお願いします。

Joe

画像の取り扱いはREALbasicを開発した技術者にとってはもう分かりきった"明白"なことのうちのひとつです。あなたが他の言語でグラフィックを扱っていたならば、それを扱うのは実際には難しいことではありません。しかし、確かにREALbasicのヘルプはプログラム経験のない人にとっていくつかのステップが省いてあります。

基本的に、REALbasic内部で扱うことのできる画像には次の3つのタイプがあります。

ではこれらを逆の順に、すなわち使用頻度が最も低いものから最も高いものの順に見ていくことにしましょう。

RB内部でプログラムによって描画する画像。線、点、円、多角形などを描画するために、graphicsオブジェクト内で使用可能なREALbasicの描画コマンドを使用することができます。画像オブジェクトにそのような描画コマンドの結果を保存すれば、画像を手に入れることができます。

プログラムの実行中に読み込む画像ファイル。画像オブジェクトを入手する別の方法は、ファイルからそれをロードすることです。あなたのコンピュータにQuickTimeがインストールされていれば、QuickTimeのサポートするどんなグラフィック・フォーマット(かなり多種多様)も開くことができます。

次に、ファイルから画像をロードして、それをwindow1の背景として表示するプログラムを示します。

   dim f as folderItem 
dim p as picture

f = getOpenFolderItem("picture")
if f = nil then
return
end if

p = f.openAsPicture

if p <> nil then
window1.backdrop = p
end if

これを試すには、このコードをpushButtonのactionイベント内に挿入してください。

"picture"と呼ばれる特別なファイル・タイプが定義されていることに注意しましょう(Editメニューの"File Types")。それは、あなたがどんなファイルでも開くことができるようにTypeとCreatorに????と????を使用します。画像がうまく開けた(pnot nil)ことを確認した後、window1backdroppへ設定します。

RBプロジェクトにインポートした画像。画像を扱う方法でもっとも良く用いられるものは、プロジェクトに画像をインポートすることです。すなわち、REALbasicのプロジェクト・ウィンドウへ画像をドラッグすることによってこれを行います。

上図では、私は"pfs.jpg"と呼ばれる画像を、RBプロジェクト・ウィンドウへドラッグしました(pfsと表示されています)。イタリックで表示されている名前は、それがリンクされた項目であるということを示しているのに注意してください。それは実際の画像はプロジェクト・ファイルには埋め込まれていないことを意味します(だからオリジナルのアイテムを削除しないようにしてください。さもないとプロジェクトがコンパイルされないでしょう)。

このようにインポートされた画像に対しては、項目へ画像を指定することのできるREALbasicのメニュー上に、その名前が表示されます。例えば、ウィンドウやキャンバスは、あなたが指定したどんな画像も表示することのできるbackdrop属性を持っています。次のようにポップアップリストからインポートされた画像を選択して背景に指定することができます。

また次のようにプログラムによって背景を指定することもできます。

 window1.backdrop = pfs 

通常それを項目のOpenイベントに入力すれば、コードはその項目が開かれたときに実行されるでしょう。もしこれをプログラムによって行うのであれば、それを先に示した画像を取得する始めの2つの方法のうちのどちらか1つを使って入手した画像を背景に指定することができるでしょう。

画像を取得する3つの方法のどれを用いても、最後には画像オブジェクトになります。画像オブジェクトは、画像を現わすREALbasicオブジェクトの1つです。いったん画像オブジェクトを入手すれば、もし望むならば、それを表示したり、動かしたり、ピクセルごとにプログラムによって操作したりといったことをRBに命令することができます。

Joeさん、あなたの質問にうまく答えられたことを期待します。画像操作を楽しんでください!


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

INDEXに戻る