Adsense_top

2009年9月19日土曜日

C# 変形ウインドウにマウス操作を追加した。

先日書いた、「画像を使った変形ウインドウを作ってみた。」の続きです。

前回は画像を読み込んでウインドウを作っていたため タイトルバーないためにCloseBoxの代わりに、フォームのクリックイベットで閉じるようにして逃げていました。
また、同じくタイトルバーがないために、ドラッグしてのフォームの移動が出来ませんでした

今回は上記の点を変更し、独自に画像ファイルを使用してCloseBoxを表示し、フォームのドラッグも出来るように変更してみます。
前回のソースを変更して対応していますので、前回説明した部分ははしょってるかもしれませんので、前回分をみられていない方は前回のエントリーも参照してください

以下のような画像ファイルを用意しました。
Skin.bmp
Close.bmp

20 X 60 の中に、20 X 20で、左から、「通常状態」「マウスがポイントしている状態」「Boxを押下している状態」の3つをまとめて描いてあります。

クラス変数は、画像を格納する変数と、マウスの状態を格納する変数、背景画像上の CloseBoxの描画位置とサイズ情報などを格納した変数を用意しました。
// 背景画像とCloseBox画像を格納する変数
private Bitmap backBmp = null;
private Bitmap closeBmp = null;
// マウスの左ボタンが押下されているかの状態を格納
private bool isMouseDown = false;
// マウスポインタがCloseBox上に位置しているかを格納
private bool isMouseOverBox = false;
// マウスがCloseBox上で押下されたかを格納
private bool isMouseDownOnClose = false;
// マウスポインタの位置を格納
private Point mousePoint;
// CloseBoxの位置とサイズを格納した、
// readonly修飾しているので実質的な実行時定数
private readonly Point boxLocation = new Point(323, 43);
private readonly Size boxSize = new Size(20, 20);
// 「arayan's」と書かれた背景画像上ボタンの位置とサイズを格納した、
// readonly修飾しているので実質的な実行時定数
private readonly Point titleLocation = new Point(16, 15);
private readonly Size titleSize = new Size(137, 30);

フォームのコンストラクタには、CloseBox画像の読み込みを追加しています。
//
// コンストラクタ(初期化処理)
//
public Form1()
{
InitializeComponent();
// 今回はこのイベントハンドラの登録およびハンドラは削除
//this.Click += new System.EventHandler(this.Form1_Click);

// 以下3つのイベントハンドラ登録部今回追加
this.MouseDown += new System.Windows.Forms.
MouseEventHandler(this.Form1_MouseDown);
this.MouseUp += new System.Windows.Forms.
MouseEventHandler(this.Form1_MouseUp);
this.MouseMove += new System.Windows.Forms.
MouseEventHandler(this.Form1_MouseMove);

// 境界線なしにする。
this.FormBorderStyle = FormBorderStyle.None;
// 。ダブル バッファリングを使用するように動作を設定する。
this.SetStyle(ControlStyles.DoubleBuffer
| ControlStyles.UserPaint
| ControlStyles.AllPaintingInWmPaint, true);
// 背景画像ファイルを読み込む
bmp =(Bitmap) Bitmap.FromFile("skin.bmp");

// CloseBox画像を読み込む(今回追加部)
closeBmp = (Bitmap)Bitmap.FromFile("Close.bmp");

// ウインドウサイズを調整(前回分とは少し変更しました。)
this.Width = bmp.Width;
this.Height = bmp.Height;
// ウインドウ領域を設定する。
this.Region = BitmapToRegion(bmp, Color.FromArgb(255, 0, 255));
// 原点の色を使う場合は以下のようにする
// this.Region = BitmapToRegion(bmp, bmp.GetPixel(0, 0));
}
背景描画の部分にCloseBoxの描画メソッドのコール追加しています。
BitmapToRegionメソッドは前回のエントリーに書いたコードを参照してください。
//
// コントロールの背景を描画。
// (OnPaintBackground をオーバーライド)
//
protected override void OnPaintBackground(PaintEventArgs e)
{
// 座標単位を設定
e.Graphics.PageUnit = GraphicsUnit.Pixel;
// 背景描画(画像を描画)
e.Graphics.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height);

// CloseBoxの描画メソッドを呼ぶ(今回追加部)
DrawCloaseBox(e.Graphics);
}
//
// CloseBoxを状態に合わせて異なる画像を描画する(今回追加部)
//
private void DrawCloseBox(Graphics g)
{
// closeBmp中の今回使用するCloseBox画像の矩形を格納する変数
Rectangle rectSrc;
if (isMouseOverBox)
{
if (isMouseDown)
{
// CloseBoxが押下された画像
rectSrc =
new Rectangle(new Point(boxSize.Width * 2, 0), boxSize);
}
else
{
// CloseBoxがアクティブ状態の画像
rectSrc =
new Rectangle(new Point(boxSize.Width, 0), boxSize);
}
}
else
{
// 通常時の画像
rectSrc =
new Rectangle(new Point(0, 0), boxSize);
}
// 状態にあった画像でCloseBoxを描画
g.DrawImage(this.closeBmp, new Rectangle(boxLocation,boxSize),
rectSrc, GraphicsUnit.Pixel);
}
「OnPaintBackground」では、前回分に追加してCloseBoxを描画するメソッド「DrawCloseBox」をGraphicsオブジェクトを渡して読んでいます。
「DrawCloseBox」では、マウスイベントハンドラで設定したフラグで状態を判断し、状態にあったCloseBox画像を描画しています。
下図のような表示になります。
  

以下は今回追加したイベントハンドラ部でです。
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
// マウス左クリック時位置を記録
if (e.Button == MouseButtons.Left)
mousePoint = new Point(e.X, e.Y);
// CloseBox上の押下か?を記録
isMouseDownOnClose = isMouseOverButton;
// マウスの押下状態を記録
isMouseDown = true;
// 再描画
Refresh();
}
ここでは、マウス左ボタンの押下状態を記録。
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
// マウス押下記録を消去
isMouseDown = false;
// CloseBox上にマウスがあり且、CloseBox上で
// 押下さたのであればウインドウを閉じる
if (isMouseOverButton && isMouseDownOnClose)
this.Close();
else
isMouseDownOnClose = false;
}
ここでは、変数の値を確認判定して、条件を満たしていればウインドウを閉じています。
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
// マウス左ボタンか確認
if ( e.Button == MouseButtons.Left)
{
// ポインタ位置が「arayan's」ボタンの中にある場合
if (e.X > titleLocation.X)
&& (e.X < titleLocation.X + titleSize.Width)
&& (e.Y > titleLocation.Y)
&& (e.Y < titleLocation.Y + titleSize.Height))
{
// フォームの位置をMouseDown時からの移動分調節(移動)する。
this.SetDesktopLocation(this.Left - mousePoint.X + e.X,
this.Top - mousePoint.Y + e.Y);
}
// ポインタ位置がCloseBoxの中にある場合
if ((e.X > buttonLocation.X)
&& (e.X < buttonLocation.X + buttonSize.Width)
&& (e.Y > buttonLocation.Y)
&& (e.Y < buttonLocation.Y + buttonSize.Height))
{
// 変数にフラグを立てる
isMouseOverButton = true;
}
else
{
isMouseOverButton = false; ;
}
// 再描画
Refresh();
}
}
ここで、フォームのドラッグを処理しています。また、マウスがCloseBox上に位置するかも記録しています。

以上で「arayan's」ボタンをタイトルバーのようにドラッグで移動できるようになり、右上のボタンで閉じることが出来るようになりました。
画像を使ったウインドウは大体出来たので、着せ替えが出来るようにし本当の意味でのスキンを考えてみようかと思います。

2009年9月8日火曜日

C# 画像を使った変形ウインドウを作ってみた。

前回の変形ウインドウは丸いだけで面白くもないので、画像を読み込んで、その画像形状のウインドウを表示してみました。

以下のような画像を用意しました。画像の上手下手やセンスは目を瞑ってください。
マゼンタ(ピンクっぽい色)のところが透過する部分になります。

使用するクラス変数は Bitmap画像を格納する bmp だけです。
// 背景画像格納する変数
private Bitmap bmp = null;

以下が今回のメインのウインドウの形状を切り抜く部分です。
コードとしては、ひたすら透過色以外の部分の点を抜き出しているだけです
//
// 画像からウインドウの領域を作成し返します。
//
// 引数 bmp:対象画像
// 引数 transparencyKey:透過色
private Region BitmapToRegion(Bitmap bmp,
Color transparencyKey)
{
int height = bmp.Height;
int width = bmp.Width;
int xStart = 0;
System.Drawing.Drawing2D.GraphicsPath path =
new System.Drawing.Drawing2D.GraphicsPath();
// 縦方向の走査
for (int i = 0; i < height; i++)
{
// 横方向の走査
for (int j = 0; j < width; j++)
{
if (bmp.GetPixel(j, i) != transparencyKey)
{
// 透過色でない部分の連続を1ピクセル高の矩形にまとめ、
// GraphicsPathに追加していく
// (点ごとに登録処理するより効率がよいので)
xStart = j;
while ((j < width) &&
(bmp.GetPixel(j, i) != transparencyKey))
j++;
path.AddRectangle(
new Rectangle(xStart, i, j - xStart, 1));
}
}
}
// GraphicsPathからRegionを作成し返す。
Region region = new Region(path);
path.Dispose();
return region;
}

以下の部分は、まんま、画像を書いているだけです。
//
// コントロールの背景を描画。
// (OnPaintBackground をオーバーライド)
//
protected override void OnPaintBackground(PaintEventArgs e)
{
// 座標単位を設定
e.Graphics.PageUnit = GraphicsUnit.Pixel;
// 背景描画(画像を描画)
e.Graphics.DrawImage(bmp, 0, 0, bmp.Width, bmp.Height);
}

次のイベントハンドラは、枠なしのウインドウにしたので、閉じることが出来にので,クリックでとりあえず閉じられるようしているだけです。
//
// マウスクリックでウインドウを閉じる
//
private void Form1_Click(object sender, EventArgs e)
{
this.Close();
}

コンストラクタでは、ダブルバッファを使えるようにするなどの設定と、ウインドウの形状を設定しています。
クリックイベントは見て分かり易いようにココに書いているだけです。
VisualStudioのWindows フォーム デザイナに任せて勿論OKです。
//
// コンストラクタ(初期化処理)
//
public Form1()
{
InitializeComponent();
// ウインドウを閉じるためのイベントを登録
this.Click += new System.EventHandler(this.Form1_Click);

// 境界線なしにする。
this.FormBorderStyle = FormBorderStyle.None;
// 。ダブル バッファリングを使用するように動作を設定する。
this.SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint, true);
// 画像ファイルを読み込む
bmp =(Bitmap) Bitmap.FromFile("skin.bmp");
// ウインドウサイズを調整
if (this.Width < bmp.Width)
this.Width = bmp.Width;
if (this.Height < bmp.Height)
this.Height = bmp.Height;
// ウインドウ領域を設定する。
this.Region =
BitmapToRegion(bmp, Color.FromArgb(255, 0, 255));

// 原点の色を使う場合は以下のようにする
// this.Region = BitmapToRegion(bmp, bmp.GetPixel(0, 0));
}

一番下のコメントアウトしている部分は、よく原点の色を透過色に使う方おられるので書いておきました。
で、結果以下のように表示することが出来ます。

前回の丸よりはスキンぽくなりました。画像の造りが悪くエッジが白く出ていますが...
面白くなって来たので、このシリーズ続けるかも知れません


上記メソッド「BitmapToRegion」を改良し高速化したものを「C# 画像を使った変形ウインドウの改良」としてエントリーしました。そちらも合わせてご覧ください。

2009年8月31日月曜日

C# 変形ウインドウの作成

通知用に通常の形でないウインドウが必要になったので、試してみました。
とりあえず基本ということで丸いウインドウ。


public Form1()
{
InitializeComponent();
System.Drawing.Drawing2D.GraphicsPath path =
new System.Drawing.Drawing2D.GraphicsPath();
path.AddEllipse(0, 0, this.Width, this.Height);
this.Region = new Region(path);
}

本当はWPFでやればいいのでしょうが、簡単だったのでメモしておきます。


2009年8月22日土曜日

C# Twitter API ストリーム系.. その3

C# Twitter API ストリーム系に挑戦してみる」「C# Twitter API ストリーム系.. その2」の続きです。
前回までに書いたコードの使用方法を確認の意味で書いていきます。まだ読まれていいない方はそちらもお読みください。


分かりやすいようにForm1上にstartButtonとstopButtonがあるものとして書いていきます。
まずは、Twitterサーバーへの要求の開始のしかたです。



private void startButton_Click(object sender, EventArgs e)
{
this.Received += new ReceiveEventHandler(Form1_Received);
ArgsObject obj = new ArgsObject("user", "password",
@"http://stream.twitter.com/follow.xml",
HttpMethod.POST,
new string[] { "follow=42123706" }, 2048);
Thread thread = new Thread(this.Execute);
thread.Start(obj);
}

ArgsObjectクラスのコンストラクタの引数は、順に アカウント名・パスワード・ファイル形式を指定した形でのAPIのURL・HTTPメソッド・APIの引数の配列・読み取りストリームのバッファサイズです。

ここで引数に使用しているIDは、「Google_News_jp」のものです。


受信待機を中止する場合は、Stopメソッドを呼び出します。


最初のコードで割り当てているイベントで受信内容を受け取ります。



void Form1_Received(string result)
{
Console.WriteLine(result);
}

ここでは、コンソールに書きだしていますが実際にコントロール上に表示するのであれば、「InvokeRequired」の確認して、delegateを介して操作するように変更してください。また今回は、「stream.twitter.com/follow.format」APIの受信内容が一度に受けられるバッファサイズを指定しましたが、イベントはバッファサイズ毎に起動されますので、一度のイベントで1レコードではないので、レコードの切れ目などを判定する必要がある場合がありますので注意してください。
他注意点としては、接続維持のための決まったデータが一定時間ごとに送られて来ますので捨てる必要があります。実際の受信間隔、受信データはテストして実際に確認してみてください。


2009年8月19日水曜日

C# Twitter API ストリーム系.. その2

昨日の「C# Twitter API ストリーム系に挑戦してみる」の続きです。
先ずは、残りのメソッドや列挙型


受信イベントの発行



private void OnReciveded(string content)
{
if (Received != null)
Received(content);
}

メソッドで使用するデータを格納するオブジェクトの構造体



public struct ArgsObject
{
public string UserId;
public string Password;
public HttpMethod Method;
public string Url;
public int BufferSize;
public string[] Parameters;
public ArgsObject(string userId, string password,
string url, HttpMethod method,
string[] parameters, int bufferSize)
{
this.UserId = userId;
this.Password = password;
this.Url = url;
this.Method = method;
this.Parameters = parameters;
this.BufferSize = bufferSize;
}
}

受信待機を終了させるメソッド



private void Stop()
{
state.Request.Abort();
}

非同期要求で使用する状態オブジェクトのクラス



public class RequestState
{
public int BufferSize = 1024;
public StringBuilder StringValue = new StringBuilder
public byte[] Buffer
public HttpWebRequest Request = null;
public HttpWebResponse Response = null;
public Stream Stream
public RequestState(int bufferSize)
{
BufferSize = bufferSize;
Buffer = new byte[BufferSize];
}
public RequestState()
{
Buffer = new byte[BufferSize];
}
public void DataClear()
{
StringValue.Length = 0;
}
}

HTTP使用するメソッドを指定する列挙型



public enum HttpMethod
{
POST,
GET,
DELETE
}

Twitterから受信するファイル形式を指定する列挙型



public enum ResultFormat
{
json,
xml,
rss,
atom
}

まぁ、ここまでのソースを読めば大体の使い方は読めているとは思いますが...
そろそろ眠たくなってきましたので、使用方法などは次回にまわさせていただきます。


2009年8月18日火曜日

C# Twitter API ストリーム系に挑戦してみる

久しぶりに書くことが出来ました。
Twitter API にβらしいのですが、ストリーム系のものがあります。
リアルタイムにフォローしたりすることが出来ます。Twitterらしい機能ですので、書いてみました。
MSDNのサンプルを参考にしました。


先ずはクラス変数とイベント



public event ReceiveEventHandler Received;
RequestState state = null;
public delegate void ReceiveEventHandler(string result);

ワーカースレッドとして呼び出すメソッドです。



private void Execute(object obj)
{
// 引数オブジェクトを実際の型にキャスト
// ArgsObjectクラスについては後述します。
ArgsObject args = (ArgsObject)obj;
// ワーカースレッドでエラーを処理しないと
// プログラム自体が落ちてしまうため処理は必須
try
{
StringBuilder paramBuilder = new StringBuilder();
foreach (string param in args.Parameters)
{
if (paramBuilder.Length > 0)
paramBuilder.Append("&");
paramBuilder.Append(param);
}
HttpWebRequest request =
(HttpWebRequest)WebRequest.Create(args.Url);
System.Net.ServicePointManager.Expect100Continue = false;
request.Method =
Enum.GetName(typeof(HttpMethod), args.Method);
request.ContentType =
"application/x-www-form-urlencoded";
request.Credentials =
new NetworkCredential(args.UserId, args.Password);
// 非同期動作で使用する状態オブジェクト
state = new RequestState(args.BufferSize);
state.Request = request;

StreamWriter writer =
new StreamWriter(request.GetRequestStream());
if (paramBuilder.Length > 0)
writer.Write(paramBuilder.ToString());
writer.Close();

// 非同期要求
IAsyncResult iAasyncResult =
(IAsyncResult)request.BeginGetResponse(
new AsyncCallback(ResponseCallback), state);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

レスポンス受信



private void ResponseCallback(IAsyncResult ar)
{
try
{
// 非同期要求用状態オブジェクト
RequestState state = (RequestState)ar.AsyncState;
HttpWebRequest request = state.Request;
state.Response =
(HttpWebResponse)request.EndGetResponse(ar);
// ストリームから読み込む
Stream stream = state.Response.GetResponseStream();
state.Stream = stream;
// 非同期読み込み開始
IAsyncResult iAsyncResult =
stream.BeginRead(
state.Buffer, 0, state.BufferSize,
new AsyncCallback(ReadCallBack), state);
}
catch(Exception e)
{
Console.WriteLine(e.Message);
}
}

レスポンスのデータの読み込み



private void ReadCallBack(IAsyncResult ar)
{
try
{
RequestState state = (RequestState)ar.AsyncState;
// 非同期読み込み(待機)
Stream stream = state.Stream;
int read = stream.EndRead(ar);
if (read > 0)
{
state.StringValue.Append(
Encoding.ASCII.GetString(state.Buffer, 0, read));
if (state.StringValue.Length > 1)
{
string stringContent =
state.StringValue.ToString();
// 受信イベントを
OnReciveded(stringContent);
state.DataClear();
}
IAsyncResult iAsyncResult =
stream.BeginRead(state.Buffer, 0, state.BufferSize,
new AsyncCallback(ReadCallBack), state);
}
else
{
if (state.StringValue.Length > 1)
{
string stringContent = state.StringValue.ToString();
OnReciveded(stringContent);
state.DataClear();
}
stream.Close();
}
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}

今回も出来るだけ簡潔に書くように心がけましたので、結構な手抜きになっています。

長くなって来ましたので、今回書き残したメソッドの中で使われている、クラスやメソッドと使用方法は次回(明日か明後日)に分けて書くことにします。


2009年7月15日水曜日

Twitter API 汎用性.の記事、間違えていました。

前のエントリー「C# Twitter API 汎用性...」の「Execute」メソッド間違えていました。

private string Execute(string url, HttpMethod method,
string user, string pass, params string[] parameters)
{
��
}

とあるのは、正しくは

private string Execute(string url, HttpMethod method,
string user, string pass, string[] parameters)
{
��
}

です。
最後の引数に「params」がつけていましたが不要です。このままでは動きません。
失礼しました。


C# Twitter API 汎用性を上げて、検索をしてみる

前回などに使ったAPIの実行部分を、使い回しが利くように変更しました。

//
// APIを使用し応答を返す
//
private string Execute(string url, HttpMethod method,
string user, string pass, params string[] parameters)
{
StringBuilder paramBuilder = new StringBuilder();
if (parameters != null)
{
foreach (string param in parameters)
{
if (paramBuilder.Length > 0)
paramBuilder.Append("&");
paramBuilder.Append(param);
}
}
HttpWebRequest request =
(HttpWebRequest)HttpWebRequest.Create(url);
request.Method =
Enum.GetName(typeof(HttpMethod), method);
request.Credentials =
new NetworkCredential(user, pass);
request.ContentType = "application/x-www-form-urlencoded";
//request.UserAgent = "arayan's Twitter Client";
request.Timeout = 10000;
ServicePointManager.Expect100Continue = false;
StreamWriter writer =
new StreamWriter(request.GetRequestStream());
if(paramBuilder.Length > 0)
writer.Write(paramBuilder.ToString());
writer.Close();
WebResponse response = request.GetResponse();
StreamReader reader =
new StreamReader(response.GetResponseStream());
string result = reader.ReadToEnd();
reader.Close();
response.Close();
return result;
}

private enum HttpMethod
{
POST,
GET,
DELETE
}

以下のように呼び出します。
例は「search」APIで「C#」を含む「日本語」の投稿を取得します。
「userid」「password」には、認証に使用するユーザーIDとパスワードを設定ます。

string[] parameters = new string[]{
"q=" + HttpUtility.UrlEncode("C#", Encoding.UTF8)),
"lang=" + HttpUtility.UrlEncode("ja", Encoding.UTF8))
};
string result =
Execute("http://search.twitter.com/search.json",
HttpMethod.GET, userid, password, parameters);


このエントリーの「Execute」メソッドが間違えていました。詳細は次のエントリーをご覧下さい。

2009年7月9日木曜日

C# Twitter API でつぶやいてみた。

今日はTwitter APIを使って、つぶやいて(投稿)みました
今回は、前回と違い認証を行う必要があります。殆どAPIで認証が必要ですので、他のAPIの雛形になる思います。
投稿に使うAPIは「statuses/update」です。


今回も画面がないと淋しいので、前回にと同じく一応画面ありです。


フォームにアカウント名を指定する「userTextBox」とパスワードを指定する「passTextBox」、メッセージを入力する「mesTextBox」()受信したXMLを表示する「resTextBox」、「button1」を配置しました。
「mesTextBox」のMaxLengthは、160文字以内におさめる必要があり、ただし、140文字を超えた部分は表示される保証がないため「140」にします。

using System;
using System.Windows.Forms;
using System.Net;
using System.IO;
using System.Web;

public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private string PostData(string url, string user,
string pass, string mes)
{
// 投稿内容をURLエンコードする。
string encMes = System.Web.HttpUtility.UrlEncode(mes);

HttpWebRequest request =
(HttpWebRequest)HttpWebRequest.Create(url);
request.Method = "POST";
// 認証情報を設定
request.Credentials =
new NetworkCredential(user, pass);
// Content-typeヘッダを設定
request.ContentType =
"application/x-www-form-urlencoded";
// request.UserAgent = "Twitter API Tester"; 無くても大丈夫
request.Timeout = 10000; // 規定値は100,000(100秒)
// Twitterのサーバーが「100-Continue 」ヘッダを
// サポートしていないので、これを設定しないと
// 「417」ステータスコードが返ってくるらしい
System.Net.ServicePointManager.Expect100Continue = false;
// 書き込む
StreamWriter writer =
new StreamWriter(request.GetRequestStream());
writer.Write("status={0}", encMes);
writer.Close();
// 読み込む
WebResponse response = request.GetResponse();
StreamReader reader =
new StreamReader(response.GetResponseStream());
string result = reader.ReadToEnd();
// 後処理
reader.Close();
response.Close();

return result;
}

private void button1_Click(object sender, EventArgs e)
{
const string EXP = "http://twitter.com/statuses/update.xml";
string user = userTextBox.Text;
string pass = passTextBox.Text;
string mes = mesTextBox.Text;
resTextBox.Text = PostData(EXP, user, pass, mes);
}
}

こんな感じです。ユーザー名とパスワードのところは隠しているため、汚らしくてすみません。


前回同様 XMLでの返答を受けますので、「statuses/update」に拡張子「xml」を付加しています。
やり始めたばかりなので、理解する意味もこめて前回同様に出来るだけ簡潔に最小のコードにするように努めました。
また、同一内容の投稿を連続してしようとした場合、最初の投稿以外は無視されますので、同じ内容を続けて投稿動作の可否テストとしは無駄になります。(レスポンス確認としては意味はありますが...)


今回もTwitter APIについては、「観測気球」様のTwitter API 仕様書 (日本語) [最新版]を参考にさせていただきました。


2009年7月7日火曜日

C# Twitter APIを使ってみた。

Twitterがメチャ盛り上がってきているようなので、Twitter API を使ってみようと思います。
今回は、認証が必要のない、一番簡単そうなAPI「statuses/user_timeline」を使って、指定したアカウントのタイムラインを読み込みリストに表示します。
Twitter APIについては、「観測気球」様のTwitter API 仕様書 (日本語) [最新版]を参考にさせていただきました。

コンソールに表示でも良かったのですが、画面がないと淋しいので、一応画面ありです。
フォームにアカウント名を指定する「textBox1」と受信したXMLを表示する「textBox2」、タイムラインをリストで表示する「listBox1」、「button1」を配置しました。
private const string EXP =
"http://twitter.com/statuses/user_timeline/{0}.xml";

public Form1()
{
InitializeComponent();
textBox1.Text = "twj";
}
//
// twitterサーバーからデータを取得します。
//
public string ReadData(string account)
{
// URL作成
string url = string.Format(EXP,account);

HttpWebRequest request =
(HttpWebRequest)HttpWebRequest.Create(url);
WebResponse response = request.GetResponse();
StreamReader reader =
new StreamReader(response.GetResponseStream());
string result = reader.ReadToEnd();
reader.Close();
response.Close();

// WebClientを使うなら、こんな感じ
// WebClient client = new WebClient();
// client.Encoding = Encoding.UTF8;
// byte[] data = client.DownloadData(url);
// string result =Encoding.UTF8.GetString(data);

return result;
}
private void button1_Click(object sender, EventArgs e)
{
string account = textBox1.Text;
string result = ReadData(account);
// 取得XML表示
textBox2.Text = result;
// XMLドキュメントを読み込む
XmlDocument doc = new XmlDocument();
doc.LoadXml(result);
//XPath式を使って[text]ノードのリストを抜き出す。
XmlNodeList nodes = doc.SelectNodes("//statuses/status/text");
// リストボックスに[text]を登録
foreach(XmlNode node in nodes)
{
listBox1.Items.Add(node.InnerText);
}
}

こんな感じになりました。

今回は出来るだけシンプルに短いソースを心がけましたので、他のAPIの使用を前提にした汎用性は持たせていません。
また、xml, json, rss, atomの形式のデータが取得できるようですが、馴染が深いxml形式を今回は使用しました。
rss, atom形式のAPIによっては使えないものがあるようです。
定数EXPは、「statuses/user_timeline」APIを用いて、URLの最後のファイル名にあたるところの「{0}.xml」({0}はstring.Formatを使って、アカウント名「twj」を挿入するプレスホルダです。)は、アカウント名のデータを「xml」形式で得することを指定しています。
サンプル画像で使用しているIDの「twj」はTwitter日本語ホームページ開発者(社?)のアカウントで、公式アカウントと一般的に言われているものを例として使わせていただきました。

2009年7月4日土曜日

DataGridView セルの結合仕切り直す事に。

DataGridViewでのセル結合をDataGridViewTextBoxColumnを拡張することにより、結合専用のセルを作成してきましたが、どうしても解決出来ない事や、実装できたとしても無理がある方法をとらざる得ない部分が幾つかある事が見えてきました。
このまま進めるか検討した結果、仕切り直す事にしました。多分ですがDatGridView自身の拡張する方法が正しい方法だと思います。また、WPFでする方法も考えれるのかと思います。
 どのようにするか、考えがまとまった時点で、このテーマを再開するつもりです。


2009年6月24日水曜日

アフィリエイトをはじめました

アフィリエイトをはじめました。右下にあるバーナーです。
これってどれぐらい儲かるのかな。まだ始めて数日ですが、全く報酬は発生していません。
ページの人気に比例するだろうことは容易に想像がつきますので、マメに記事を書くことが効果的なのかななど考えます。ただ、見てくれている人もいるので、適当なソースコードを載せることも出来ない(と言いつつミスったコード載せて修正をしていますが(^-^;) )し。
Visual Studio 2010が出る頃までに購入(個人の分)の足しになる程度の報酬は稼げないものでしょうか。


2009年6月21日日曜日

DataGridView セルの結合 2-5 コードまとめ

DataGridView セルの結合のここまでのソースコードを一旦まとめました。
問題点が多々あります。問題点を検討して書こうかと思いましたが、コードが長くなりましたので、今日はここまでのコードだけ載せておきます。
public class DgvMergebleTextBoxColumn {
  // 結合行数
  private int mergeRows = 1;
  // コンストラクタ
  public DgvMergebleTextBoxColumn() {
    this.CellTemplate = new DgvMergebleTextBoxCell();
  }
  // 結合行数を設定または取得します。
  public int MergeRows {
    get {
      return
      ((DgvMergebleTextBoxCell)this.CellTemplate).MergeRows;
    }
    set {
      if(this.mergeRows == value)
        return;
      //セルテンプレートの値を変更する
      ((DgvMergebleTextBoxCell)this.CellTemplate).MergeRows
      = value;
      //DataGridViewにすでに追加されているセルの値を変更する
      if(this.DataGridView == null)
        return;
      int rowCount = this.DataGridView.RowCount;
      for(int i = 0; i < rowCount; i++) {
        DataGridViewRow r = this.DataGridView.Rows.SharedRow(i);
        ((DgvMergebleTextBoxCell)r.Cells[this.Index]).MergeRows
        = value;
      }
    }
  }
  // MergeRowsプロパティを追加しているので、
  // Clone()をオーバーライドします。
  public override object Clone() {
    DgvMergebleTextBoxColumn col =
    (DgvMergebleTextBoxColumn)base.Clone();
    col.MergeRows = this.MergeRows;
    return col;
  }
}

public class DgvMergebleTextBoxCell : DataGridViewTextBoxCell {
  // 結合行数
  private int mergeRows = 1;
  public override object Clone() {
    DgvMergebleTextBoxCell cel =
    (DgvMergebleTextBoxCell)base.Clone();
    cel.MergeRows = this.MergeRows;
    return cel;
  }
  ///
  /// DataGridView 内でセルがいくつの行にまたがって表示されるかを
  /// 示す値を取得または設定します。
  ///
  public int MergeRows {
    get { return mergeRows; }
    set { mergeRows = value; }
  }
  // ホストされる編集コントロールの位置とサイズを設定します。
  public override void PositionEditingControl(
  bool setLocation, bool setSize,
  Rectangle cellBounds,
  Rectangle cellClip,
  DataGridViewCellStyle cellStyle,
  bool singleVerticalBorderAdded,
  bool singleHorizontalBorderAdded,
  bool isFirstDisplayedColumn,
  bool isFirstDisplayedRow) {
    // 結合するセルの高さに合わせるために、結合するセルの高さを足す
    Rectangle mergeCellBounds = cellBounds;
    for(int i = 1; i < mergeRows; i++) {
      if(RowIndex + i < this.DataGridView.Rows.Count) {
        mergeCellBounds.Height +=
        DataGridView.Rows.SharedRow(RowIndex + i).Height;
      }
    }
    if(RowIndex % mergeRows == 0) {
      base.PositionEditingControl(setLocation, setSize,
      mergeCellBounds,
      mergeCellBounds, cellStyle,
      singleVerticalBorderAdded,
      singleHorizontalBorderAdded,
      isFirstDisplayedColumn,
      isFirstDisplayedRow);
    } else {
      int row = (RowIndex / mergeRows) * mergeRows;
      for(int k = row; k < RowIndex; k++) {
        mergeCellBounds.Y -=
        DataGridView.Rows.SharedRow(k).Height;
      }
      base.PositionEditingControl(setLocation, setSize,
      mergeCellBounds,
      mergeCellBounds, cellStyle,
      singleVerticalBorderAdded,
      singleHorizontalBorderAdded,
      isFirstDisplayedColumn,
      isFirstDisplayedRow);
    }
  }
  public override void InitializeEditingControl(
  int rowIndex,
  object initialFormattedValue,
  DataGridViewCellStyle dataGridViewCellStyle) {
    base.InitializeEditingControl(rowIndex,
    initialFormattedValue,
    dataGridViewCellStyle);
    DataGridViewTextBoxEditingControl ctl =
    this.DataGridView.EditingControl
    as DataGridViewTextBoxEditingControl;
    // 結合された列の上端の行インデックスを算出しセルを取得
    int row = (rowIndex / mergeRows) * mergeRows;
    DataGridViewCell cell =
    this.DataGridView[ColumnIndex, row];
    // 初期化した編集コントロールに、
    // 結合された列の上端セルの値を代入
    ctl.Text = Convert.ToString(cell.Value);
  }
  protected override object GetValue(int rowIndex) {
    if(rowIndex % mergeRows == 0) {
      // 結合された列の上端行のセルの場合のみ値を返す
      return base.GetValue(rowIndex);
    } else {
      // 結合された列の上端行のセル以外の場合は、null値を返す
      return null;
    }
  }
  protected override bool SetValue(int rowIndex, object value) {
    if(this.DataGridView == null || rowIndex % mergeRows == 0) {
      // DataGridViewにまだ追加されていないセルの場合と
      // 結合された列の上端行セルの場合は通常通り値を設定します。
      return base.SetValue(rowIndex, value);
    } else {
      try {
        // 結合された列の上端行のインデックスを計算
        int topRowIndex = (rowIndex / mergeRows) * mergeRows;
        // DataGridViewにすでに追加されていて、結合された列の
        // 上端行以外のセルの場合、結合された列の上端行のセルに
        // 値を設定します。
        this.DataGridView[this.ColumnIndex,
        TopRowIndex(rowIndex)].Value = value;
        return true;
      } catch(Exception ex) {
        // 何らかのエラーが発生した場合は、falseを返します。
        return false;
      }
    }
  }
  protected override void Paint(Graphics graphics,
  Rectangle clipBounds,
  Rectangle cellBounds,
  int rowIndex,
  DataGridViewElementStates cellState,
  object value, object formattedValue,
  string errorText,
  DataGridViewCellStyle cellStyle,
  DataGridViewAdvancedBorderStyle advancedBorderStyle,
  DataGridViewPaintParts paintParts) {
    Rectangle rect = cellBounds;
    int topRowIndex = TopRowIndex(rowIndex);
    bool isPrint = true;
    object topFormattedValue = null;
    object topValue = null;
    bool cellSelected = false;
    for(int i = 0; i < mergeRows; i++) {
      // 端数行がある場合、マージ行数より少ない行でも表示を
      // 行うために結合行数以下で切り上げる
      if(topRowIndex + i >= this.DataGridView.Rows.Count) {
        break;
      }
      DataGridViewCell cell =
      this.DataGridView[ColumnIndex, topRowIndex + i];
      if(i == 0) {
        // 結合行の最上端セルの値を取得
        topFormattedValue = cell.FormattedValue;
        topValue = cell.Value;
      }
      if(!cell.Equals(this) && !cell.OwningRow.IsNewRow) {
        rect.Height += cell.OwningRow.Height;
      }
      // 描画矩形のY座標を、結合行の最上端セルの座標にあわせる
      if(i < (rowIndex % mergeRows))
        rect.Y -= cell.OwningRow.Height;
      // 結合されたセルのグループ内に選択されたセルがあるか確認
      if((cell.State & DataGridViewElementStates.Selected) != 0)
        cellSelected = true;
    }
    // グループ内に選択されたセルがあった場合は、
    // UIの状態を選択状態にする。
    if(cellSelected)
      cellState |= DataGridViewElementStates.Selected;
    // マージしていることになっている他のセルも
    // 書き直す必要があるためDataGridViewのGraphicsを取得
    Graphics g = this.DataGridView.CreateGraphics();
    // Graphics オブジェクト、描画矩形を設定して描画する。
    base.Paint(g, rect, rect, rowIndex, cellState, topValue,
    topFormattedValue, errorText, cellStyle,
    advancedBorderStyle, paintParts);
  }
  protected override void OnKeyDown(KeyEventArgs e, int rowIndex) {
    // カレントセルにする行Index
    int curIndex = -1;
    // 列ヘッダの場合は、基底クラスに任す
    if(rowIndex >= 0) {
      // ↑キー
      if(e.KeyCode == Keys.Up) {
        if(e.Control) {
          // Ctrlキーが押下されていた場合は
          // 最初の行をカレントセルに設定
          curIndex = 0;
        } else {
          if(TopRowIndex(rowIndex) > 0) {
            // 一つ上の結合セルのグループの上端の行を
            // カレントセルのIndexに設定
            curIndex =
            TopRowIndex(TopRowIndex(rowIndex) - mergeRows);
          } else {
            // 自身が一番上の結合グループのセルの場合は、
            // 自身の結合グループの上端の行(Index=0)を
            // カレントセルのIndexに設定
            curIndex = 0;
          }
        }
        // カレントセルを設定
        this.DataGridView.CurrentCell =
        this.DataGridView[ColumnIndex, curIndex];
        e.SuppressKeyPress = true;
      }
        // ↓キー
      else if(e.KeyCode == Keys.Down) {
        if(e.Control) {
          // Ctrlキーが押下されていた場合は最後の行を
          // カレントセルのIndexに設定
          curIndex =
          TopRowIndex(this.DataGridView.RowCount - 1);
        } else {
          if(TopRowIndex(rowIndex) + mergeRows
          <= this.DataGridView.RowCount) {
            // 自身のセルの属する結合グループより下に、
            // 行が存在する場合、自身のグループの下端の
            // 次の行をカレントセルのIndexに設定
            curIndex =
            TopRowIndex(TopRowIndex(rowIndex) + mergeRows);
          } else {
            // 自身のセルの属する結合グループより下に、
            // 行がない場合は、自身のグループの中の下端の行を
            // カレントセルのIndexに設定。
            // MergeRow分の行がない可能性もあるので、
            // DataGridViewの下端のIndexを設定している。
            curIndex =
            TopRowIndex(this.DataGridView.RowCount - 1);
          }
        }
        // カレントセルを設定
        this.DataGridView.CurrentCell
        = this.DataGridView[ColumnIndex, curIndex];
        e.SuppressKeyPress = true;
      }
    } else {
      // ↑↓キー以外は通常の処理へ
      base.OnKeyDown(e, rowIndex);
    }
  }
  // 現在のセルが所属する結合セルのグループの
  // 上端のセルのインデックスを返します。
  private int TopRowIndex(int rowIndex) {
    return (rowIndex / mergeRows) * mergeRows;
  }
}

次回は、スクロール時の描画の問題点をはじめ多々ある問題点の洗い出しなどをしたいと思っています。

2009年6月16日火曜日

「ジェネリック・クラスってすごい」のソース変でした

昨日のエントリー「ジェネリック・クラスってすごい」に書いたコードがおかしくなっていました。
<>の処理をせずに<PRE>タグで囲んでいたため<>の中身がごっそり抜けていました。
正しくは下記のとおりです。

Dictionary<string, Dictionary<string, string>> dicLanguage =
new Dictionary<string, Dictionary<string, string>>();
Dictionary<string, string> dicJapanese =
new Dictionary<string, string>();
Dictionary<string, string> dicEnglish =
new Dictionary<string, string>();
Dictionary<string, string> dicGerman =
new Dictionary<string, string>();

dicJapanese.Add("朝", "おはよう");
dicJapanese.Add("昼", "こんにちは");
dicJapanese.Add("夜", "こんばんは");
dicEnglish.Add("朝", "Good morning");
dicEnglish.Add("昼", "Hello");
dicEnglish.Add("夜", "Good evening");
dicGerman.Add("朝", "Guter Morgen");
dicGerman.Add("昼", "Guten Tag");
dicGerman.Add("夜", "Guter Abend");
dicLanguage.Add("日本語", dicJapanese);
dicLanguage.Add("英語", dicEnglish);
dicLanguage.Add("ドイツ語", dicGerman);
foreach (string key in dicLanguage.Keys)
{
foreach (string key2 in dicLanguage[key].Keys)
{
Console.WriteLine("{0} : {1} : {2}",
key, key2, dicLanguage[key][key2]);
}
}

失礼いたしました。


2009年6月15日月曜日

ジェネリック・クラスってすごい

当エントリーは、ソースコードのコピーの際、ミスをしているため書かれているソースコードは正しくありません。
正しくは
明日のエントリーに書き直しています。


今日、Dictionary<TKey, TValue> クラス を使っていて、こんなことが出来る事に気がつきました。 Dictionaryの値にDictionaryを入れることが出来てしまうのです。

ソース

Dictionary dicLanguage =
    new Dictionary();
Dictionary dicJapanese = new Dictionary();
Dictionary dicEnglish = new Dictionary();
Dictionary dicGerman = new Dictionary();

dicJapanese.Add("朝", "おはよう");
dicJapanese.Add("昼", "こんにちは");
dicJapanese.Add("夜", "こんばんは");
dicEnglish.Add("朝", "Good morning");
dicEnglish.Add("昼", "Hello");
dicEnglish.Add("夜", "Good evening");
dicGerman.Add("朝", "Guter Morgen");
dicGerman.Add("昼", "Guten Tag");
dicGerman.Add("夜", "Guter Abend");
dicLanguage.Add("日本語", dicJapanese);
dicLanguage.Add("英語", dicEnglish);
dicLanguage.Add("ドイツ語", dicGerman);
foreach (string key in dicLanguage.Keys)
{
foreach (string key2 in dicLanguage[key].Keys)
{
Console.WriteLine("{0} : {1} : {2}",
key, key2, dicLanguage[key][key2]);
}
}

出力結果
日本語 : 朝 : おはよう
日本語 : 昼 : こんにちは
日本語 : 夜 : こんばんは
英語 : 朝 : Good morning
英語 : 昼 : Hello
英語 : 夜 : Good evening
ドイツ語 : 朝 : Guter Morgen
ドイツ語 : 昼 : Guten Tag
ドイツ語 : 夜 : Guter Abend

僕だけが知らなかったのかもしれませんが、ちょっと感激しました。
List<T> クラスでもList<List<string>> みたいなことも出来ました。
何に使えるかは直ぐには思いつきませんが、取りあえず覚えていて損はないかと...。


 


2009年6月14日日曜日

C# DataGridView セルの結合 その5

今回はキーボードでのセル移動の部分を考えました。
protected override void OnKeyDown(KeyEventArgs e, int rowIndex) {
  // カレントセルにする行Index
  int curIndex = -1;
  // 列ヘッダの場合は、基底クラスに任す
  if(rowIndex >= 0) {
    // ↑キー
    if(e.KeyCode == Keys.Up) {
      if(e.Control) {
        // Ctrlキーが押下されていた場合は
        // 最初の行をカレントセルに設定
        curIndex = 0;
      } else {
        if(TopRowIndex(rowIndex) > 0) {
          // 一つ上の結合セルのグループの上端の行を
          // カレントセルのIndexに設定
          curIndex =
          TopRowIndex(TopRowIndex(rowIndex) - mergeRows);
        } else {
          // 自身が一番上の結合グループのセルの場合は、
          // 自身の結合グループの上端の行(Index=0)を
          // カレントセルのIndexに設定
          curIndex = 0;
        }
      }
      // カレントセルを設定
      this.DataGridView.CurrentCell =
      this.DataGridView[ColumnIndex, curIndex];
      e.SuppressKeyPress = true;
    }
      // ↓キー
    else if(e.KeyCode == Keys.Down) {
      if(e.Control) {
        // Ctrlキーが押下されていた場合は最後の行を
        // カレントセルのIndexに設定
        curIndex =
        TopRowIndex(this.DataGridView.RowCount - 1);
      } else {
        if(TopRowIndex(rowIndex) + mergeRows
        <= this.DataGridView.RowCount) {
          // 自身のセルの属する結合グループより下に、
          // 行が存在する場合、自身のグループの下端の
          // 次の行をカレントセルのIndexに設定
          curIndex =
          TopRowIndex(TopRowIndex(rowIndex) + mergeRows);
        } else {
          // 自身のセルの属する結合グループより下に、
          // 行がない場合は、自身のグループの中の下端の行を
          // カレントセルのIndexに設定。
          // MergeRow分の行がない可能性もあるので、
          // DataGridViewの下端のIndexを設定している。
          curIndex =
          TopRowIndex(this.DataGridView.RowCount - 1);
        }
      }
      // カレントセルを設定
      this.DataGridView.CurrentCell
      = this.DataGridView[ColumnIndex, curIndex];
      e.SuppressKeyPress = true;
    }
  } else {
    // ↑↓キー以外は通常の処理へ
    base.OnKeyDown(e, rowIndex);
  }
}

コードをみて、アレッ?て感じた方が多いのではないでしょうか。Shiftキーを処理していません。
MultiSelectを無視しちゃっています。
ここで、中途半端に処理しちゃうととんでもない方向に向いて行ってしまいそうなので、敢えて無視しました。取りあえず、限定した使用方法では使えるかなぁって感じにだけ収めました。
次回にでも、ここまでを纏めて、問題点などを改めて考えてみたいと思っています。

2009年6月8日月曜日

C# DataGridView セルの結合 その4、コード変更

その4」の描画部分のコードに問題がありました。
結合セル内で、キーボードで「UP」キーを押下すると、結合セルからフォーカスが抜けていない時でも、表示が選択状態でなくなってしまいます。
上記操作をした場合新しい選択セルの描画->元のセルの描画の順にイベントが起こるためこのような感じになります。
結合セルのうグループ内で選択されたセルがある場合、選択状態(規定では青色)描画するようにしました。
protected override void Paint(Graphics graphics,
  Rectangle clipBounds,
  Rectangle cellBounds,
  int rowIndex,
  DataGridViewElementStates cellState,
  object value, object formattedValue,
  string errorText,
  DataGridViewCellStyle cellStyle,
  DataGridViewAdvancedBorderStyle advancedBorderStyle,
  DataGridViewPaintParts paintParts) {
  Rectangle rect = cellBounds;
  int topRowIndex = TopRowIndex(rowIndex);
  bool isPrint = true;
  object topFormattedValue = null;
  object topValue = null;
  bool cellSelected = false;  // <---- ★追加
  for(int i = 0; i < mergeRows; i++) {
    // 端数行がある場合、マージ行数より少ない行でも表示を
    // 行うために結合行数以下で切り上げる
    if(topRowIndex + i >= this.DataGridView.Rows.Count) {
      break;
    }
    DataGridViewCell cell =
    this.DataGridView[ColumnIndex, topRowIndex + i];
    if(i == 0) {
      // 結合行の最上端セルの値を取得
      topFormattedValue = cell.FormattedValue;
      topValue = cell.Value;
    }
    if(!cell.Equals(this) && !cell.OwningRow.IsNewRow) {
      rect.Height += cell.OwningRow.Height;
    }
    // 描画矩形のY座標を、結合行の最上端セルの座標にあわせる
    if(i < (rowIndex % mergeRows))
      rect.Y -= cell.OwningRow.Height;
    // ★以下2行追加
    // 結合されたセルのグループ内に選択されたセルがあるか確認
    if((cell.State & DataGridViewElementStates.Selected) != 0)
      cellSelected = true;
  }
  // ★以下2行追加
  // グループ内に選択されたセルがあった場合は、
  // UIの状態を選択状態にする。
  if(cellSelected)
    cellState |= DataGridViewElementStates.Selected;

  // マージしていることになっている他のセルも
  // 書き直す必要があるためDataGridViewのGraphicsを取得
  Graphics g = this.DataGridView.CreateGraphics();
  // Graphics オブジェクト、描画矩形を設定して描画する。
  base.Paint(g, rect, rect, rowIndex, cellState, topValue,
  topFormattedValue, errorText, cellStyle,
  advancedBorderStyle, paintParts);
}

次は、キーボード操作でのセル移動を考えるつもりです。その過程で、上記描画部のコードを更に変更する可能性もありますが、とりあえずここまでのコードで、辻褄が合うようにしました。

2009年6月5日金曜日

C# DataGridView セルの結合 その4

前回の続きです。「ここ」に「C# DataGridView セルの結合 その3」で書き忘れていたコードがありましたので追加しています。
今回は、描画の部分を考えました。」
「DgvMergebleTextBoxCell」クラスでPaintメソッドをオーバーライドしています。
結合されたどのセルを選択されても、結合セルのグループの上端セルの値を取ってきて表示するようにしています。
protected override void Paint(Graphics graphics,
  Rectangle clipBounds,
  Rectangle cellBounds,
  int rowIndex,
  DataGridViewElementStates cellState,
  object value, object formattedValue,
  string errorText,
  DataGridViewCellStyle cellStyle,
  DataGridViewAdvancedBorderStyle advancedBorderStyle,
  DataGridViewPaintParts paintParts) {

  Rectangle rect = cellBounds;
  int topRowIndex = TopRowIndex(rowIndex);
  bool isPrint = true;
  object topFormattedValue = null;
  object topValue = null;
  for(int i = 0; i < mergeRows; i++) {
    // 端数行がある場合、マージ行数より少ない行でも表示を
    // 行うために結合行数以下で切り上げる
    if(topRowIndex + i >= this.DataGridView.Rows.Count) {
      break;
    }
    DataGridViewCell cell =
    this.DataGridView[ColumnIndex, topRowIndex + i];
    if(i == 0) {
      // 結合行の最上端セルの値を取得
      topFormattedValue = cell.FormattedValue;
      topValue = cell.Value;
    }
    if(!cell.Equals(this) && !cell.OwningRow.IsNewRow) {
      rect.Height += cell.OwningRow.Height;
    }
    // 描画矩形のY座標を、結合行の最上端セルの座標にあわせる
    if(i < (rowIndex % mergeRows))
      rect.Y -= cell.OwningRow.Height;
  }
  // マージしていることになっている他のセルも
  // 書き直す必要があるためDataGridViewのGraphicsを取得
  Graphics g = this.DataGridView.CreateGraphics();
  // Graphics オブジェクト、描画矩形を設定して描画する。
  base.Paint(g, rect, rect, rowIndex, cellState, topValue,
  topFormattedValue, errorText, cellStyle,
  advancedBorderStyle, paintParts);
}

こんな感じになります。





それっぽくなりましたが、幾つか問題があります。実際に動かしてテストしてみれば、すぐに見つけることが出来ます。徐々に修正していきたいと思います。
ここまでは、大雑把感じで書けてきましたが、ここから結構細かいところに気をつけていかなければならないような気がします。とりあえず行ける所まで行きたいと思います。

2009年6月4日木曜日

C# DataGridView セルの結合 その3の追加

05/30のエントリー「DataGridView セルの結合 その3」見ていたら、ソースに書き忘れを見つけました。
SetValueをオーバーライドしているところで、「TopRowIndex」メソッドが使われていますが、これは別途定義したものです。
結合したセルの上端のインデックスを計算しているメソッドです。以下のコードです。
// 現在のセルが所属する結合セルのグループの
// 上端のセルのインデックスを返します。
private int TopRowIndex(int rowIndex) {
  return (rowIndex / mergeRows) * mergeRows;
}

2009年5月30日土曜日

C# DataGridView セルの結合 その3

C# DataGridView セルの結合 その2」の続きです。
結合されたセルの選択時に結合前の各セルの値が編集コントロールにセットされる問題が残していました。Excelなどでセルを結合させると左上端のセルの値を保持し、それ以外のセルの値は空にしているのに倣おうと思います。つまり、結合されたセル範囲で上端セルに値を保持します。編集は、そのセルに対して編集処理を行います。
C# DataGridView セルの結合 その2」の「DgvMergebleTextBoxCell」クラスに追加します。
public override void InitializeEditingControl(
  int rowIndex,
  object initialFormattedValue,
  DataGridViewCellStyle dataGridViewCellStyle) {

  base.InitializeEditingControl(rowIndex,
  initialFormattedValue,
  dataGridViewCellStyle);
  DataGridViewTextBoxEditingControl ctl =
  this.DataGridView.EditingControl
  as DataGridViewTextBoxEditingControl;
  // 結合された列の上端の行インデックスを算出しセルを取得
  int row = (rowIndex / mergeRows) * mergeRows;
  DataGridViewCell cell =
  this.DataGridView[ColumnIndex, row];
  // 初期化した編集コントロールに、
  // 結合された列の上端セルの値を代入
  ctl.Text = Convert.ToString(cell.Value);
}

上記コードは、編集コントロールの初期化時に、結合セル範囲の上端セルの値を編集コンロロールの編集初期値として設定するようにオーバーライドしています。
protected override object GetValue(int rowIndex) {
  if(rowIndex % mergeRows == 0) {
    // 結合された列の上端行のセルの場合のみ値を返す
    return base.GetValue(rowIndex);
  } else {
    // 結合された列の上端行のセル以外の場合は、null値を返す
    return null;
  }
}
protected override bool SetValue(int rowIndex, object value) {
  if(this.DataGridView == null || rowIndex % mergeRows == 0) {
    // DataGridViewにまだ追加されていないセルの場合と
    // 結合された列の上端行セルの場合は通常通り値を設定します。
    return base.SetValue(rowIndex, value);
  } else {
    try {
      // 結合された列の上端行のインデックスを計算
      int topRowIndex = (rowIndex / mergeRows) * mergeRows;
      // DataGridViewにすでに追加されていて、結合された列の
      // 上端行以外のセルの場合、結合された列の上端行のセルに
      // 値を設定します。
      this.DataGridView[this.ColumnIndex,
      TopRowIndex(rowIndex)].Value = value;
      return true;
    } catch(Exception ex) {
      // 何らかのエラーが発生した場合は、falseを返します。
      return false;
    }
  }
}

続いて、結合セル範囲の上端セルに値が集中するようにしました。また、その他のセルでは、空の値を返すように、基本クラスのメソッドをオーバーライドしました。

今日はここまでにします。見た目的に、あまり変化がなく面白くないので、次回は表示に関する描画部を考えて見ようと思っています。

2009年5月29日金曜日

C# DataGridView セルの結合 その2

「その1」で書いた分にフォーカスが入った場合の描画処理を追加が考えていたより厄介なようです。
手間が掛かるのであればと言う事で、DataGridViewTextBoxColumnを拡張して結合可能セルの作成に挑戦する事にしました。
と言っても、本当の意味での結合をさせるには、セルを拡張するのではなくDataGridView自体を拡張を行う必要がある事は明らかですが、ハッキリ言って私には荷が重過ぎます(キッパリ)。
取りあえず、編集可能で見た目はセルが結合されているように見えるものにはしたいと思います。
先ずは、行の結合が出来るものから少しずつ...(列の結合は行の結合が完成したら...)。
アドバイス大歓迎ですので、あれば下さい。

DataGridViewTextBoxColumnとDataGridViewTextBoxCellを継承して拡張します。
DataGridViewTextBoxEditingControl に関しては、今のところは拡張せずに使う予定です。

一気には出来ませんので、今回は編集時に結合されているように編集コントロール(TextBox)のサイズを合わせて表示するようにします。
public class DgvMergebleTextBoxColumn : DataGridViewTextBoxColumn{
  // 結合行数
  private int mergeRows = 1;
  // コンストラクタ
  public DgvMergebleTextBoxColumn() {
    this.CellTemplate = new DgvMergebleTextBoxCell();
  }
  // 結合行数を設定または取得します。
  public int MergeRows {
    get {
      return
      ((DgvMergebleTextBoxCell)this.CellTemplate).MergeRows;
    }
    set {
      if(this.mergeRows == value)
        return;
      //セルテンプレートの値を変更する
      ((DgvMergebleTextBoxCell)this.CellTemplate).MergeRows
      = value;
      //DataGridViewにすでに追加されているセルの値を変更する
      if(this.DataGridView == null)
        return;
      int rowCount = this.DataGridView.RowCount;
      for(int i = 0; i < rowCount; i++) {
        DataGridViewRow r = this.DataGridView.Rows.SharedRow(i);
        ((DgvMergebleTextBoxCell)r.Cells[this.Index]).MergeRows
        = value;
      }
    }
  }
  // MergeRowsプロパティを追加しているので、
  // Clone()をオーバーライドします。
  public override object Clone() {
    DgvMergebleTextBoxColumn col =
    (DgvMergebleTextBoxColumn)base.Clone();
    col.MergeRows = this.MergeRows;
    return col;
  }
}

public class DgvMergebleTextBoxCell : DataGridViewTextBoxCell {
  // 結合行数
  private int mergeRows = 1;
  public override object Clone() {
    DgvMergebleTextBoxCell cel =
    (DgvMergebleTextBoxCell)base.Clone();
    cel.MergeRows = this.MergeRows;
    return cel;
  }
  /// 

  /// DataGridView 内でセルがいくつの行にまたがって表示されるかを
  /// 示す値を取得または設定します。
  /// 

  public int MergeRows {
    get { return mergeRows; }
    set { mergeRows = value; }
  }
  // ホストされる編集コントロールの位置とサイズを設定します。
  public override void PositionEditingControl(
  bool setLocation, bool setSize,
  Rectangle cellBounds,
  Rectangle cellClip,
  DataGridViewCellStyle cellStyle,
  bool singleVerticalBorderAdded,
  bool singleHorizontalBorderAdded,
  bool isFirstDisplayedColumn,
  bool isFirstDisplayedRow) {
    // 結合するセルの高さに合わせるために、結合するセルの高さを足す
    Rectangle mergeCellBounds = cellBounds;
    for(int i = 1; i < mergeRows; i++) {
      if(RowIndex + i < this.DataGridView.Rows.Count) {
        mergeCellBounds.Height +=
        DataGridView.Rows.SharedRow(RowIndex + i).Height;
      }
    }
    if(RowIndex % mergeRows == 0) {
      base.PositionEditingControl(setLocation, setSize,
      mergeCellBounds,
      mergeCellBounds, cellStyle,
      singleVerticalBorderAdded,
      singleHorizontalBorderAdded,
      isFirstDisplayedColumn,
      isFirstDisplayedRow);
    } else {
      int row = (RowIndex / mergeRows) * mergeRows;
      for(int k = row; k < RowIndex; k++) {
        mergeCellBounds.Y -=
        DataGridView.Rows.SharedRow(k).Height;
      }
      base.PositionEditingControl(setLocation, setSize,
      mergeCellBounds,
      mergeCellBounds, cellStyle,
      singleVerticalBorderAdded,
      singleHorizontalBorderAdded,
      isFirstDisplayedColumn,
      isFirstDisplayedRow);
    }
  }
}

以下のようにデータを入れてテストしています
int num = 1;
for (int i = 0; i < 9; i++) {
  dataGridView1.Rows.Add(num++, num++);
}

2列目に先に書いた「DgvMergebleTextBoxColumn」を設定し1行目を編集状態にした図です。

キャプチャするときに消えてしまっていますが、値「2」の後ろにキャレットが点滅しています(編集状態)


実際に動かすと分かると思いますが、選択したセルの値を編集することになり、問題があります。
次回はその点を改善したいと思います。

2009年5月21日木曜日

C# DataGridView セルの結合 その1

DataGridViewでセルを結合にチャレンジしてみました。
まずは見た目だけのセル結合です。ラベルのように表示するだけであれば、この方法で十分だと思います。
セルの描画部を処理するだけです。


上記図は、以下の4列のDataGridViewTextBoxColumnを追加し以下のコードでデータを挿入しています。
int num = 1;
for (int i = 0; i < 10; i++)
dataGridView1.Rows.Add(num++, num++, num++, num++);

これを基に、1列目(Column1)では2行を結合し1セルとします。次に1行おきに2列目、3列目を結合します。


結果このような感じになります。
以下のように「CellPainting」イベントを処理しています。
コードを見ていただければわかりますが大した事はしていません。シコシコと描画処理をしているだけです。

private void dataGridView1_CellPainting(object sender, 
 DataGridViewCellPaintingEventArgs e) {
  DataGridView dv = (DataGridView)sender;
  // 行・列共にヘッダは処理しない
  if(e.RowIndex < 0 || e.ColumnIndex < 0)
    return;

  Rectangle rect;
  DataGridViewCell cell;
  // 1列目の処理
  if(e.ColumnIndex == 0) {
    rect = e.CellBounds;
    // 奇数行(1,3,5..行目 = RowIndexは0,2,4..)
    if(e.RowIndex % 2 == 0) {
      cell = dataGridView1[e.ColumnIndex, e.RowIndex + 1];
      //一つ下の次のセルの高さを足す
      rect.Height += cell.Size.Height;
    }
      // 偶数行の処理
    else {
      cell = dataGridView1[e.ColumnIndex, e.RowIndex - 1];
      // 一つ上のセルの高さを足し、矩形の座標も一つ上のセルに合わす
      rect.Height += cell.Size.Height;
      rect.Y -= cell.Size.Height;
    }
    // セルボーダーライン分矩形の位置を補正
    rect.X -= 1;
    rect.Y -= 1;
    // 背景、セルボーダーライン、セルの値を描画
    e.Graphics.FillRectangle(
    new SolidBrush(e.CellStyle.BackColor), rect);
    e.Graphics.DrawRectangle(
    new Pen(dv.GridColor), rect);
    TextRenderer.DrawText(e.Graphics,
    cell.FormattedValue.ToString(),
    e.CellStyle.Font, rect, e.CellStyle.ForeColor,
    TextFormatFlags.HorizontalCenter
    | TextFormatFlags.VerticalCenter);
    // イベント ハンドラ内で処理を行ったことを通知
    e.Handled = true;
  }
    // 2列目と3列目の結合処理
  else if(e.ColumnIndex == 1) {
    // 奇数行のみ列結合
    if(e.RowIndex % 2 == 0) {
      rect = e.CellBounds;
      cell = dataGridView1[e.ColumnIndex + 1, e.RowIndex];
      // 一つ右のセルの幅を足す
      rect.Width += cell.Size.Width;
      rect.X -= 1;
      rect.Y -= 1;
      e.Graphics.FillRectangle(
      new SolidBrush(e.CellStyle.BackColor), rect);
      e.Graphics.DrawRectangle(new Pen(dv.GridColor), rect);
      TextRenderer.DrawText(e.Graphics,
      e.FormattedValue.ToString(),
      e.CellStyle.Font, rect, e.CellStyle.ForeColor,
      TextFormatFlags.HorizontalCenter
      | TextFormatFlags.VerticalCenter);
      e.Handled = true;
    } else {
      // 2列目の偶数行は、結合を行わないので、通常の描画処理に任せる
      e.Paint(e.ClipBounds, e.PaintParts);
    }
  } else {
    // 3列目の奇数行は描画処理をせずに、
    // イベントハンドラ内で処理を完了したこと通知
    if(e.RowIndex % 2 == 0 && e.ColumnIndex == 2)
      e.Handled = true;
  }
}

正しくは、値の描画の処で「e.CellStyle.Alignment」を確認すべきですが、ここでは簡単にするためにセンターに描画しています。

他に、セルをReadOnlyにする処理や、結合セルのフォーカスがある場合の描画処理、キー入力でのフォーカスの移動の処理が必要です。

上記、未実装の部分は別の日に考えます。あしからず...

2009年5月2日土曜日

C# 超簡易パケット(メール)中継・ウォッチプログラム

ちょっとしたプログラムを作ってみました。
今、メール機能を含むソフトを考えていますので、そのソフトのテストに使えるのではと思い作りました。

機能的には、PC上のクライアントプログラムとサーバ間に流れるパケットデータを覗き見することができます。覗き見と言うと隠れて密かに見るようですが、そこまでの機能はありません。
どちらかと言うとパケットデータを中継し、パケットの内容を表示する感じのイメージです。
クライアントプログラムの接続先として当プログラムを設定し、当プログラムから実際の接続先へ接続します。反対にサーバーからの返答も当プログラムが一旦受信してからクライアントプログラムに送信します。

とりあえず簡単なプログラムなので、実際に動かしてみてください。

まずは、ウインドウのデザインです。

・主な規定値から変更したプロパティ
 portNumericUpDown
            Minimum : 0
            Maximum : 65535
 stopButton
            Enabled : false
 messageTextBox
            Anchor : Top, Bottom, Left, Right

まず参照(using ディレクティブ)部です。
using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;

次は変数宣言部です。
// スレッド停止通知用フラグ
bool stop = true;
// プリフィックス[inner]がクライアント方向用
// プリフィックス[outer]がサーバー方向用

// クライアント接続
TcpClient innerClient = null;
TcpClient outerClient = null;
// リスナー設定
TcpListener innerListener = null;
// 読み書き用ストリーム
NetworkStream innerStream = null;
NetworkStream outerStream = null;
// スレッド
Thread innerThread = null;
Thread outerThread = null;
// スレッド間でTextBoxへの文字列出力のためのデリゲート
delegate void SetTextCallback(string text);
// 接続先ホスト名を保持する
string host;
// 接続先ポート番号を保持する
int port;
コメントとして「クライアント方向」「サーバー方向」と書いていますが、クライアント方向は、OutlookなどのPC内のソフトとの送受信を担当するという意味です。反対にサーバー方向はメールサーバーなどのインターネット側、つまりPCの外側を担当します。

コンストラクタでは、今回は特に何もしていないので飛ばしますが、必要に応じてホスト名やポート番号の初期値をセットする部分を入れてください。
コントロールイベント部です。
//
// 接続監視を始める
//
private void startButton_Click(object sender, EventArgs e)
{
stop = false;
host = hostTextBox.Text;
port = (int)portNumericUpDown.Value;
stopButton.Enabled =
!(startButton.Enabled = hostTextBox.Enabled =
portNumericUpDown.Enabled = false);
innerListener = new TcpListener(IPAddress.Any, port);
// クライント方向のスレッドを開始
innerThread = new Thread(new ThreadStart(Inner));
innerThread.Start();
}

//
// 接続監視および中継を中止する
//
private void stopButton_Click(object sender, EventArgs e)
{
stop = true;

stopButton.Enabled =
!(startButton.Enabled = hostTextBox.Enabled =
portNumericUpDown.Enabled = true);
Abort();
SetText("--待機中止--");

}
//
// 接続待ち状態であれば、スレッドを止めてかウインドウを閉じる
//
private void Form1_FormClosing(object sender,
FormClosingEventArgs e)
{
if (!stop) Abort();
}
「startButton_Click」で接続待ち状態に入り受信内容を読み取る「Inner」メソッドを別スレッドとして開始します。「stopButton_Click」では、処理を行っているスレッドを終了させます。「Form1_FormClosing」は、フォームを閉じる前に、別スレッドの処理が完了しているか確認を行っています。
次は、このプログラムの中心機能部分である、データの送受信部分です。
//
// クライアント方向に対する処理
//
private void Inner()
{
try
{
// 接続待機
innerListener.Start();
SetText("--接続待機中--");
innerClient = innerListener.AcceptTcpClient();
// 接続されたら、サーバー方向接続を開始
StartOuter();
//接続
SetText("--接続されました--");
innerStream = innerClient.GetStream();

Byte[] bytes = new Byte[1024];
int i;
// 停止フラグがtrueに変更されたら処理を抜ける
while (!stop)
{
// Console.WriteLine(innerClient.Connected);
try
{
if ((i = innerStream.Read(bytes, 0, bytes.Length)) != 0)
{
// ホストにデータを流す
outerStream.Write(bytes, 0, i);
// TextBoxに中継したデータを追加する
String data =
System.Text.Encoding.UTF8.GetString(bytes, 0, i);
SetText(String.Format("C: {0}", data));
}
}
catch(IOException)
{
break;
}
}
// 閉じる
innerStream.Close();
innerClient.Close();
stop = true;
}
catch (ThreadAbortException)
{
// 何もしない
;
}
catch (SocketException)
{
// 何もしない
;
}
// 再度接続待ちをはじめる
ReStart();
}

//
// ホスト側処理のスレッドを実行する
//
private void StartOuter()
{
outerThread = new Thread(new ThreadStart(Outer));
outerThread.Name = "OUTER";
outerThread.Start();
}

//
// ホスト方向に対する処理
//
private void Outer()
{
try
{
outerClient = new TcpClient();
outerClient.Connect(host, port);
outerStream = outerClient.GetStream();
SetText("--ホストに接続しました--");
Byte[] bytes = new Byte[1024];
int i;

//メッセージを受信
while (!stop)//!stop && outerStream.CanRead)
{
if ((i = outerStream.Read(bytes, 0, bytes.Length)) != 0)
{
// クラインとデータを流す
innerStream.Write(bytes, 0, i);
// TextBoxに中継したデータを追加する
String data =
System.Text.Encoding.UTF8.GetString(bytes, 0, i);
SetText(String.Format("S: {0}", data));
}
else
{
break;
}
}
// 閉じる
outerStream.Close();
outerClient.Close();
stop = true;
}
catch (ThreadAbortException)
{
// 何もしない
;
}
catch (SocketException)
{
// 何もしない
;
}
}
「Inner」では、クライントプログラムからの接続を待ち、接続要求を受け付けた後は、サーバー側の接続を開始し、クライアント側の受信ストリームからデータを読み込み、サーバー側にデータを流し同時にフォームのTextBoxに、データを文字列に変換し追加しています。「StartOuter」でサーバー側の接続を受け持つ「Outer」メソッドを別スレッド開始います。「Outer」ではサーバー側に接続し、サーバー側の返答を受信しクライアントにデータを流し同時にフォームのTextBoxに、データを文字列に変換し追加しています。
残りのメソッドです。
//
// 再度接続待ちに入る
//
private void ReStart()
{
stop = false;
SetText("再スタート");
innerThread = new Thread(new ThreadStart(Inner));
innerThread.Start();
}

//
// TextBoxに表示する
//
private void SetText(string text)
{
if (this.messageTextBox.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.messageTextBox.AppendText(text + "\r\n");
}
}

//
// 通信用スレッドを終了させる
//
private void Abort()
{
if (outerThread != null && outerThread.IsAlive)
{
outerThread.Abort();
outerThread.Join();
}
innerListener.Stop();
if (innerThread.IsAlive)
{
innerThread.Abort();
innerThread.Join();
}
// 念のため
if (outerStream != null) outerStream.Close();
if (outerClient != null) outerClient.Close();
if (innerStream != null) innerStream.Close();
if (innerClient != null) innerClient.Close();
}
「ReStart」は、送受信完了後の再接続待ちに入るためのメソッドです。「SetText」は、送受信を行っている別スレッドからデリゲートを介して、フォーム上のTextBoxに文字列を追加するものです。「Abort」は、強制的に送受信を行っている別スレッドを止めるためのメソッドです。
以下の図は、Outlookで送信メールサーバーを「127.0.0.1」に設定し、今回作成したしたプログラムのホスト欄に実際のサーバー名を入力し、Outlookの「アカウント設定のテスト」を行った時の実際の送信内容を中継しているところです。



2009年4月23日木曜日

「光センサー液晶パッド」搭載、シャープ メビウス

先日、モニター募集をしていたシャープの新メビウスが発表されました
「光センサー液晶パッド」搭載、つまり「タッチパッド」の部分がタッチパネルになっていて、パソコン版「ニンテンドーDS」みたいな感じ。


実際、どんな活用方法があるのか今のところ考えつきませんが、タブレットPC用のソフトが使えそうなので、結構楽しいことになるかも。
タブレットPC用のプログラムは作った事はありませんが、同様のマシンが普及がタブレットPCの普及にも影響するかも。
実機に是非触れて見たいものです。今日発売開始なので、この週末見に行ってきます。
モニターに選ばないかなぁー。


参考ページ
シャープ、メビウス公式ページ


2009年4月20日月曜日

64bit OSへの移行時期

ITmediaに「64ビット版が選択できる武闘派ノート──「dynabook Qosmio GX/FX」」との記事が書かれていた。
SonyのVAIOにも64bitOSのものが出ている。BTOメーカーだけでなく普通のメーカーから発売され始めたことから、そろそろ移行時期なのかな。
低価格パソコンにもメモリ4G(32bit Windowsでは実際は3G超までしか利用されないらしい)が搭載されていることから、メーカーとしても新たなセールスポイント・価格維持のためにもメーカー主導で移行して行くのでしょうか。
Microsoft Office 14(次期バージョン)にも64bit版が用意されることからも近いと考えてもいいと思います。
Officeの件は別にして、Microsoftの煽りが少ないのなぜなんだろう?
次期Windows(Windows 7)のマシンを買う時には、個人的には移行のタイミングなのかなぁと、思っています。


参考ページ
Windows 7は最後の32ビットWindowsか
64 bit 版 Windows XP は 32 bit 版とどう違うか


 


2009年4月18日土曜日

C# NumericUpDownとDateTimePickerの表示内容を空に。

今日は、Tips的なものが浮かばないので、小技って言うよりC#の小ネタです。



  • NumericUpDownの表示内容を消す方法
    numericUpDown1.Text = string.Empty;

Visual Studioのインテリセンスでは表示されないので気が付いていない人もいるかと思いますが、「.Text」が使えます。
ただし、上記コードで表示は消えますが、「.Value」の値は変更されていませんので注意が必要です。




  • DateTimePickerの表示内容を消す方法
    dateTimePicker1.Format =
System.Windows.Forms.DateTimePickerFormat.Custom;
dateTimePicker1.CustomFormat = "g";

NumericUpDown同様内容は保持されたままです。
予めFormtプロパティをCustomにしておいて下記のように使えば良いかと。

    private void dateTimePicker1_KeyDown(object sender,
KeyEventArgs e)
{
DateTimePicker dp = (DateTimePicker)sender;
if (e.KeyCode == Keys.Delete)
{
dp.CustomFormat = "g";
}
else
{
dp.CustomFormat = "yyyy/MM/dd";
}
}

private void dateTimePicker1_ValueChanged(object sender,
EventArgs e)
{
DateTimePicker dp = (DateTimePicker)sender;
dp.CustomFormat = "yyyy/MM/dd";
}

どちらも、どうでもいいようなネタですが、必要な人もいるかも...
 


2009年4月17日金曜日

C# DataGridViewの列ヘッダを縦書きにする

シャープ・メビウスのモニター募集に応募してみたら、アンケートにブログに関することがあり、前に立ち上げたまま何も書かずにいたココを思い出したので、これをきっかけ書きけ始めようと思います。
いきなりネタがないので、メモ代りにプログラミングのコードを書いておくことにします。

C#でWindows Form上のDataGridViewの列ヘッダを縦書きにするコードです。
ColumHeaderDefaultCellStyleのGdiVerticalFontをtrueにしても縦書きにならないようなので...

dataGridView1をFormに配置し列を1列追加しした状態です。
dataGridView1のColumnHeaderHeightModeをAutoSize以外にして、ColumnHeaderHeightを縦書き文字が納まるだけ高さを設定しておきます。
dataGridView1のCellPaintingイベントに以下のように書きます。

private void dataGridView1_CellPainting(object sender,
DataGridViewCellPaintingEventArgs e)
{
DataGridView dv = (DataGridView)sender;
// 列ヘッダ
if (e.RowIndex < 0)
{
// 縦書き対象列か確認(1列目)
if (e.ColumnIndex == 0)
{
// 列ヘッダの背景と境界線を描画
e.Paint(e.ClipBounds,
DataGridViewPaintParts.Background
| DataGridViewPaintParts.Border);
StringFormat sf = new StringFormat();
//縦書きにする
sf.FormatFlags = StringFormatFlags.DirectionVertical
| StringFormatFlags.NoWrap;
//上詰め
sf.Alignment = StringAlignment.Near;
// 左右中央
sf.LineAlignment = StringAlignment.Center;
Rectangle rect = e.CellBounds;
// 境界線に文字が掛からないように、描画範囲を少し縮める。
rect.Inflate(-2, -2);
//縦書き文字の描画
string text = dv.Columns[e.ColumnIndex].HeaderText;
Font font = dv.ColumnHeadersDefaultCellStyle.Font;
Brush foreBrush = new SolidBrush(
dv.ColumnHeadersDefaultCellStyle.ForeColor);
e.Graphics.DrawString(text, font, foreBrush, rect, sf);
e.Handled = true;
}
}
}

こんな感じになります。



先に、ColumnHeaderHeightを縦書き文字が納まるだけ高さを設定しておくようにと書きましたが、実行時にヘッダ文字列を登録する場合は登録直後に、あらかじめ高さがわからない時はFormのLoadイベントに、以下のようにして高さを調整するコード書いておくとOKです。
Graphics graphics = dataGridView1.CreateGraphics();
Font font = dataGridView1.ColumnHeadersDefaultCellStyle.Font;
StringFormat sf = new StringFormat();
sf.FormatFlags = StringFormatFlags.DirectionVertical;
sf.Alignment = StringAlignment.Near;
sf.LineAlignment = StringAlignment.Center;
string s = dataGridView1.Columns["tateColumn"].HeaderText;
SizeF stringSize = graphics.MeasureString(s, font, 1000, sf);
if (dataGridView1.ColumnHeadersHeight < (int)stringSize.Height)
{
dataGridView1.ColumnHeadersHeight = (int)stringSize.Height + 5;
}


dataGridView1のColumnHeaderHeightModeをAutoSize以外にするのを忘れないにように。