インフラジスティックス・ジャパン株式会社のBlog

インフラジスティックス・ジャパン株式会社のチームメンバーが技術トレンド、製品Tips、サポート情報からライセンス、日々の業務から感じることなど、さまざまなトピックについてお伝えするBlogです。

タッチ対応のドット絵おえかきアプリを作ろう!

image

 こんにちは!今回は WindowsForms 向けのグリッドコントロールを使って、タッチ対応のドット絵おえかきアプリの作り方をご紹介したいと思います。

 

おえかきの様子はこちらから(HTML5対応ブラウザのみ再生可能)

いかがですか?
筆者の絵心はさておき、ここでは 80列 × 80行 のグリッドを使っていますが、一見するととてもグリッドコントロールを使っているようには見えないですよね。
お固い業務システムで使われるUI部品も使い方次第では面白いものができそうですね!

ポイントとしては、

・グリッドコントロールを使って、セルでドットを表現。
・条件付きのセル外観を指定することで、セルの背景色が変化。
・パン操作のイベントをハンドリングしてセルの値を変える。
・タッチ操作が完了した時のイベントをハンドリングして一連のタッチ操作ごとに状態を記憶する。※Undo(戻るボタン)に対応するため。

です。

それぞれどのように実装しているのか、コードと共に説明していきます。
※本記事の最後に、このサンプルプロジェクトのリンクがございますので、ご興味ある方はサンプルをダンロードしてみて下さい。

 

グリッドでドット絵の表現

まず、グリッドのドット絵の表現部分です。今回のサンプルでは、タッチ対応している UltraWinGrid コントロールを使い、12px × 12px のセルを 6400個( 80列 × 80行 )用意します。

以下は Load イベントでのコードです。※デザイナ側で既にグリッドや各ボタンは配置済みです。

        const int columnsCountWidth = 80;  //列数
        const int columnsCountHeight = 80; //行数
        const int cellSize = 12; //1セルのサイズ

        //Undo用のデータ保持スタック
        private Stack<DataTable> dtHistory = new Stack<DataTable>();

        private void Form1_Load(object sender, EventArgs e)
        {

            this.Width = 1000;
            this.Height = 1000;

            //データバインド
            DataTable dt = GetData();
            this.ultraGrid1.DataSource = dt;

            dtHistory.Push((DataTable)(dt.Copy()));
            dt.AcceptChanges();

            // **********************************************
            // 条件付き書式でタッチ後のセルの外観(Appearance)を定義
            // **********************************************
            //条件合致の際の外観(Appearance)
            Infragistics.Win.Appearance appearanceForCondition = new Infragistics.Win.Appearance();
            appearanceForCondition.BackColor = Color.Black;
            appearanceForCondition.ForeColor = Color.Black;
            //条件式と外観の設定
            Infragistics.Win.ConditionValueAppearance conditionValueAppearance = new Infragistics.Win.ConditionValueAppearance(new Infragistics.Win.ICondition[] {
            ((Infragistics.Win.ICondition)(new Infragistics.Win.OperatorCondition(Infragistics.Win.ConditionOperator.Equals, "1", true)))}, new Infragistics.Win.Appearance[] {
            appearanceForCondition});


            // **********************************************
            // その他列の設定
            // **********************************************
            //キー列は非表示
            this.ultraGrid1.DisplayLayout.Bands[0].Columns["Number"].Hidden = true;
            //セルは選択のみ
            ultraGrid1.DisplayLayout.Override.CellClickAction = Infragistics.Win.UltraWinGrid.CellClickAction.CellSelect;
            //選択操作を無効にする。
            ultraGrid1.DisplayLayout.Override.SelectTypeCell = SelectType.None;
            ultraGrid1.DisplayLayout.Override.SelectTypeRow = SelectType.None;
            ultraGrid1.DisplayLayout.Override.SelectTypeCol = SelectType.None;
            ultraGrid1.DisplayLayout.Override.ActiveCellAppearance = null;
            ultraGrid1.DisplayLayout.Override.ActiveRowAppearance = null;
            //各セルの起きさの設定
            ultraGrid1.DisplayLayout.Override.MinRowHeight = 1;
            ultraGrid1.DisplayLayout.Override.DefaultColWidth = cellSize;
            ultraGrid1.DisplayLayout.Override.DefaultRowHeight = cellSize;
            //ヘッダ非表示
            ultraGrid1.DisplayLayout.Bands[0].HeaderVisible = false;
            ultraGrid1.DisplayLayout.Bands[0].ColHeadersVisible = false;
            ultraGrid1.DisplayLayout.GroupByBox.Hidden = true;

            //各列ごとの定義
            for (int index = 1; index <= columnsCountWidth + 1; index++)
            {
                //条件付き外観の適用
                this.ultraGrid1.DisplayLayout.Bands[0].Columns[index].ValueBasedAppearance = conditionValueAppearance;
                //ReadOnlyに設定
                ultraGrid1.DisplayLayout.Bands[0].Columns[index].CellActivation = Infragistics.Win.UltraWinGrid.Activation.ActivateOnly;    
            }

            //イベントハンドリング
            ultraGrid1.PanGesture += ultraGrid1_PanGesture;
            ultraGrid1.GestureCompleted += ultraGrid1_GestureCompleted;
        }

Loadイベント内で行っているおおまかな流れとしては、

・グリッドに対してデータ(ここではDataTable)をバインド
・各列に対して条件付き外観を設定
・ドット絵おえかきアプリに必要なグリッドコントロールの各種設定
・タッチ対応のイベントハンドラの追加

です。

条件付き外観の設定部分(21行目~31行目)では、セルに”1”という値が入った場合は、背景色と文字色を黒にするという条件付き外観(ConditionValueAppearance)を全ての列に対して適用しています。
ユーザがグリッド上を指でなぞった際に、なぞられたセルの値を”1”に変えることで、セルが黒く塗りつぶされるという仕組みです。

 

パン操作イベントとセルの値変更

続いて、ユーザが指でグリッドをなぞっている時に、タッチされているセルの値を変える処理を加えます。
弊社の WindowsForms コントロールでは、ズームや回転、2本指タップなど様々なタッチジェスチャをサポートしています。

オンラインヘルプ タッチジェスチャのイベント:

jp.infragistics.com

今回は、ユーザのパン操作をを取得したいので、PanGesturイベントをハンドリングします。

        // **********************************************
        // PanGestureイベント(文字の描画)
        // **********************************************
        void ultraGrid1_PanGesture(object sender, Infragistics.Win.Touch.PanGestureEventArgs e)
        {
            //タッチされているエレメントの取得
            UIElement el = ultraGrid1.DisplayLayout.UIElement.ElementFromPoint(e.Location);

            UltraGridCell cell = GetCell(el);// エレメントからセルの取得

            if (cell != null)//セルが取得できたかどうかの判定
            {

                cell.Value = "1";// 値を設定

            }
            e.Handled = true;
        }

        //エレメントからセルを取得
        private UltraGridCell GetCell(UIElement element)
        {
            if (element == null || element.Parent == null)

                return null;

            if (element.Parent is CellUIElement)

                return ((CellUIElement)element.Parent).Cell;
            else
                return GetCell(element.Parent);
        }

PanGestureイベントでは、引数からタッチされているロケーション情報が取得できるため、グリッドの Element FromPoint メソッドから指の直下にあるエレメントを取得し、そのエレメントがCellUIElementであった場合にそのセル値を”1”に変更しています。
また、本来のパン操作により範囲選択のような挙動が起きてしまうため、イベント内の最後で e.Handled に True を設定し、そのデフォルト動作をキャンセルさせています。

 

タッチ操作完了時のイベントとUndoの組み込み

このおえかきアプリでは、動画にもあるようにUndo動作(戻るボタン)を組み込んでいます。PanGesture イベントにて連続的にセルの値を変更していますが、Undoを実装するためには一連のパン操作が終わったタイミングで、グリッドの状態を記録していく必要があります。今回は、一覧のタッチ操作が終わったタイミングで呼ばれるイベント GestureCompleted でDataTableのスタックにグリッドの状態を記録していく方法をとっています。(Redoも行いたい場合は、スタックじゃなくてリストとか用意しましょう。)

まず、初めのForm_Loadイベントのグリッドのデータバインドの直後で、事前に用意したDataTableのスタックに、初期状態を追加(Push)しています。
続いて、GestureCompleted イベントでの処理は以下のとおり。

        // **********************************************
        // GestureCompletedイベント
        // **********************************************
        void ultraGrid1_GestureCompleted(object sender, Infragistics.Win.Touch.GestureCompletedEventArgs e)
        {
            // **********************************************
            // 指を話したタイミングで最新のデータ状態を記憶する。
            // **********************************************
            DataTable dt = ultraGrid1.DataSource as DataTable;
            dt.AcceptChanges();
            dtHistory.Push((DataTable)(dt.Copy()));

        }

ポイントとしては、バインドしているDataTableを直接 Push するのではなく、Copy() を入れるということです。※こうしないと常に最新のグリッドのDataTableが参照されます。

最後に、戻るボタン押下時の処理を追加します。

        // **********************************************
        // 戻るボタン押下時
        // **********************************************
        private void ultraButton2_Click(object sender, EventArgs e)
        {
            if (dtHistory.Count <= 1)
                return;

            dtHistory.Pop(); //最新データを削除
            ultraGrid1.DataSource = dtHistory.Peek().Copy(); //一つ前のデータを取得しデータソースに設定
        }

一番最後に積まれたデータを削除し、1つ前のデータをグリッドの再バインドする事で、1つ前の状態に戻しています。

その他、最初からボタンの処理を組み込んだら、

        // **********************************************
        // 最初からボタン押下時
        // **********************************************
        private void ultraButton1_Click(object sender, EventArgs e)
        {
            //初期データバインド
            DataTable dt = GetData();
            this.ultraGrid1.DataSource = dt;
            dtHistory.Clear();
            dtHistory.Push((DataTable)(dt.Copy()));
            dt.AcceptChanges();        
        }

完成です!

へー、こんなことできるんだ!と感心いただけた方もそうでもなかった方も、ご紹介したように WindowsForms でも UI 部品でタッチ対応しているものもあるという事を頭の片隅におきつつ、チャンスがあれば是非使ってみてください。

image 

 

関連記事:

WindowsFormsコントロールのタッチサポートについて(イベント編)

WindowsFormsコントロールのタッチサポートについて(デザイン編)

 

本稿のサンプル:
ダウンロード

 

弊社製品は機能制限なしのトライアル版もご用意しています。ダウンロードはこちらから。

jp.infragistics.com