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



前回、作ったアプリが思い通りに動かない、という事態が発生したところで終了しました。

この『思い通りに動かない』という現象はプログラミングにおいては(悲しい事に)頻繁に発生するイベントです。今回は、その続きとして、これらの原因と対策について考えてみましょう。


●原因を考える 〜怪しい奴から〜

さて「アンドゥ機能が働かない」ことについて、じっくり考えてみましょう。そもそも、アンドゥ機能が無事に動作するためには、いくつかの動作が正常でなければいけません。それは次の2点です。

  ・アンドゥを割り当てたボタンがキチンと動作していること
  ・アンドゥすべき画像がキャプチャされていること

裏を返せば、これらの少なくともどちらかが動作していないため、不具合が発生しているわけです。そこで、次のポイントについて、順番に調べてみることにしましょう。

  ・アンドゥボタンは働いているのか?
  ・キャプチャできているのか?

まず最初は、疑いの濃厚なアンドゥボタンを調べてみましょう。アンドゥボタンには、次の働きがありました。

  1)変数に保存されている画像を表示する
  2)止まってしまった手書き機能を再開する

実際に試してみると、アンドゥボタンをタップしても手書き機能が中断されませんから、2)が正常に処理されて、手書き機能が無事に再開されているようにみえます。したがって、このボタンは正常に動作している、と考えてもよいでしょう。

あれ? ちょっと待ってください。ボタンのイベントそのものが機能していなかったらどうなりますか? そう、ボタンのイベントが無視されていれば、DisplaySignatureも実行されませんので、手書き機能は止まりません。つまり、ボタンの処理が正常であろうとなかろうと、手書き機能は止まらないことになります。

そこで、このボタンが働いているのかどうかをチェックしましょう。いちばん簡単なのは、音を鳴らすことでしたね。そこで、念のためボタンのタップイベント中のそれぞれのコマンドの前後にBeepを入れてみましょう。

【アンドゥボタンのタップイベント】
  Beep
  Gad.DisplaySignature strVar
  Beep
  Gad.startSignatureCapture
  Beep

実行してみると、


アンドゥをタップ……

3回音が鳴ります

確かに3回鳴っています。プログラムが気まぐれに実行する行をジャンプしていなければ、どちらのコマンドも正常に実行されていると考えるしかありません。

ということで、アンドゥボタンの処理は正常と考えてよいでしょう。これで、怪しい方の候補が消えました。


●原因を考える 〜そして核心へ〜

では、次に、無事にキャプチャがされているかどうかを疑うことにしましょう。いったんアンドゥボタン側のBeepを外して、フォームのイベント側にBeepを入れましょう。

【フォームのイベント】
  If GetEventType()=nsbPenDown Then
    Beep
    strVar = Gad.EndSignatureCapture
    Beep
    Gad.startSignatureCapture
    Beep
  End if

こちらも実行してみると、


お絵描きを開始する時に…

やはり、音が3回鳴ります

確かに、ペンがスクリーンに接触した時に音が鳴っています。一見イベントとしては正しいように思えます。しかし、アンドゥボタンをタップした時も、音が鳴ることに気が付きます。


アンドゥボタンでも音が……あれ?

あれ? 先ほどボタン側のBeepは、外したと思うのですが……念のため、確認してみましょう。

【アンドゥボタンのタップイベント】
  Gad.DisplaySignature strVar
  Gad.startSignatureCapture

確かに、ボタン側のイベント内で音を鳴らすコマンドは見当りません。これは、どういうことでしょうか?

そこで、何が起こっているのか、イベントの種類と順番をチェックしてみることにします。前回紹介したラベルオブジェクトを使ったイベントチェック法を使ってみましょう。

適当なラベルを配置して、同じように、ELabという名前にします。そして、フォームのイベントを次のようにします。

【フォームのイベント】
  If GetEventType()=nsbPenDown Then
    strVar = Gad.EndSignatureCapture
    Gad.startSignatureCapture
    ELab.Label="D"+Left(ELab.Label,9)
  End if

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

これで、フォームのイベントとして画面をタップしたときに"D"、画面から離れた時に"U"が表示されます。また、アンドゥボタンには、"D""U"以外の記号、"*"を表示するようにしてみました。

【アンドゥボタンのタップイベント】
  Gad.DisplaySignature strVar
  Gad.startSignatureCapture
  ELab.Label="*"+Left(ELab.Label,9)

さて、実行です。


上の方にラベルがあります

ペンが接触するとDが、

離れるとUが追加されます

実際に、手書きをするときは、"D"→"U"と順に発生していますから、イベントは無事に発生し、キャプチャも正常にされていると考えられます。次に、アンドゥボタンをタップすると、


タップでDが追加され、

離すと*Uが追加されます

もちろんアンドゥしませんが

ボタンを押したことを表す"*"の前後に"D"と"U"が発生していますね。"D"→"*"→"U"という発生順、似たような動作を、どこかで見た覚えがありませんか? そう、前回お送りしたGadGetのタップイベントに似ていますね。

少し順番の違いはありますが、NS Basicではスクリーン上のオブジェクトをタップした場合、それとは別に、フォームのPenDownとPenUpのイベントが発生します(Ver.2.xを元に検証しています。今後のバージョンでは改良される可能性もあります)。

これで、動かなかった原因が見えてきましたね。実は、アンドゥボタンを押しているつもりが、その前に発生するフォームのPenDownイベントによって、手書きメモがキャプチャされてしまっていた訳です。

そして、アンドゥボタンのイベントコードを実行する時には、直前のPenDownのイベントでキャプチャしたメモを表示しているため、何も起きていないように見えた、ということです。

言葉で書くとよくわかりませんが、時間を追って図にしてみると、よくわかります。

実行の様子 説明 アンドゥ用変数の中身
起動直後の様子です 何も入っていません。
ペンが接触するとDが表示されます。
また、ここで画像がキャプチャされ、変数に代入されます。

直前の状態は白紙ですね
ペンが離れるとUが表示されます。
この操作では変数の内容は変化しません。

変化なしです
アンドゥボタンをタップするとDが表示されます。
ということは、フォームのPenDownイベントが発生しています。
つまり、ここで画像がキャプチャされ、変数に代入されます。

アンドゥをタップした時の状態です
次に、ペンが離れる瞬間に*が追加されます。
アンドゥボタンのタップイベントが処理される瞬間です。
ここで、DisplaySignatureによって直前の画像が表示されます。
直前の画像って…あれ?

アンドゥをタップした時の状態です
その直後、Uが表示されます。
これはフォームのPenUpイベントの処理ですが、特になにもしませんね。

アンドゥをタップした時の状態です

原因はわかりましたか?

こうやってじっくり眺めてみると、プログラムは作った通りに処理されていることに気付きます。ただ、その組み合わせ方に問題があっただけです。

まぁ、プログラムは作った通りに杓子定規に処理されることを再認識させられましたが、間違っているのが自分の組み方だったところが面白くないですね(笑)。


●さて、いよいよ完成へ

さて、気を取り直して修正にとりかかりましょう。
まず、明らかなのは、直前の状態を取得できるイベントには「フォームのPenDownイベントしかない」ということです。ですから、これを利用することに的を絞って解決の道を探ることにしましょう。

動かなかった原因は、アンドゥボタンをタップしたときもキャプチャしてしまう、という点でした。そこで、PenDownがGadgetコントロール上で発生した時だけ、キャプチャするようにすれば解決です。

難しそうでしょうか? いえ、思ったより簡単です。GetPenというコマンドを使えば、イベントが発生した時の座標を取得できますので、その座標がGadget内であればよいわけです。

【フォームのイベント】
  Dim X as Integer
  Dim Y as Integer

  If GetEventType()=nsbPenDown Then
    GetPen X,Y,nsbPenDown
    If X>Gad.Left and X<Gad.Left+Gad.Width and Y>Gad.Top and Y<Gad.Top+Gad.Height Then
      strVar = Gad.EndSignatureCapture
      Gad.startSignatureCapture
    End if
  End if

このように、Gadgetの領域内でタップした時だけキャプチャするようにします。

ただ、ここで仕様上の問題が1つあります。NS Basicでは座標関連のプロパティをプログラム中から参照することができず、コンパイル時にエラーになってしまいます。そのため、直接数値で指定する必要があります(Ver.2.xを元に検証しています。今後のバージョンでは改良される可能性もあります)。

適当に貼り付けたGadgetのサイズや位置を調べるには、プロパティウィンドウを見ればよいのですが、例えば私の場合、次のような配置になっていました。

【従来品】
  Width 145
  Height 100
  Left 8
  Top 25

これらの数字から、それぞれの値を計算して直接、条件式に書き込めばよいです。ん〜、でも、ちょっと不恰好ですね。4つも条件を入れるのは『エレガント』ではありません。

そこで、思い切ってGadgetのデザインを次のように改良しちゃうわけです。

【改良ワイドサイズ】
  Width 160
  Height 100
  Left 0
  Top 25

このように、横幅だけを画面一杯にします。こうすると、サンプルのプログラム中で、チェックすべき範囲は縦方向だけで済むようになりますから、プログラムも単純になります。

ときには、プログラムの都合にあわせて、デザインを有利な形にすることも大切です。

【フォームのイベント】
  Dim X as Integer
  Dim Y as Integer

  If GetEventType()=nsbPenDown Then
    GetPen X,Y,nsbPenDown
    If Y>25 and Y<125 Then
      strVar = Gad.EndSignatureCapture
      Gad.startSignatureCapture
    End if
  End if

さて、実行してみましょう。


お絵描きしましょう

手書きをして、アンドゥっと、


アンドゥをタップすると

無事動作しました!

おお! できましたね。

ということで、動かなかった原因をクリアして、動作するところまで到達しました。しかし、これで「完成」したわけではありません、やっと動作の検証ができるようになっただけです。


●まだまだ、これから

それでは検証をはじめてみましょう。アンドゥ機能は無事に機能することがわかりましたので、このアンドゥボタンはもう大丈夫と思うでしょう? そこで、起動した直後、すぐにアンドゥボタンをタップしてみてください。

  (リセットピンを用意しましょう)

とりあえず、リセットしなければなりませんが、実は、これはある程度予想のつくトラブルです。

【GadgetのDisplaySignatureの一般的な書式】
  Gad.DisplaySignature strVar

起動直後では、このコマンドに渡される表示すべきデータは、変数の初期値、つまり、strVar=""ですね。

しかし、ハンドブックには、null文字は不可、とありますのでエラーが出てしまったと考えられます(空文字と、Nullって実際には違うんですけどねぇ)。ということで、これを解決する方法は、比較的簡単に2つほど思いつきます。

1つは、strVar=""の時には、DisplaySignatureを実行しないという方法、もう1つは、何らかの適当な初期値を用意する方法です。

例えば、前者の方法では、アンドゥボタンのコードを、

【アンドゥボタンのイベントコード】
  If strVar<>"" Then
    Gad.DisplaySignature strVar
    Gad.startSignatureCapture
  End if

とすればよいでしょう。

一方、適当な初期値を与えるには……こちらは画像データのフォーマットを知らなければできないように思いますね? いえいえ、データを知らなくても、有効なデータがあればよいわけですから、あまり難しく考えなくても結構です。

例えば、フォームのAfter()イベントには、手書き機能を始めるために、次のコードが入っています。

【フォームのAfterイベントコード】
  Gad.StartSignatureCapture

これを、

【フォームのAfterイベントコード】
  strVar = Gad.EndSignatureCapture
  Gad.StartSignatureCapture

とするだけで、初期値として何も書かれていない状態のデータが変数strVarに代入されるわけです。

ここで問題になるのは、どちらの方法を選択するか、という点です。しかし、それほど悩む必要もないでしょう。前者の方法は、アンドゥボタンをタップするたびに条件判定を行う必要があるんですが、後者の方法は最初に1回だけ実行するだけです。そう、後者を選択するのが賢明です。

早速、これを組み込んだモノを実行してみると、先ほどの問題が解決されたことが確認できるはずです。

作りたいものが作れるのがプログラミングの楽しみですが、思い通りに動作しない場合も少なくありません。

そうなった時に、ユーザーグループの掲示板などに、救いを求めるのも1つの方法ですが、やはり「自分で色々試す」ということは、プログラミング習得のためのスキルではないかと思います。

例えば、知らないコマンドやオブジェクトを使うために、簡単なプログラムで動作を確認してみるとか、実際に作ったものの不具合がないか順に試してみるとか、色々な場面で「試す」作業が発生します。

今回のサンプルでは、完成したと思っても「起動直後にアンドゥするとエラーになる」という問題が発覚しました。もし、ネット上に喜び勇んで公開していたら、数時間後にはバグ修正のアップデート、というちょっと恥ずかしい事態になっていたでしょう。
そうならないためにも「作るために、試す」ということを心に留めておきましょう。まぁ、試すのはタダですからね(笑)。

でも、検証というのは結構面倒です。もしかしたら、プログラミングと同じくらいの時間と労力が必要な場合もあります。でも、使ってくれる人のことを考えれば、気を抜いてはいけません。プログラムとはユーザーが使うことができて、はじめて完成、と覚えておきましょう。「使うために、試す」ことも忘れずに、もちろん、自分がユーザーでもOKです。

さて、ここで終わっては面白くありません。講座に面白さだけを求めているわけではありませんが、実際のところ、この手書きメモに不満がありませんか?

私は試していて2つほど気がつきました。1つは、消去の機能にアンドゥが使えないことです。何だか、ついついタップして消えてしまいそうで不安です。もう1つは、思ったより手書きが「滑らか」に感じられない「ような気がする」ことです。皆さんは、どう感じますか?

もちろん、保存や読み込みの機能も欲しいトコロですが、とりあえずは、この「滑らか」ではない不満を解消すべく、次回も、この話題を続けてみたいと思います。



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