CURB2 Touch! 開発記 ~WPFでタッチ対応アプリを作る

2010年12月30日(木)に従来のCURB2のスレートPC暫定対応版であるVer.2.51を公開した後、Windows7のタッチ機能(ジェスチャなど)に対応できるように、MS VisualStudio2010でWPFアプリとしてCURB2を1から作り始めた。この年末年始は、これに全てを費やしてしまったなぁ・・・
WPF(Windows Presentation Foundation)でプログラムを作るのは初めてであり、いろいろと戸惑ったので、ここではその経験から得たポイントというか、覚え書きのようなことを記しておこうと思う。
※作成したソフトのダウンロードは、このページの最下部でできます。

画面構成の仕方が特徴的

ウィンドウにパーツ(コントロール)を配置する方法に最初手間取った。Panel系のものをコンテナ(入れ物みたいなもの)として置き、その中にパーツを配置していくようだった。
そのPanel系のコンテナには標準ではGridが使われていて、他にもCanvasとかStackPanelとかDockPanelがある。そして、この種類によって、配置の仕方が違うのであった。

CURB2のメイン画面は、ウインドウを広げたり縮めたりとサイズを変更しても、形が崩れないようにしないといけない。右にあるグラフの式を置く場所は幅が変化せず、グラフを描画する場所はウインドウの残りを使い切って、ウインドウの変化と共に伸縮するようにしたい。
これに対応するためにDockPanelを使うことにした。DockPanelは配置した順番にコントロールがウインドウの四辺に貼り付くのが特徴で、メニューバー(Menu)やステータスバー(StatusBar)もウインドウの上下に確実に配置できた。

※コード1:MainWindow.xaml
  1. <DockPanel>
  2. <Menu Height="23" Name="menu1" DockPanel.Dock="Top">
  3. ・・・この間にMenuItemを書く
  4. </Menu>
  5. <StatusBar Height="23" Name="statusBar1" DockPanel.Dock="Bottom">
  6. ・・・この間にStatusBarItemを書く
  7. </StatusBar>
  8. <Border Name="border1" Width="200" DockPanel.Dock="Right">
  9. <StackPanel>
  10. この間にy=ax^2などの関数の式を設定するところを10個つくる
  11. </StackPanel>
  12. </Border>
  13. <ScrollBar Name="scrollBar1" />
  14. <ScrollBar Name="scrollBar2" />
  15. <Border Name="imageBorder" SizeChanged="imageBorder_SizeChanged">
  16. <Image Name="image1" Stretch="None" />
  17. </Border>
  18. </DockPanel>
  19.  
9行目からのBorderが、画面右のy=ax^2などの関数の式を入力する部分を作っている。
Borderの中にStackPanelを配置し、上から順に10個きれいに並べてみた。結構簡単に美しい画面構成ができる。
15~17行目がグラフを描画する場所である。最初<Image>だけを配置していたが、後の記事に出てくるようにImageの大きさがつかめないので、Borderで囲むことで解決した工夫した部分である。

Gridをうまく使ったのが「数値入力ツール」のウインドウだ。Gridで5×5マスの設定にして、ボタンを電卓のように配置した。
これも結構簡単に画面が構成でき、感動した。

今回やってみてウインドウの構成にあったPanel系のコンテナを使い分けると、手軽に美しい画面が作れることがわかった。慣れれば使いやすいものである。~ XAMLすごし!~

ステータスバーのテキストを簡単に変更できないの???

ステータスバーには、座標を表示したい。
そこで、StatusBarにItemを1つ追加し、そこのContentに座標を書けば良いだろうと思って、やってみようとしたが、どうもContentを書き換えるには、一度削除して、新しいItemをAddするという方法をとるらしい。
これは面倒だ。どうしよう・・・と考えていて思いついた。TextBlockをStatusBarに配置して、そのテキストブロックのTextを書き換えれば早い!と。
※コード2:MainWindow.xaml StatusBarのあたり
  1. <StatusBar Height="23" Name="statusBar1" DockPanel.Dock="Bottom">
  2. <StatusBarItem>
  3. <TextBlock Name="tbStatus" Text="マウスカーソルの位置:(0,0)" />
  4. </StatusBarItem>
  5. </StatusBar>
  6.  
xamlで上記のようにTextBlockを配置し、MouseMoveのイベントハンドラに次のように設定してできあがりである。
  1. private void image1_MouseMove(object sender, MouseEventArgs e)
  2. {
  3. x = e.GetPosition(this).X;
  4. y = e.GetPosition(this).Y;
  5.  
  6. tbStatus.Text = "マウスカーソルの位置:("
  7.             + x.ToString("F") +","
  8.             + y.ToString("F") +")";
  9.  

Imageコントロールに振り回されまくる・・・

余白一杯に広がっているはずなのに、幅も高さもゼロ???

コード1にはもう工夫した跡が残されているが、ここにたどり着くのに3日3晩を費やした。
コード1の15~17行目の部分。最初はBorderでくくらず、Imageのみを配置してあった。そして、そのImageコントロールはHeight,WidthともにAutoにしてあり、HorizontalAlignmentもVerticalAlignmentもStretchにしたから、勝手に画面一杯に広がるのだろうと想像していた。しかも、VisualStudioのデザイン画面では実際に広がっていた。
そこで、実行時に実際の大きさをImageコントロールのHeight,Widthのプロパティから読み取ればいいだろうと考えていたのだが、これが大失敗の始まりだった。いくらやってもこの2つのプロパティ値は0を返すのだ。
そこで実行時の実際の大きさをとるには、別のプロパティかな?と探してみると、ActualHeightとActualWidthが実際の大きさが入っていると知った。やったぞ!と言う思いで早速書き換えて実行すると・・・やっぱりダメ。やっぱりこれも0を返してきた。もうだめだ・・・どうしよう・・・と思っていた時にひらめきが。ImageコントロールをBorderで囲ってやれば、そのBorderの大きさがImageコントロールのそのものの大きさと同じだと。
それでコード1の15~17行目のようになった。無事解決。
ここでわかったのですが、Imageコントロールの大きさは、そこに作成した画像などの大きさによって決まるようです。私の持っていたイメージは、Imageコントロールは画用紙のようになっていて、そこに四角や丸や線などをかいていく感じだったのですが、そうではないのです。Imageコントロールのなかに四角をかくと、その四角の大きさがそのときのImageコントロールの大きさとなり、ActualHeightとActualWidthの値になるのですね。

描画すると、画面が乱れる???

上記のようにやっとImageがうまく扱えるようになり、線などが好きに引けるようになった。ちなみに線を引くには、DrawingContextを使用する。
DrawingContext dc = dg.Open();
こんな命令でDrawingContextを作り、そこにDrawLine命令で線がかける。
dc.DrawLine(new Pen(new SolidColorBrush(Color.Blue),1), 
                new Point(0, 0), new Point(100, 100));
 
あとはどんどん作図していって、最後に
dc.Close();
で、実際には画面上に表示されることになる。簡単じゃないの!と思ったのもつかの間、y=ax^2のグラフをかかせていたら、座標軸がなんだか勝手に下にずれていく・・・どうしたんだ!とプログラムを見直すも、間違いは見つからず、また途方に暮れることになる。そのときまたひらめいた。『Imageコントロールの大きさはそこにかいた画像などで変化するんだったんだ!』きっと、かいたグラフがImageコントロールの想定した範囲をはみ出したので、勝手に拡大だか縮小だか場所移動だかをしたので、画面に見える部分がおかしくなったんだと。

Imageコントロールを囲んでいるBorderの大きさをはみ出ないようにコーディングし直した所、無事に解決に至った。Imageコントロールは、描画にいろいろと手間がかかるものだ。

マルチタッチに対応してみよう

そんなわけで紆余曲折のお正月を送り、やっとCURB2の形ができあがった。ここまでの所は以前から公開していたCURB2のver.2.5とほぼ同じ動作である。そして、ここからが今回の目玉!タッチイベントに対応させたプログラミングだ。
WPFでは、標準でタッチイベントに対応しているので、例えば今回のグラフがかいてあるImageコントロールにタッチしたときの操作を追加する場合、ImageコントロールのプロパティにあるIsManipulationEnabledをtrueにする(チェックする)だけで、マルチタッチのジェスチャに対応できるようになる。あとはイベントに対応するプログラムを書くだけだ。
Imageコントロールが指でタッチされると、最初にManipulationStartinのイベントが発生し、続いてManipulationDeltaのイベントで、どんなタッチがされたかがわかる。そこで、VisualStudioでImageコントロールのプロパティを開き、イベントにある上記2つの項目を順にダブルクリックして、イベントハンドラを作った。そして、次のようにコーディングを行った。
  1. private void image1_ManipulationStarting(object sender,
  2. ManipulationStartingEventArgs e)
  3. {
  4. e.ManipulationContainer = this;
  5. e.Handled = true;
  6. }
  7. private void image1_ManipulationDelta(object sender,
  8. ManipulationDeltaEventArgs e)
  9. {
  10. if (e.DeltaManipulation.Scale.X > 1) {
  11. 画面を拡大させる処理
  12. }
  13. else if (e.DeltaManipulation.Scale.X < 1) {
  14. 画面を縮小させる処理
  15. }
  16. scrollBar1.Value = scrollBar1.Value
  17. + e.DeltaManipulation.Translation.X
  18. / imageBorder.ActualWidth * 100;
  19. scrollBar2.Value = scrollBar2.Value
  20. + e.DeltaManipulation.Translation.Y
  21. / imageBorder.ActualHeight * 100;
  22.  
  23. e.Handled = true;
  24. }
  25.  
e.DeltaManipulation.Scaleで指のピンチの動作を読み取って拡大率を返してくる。
ヘルプを参考にすると、Scaleが0.5だと50%に縮小、2だと100%拡大だと書いてある。まあ、0.5倍、2倍という意味だろうと思うことにした。ところが、ScaleはVector値であり、0.5だとか2だとか言う数値は返さないのだ。そこで、ScaleのプロパティXを使うことにした。いくつか試した所、Xが1を境に拡大縮小に分かれるらしい。
次に移動だが、こちらはe.DeltaManipulation.Translationで移動量を返してくる。そこで、そのプロパティであるXとYで横方向、縦方向の移動量だと決めてかかり、コーディングしたのが16~21行だ。ちなみに、CURB2では、スクロールバーを移動すると座標軸の位置が動くようになっているので、スクロールバーの位置を移動量に会わせて変化させている。

このように、ある意味簡単にマルチタッチ対応にできてしまったが、まだこの部分はヘルプやMSDNに書かれている情報も少なく、試行錯誤しなくてはならない部分である。

スレートPCで実行してみると・・・

起動が遅い

やっとできたぞ!ということで、スレートPCにソフトをコピーして、起動してみることにした。
なお、これまでは開発環境であるVistaパソコンで動作確認しながら作ってきていたのだが、タッチ部分はVistaでは確認できないので、Windows7がインストールされたスレートPC(マイクロソフト社よりご提供された品物:マイクロソフト様ありがとうございます!)を利用しないといけないことになる。
起動した所、ウインドウの枠が表示され、その後全体が表示されるまでに3秒くらい待たされた。やっぱり中間言語を利用した.NETだとしかたないのかな・・・
そう思ってWEB検索してみると、WPFプログラムは最初の起動が遅いらしい。原因はフォントキャッシュを作るからなのだそうだ。でも、自分の所だと2回目以降の起動もまだ遅い気がするが・・・

MSDNに次の記事があった。
WPF アプリケーションのパフォーマンスの最適化
レイアウトに使うパネルの種類(DockPanelとかGridとか)でも速度は変わるらしい。またリソースの使い方でもパフォーマンスは変わるらしい。WPFアプリにあったプログラミングをもっと勉強しないといけないと言うことだな。

数値入力ツールでの操作が快適でない

起動時の遅さはまあ待っていれば良いことだが、アプリ動作中のもたつきは、そのアプリの使いやすさに大きく影響する。
CURB2では、テキストボックスに数値を入力する際に、キーボードの無いスレートPCでの操作に配慮して、数値入力ツールという、電卓みたいなウインドウ(上の2枚目の画像参照)を使えるようにしてある。ところがこれが快適に動かないのだ。
数値入力ツールのウインドウが開いた直後に数値ボタンをタッチすると、反応がない。2~3秒待ってからタッチするとやっと動作するという具合である。
開発環境のVistaパソコンではマウス操作ではあるが結構快適に動いた。それを考えると、PCのスペックの問題なのか、OSの問題なのか、タッチ機能の問題なのか・・・
ちなみに、Borland C++Builderで作ったCURB2 ver.2.5 の方も、スレートPCへの暫定対応という事で、この数値入力ツールの仕組みを取り入れているが、スレートPCで快適に動作している。

タッチ操作で”ぐりぐり”のはずだけど・・・

さて、最後に期待のマルチタッチ、指ジェスチャでの入力である。これぞスレートPCの強み!かっこよさ!
まずはグラフ画面を指1本でなぞってみると・・・おお!動いた!!感動の瞬間である。ただ、動作は遅いけどね。
次は指2本でピンチの動作をしてみた。すごいぞ!拡大も縮小もうまくいっている!でも、ゆっくりだけどね。
"ぐりぐり"・・・ん~、仕方ないね。スペック不足かな。WPFのグラフィックは標準でDirectXを使用すると聞いていたので、ちょっとは期待していたけど、自分的にプログラムの描画ルーチンも見直して、スピードアップを図る必要があると思う。まだまだ、勉強しないとだ。

#ちなみに、これだと授業で使うのは厳しいねぇ・・・


ファイル・ダウンロード



番外編

良い情報を見つけました。Windows タッチ ジェスチャの概要(MSDN)
これによると、マルチタッチのピンチ(ズーム)のジェスチャをすると、マウスイベントの[CTRL]+マウスホイールのスクロールも発生するのだそうです。

そこで、Borland C++ Builderで作っていた方のCURB2をバージョンアップしてみました。
スレートPCで起動させて、ピンチの操作をすると、きちんとグラフが拡大縮小されます。

  • 2乗に比例する関数のグラフ描画実験ツール+α「CURB2」ver.2.52 CURB252.zip

動作はWPF版より少し軽いかな。ちなみに、WPF版はズームしながら座標軸の移動などもできますので、スペックの高いパソコンでしたら、CURB2 touch!(WPF版)の方が機能は上です(印刷機能がありませんけど)。

時代は変わって

2018年度、私の勤務校にWindows10のタブレット端末が導入されました。そこで、ちょうど中学3年を担当していたので、CURB2で授業をしたら、あれ、この「CURB2touch!」の快適なこと!!!
調子に乗って、バージョンアップしましたよ。(2019/03/21 ver.1.10)



コメント・アドバイス・感想など、ありましたらお気軽にどうぞ!
名前:
コメント:




最終更新:2019年03月29日 20:04