C# MouseEnterとMouseHoverの違いについて サンプルコード


力技の1号、楽しみですね

今回はマウスイベントの中の

MouseEventMouseHoverについて検証したときのコードを載せます。
とりあえず一度実行してみてください。

FormSample3.cs


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

namespace myapp
{
class FormSample3{
public class Form1 : Form
{
private Panel panel1 = new Panel();
private Panel panel2 = new Panel();

//各コンポーネントの初期化
private void InitializeComponent(){
//Form
this.Size = new Size(300,300);
this.Load += new System.EventHandler(this.Form1_Load);

//panel1
this.panel1.Size = new Size(100,100);
this.panel1.Location = new Point(10,10);
this.panel1.BackColor = Color.White;
this.panel1.BorderStyle = BorderStyle.FixedSingle;
this.panel1.MouseEnter += new EventHandler(mouseEnter);
this.panel1.MouseLeave += new EventHandler(mouseLeave);

//panel2
this.panel2.Size = new Size(100,100);
this.panel2.Location = new Point(10,150);
this.panel2.BackColor = Color.White;
this.panel2.BorderStyle = BorderStyle.FixedSingle;
this.panel2.MouseHover += new EventHandler(mouseHover);
this.panel2.MouseLeave += new EventHandler(mouseLeave);

//システムで設定されているマウスホバー時間(初期設定では400)
Console.WriteLine("MouseHoverTime:" + SystemInformation.MouseHoverTime + "(msec)");
}
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender,EventArgs e)
{
this.Controls.Add(panel1);
this.Controls.Add(panel2);
}

//マウスイベント処理
private void mouseEnter(object sender, EventArgs e)
{
this.panel1.BackColor = Color.Red;
}
private void mouseHover(object sender, EventArgs e)
{
this.panel2.BackColor = Color.Blue;
}
private void mouseLeave(object sender, EventArgs e)
{
this.panel1.BackColor = Color.White;
this.panel2.BackColor = Color.White;
}
}
static void Main(){
Application.Run(new Form1());
}
}
}


実行すると、フォーム上に矩形のパネルが2つ表示されます。



この上にマウスを載せると、





パネルの色がそれぞれ変化します。

マウスカーソルの代わりにおほしさまを乗せてあります。



さて、このパネルの色の変化の仕方に少し違いがあるのですが、お気づきでしたでしょうか。

コードを見てみると、

this.panel1.MouseEnter += new EventHandler(mouseEnter);


this.panel2.MouseHover += new EventHandler(mouseHover);


HoverとEnterイベントがそれぞれに割り振ってあります。

違いはイベントが発生するタイミングです。



Enterはコントロール(今回はpanel1)にマウスカーソルが乗った瞬間にイベントが発生します。

対するEnterはコントロール(今回はpanel2)にマウスカーソルが乗った状態で、一定の時間が経過すると発生します。

なので、すごい速さでpanel2の上を通過すると色が変わりません。

あれです、デスクトップアイコンとかにカーソル乗せたとき

Enterのタイミングでアイコンの縁取りの色が変わり
Hoverのタイミングでアイコンの詳細情報がじわっと浮かび上がってくる

と考えるとわかりやすいです。



MouseHoverTime

MouseHoverイベントを利用するときの待ち時間はどこでわかるかというと、

SystemInformation.MouseHoverTime


このプロパティで知ることができます。
このサンプルでは、MouseHoverTimeの値をコンソール(コマンドプロンプト)に表示しています。

このプロパティは読み取り専用なので、プログラムからは変更することができません。

ユーザがシステム側から任意に変更できるようです(未検証)。

参考:Windows 7のWindows Aeroによるタスクバーのサムネイル表示を高速化する


ちなみにデフォルトは400、つまり0.4秒です。
数字で聞くと短く思えますが、実際に操作していると結構いらっとしますね。

即時に反応させたいイベントのときはMouseEnter、
じっくり(?)反応させたいイベントのときはMouseHoverと使い分けていきましょう。



それでは、今回はこの辺りで。

C# MouseMoveイベントサンプルコード

こんばんは。
やっぱり腹式呼吸ができません。

さて、C#の超簡単なサンプルコードを書いてみました。

FormSample1.cs

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

namespace myapp
{
class FormSample1{
public class Form1 : Form
{
private Label label = new Label();

private void InitializeComponent(){
//Form1
this.Size = new Size(300,300);
this.MouseMove += new MouseEventHandler(mouseMove);

//label
label.Text = "start";
label.Location =new Point(150,150);
label.MouseMove += new MouseEventHandler(mouseMove);

this.Load +=new System.EventHandler(this.Form1_Load);
}
public Form1(){
InitializeComponent();
}
private void Form1_Load(object sender,EventArgs e)
{
this.Controls.Add(this.label);
}
private void mouseMove(object sender, MouseEventArgs e){
//取得した座標をクライアント座標に変換
System.Drawing.Point sp = System.Windows.Forms.Cursor.Position;
System.Drawing.Point cp = this.PointToClient(sp);
int x = cp.X,y = cp.Y;

//位置判定
if(x >150){
label.Text = "→";
}else{
label.Text = "←";
}
this.Text = (x.ToString() +"," + y.ToString());
}
}
static void Main()
{
Application.Run(new Form1());
}
}
}


実行すると





Form上のマウスの位置に応じて矢印が左右に動きます。

マウスカーソルがキャプチャできなかったので代わりにお星さまつけときました。

また、タイトルバーの部分にはForm上のマウスカーソルの座標x,yが表示されています。

それだけのプログラムです。

でもちまちま動く矢印めっちゃかわいい……こう、なんかこねくりまわしたくなるかわいさ……


では、ポイント。

MouseMove


これは、Controlクラスがもっているイベントです。
Formとかボタンの上でマウスカーソルが動いたときに発生します。
ポイントは、ラベルとフォーム両方に同じイベントが関連付けられていることです。
フォームだけでいいんじゃね? って思うかもしれませんが、そうするとラベルの上でだけMouseMoveイベントが発生しません。
あくまで、そのコントロールの上を直接マウスカーソルが動いたときにだけ反応します。

ふたつめ。

System.Drawing.Point sp = System.Windows.Forms.Cursor.Position;
System.Drawing.Point cp = this.PointToClient(sp);
int x = cp.X,y = cp.Y;


マウスイベントが発生したとき、Cursor.Psitionがもらってくるのはウィンドウ画面そのものの座標です。
しかし今回必要なのはマウスがフォームのどこにあるかです。
そこで、画面の座標をフォーム上の座標(クライアント座標)に変換してあげます。

参考:画面座標をクライアント座標(コントロール上の座標)に変換する


以上、超簡単なMouseMoveイベントのサンプルでした。

今度は矢印じゃなくて、右向きのキャラ画像左向きのキャラ画像でやってみたいです。


では、今回はこの辺りで。

InitializeComponent()とクラスの分割についての話

3,000Hitありがとうございます!!わーい!

ご訪問&拍手もありがとうございます~。

のんびりやっていきますのでよろしくお願いします~。



さて。

何回か前の記事

「'InitializeComponent' は現在のコンテキスト内に存在しません。」とか言って怒られるんだけどなんなの?

みたいに言ってましたが、原因がわかりました。



だって作ってねーもん。(ばーん)



そりゃそうだだってデザイナー使わず全部べた打ちして作ったんだもん自分で定義してないメソッド呼び出せるはずないじゃないかアホかアチャー

無意識にVisualStudioに頼っていたということですねまだまだ自立が足りない。

というわけで今日はInitializeComponent()とクラスの分割についてのお話。

多分知ってる人が多いと思うけど自力で理解できたのが嬉しいから書いておく。



InitializeComponent()ってそもそもなんで出てくるの。

InitializeComponent()は、Visual studioで「Windowsフォーム」を作成すると勝手に生成されます。

右クリック→追加でWindowsフォーム「sampleForm.cs」を作ると

sampleForm.cs

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

namespace Project1
{
public partial class sampleForm : Form
{
public sampleForm()
{
InitializeComponent();
}
}
}


ほら、勝手にInitializeComponent()がいる!

そして、同時に生成される謎のファイル

「sampleForm.Designer.cs」!!

誰! 何この子!!

デザインビューでなくコードビューで中を見てみると

namespace Project1 
{
partial class sampleForm
{
///
/// 必要なデザイナー変数です。
///

private System.ComponentModel.IContainer components = null;

///
/// 使用中のリソースをすべてクリーンアップします。
///

/// マネージ リソースが破棄される場合 true、破棄されない場合は false です。
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows フォーム デザイナーで生成されたコード

///
/// デザイナー サポートに必要なメソッドです。このメソッドの内容を
/// コード エディターで変更しないでください。
///

private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.Text = "sampleForm";
}

#endregion
}
}


だそうです。

デザインビューを使うとこの子もいっしょに生成されるんです。

そして注目すべきはここ

private void InitializeComponent()


こいつだ!!!!
そしてその下を見ていくと、どうやら「this」つまりこのフォームそのものの設定をしているんですね。

さてここで、デザインビューからボタンを一個追加してみます。
そしてボタンをダブルクリックしボタンイベントも追加。、

すると、

sampleForm.cs

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

namespace Project1
{
public partial class sampleForm : Form
{
public sampleForm()
{
InitializeComponent();
}

private void button1_Click(object sender, EventArgs e)
{

}
}
}


sampelForm.csには勝手にボタンクリックイベントが追加されます。

そして、

sampleForm.Designer.cs

namespace Project1 
{
partial class sampleForm
{
///
/// 必要なデザイナー変数です。
///

private System.ComponentModel.IContainer components = null;

///
/// 使用中のリソースをすべてクリーンアップします。
///

/// マネージ リソースが破棄される場合 true、破棄されない場合は false です。
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}

#region Windows フォーム デザイナーで生成されたコード

///
/// デザイナー サポートに必要なメソッドです。このメソッドの内容を
/// コード エディターで変更しないでください。
///

private void InitializeComponent()
{
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(102, 111);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(75, 23);
this.button1.TabIndex = 0;
this.button1.Text = "button1";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// sampleForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 262);
this.Controls.Add(this.button1);
this.Name = "sampleForm";
this.Text = "sampleForm";
this.ResumeLayout(false);

}

#endregion

private System.Windows.Forms.Button button1;
}
}


なんかめっちゃ増えてる!!



でもよく読んでいくと

this.button1 = new System.Windows.Forms.Button();


ボタンをインスタンス化して

this.button1.Name = "button1";


ボタンに名前つけて

this.button1.Click += new System.EventHandler(this.button1_Click);


ボタンにクリックイベント追加して

this.Controls.Add(this.button1);


フォームに作ったボタン追加して

private System.Windows.Forms.Button button1;


(順番は前後するけれど)ボタンを宣言して…

って自力で追加するのとそんなに処理変わらないわけです。
他にごちゃごちゃ書いてあるのは色とかサイズとか位置の設定だし。

そういうコンポーネント(Component)の初期化(Initialize)をしてくれるのが

InitializeComponent()メソッドなわけだー。なるほど。
じゃあ自力べた打ちプログラムにはいらないわー。



…それでは、なぜデザイナーなんてもの勝手に作るんだ!?と思うかもですけど、
デザイン部とコードを切り離して既述するのは合理的な考え方だよな。

このデザイナーコード、Visual studioで開発するといつもお世話になるから特別なコードなんだと思い込んでたけど

sampleForm.Designer.cs

partial class sampleForm


ここ見ると、単なるsampleFormの分割クラスなんですねー。

参考:方法: 1 つのクラスを複数の部分クラスに分割する (クラス デザイナー)



Visual studioとかEclipseとか統合開発環境って結構気を使ってコードの自動生成してくれるから大量の文字列におののいてるとこあったけど、

コードが読めれば臆することはない! …かもな!



いやー積年の疑問と畏怖が払拭されて爽やかな気分だわーと喜びつつふとVisual studioに目をやったら。



「sampleForm.resx」



なんかまた自動生成されてるーーーー!!
お前は誰だー!!!????



次回「新たなる謎 リソースファイル!!」

ただし次回の予定は未定だ!!

今回はこの辺りでっ!!

C# 「2次元配列」と「配列の配列」の話



お久しぶりです。

C#とかJava書いてるとすぐ配列の宣言・初期化を忘れてしまうので、よく調べているのですが、
その中で、C#の配列宣言に

int[][] array;



int[,] array;

があることに気がつきました。

C言語のときは[][]で2次元配列を表していた気がするので慣習的に前者を使うことが多かったんですが、
後者ってどうやって使うんだ? 前者と何が違うの? と気になって調べてみました。

で、わかりやすい記事にたどり着いたのでメモ。

君は、C#の「2次元配列」と「配列の配列」を正しく使い分けることができるか?




なるほど、私がC#で今まで2次元配列だと思って使っていたのは「配列の配列」だったのか…!?

か、カルチャーショックだ…。

ちなみに配列の配列はジャグ配列って呼ぶらしいです。
参考:ジャグ配列

いやぁ、今作ってるプログラムも全部配列の配列使ってるなー。
面倒なので次のプロジェクトから直そう。



それでは、今回はこの辺りで。




続きを読む

C# Chartの折れ線グラフで不連続な部分を作る

引き続きChartの話です。

まずは以下のサンプルコードを実行してみたいと思います。

ChartSample2.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;

namespace myapp
{
class ChartSample1
{
public partial class Form1 : Form
{
//Chartオブジェクトを生成
private System.Windows.Forms.DataVisualization.Charting.Chart chart;

//コンストラクタ
public Form1(){
//フォームおよびフォームに配置されるコンポーネントを初期化
//InitializeComponent();

//フォームのサイズを設定
this.Size = new Size(400,400);

//Formにロードイベントを追加
this.Load += new System.EventHandler(this.Form1_Load);
}
private void Form1_Load(object sender, EventArgs e)
{
//Chartをインスタンス化
this.chart = new System.Windows.Forms.DataVisualization.Charting.Chart();

//Chartの各要素のクリア
chart.ChartAreas.Clear();
chart.Titles.Clear();
chart.Series.Clear();

//ChartArea(グラフ領域)
ChartArea area = new ChartArea("area1");

//Title(グラフのタイトル)
Title title = new Title("title1");

//ChartAreaにTitleを紐付ける
title.DockedToChartArea = "area1";

//Series(グラフに表示されるひとつの系列)
Series series1 = new Series();
Series series2 = new Series();

//Seriesの種類を設定
series1.ChartType = SeriesChartType.Line;
series2.ChartType = SeriesChartType.Line;

//Seriesのデータを設定(Sin波,Cos波)
for(int i=0;i<360;i++)
{
series1.Points.AddXY(i,Math.Tan(i*Math.PI/180.0) );
series2.Points.AddXY(i,Math.Cos(i*Math.PI/180.0) );
}

//ChartAreaにSeriesを紐付ける
series1.ChartArea = "area1";
series2.ChartArea = "area1";

//ChartArea,Title,SeriesをChartに追加
chart.ChartAreas.Add(area);
chart.Titles.Add(title);
chart.Series.Add(series1);
chart.Series.Add(series2);

//Chartをフォームのコントロールに追加
this.Controls.Add(this.chart);
}
}
static void Main() {
Application.Run(new Form1());
}
}
}



昨日のサンプルのSin波をTan波に変更しただけです。
しかし、実行すると



このように、Tan波の特性上とんでもないグラフになってしまいます。
Cos波もいい迷惑です。

グラフの表示はすべての系列データをグラフに描画する自動調節機能がオンになっているのでこういうことになってしまいます。

これを見やすいグラフにするには


  1. スケールをいじってデータの表示範囲を制御する

  2. Seriesに格納する値を「なし」にする



の2つの対処法があります。
ほかにもあるかもしれません。


まず、1つめの方法。

AxisクラスのMaximum、Minimumというプロパティに値を設定することで、X軸やY軸の表示範囲を指定することができます。

先ほどのコードに

area.AxisY.Maximum = 1.5; 
area.AxisY.Minimum = -1.5;


この二つの命令を足します。

実行結果



Y軸の範囲が指定され、見やすいグラフになりました。


続いて2つめ、範囲外のデータをそもそもなかったことにしてしまう方法です。

先ほどのコードからY軸範囲指定の命令を消し、データを格納しているfor文の中をこう書き換えます。

if(Math.Tan(i*Math.PI/180.0) < 1.5 && Math.Tan(i*Math.PI/180.0) > -1.5){ 
series1.Points.AddXY(i,Math.Tan(i*Math.PI/180.0) );
}
else{
series1.Points.AddXY(i,Double.NaN );
}
series2.Points.AddXY(i,Math.Cos(i*Math.PI/180.0) );


ポイントは、範囲外だったときのデータを

Double.NaN

にしてしまうところです。

これは、値がない(非数)、ということを示すDouble型のフィールドです。

参考:Double.NaN フィールド


これがデータとして格納された場合、グラフは表示されません。

実行結果



二つの結果を見比べてわかるように、2つめのグラフはデータ自体に欠損ができてしまうので、使うときは注意が必要です。
ただ、この記事を書こうと思ったのが、不連続な折れ線グラフを描画したくて、結果として2つめのやり方で問題解決したから、だったので、使いどころをみきわめていきましょー。



それでは、今回はこの辺りで。