ラベル イベント の投稿を表示しています。 すべての投稿を表示
ラベル イベント の投稿を表示しています。 すべての投稿を表示

2018年3月8日木曜日

マウスの座標を取得し続ける(その2)

前回の続きです。マウスの座標を取得し続ける方法の学習です。

前回、サブスレッドを使ってマウスの座標を取得し続ける方法を学びました。しかし、やりたいことを整理すると、


  • マウスカーソルがウインドウの上にあるときだけ座標を取得できれば良い
  • 座標を取得するのは、どこから輪切りにするかを決めるときだけ


ということになって、ひたすら律儀に座標を取得し続ける必要はなさそうですし、座標の取得をしている最中に、他の込み入った処理をしなければならないこともなさそうです。

それで色々と調べ回ったところ、マウスに関するイベントを利用してあげれば、それほど面倒な処理をしなくてもやりたいことが出来そう、ということが分かりました。

イベントとは、マイクロコンピューターの世界で言う割り込み処理です。私は、ハードウエアをいじる仕事をしていた経験から、割り込み処理と言った方がピンときます。

マウスに関係するイベントは全部で6種類あります。ここで言うマウスに関係するイベントとは、クリックやダブルクリックではなく、マウスがコントロールの上に来たときや、マウスがコントロールの上を動いたときに発生するイベントのことです。こちらに詳細説明がありますが、機械翻訳なのでわかりにくいです。

この中で、MouseHoverイベントと、MouseMoveイベントに着目しました。MouseHoverイベントはマウスカーソルがコントロールの上にあるときに発生するイベント、MouseMoveはマウスカーソルがコントロールの上を移動すると発生するイベントです。

Mainメソッドが書かれたファイルは前回と同じなので割愛します。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace sample27
{
    public partial class Form1 : Form
    {
        public static int mouse_x = 0, mouse_y = 0; //マウスポインタの座標置き場 

        public Form1()
        {
            InitializeComponent();

            this.MaximumSize = this.Size; //ウィンドウサイズを変更できない用にする処理
            this.MinimumSize = this.Size; //ロードされたときのウィンドウサイズを取得して、Min/Maxサイズ両方に設定
        }

        // マウスが動いたときに発生するイベント
        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            getMousePosition();
        }

        // マウスがウインドウの中に入ったときに発生するイベント
        private void Form1_MouseHover(object sender, EventArgs e)
        {
            getMousePosition();
        }

        // Formが非選択になったときに発生
        private void Form1_Leave(object sender, EventArgs e)
        {
            if(!this.Focused)
            {
                label1.Text = "Lost Focus";
            }
        }

        // Formが無効化されたときに発生
        private void Form1_Deactivate(object sender, EventArgs e)
        {
            label1.Text = "Deactivated";
            mouse_x = mouse_y = (int)0xffff;
            this.Text = "x=" + mouse_x + ":y=" + mouse_y;
        }

        private void pictureBox1_MouseHover(object sender, EventArgs e)
        {
            getMousePosition();
        }

        private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
        {
            getMousePosition();
        }

        // マウスの座標を取得、ウィンドウ内の座標(クライアント座標)に変換して出力
        private void getMousePosition( )
        {
            if (this.Focused)
            {
                System.Drawing.Point cp = this.PointToClient(Cursor.Position);

                mouse_x = cp.X;
                mouse_y = cp.Y;
                
                label1.Text = "Focused";
                this.Text = "x=" + mouse_x + ":y=" + mouse_y;
            }
            else
            {
                mouse_x = mouse_y = (int)0xffff;
                label1.Text = "Not Focused";
                this.Text = "x=" + mouse_x + ":y=" + mouse_y;
            }

        }
    }
}
これがうまく動いた初版プログラムです。

上記リストでは、2種類のMouseHoverイベント、MouseMoveイベント(Form1とpictureBox1)を捕まえて、getMousePosition()メソッドでマウスカーソルのシステム座標をウインドウ内の座標に変換して求めています。

Formの上にPictureBoxが乗っているようなウインドウの場合は、PictureBoxが置いてあるエリアではFormに関するマウスイベントは発生しません。したがって、このようにFormとPictureBoxの二つのマウスイベントのハンドラーを用意しなければなりません。右の図において、Form1の中の破線の四角がPictureBoxです。

なお、マウスイベントの処理をする以外に、フォームがアクティブになったときとフォームが選択されなくなったときに発生するイベントを使って、フォームが選択されているかどうかをフォームに表示させる機能も付けてあります。

プログラムを起動してウインドウを選択し、マウスをウインドウに乗せると、マウスカーソルの座標がウインドウの上に表示されます。

ウインドウを非選択にすると、下記のようにNot Focusedと表示され、座標値が65535(0xffff)、(0xffff)になります。

今回の目標仕様では、このマウスカーソルの座標取得方法で十分と判断し、この方法を採用することにしました。

プログラムを動かしてみたところを動画にしてみました。

なお、その後の検討で、MouseHoverイベントは、コントロール上にマウスがある一定時間止まっていないと発生しないイベントということがわかり、使わなくてもイベントを取りこぼすことはないだろうと判断しました。

したがって、最終的には上記プログラムからMouseHoverイベントの処理に関するコードを抜き取ることにしました。

今回はここまでにします。


2018年3月7日水曜日

C#でWindows Formのウインドウに点を描画する

三次元データをXY平面に投影した図を描く、という目的を満足する為に必要な要素技術、その中で一番基本的なものは、ウインドウの任意の場所に点を描くことです。

点を描く方法をインターネットで調べると、結構たくさん出て来ます。

まずはこちら、C#ビギナー様がヒットしたので、このサイト様で勉強させていただきました。使用したリストを以下に示します。このコードは、このまま打ち込んでも動きません。下記の手順に従って、フォームを生成し、イベントハンドラーの登録を行った後、イベントハンドラーの中身として記述します。手順を以下に書きます。

Visual StudioでWindows Formアプリケーションを選択し、Formを一つ用意します。
図のようなフォームが現れます。
FormのプロパティからSizeを適切な大きさに設定します。この例では300x300ピクセルにしています。
フォームの大きさが設定値通りに変わります。
BackColorをBlackに設定します。
プロパティ設定ウインドウのイベントボタンを押します。イベントボタンは、稲妻のような形をしています。
Paintイベントを探して、ダブルクリックします。
 Paintイベントのイベントハンドラー
private void From1_Paint( object sender, PaintEventArgs e)
のスケルトンが自動生成されます。

コード本体はこちらです。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace sample22
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = this.CreateGraphics();
            Bitmap p = new Bitmap(1, 1);
            p.SetPixel(0, 0, Color.White);
            g.DrawImageUnscaled(p, 100, 100);
        }
    }
}

コードの中身を見ると、Paintイベントが発生したら、

  1. Form1に対してCreateGraphicsメソッドでGraphicsオブジェクトgを生成
  2. 大きさ1x1ピクセルのBitmapオブジェクトpを生成
  3. オブジェクトpに対して、SetPixelメソッドを使って、座標(0,0)に白い点を打つ
  4. オブジェクトpをGraphicsオブジェクトgの座標(100,100)に描画

という処理を行っています。オブジェクトpは、大きさ1x1ピクセルのBitmapオブジェクトなので、座標(0,0)に白い点を打つということは、オブジェクトpは大きさ1x1の白い点、ということになりますね。

このプログラムはMainメソッドがありませんが、F5キーを押してデバッグ動作をさせると動いてくれます。実行結果はは以下のようになります。


点をたくさん格子状に打つようにしたプログラムが下記です。イベントハンドラの中身を差し替えるだけで、実現出来ます。

        private void Form1_Paint(object sender, PaintEventArgs e)
        {
            int x1 = 20;
            int y1 = 20;
            int x = this.Width;
            int y = this.Height;
            Graphics g = this.CreateGraphics();
            Bitmap p = new Bitmap(1, 1);
            p.SetPixel(0, 0, Color.White);
            for( int i = x1; i< x; i += x1 )
            {
                for( int j=y1; j < y; j+= y1 )
                {
                    for( int k=1; k <1000; ++k) { }
                    g.DrawImageUnscaled(p, i, j);
                }
            }
        }

実行結果が下記です。


これらのプログラムはいずれも、Graphicsオブジェクトに、描画がなされた小さなBitmapオブジェクトを、タイルのように貼り付けて描画する、という考え方で書かれています。言ってみれば2段階の動作で描画している構造です。

もう少し直接的な描画動作ができれば、と考えます。それは、次回以降で研究したいと思います。

というわけで、 今回はこれで終わりにします。

2018年3月5日月曜日

続グラフィック・描画とPaintイベントの関係について

前回のエントリーで、Graphicメソッドを使った描画処理は、Paintイベントのイベントハンドラーの中で実施するのが、一般的な方法、ということを書きました。

インターネットで色々なコードの記述例を見ると、ほとんどの場合そのような記述になっています。

なぜそうなのか、ということを少し調べてみました。

前回のエントリーで絵が出たプログラムを書き換えて、MyFormのコンストラクタで描画処理を行うようにしてみたリストが下記リストです。Graphicsオブジェクトは、MyFormオブジェクトのCreateGraphicsメソッドを使って取得しています。

注意)
このリストは、MyFormクラスを呼び出すMainメソッドを割愛しています。実際に動かす場合にはApplication.Run( new MyForm() );が入ったMainメソッドを別ファイルで追加する必要があります。

/*
 * ようやくグラフィックの描画に入る
 */

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MyFrmApp
{
    public class MyForm : Form
    {
        public MyForm()
          {
            this.Width = 300;
            this.Height = 200;
            Graphics g = this.CreateGraphics();
            Pen p = new Pen(Color.Red);
            Brush b = new SolidBrush(Color.Blue);
            g.FillRectangle(b, 50, 50, 50, 50);
            g.DrawEllipse(p, 75, 75, 50, 50);
            g.Dispose();
          }
    }
}

このプログラムを実行すると、下記のようなウインドウが現れます。


どうでしょう?中身がありません。これは、おそらくウインドウが表示された後にPaintイベントが発生し、そのイベントによってウインドウの中が消去されてしまっていると考えられます

ここで、MyFormのコンストラクタに1行コードを追加します。

        public MyForm()
        {
            this.Width = 300;
            this.Height = 200;
            this.Show();  // 追加
            Graphics g = this.CreateGraphics();
            Pen p = new Pen(Color.Red);
            Brush b = new SolidBrush(Color.Blue);
            g.FillRectangle(b, 50, 50, 50, 50);
            g.DrawEllipse(p, 75, 75, 50, 50);
            g.Dispose();
        }

追加したthis.Show()メソッドは、自分自身を明示的に表示するメソッドです。すると、


図形が現れます。ところが、このウインドウを一度最小化(ウインドウの上にあるーマークを押す)してから再び表示させると、ウインドウの中の図形は消えてしまいます。

最小化してから再表示させると、Paintイベントが発生して、ウインドウの中を消してしまうからです。

これらの結果をまとめると、

  • 描画処理のコードが実行された後、ウインドウが表示される前にPaintイベントが発生してウインドウ内の図形が消えてしまう。
  • ウインドウを明示的に表示させるメソッドを実行した後に描画処理をすると、ウインドウ内の図形は消えないことから、上記現象は、ウインドウが表示される前に描画処理がなされ、ウインドウが表示される瞬間にPaintイベントが発生している(らしい)
  • ウインドウの最小化、最大化を実施すると、ウインドウ内の図形は消えてしまう。

このことから考えると、Graphicsオブジェクトを使ってウインドウに何かを描く処理をする場合は、Paintイベントが発生するたびにプログラムから再描画するような構造にしないといけないことになります。

そして、PaintEventArgsから取得したGraphicsオブジェクトを使って描画を行うよう描画処理を書くと、それはそのままPaintイベント発生時に再描画するプログラムになるのです。

これは、OSの仕組みと.NET Frameworkのライブラリの仕組みから、こうやって書くべき、というところでしょうね。特に、Paintイベントは自分以外の、システム全体の動作の都合で発生することもあるようですから、これ以外に方法はない、というところ。

Visual Basic用の記事ではありますが、このページのPaintイベントの項に詳しい説明があります。

今回はここまでにします。

参考文献:
Visual Basic 中学校 初級講座 第2回 絵を描く

2018年3月1日木曜日

ListBoxとComboBoxを使ってみる

コントロールを使って見る記事、もう少し続きます。

今回はListBoxとComboBoxです。

コントロールはユーザーインタフェースとして、ソフトウエアを使う人がソフトウエアに対して情報を入力したり、ソフトウエアから情報を得る為の手段です。

つまり、プログラム全体からすると、入口と出口を司る機能でしかないので、あまり深入りする必要はないと考えています。しかし、使い方を知らないと使えないと言うのも又事実なので、淡々と、「こういう物だ」という感覚を持って、慣れていく程度の捉え方で良いと思います。

ListBox


リストボックスは、リストを使って、予め用意された選択肢の中から選択するGUIです。非常に良く使われています。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MyFrmApp
{
    public class MyForm : Form
    {
        private Label label;  // Labelクラス型の変数を定義
        ListBox list;              // ListBoxクラス型の変数を定義

        public MyForm()
        {
            this.Width = 300;  // MyFormの幅を300ピクセルに設定
            this.Height = 200;  // MyFormの高さを200ピクセルに設定
            setupControls();    // MyFormに配置されるコントロールの設定
        }

        public void setupControls()
        {
            label = new Label();  // Labelクラスのインスタンスを生成し、labelに格納
            label.Text = "type text:";
            label.Font = new Font("Geneva", 12, FontStyle.Regular);
            label.Height = 30;
            label.Width = 300;
            this.Controls.Add(label);

            list = new ListBox();  // ListBoxクラスのインスタンスを生成し、listに格納
            list.Width = 100;   // listの幅を設定
            list.Height = 100;  // listの高さを設定
            list.Left = 50;        // listを設置するMyForm内の座標(左上)
            list.Top = 50;         // 同上
            list.SelectionMode = SelectionMode.MultiExtended;  // ListBoxの動作設定(複数選択可)
            list.Items.Add("Hello");         // 選択肢を追加
            list.Items.Add("Welcome");  // 選択肢を追加
            list.Items.Add("Bye");           // 選択肢を追加
            list.SelectedValueChanged += list_changed;  // イベントハンドラの登録
            this.Controls.Add(list);
        }

        private void list_changed(object sender, System.EventArgs e)
        {
            string res = "selectel: ";

            // foreachで選択されている項目を全部拾い上げる
            foreach (string obj in list.SelectedItems)
            {
                res += obj + " ";
            }
            label.Text = res;  // labelに連結した文字列を設定
        }
    }
}

起動すると、下のようなリストボックスが現れます。
リストの中から何かを選択すると、リストボックスの状態が変化した事によるイベントが発生し、下のように選択されている項目がMyForm上のラベル(変数名label)に設定されます。
複数選択した場合は下のようになります。

さらに、全部選択した場合、下のように全ての項目が抽出されます。


ComboBox


リストボックスと同様によく使われるGUIの仕組みですが、ドロップダウンリストまたはリストボックスを1行のテキストボックスと組み合わせたもので、使う人は値を直接入力することもできるし、ドロップダウンリストに定義された選択肢から選ぶこともできる作りになっています。

リスト1 MyForm.cs

Mainメソッドを記述したmain.csは前回のものがそのまま使えるので、割愛します。

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MyFrmApp
{
    public class MyForm : Form
    {
        private Label label;  // Labelクラス型の変数を定義
        ComboBox combo;  // ComboBox型の変数を定義

        public MyForm()    // コンストラクタ
        {
            this.Width = 300;   // MyFormの幅を300ピクセルに設定
            this.Height = 200;  // MyFormの高さを200ピクセルに設定
            setupControls();     // MyFormに配置するコントロールの設定
        }

        public void setupControls()
        {
            label = new Label();    // Labelのインスタンス生成
            label.Text = "type text:";
            label.Font = new Font("Geneva", 12, FontStyle.Regular);
            label.Height = 30;
            label.Width = 300;
            this.Controls.Add(label);  // labelをMyFormに設置

            combo = new ComboBox();          // ComboBoxのインスタンスを生成
            combo.Items.Add("Windows");   // comboのメニュー項目追加
            combo.Items.Add("Mac OS X");  // comboのメニュー項目追加
            combo.Items.Add("Linux");         // comboのメニュー項目追加
            combo.Width = 100;
            combo.Height = 25;
            combo.Left = 50;
            combo.Top = 50;
           // comboのイベントハンドラ設定(値が変わったとき)
            combo.SelectedValueChanged += combo_changed;  
           // comboのイベントハンドラ設定(テキストが変わったとき)
           combo.TextChanged += combo_changed;                   
            this.Controls.Add(combo);  // comboをMyFormに設置
        }

        private void combo_changed(object sender, System.EventArgs e)  // イベント処理本体
        {
            int n = combo.SelectedIndex;  // 何番目の要素が選ばれたかを保持
            string str = combo.Text;          // ComboBoxの持つテキストを保持
           // イベントが発生したとき、何が選ばれているかを
           // MyFormのlabelに設定、表示する
            label.Text = "selected: " + n + "(" + str + ")";  
        }
    }
}

起動すると、このようなウインドウが表示されます。この状態では何も選択されていません。
プルダウンメニューを開くと、このような内容です。

何かを選択すると、選択したアイテムのインデックス番号(プログラムで定義した選択肢の0から始まる順番)と、選択したアイテムがlabelに表示されます。下は、一番最初に追加したWindowsです。
次は2番目に追加したMax OS Xです。

次が3番目に追加したLinuxです。

 予め用意した選択肢ではないものを入力すると、インデックス番号が-1となって、入力した文字列が表示されます。

プログラムを見て頂くとおわかり頂けると思いますが、こういうコントロールを扱うプログラムは、オブジェクトのプロパティをいじったり、オブジェクトが持っているメソッドを呼び出したり、という操作がほとんどを占めます。

コントロールの使い方をマニュアルで調べれば、やりたいことはほとんど出来てしまうと思います。

それにしても少ないコード量でやりたいことができるのがC#と.NET Frameworkの良いところですね。Mac環境でのGUIはまだ研究していませんが、今後やっていきたいと思っています。

今回はこれで終わりです。

2018年2月25日日曜日

Windows Formアプリでテキスト入力

前回の初めてのWindows Formでは、ウインドウの中に単純なテキストの出力をしましたが、今回はテキストの入力です。その中で、「イベント」というものを取り扱います。

この段階でも、まだ普通のテキストエディタでソースを入力して、コマンドラインからコンパイル、そして起動する、ってことで対応できます。もちろんVisual StudioなどのIDEでプロジェクトを作ることも可能です。

下に学習に使用したプログラムリストを示します。今回はファイルが二つに分かれています。main.csはプログラム本体のMyFrmクラスのインスタンスを作って呼び出すだけのmainメソッドが入っており、myfrm.csにはプログラム本体Formクラスの派生クラスの定義が入っています。

ファイルが分割されていても、

csc main.cs myfrm.cs


のようにコンパイラに全ファイルを指定すると、ちゃんとmainメソッドから動いてくれます。

ちなみに、Visula Studioをインストールすると、C#のコンパイラであるCSCがインストールされますが、Windows Vista以降のWindowsでは、.NET Frameworkが標準で入っていて、その中にCSCが入っているそうです。こちらに、詳しい説明がありますので、ご興味のある方はお読みください。

さて、下記リストですが、リスト1のmain.csは、mainメソッドの定義だけです。最初にも書いたように、MyFormクラスのインスタンスを生成して呼び出す、という処理をしています。

リスト1 main.cs

using System;
using System.Windows.Forms;

namespace MyFrmApp
{
    class Program
    {

        [STAThread]
        private static void Main( string[] args )
        {
            Application.Run(new MyForm());
        }
    }
}

このmyfrm.csがプログラムの本体になります。

リスト2 myfrm.cs

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MyFrmApp
{
    public class MyForm : Form
    {
        private Label label;
        private TextBox box;
        private Button btn;

        public MyForm()
        {
            this.Width = 300;
            this.Height = 200;
            setupControls();
        }

        public void setupControls()
        {
            label = new Label();
            label.Text = "type text:";
            label.Font = new Font("Geneva", 12, FontStyle.Regular);
            label.Height = 30;
            label.Width = 300;
            this.Controls.Add(label);
            box = new TextBox();
            box.Width = 225;
            box.Top = 50;
            box.Left = 25;
            this.Controls.Add(box);
            btn = new Button();
            btn.Text = "click";
            btn.Height = 30;
            btn.Width = 100;
            btn.Top = 100;
            btn.Left = 100;
            btn.Click += btn_Click;
            this.Controls.Add(btn);
        }

        private void btn_Click(object sender, System.EventArgs e)
        {
            string str = box.Text;
            label.Text = "you write '" + str + "'.";
        }
    }
}

起動すると、まず以下ようなウインドウが出ます。


そして、ウインドウ内のテキストボックスに文字列を書いてclickボタンを押すと、以下のようになります。

type text:がyou write "入力文字列"に書き換わっていますね。

プログラムの内容は以下の通りです。

まず、Formクラスの派生クラスとしてMyFormを定義し、MyFormに貼り付けるLabel、TextBox、Buttonの部品(Control)のための変数を宣言します。これは、MyFormクラスの外からは見えなくて良いので、private宣言をしているんだと思います。

次に出てくるpublic MyFormはMyFormクラスのコンストラクタです。この中で、まず自分自身の幅と高さをthis.Width、this.Heightに数値を設定することで設定してします。

このWidthとHeightは、Formクラスにあるプロパティで、ウインドウの幅と高さを設定する為のものですが、MyFormはFormの派生クラスなので、当然MyFormにも存在するプロパティです。

WidthとHeightを設定した後、setupControls()メソッドを呼び出しています。

setupControls()メソッドの中では、Label、TextBox、Buttonのインスタンスをそれぞれ生成して、冒頭で定義したlabel、box、btn変数にインスタンスを代入しています。

そして、それぞれの幅、高さ、表示されるテキスト、labelに関してはfontの設定も行っています。

さらに、btnで示されるButtonオブジェクトをクリックしたときに発生するイベントを利用して、boxオブジェクトに入力されているテキストをlabelオブジェクトに代入してウインドウに表示させる処理を行うよう、処理の内容をbtn_Click()メソッドに定義し、btnオブジェクトがクリックされたときに発生するイベント(btn.Click)でbtn_Click()メソッドが起動するよう、

btn.Click += btn_Click;

でイベント登録をしています。なお、btn_click()の引数のobject senderとSystem.EventArg eですが、イベントの処理をするメソッドでは必ずこのような書き方をします。今の段階では、イベントでは必ずこのように書く、と覚えておけば良いと思います。

今回の学習で初めて出て来た「イベント」ですが、Windowsではマウスのクリックやキーボードの打鍵などを「イベント」として動作のきっかけにする、イベントドリブンという動作方法で処理が進んでいくものが多く、イベントの取扱方はとても重要です。

今回はこれで終わります。