7.手書きメモを作ろう 〜試してみるのはタダだから〜  その1



Palmデバイスの特徴といえば、何を思い浮かべるでしょうか
『小さくてサクサク動く』とか『クレードルで簡単同期』とか、中には『バッテリの持ちが良い』とか『1人で何台も持っているマニア(?)も多い』などもあるでしょうか。
そんな数あるの特徴の中で、『ペン(スタイラス)で操作する』などは、多くの人が認める特徴ではないかと思います。
サッと取りだし、ペンでサラサラと操作する。中には、手書きメモソフトなどを使ってみえる方も少なくないでしょう。
そんな状況で、NS Basicに目を向けてみると、ありましたありました。手書きの情報を取り扱う仕組みが。
今回は、NS Basicを使ってちょっとした手書きメモを作ってみましょう。


●Gadgetコントロール、第2の使命

Gadgetというコントロールがあります。
通常は単なるタップイベントを受け付けるだけの部品で、実行時、画面上に表示されない地味な存在です。
しかし、Ver.2.xから「シグニーチャー・キャプチャ(Signature-Capture)」という機能が与えられ、正式にハンドブックに登場しました。

この機能を使うと、簡単に「手書き機能」を実現することが出来ます。

ただ、1つだけ問題があります。
NS Basicの現バージョン(執筆時、Ver.2.11)では、ハイレゾをサポートしていません。
そのため、この手書き機能は、SONY CLIEなどのハイレゾ環境では正しく動作しませんので、強制的にローレゾにする必要があります。予め、ローレゾの環境を用意してから試すようにして下さい。
ちなみに、私は、今関さんの『SwitchDash』を使わせて頂いています。

早速ですが、新規作成で、フォーム上にGadgetコントロールを1つ配置して下さい。
配置したら(重要なコツですが)この名前を「Sig」や「Gad」などの簡単な名前に変更しておきましょう。
そして、FormのAfter()イベントに、次のコードを入力します。

  Gad.startSignatureCapture

コードは、これだけです。


※クリックすると、大きなイメージが表示されます。

さて、実行してみましょう。
通常は見えない筈のGadgetコントロールの周囲に外枠が描かれ、その中にペンで自由にお絵描きが出来ますね。

たった1行ですが、NS Basicでもお絵描き出来ることがわかりました。面白いでしょう?
でも、この面白さは、そう長くは続きません。だって、描くだけで、消すことが出来ませんから、数分もすればGadget上は真っ黒です。
ということで、画面を消すボタンをつけてみましょう。これは、ハンドブックを参照すると、次のコマンドを使えばよいようです。

  Gad.EraseSignature

では、適当にボタンを貼って、コードを記述して、試してみましょう。


適当に落書きをして…

ボタン1発で消去完了

簡単に消去ボタンも実現できてしまいました。
これで、もうしばらく遊べそうです。

…でも、手書きメモとしては使いにくいですよね。消しゴム機能ではなくてもいいから、せめて1ステップ前に戻れれば、と思いませんか?
ということで、強引な展開ですが、最終的には、1ステップ戻る機能、一般に「アンドゥ」と呼ばれる機能を持った手書きメモを目指しましょう。


●まずは、Gadgetを詳しく知ろう

機能を実現する前に、もう少し、この機能について詳しく調べてみましょう。
ハンドブックには、今まで使った機能の他に、次の2つのコマンドがあります。

  Gad.DisplaySignature
  Gad.EndSignatureCapture

働きとしては『画像データの表示』と『キャプチャ機能の終了』です。

まず、Gad.DisplaySignatureですが、これは画面に画像データを表示するコマンドです。
具体的な書式は、次の通りです。

  Gad.DisplaySignature strVar

引数の strVarは、文字列型の変数です。
そう、このキャプチャ機能で扱うデータは、文字列型のデータとして扱います。
昔々、N88 BASICなどで、整数型の配列を用意してキャラクタ定義をしたなぁ、という記憶がある方もみえるかと思いますが、NS Basicでは、この画像データを文字列型として扱います。
しかし『文字列』といっても便宜上文字列型として扱うだけで、実際に中身を見ても、それだけでは何の事かわかりません。
もう1つ重要なのは、コマンドが、Gad.で始まっていることです。つまり、これらのコマンド類はGadgetコントロールのメソッドですから、任意の位置に画像を描けるわけではなく、Gadgetコントロールの上にしか描けません。お間違えなく。

さて、このコマンドを試そうにも画像データがありませんので、ここで、Gad.EndSignatureCaptureの登場です。
これは、キャプチャを終了して、その状態を変数に代入するコマンドです。

  strVar = Gad.EndSignatureCapture

キャプチャを『終了』とは、どういう事でしょう?
早速、新しく『END』ボタンを貼って試してみましょう。

  Dim strVar as String
  strVar = Gad.EndSignatureCapture

こんなイメージですが、ここで『END』ボタンをタップします・・・しかし、何も変化はないようです…あれ?


お絵描きをして

『END』をタップ

あれれ?

いえ、ちょっと操作を続けてみてください。手書き機能が使えなくなっていることに気付くはずです。
Gadgetコントロール上に手書きをしても、反応しません。


もう書けません

つまり、この『END』によって、手書き機能が『終了』されていることがわかりました。
そして、多分、現在の状態を変数に保存していると考えられます。

…でも、本当に『終了』されて、変数に保存されているのでしょうか?
早速、表示ボタンを用意して、DisplaySignatureを試してみたいですね。
でも、ここで、変数について、ちょっと注意が必要です。
画像をキャプチャする『END』ボタンでと、画像データを表示するボタン、この2つのボタンを用意しますから、それぞれのボタンのコードから、共通に『見える』変数を用意しなければなりません。
したがって、Starupコードで、Global変数を用意して、これを使う必要があります。
(これらの変数についての詳しい話は、拙著「Basic BASIC」の第2回を参考にして下さい。)

ということで、

  Startup()
    Global strVar as String
  End Sub

一方、『END』ボタンでは、再度、変数を用意する必要はありませんので、次の1行だけにすればOKです。

  strVar = Gad.EndSignatureCapture

そして、新たに設置する表示ボタン、これは『DISP』ボタンとしましょう。これは、

  Gad.DisplaySignature strVar

になります。

試してみましょう。
手書きをして、キャプチャボタンをタップすると、画像が変数に代入されているはずです。
一旦、画面を消去した後、表示ボタンをタップすると…


『END』でキャプチャされているはず

一旦、画像を消すために『CLEAR』をタップして…

画面を消去しておきましょう。そして、再表示を試します。
 
 
はい『DISP』ボタンは機能しました

どうですか?保存した時の画像が、再び表示されましたね。
でも、『END』ボタンをタップした時、手書き機能は『終了』していますから、再度、描き始めることができません。
ちょっと面倒なので、手書き機能を再開できるよう『START』ボタンを作っておきましょう。
これで、終了したキャプチャも、手動で再開できるようになりましたね。


再表示してから

再スタートさせます

続けて手書き可能です

ついでに、ちょっと気がついたのですが、EndSignatureCaptureで停止させなくても、DisplaySignatureを使った後でも、手書き機能は終了してしまいますね。
例えば、何かを描画して、キャプチャした後、画面を消して、再スタート、表示、の順で実行すると、手書きは出来ない状態になっています。
一方、再スタートと表示の順番を逆にしても動作しますから、表示する機能は、手書き機能が動作中であるかどうかに関係なく機能するようです。
細かなことですが、こうやって、色々試しておくことも、大切じゃないでしょうか。
(※ここで、リセットが必要な手順を踏んでしまった人。次回の原稿をお待ち下さい。)


●アンドゥを考えよう

さて、キャプチャ機能を一通り試してみたところで、次はいよいよアンドゥ機能を考えてみましょう。
アンドゥ機能を実現するには『直前の状態』を、なんらかの形で保存しておかなければなりませんが、そもそも『直前』とは具体的にいつのことでしょうか?
これは、順番に考えていけば、それほど難しくはありません。手書き機能を使っている時に、『今から書くぞ!』とペンが画面に触れる瞬間、それが更新の直前になります。
これを、簡単なタイムテーブルに表してみます。


何も書かれていない状態

何かを書き始める瞬間

その直前の状態

書き終わり

次の一筆を書き始める瞬間

その直前の状態の状態

書き終わり

次の一筆を書き始める瞬間

その直前の状態の状態

書き終わり

次の一筆を書き始める瞬間

その直前の状態の状態

書き終わり

次の一筆を書き始める瞬間

その直前の状態の状態

書いている途中

書き終わり

ちょっとくどい図ですが、何かを一筆書き終わった時点で、1つ前の状態が『最新の直前の状態』になっていますね。
したがって、この直前の状態を覚えておいて、アンドゥボタンをタップしたときに表示するだけで、直前の状態の戻るアンドゥ機能が実装できることがわかります。


●具体的な方法を考えよう

さて、作ろうとする機能と、その考え方がおおよそ決まりました。残るは『どうやって?』という点になりますが、これが重要でしょう。
NS Basicは『イベントドリブン型』ですから、何らかのイベントが発生しないと、どうすることもできません。
そういうわけで、まずは、当事者のGadgetコントロールのイベントを調べてみましょう。
Gadgetコントロールは、そもそもタップのイベントを扱うコントロールですから、当然、タップをしたときにイベントを発生させます。
でも、ここで問題になるのは、タップとイベントの微妙なタイミングです。
つまり、タップのイベントが発生するのは、ペンがコントロールと接触した瞬間か、離れた瞬間か、または、それ以外か、そんな微妙な点が問題になります。
もし、ペンとコントロールが接触した瞬間なら、ラッキーですね。その瞬間なら、まだ、手書きの内容は更新されていませんし、今から更新される直前の、ちょうど求めていた『直前』がここにあることになります。
とにかく、調べてみましょう。
Gadgetコントロールのイベントに、音でもつけてみましょう。
簡単なのは、

  Beep

ですね。
これで、イベントが発生した瞬間に、音がなります。耳を澄ませて試してみましょう。


書き始め

書いている途中

書き終わり あっ!!

どうですか?
残念ながら、ペンが離れた瞬間にイベントが発生していますね。
(※音が鳴らないのは、サウンドの設定ですよ。)
ということで、Gadgetコントロールからは、『直前』のタイミングを得ることが出来ないようです。
こればかりは仕方ありませんので、次の方法を考えることにしましょう。

ペンでタップ、といえば画面へのタップイベントがありました。
うれしいことに、ペンが画面に接触した『PenDown』と、ペンが画面から離れた『PenUp』という2つのイベントを識別できます。
早速試してみましょう。フォームのイベントに次のコードを記述します。

  If GetEventType()=nsbPenDown Then
    Beep
  End if

  If GetEventType()=nsbPenUp Then
    Beep
  End if

これで、どちらのイベントが発生しているかBeep音で…って、わかりませんよね。
どちらのBeepがどちらのイベントに対応して鳴っているのか区別がつきませんから、この方法はNGです。
同じ音を扱うコマンドで、Sound、というコマンドがあります。これは、周波数を指定しますから、音の高低で識別ができそうです。
…でも、それほどシビアな耳を持っているわけではありませんし、聞き逃す可能性もありますから、できれば目で確認したいトコロです。

そういうわけで、イベントを確認するためだけの仕組みを考えてみました。
画面にラベルを1つ貼り付けます。名前をEvent Labelの略で「ELab」とでもしておきましょうか。
初期値は、"----------"と、ハイフンを10個入れておきます。
次に、フォームのイベントを次のようにしましょう。。

  If GetEventType()=nsbPenDown Then
    ELab.Label="D"+Left(ELab.Label,9)
  End if

  If GetEventType()=nsbPenUp Then
    ELab.Label="U"+Left(ELab.Label,9)
  End if

こうすることで、Downイベント時は、ラベルに『D』が、Upイベントの時は『U』の文字が追加されます。
仕組みは簡単、今、ラベルに表示されている10文字のうち、左から9文字、つまり、先頭から9文字取り出して、新しい1文字を先頭に加えています。
こうすることで、新しいイベントが発生するたびに、そのイベントを認識する文字『D』や『U』の文字が追加され、見かけ上は、文字列が横スクロールして見えるわけです。
また、10文字分ありますから、直近の10イベントまで発生の状況を把握することができます。
ついでですから、Gadgetのイベントにも入れておきましょう。

  ELab.Label="G"+Left(ELab.Label,9)

Gadgetですから『G』です。安直ですね(笑)
早速、試してみましょう。


イベントの状態を目で見る仕組み。
画面上部のラベルに注目

ペンが触れると『D』の文字が表示されました

移動中はイベントは発生しないようです
 
 
ペンが離れると『U』『G』の順に発生しました

微妙なタイミングですが、ペンが画面に触れた瞬間に『D』の文字が追加されるのがわかります。これぞ、求めていた『直前』のタイミングです。
また、Gadgetに手書きをする場合、PenDown→PenUp→Gadgetの順にイベントが発生していることもわかります。優先順位的に、Gadgetは最後なんですね。
(※PalmOSやNS Basicのランタイムのバージョンによって、優先順位が異なる場合があります。)
ちなみに、Gadget上にペンを置き、ペンを移動させて、Gadgetの外でペンを離すと、手書きの線が描かれているのに関わらず、Gadgetイベントが発生していないこともわかります。ちょっとした好奇心ですが、色々と試してみることも大切でしょう。


●さぁ、プログラミング!

さてさて、必要なタイミングが発見できましたので、プログラミングに移りましょう。

まず、最初に考えるのは、アプリケーションとして必要な機能でしょう。
そもそも、今まで、色々と試してきた過程上、画面上にたくさんボタンが並んでいますが、手書きメモとして考えれば、不要なボタンもいくつかあると思われます。
例えば、手書き機能の『START』ボタンなどは、存在自体がナンセンスで、手書きメモには『常に手書きができる状態』であることが当たり前ですね。
もちろん、それを満たすためには、『手書き状態を終わらせないように』することがポイントですが、今まで試した経験から、EndSignatureCaptureと、DisplaySignatureを実行すると、手書き機能が終わってしまうことを、私達は知っています。
そこで、常にそれらのコードの後には、StartSignatureCaptureを入れて置けばよいことがわかります。
やっぱり、色々試しておいて正解でしょう?
また、実行開始直後も、手書き機能が始まっていなければなりませんから、FormのAfter()にも、同じモノが必要になります。
一方、『常に手書き状態』ですから、『END』ボタンで停止する必要もありません。
不要になった終了ボタンは?と言えば、そう、この『END』ボタンが「直前の状態を保存する機能」ですから、先ほど発見した、直前のイベント、つまり、FormのPenDownイベントに割り当てれば、よいのです。
したがって、FormのEvents()のコードは、

  If GetEventType()=nsbPenDown Then
    strVar = Gad.EndSignatureCapture
    Gad.startSignatureCapture
  End if

とすればよいですね。 注意するのは、EndSignatureCaptureによって手書き機能が終わっていますので、startSignatureCaptureで再スタートさせることを忘れないようにしましょう。

また『DISP』ボタンは、キャプチャした直前の状態を表示する機能でしたから、これがそのままアンドゥ、というわけです。
なので、ボタンの表記を『UNDO』にするだけですが、やはり、DisplaySignatureによって手書き機能が終わってしまうので、再スタートさせておく必要があります。

  Gad.DisplaySignature strVar
  Gad.startSignatureCapture

画面の消去はあった方がよいですから、そのままで結構でしょうし、また、GadGetのイベントは不要ですから削ってしまいましょう。
そして、イベント状態を教えてくれたELabも、もう不要ですから、消しちゃいましょう。

さて、再編成しましたが、まとめておくと、次のようになります。

テストアプリ 本番アプリ
CLEARそのまま
END ボタンは不要。
EndSignatureCaptureは『直前』へ移動。
具体的には、FormのPenDownイベント。
DISP 再表示ということで『UNDO』に改名。
DisplaySignatureなので、手書き機能が止まってしまう点に注意。
StartSignatureCaptureで、再スタートが必要。
START不要。
常に『手書きできる』状態にしておくため。

さて、これで、アンドゥ機能のついた手書きメモの完成です。
早速実行してみましょう。


スッキリした画面構成

適当に手書きをしましょう

アンドゥで1つ前の状態に

…ダメじゃん!?

あれれ?ダメじゃん!?
アンドゥ後でも、手書きが続けられる点は成功ですが、肝心のアンドゥが機能しませんね…
さて、どこが悪いのでしょうか?

と内容が緊迫してきたところで、今回はここまでにしておきましょう。
次回は、この問題を解決してみたいと思いますのが、皆さんも、色々試して、解決策を考えておいて下さい。



copyright(C)2002 Hacker Dude-san all rights reserved.