64bit対応ユーザーコントロール作成方法

64bitに対応したユーザーコントロール(カスタムコントロール)を作成するには、前提条件として

 

Visual Studio はOSが64bitであっても32bitで動作している!

 

という点に注意しないといけません。

つまり、プラットフォームを64bitで作成したユーザーコントロールをVisual Studioのフォームデザイナでフォーム上に配置すると、32bitプログラムから64bitプログラムを呼び出す事になるので、フォームデザイナがエラーとなり、フォームを表示してくれません。

 

これを回避するためには、ユーザーコントロールのプラットフォームをAny CPUに設定して下さい。

 

この時点で、Any CPUの設定がないC++/CLIのユーザーコントロールは64bit対応ができないという事になります。

これを知らずにC++/CLIのユーザーコントロールを32bitである程度作成してしまい、64bitに対応させようと思った時に、エライ目に会いました...→結局、C#で作り直すハメに。

 

次に私がハマったのが、ユーザーコントロールからアンマネージのライブラリを呼ぶ場合の処理。

 

作成したプログラムのプラットフォームがAny CPUの時の挙動ですが、

32bitOSの場合:32bitのライブラリを呼び出す

64bitOSの場合:64bitのライブラリを呼び出す

となります。

 

つまり、64bitOS環境でユーザーコントロールを開発すると、Visual Studioで開発中にフォームデザイナでは、Any CPUのユーザーコントロールから、64bitのライブラリを呼び出そうとします。

ユーザーコントロールはフォームに配置した時点で、コントロールのプロパティやコンストラクタ、各種イベント処理が動いてしまうので、この時に64bitライブラリの関数が呼ばれてしまうと、フォームデザイナがエラーとなってしまいます。

 

これを回避するには、フォームがデザインモードかどうかを調べる

 System.ComponentModel.Component.DesignModeプロパティ

を用います。

この値がtrueの場合、64bitライブラリの関数を呼び出さないようにすると、回避できます。

 

さらに話をややこしくするのが、DesignModeプロパティはコンストラクタや入れ子となったユーザーコントロールでは正しい値が取得できません

そのため、DesignModeプロパティを取得する際には注意して下さい。

(参考)

http://support.microsoft.com/kb/839202/ja

http://social.msdn.microsoft.com/Forums/ja-JP/csharpexpressja/thread/d946a597-0ba4-4880-b99b-13c728e2f39f

 

まとめると、

●ユーザーコントロールはVB.NETもしくはC#でプラットフォームをAny CPUにして作成する。

●デザイン時に64bitライブラリの関数を呼び出さい。

 

という感じでしょうか?

 

VB.NETやC#だけで作るならプラットフォームは全てAny CPUにしておくと、あまり64bit対応の事は気にしなくても大丈夫ですね。

また、逆に32bit対応のライブラリしかなく、32bitで動くプログラムを作る場合は、64bit対応のライブラリが呼び出されないように、プラットフォームをAny CPUにはせずに、Win32に設定しておく必要があります。

 

Visual Studioへ戻る

【C++/CLI】配列の配列(ジャグ配列)

配列の配列をジャグ配列と言います。

サンプルコードは以下の通り。

//////////////////////////////////////////////
// 配列の配列(ジャグ配列) その1
//////////////////////////////////////////////
array<array^>^ jag1 = gcnew array<array^>(2);

int index1 = 0;

for (int j = 0; j < jag1->Length; j++)
{
	jag1[j] = gcnew array(3);
	for (int i = 0; i < jag1[j]->Length; i++)
	{
		jag1[j][i] = index1++;
	}
}
// jag1[0][0] = 0  jag1[0][1] = 1  jag1[0][2] = 2
// jag1[1][0] = 3  jag1[1][1] = 4  jag1[1][2] = 5

//////////////////////////////////////////////
// 配列の配列(ジャグ配列) その2
//////////////////////////////////////////////
array<array^>^ jag2 = gcnew array<array^>(3);

int index2 = 0;

for (int j = 0; j < jag2->Length; j++)
{
	jag2[j] = gcnew array(j + 1);
	for (int i = 0; i < jag2[j]->Length; i++)
	{
		jag2[j][i] = index2++;
	}
}

// jag2[0][0] = 0
// jag2[1][0] = 1  jag2[1][1] = 2
// jag2[2][0] = 3  jag2[2][1] = 4  jag2[2][2] = 5

上記の例は、配列の配列までの例ですが、配列の配列の配列も可能です。

array<array<array<int>^>^>^ jag;

 

このジャグ配列は、個人的にはあまり用いていませんが、上記の ジャグ配列 その2 の例でも示しているように、必ずしも1行あたりの配列の要素数は同じである必要は無いので、例えば、CSVファイルなどのように、1行あたりの要素数が不定になる可能性のある配列などに用いると便利です。

 

【C++/CLI】配列の確保(Arrayクラス)

配列の確保は

 

array<型>^変数名 = gcnew array<型>(要素数);

 

のように行います。

 

多次元の場合は

 

array<型, 次数>^変数名 = gcnew array<型, 次数>(0次の要素数, 1次の要素数, 2次の要素数・・・);

 

コード例は以下の通り

 

///////////////////////////////////////////////
// 配列の初期化
///////////////////////////////////////////////
array^a = gcnew array {0, 1, 2, 3, 4};
// a[0] = 0
// a[1] = 1
// a[2] = 2
// a[3] = 3
// a[4] = 4

///////////////////////////////////////////////
// 配列の確保
///////////////////////////////////////////////
array^b = gcnew array(6);
int Len = b->Length;	// 配列の全個数

for (int i = 0; i < Len; i++)
{
	b[i] = i * 2;
}
// b[0] = 0
// b[1] = 2
// b[2] = 4
// b[3] = 6
// b[4] = 8
// b[5] = 10

///////////////////////////////////////////////
// 二次元配列の確保
///////////////////////////////////////////////
array<int, 2>^c = gcnew array<int, 2>(2, 3);

int iLen = c->GetLength(1);	// 1次の配列の個数  (= 3)
int jLen = c->GetLength(0);	// 0次の配列の個数 (= 2)

for (int j = 0; j < jLen; j++)
{
	for (int i = 0; i < iLen; i++)
	{
		c[j, i] = j * 10 + i;
	}
}
//c[0, 0] = 0	c[0, 1] = 1		c[0, 2] = 2
//c[1, 0] = 10	c[1, 1] = 11	c[1, 2] = 12

///////////////////////////////////////////////
// ポインタで二次元配列を参照し一次元配列へ代入
///////////////////////////////////////////////
pin_ptrp = &c[0, 0];
array^d = gcnew array(6);

for (int i = 0; i < iLen * jLen; i++){
	d[i] = p[i];
}
// d[0] = 0
// d[1] = 1
// d[2] = 2
// d[3] = 10
// d[4] = 11
// d[5] = 12

ここで、多次元配列の場合、メモリに格納されている順番は右側の添え字から順番に格納されているので、ご注意下さい。
ポインタで参照している部分を参考にして下さい。

 

良く使うメソッド、プロパティは以下の通り

メソッド

  • GetLength(dimension)
    指定した次元(dimension)の要素数を取得します。
  • Resize(array, newSize)
    配列(array)のサイズをnewSizeに変更し、各要素は新しい配列へコピーされます。

 

プロパティ

  • Length
    全ての次元内の要素数を取得します。

 

【C++/CLI】文字列の右寄せ、左寄せ、中央寄せ描画

文字列を描画する時にはSystem::Drawing::Graphics::DrawStringメソッドを用いますが、文字列を描画するのに、右寄せ、左寄せ、中央寄せを指定するには、6つあるオーバーロードのうち、System::Drawing::StringFormatのあるメソッドを用います。

 

具体的には、

  • Graphics.DrawString (String, Font, Brush, PointF, StringFormat)
  • Graphics.DrawString (String, Font, Brush, Single, Single, StringFormat)
  • Graphics.DrawString (String, Font, Brush, RectangleF, StringFormat)

のいづれかを用います。

 

この3つのメソッドのうち、上の2つが点を基準として文字列を描画し、最後の1つは領域を基準として文字列を描画します。

 

また、基準位置に対して、StringFormatクラスを用いて上下方向左右方向に位置を調整します。

 

StringFormat.Alignmentプロパティ水平方向を調整し、
StringFormat.LineAlignmentプロパティ垂直方向を調整します。

 

何はともあれ、サンプルコードと実行結果を見て頂くと分かり易いと思います。

 

【実行結果】

 

【サンプルコード】

private: System::Void pictureBox1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) {

	Graphics^ g = e->Graphics;

	System::Drawing::Font^ drawFont = gcnew System::Drawing::Font( "Arial",10 );

	// テキスト レイアウト情報
	StringFormat^ drawFormat = gcnew StringFormat;

	//////////////////////////////////////////
	//
	// 点基準
	//
	//////////////////////////////////////////

	// 基準点の描画 (350, 60)
	g->FillEllipse(Brushes::Red, Rectangle(345, 55, 10, 10));
	// 右左寄せ
	drawFormat->Alignment = StringAlignment::Far;
	g->DrawString("右寄せ(StringAlignment::Far)", drawFont, Brushes::Black, 350, 60, drawFormat);

	// 基準点の描画 (50, 100)
	g->FillEllipse(Brushes::Red, Rectangle(45, 95, 10, 10));
	// 左寄せ
	drawFormat->Alignment = StringAlignment::Near;
	g->DrawString("左寄せ(StringAlignment::Near)", drawFont, Brushes::Black, 50, 100, drawFormat);

	// 基準点の描画 (200, 140)
	g->FillEllipse(Brushes::Red, Rectangle(195, 135, 10, 10));
	// 中央寄せ
	drawFormat->Alignment = StringAlignment::Center;
	g->DrawString("中央寄せ(StringAlignment::Center)", drawFont, Brushes::Black, 200, 140, drawFormat);

	//////////////////////////////////////////
	//
	// 領域基準
	//
	//////////////////////////////////////////

	// 基準領域の描画 (50, 200)-(350, 300)
	Rectangle rect = Rectangle(50, 200, 300, 100);
	g->DrawRectangle(Pens::Red, rect);
	g->DrawLine(Pens::Cyan, 50, 250, 350, 250);
	g->DrawLine(Pens::Cyan, 200, 200, 200, 300);

	// 左上寄せ
	drawFormat->Alignment = StringAlignment::Near;
	drawFormat->LineAlignment = StringAlignment::Near;
	g->DrawString("(Near, Near)", drawFont, Brushes::Black, rect, drawFormat);
	// 中央上寄せ
	drawFormat->Alignment = StringAlignment::Center;
	drawFormat->LineAlignment = StringAlignment::Near;
	g->DrawString("(Center, Near)", drawFont, Brushes::Black, rect, drawFormat);
	// 右上寄せ
	drawFormat->Alignment = StringAlignment::Far;
	drawFormat->LineAlignment = StringAlignment::Near;
	g->DrawString("(Far, Near)", drawFont, Brushes::Black, rect, drawFormat);

	// 左中央寄せ
	drawFormat->Alignment = StringAlignment::Near;
	drawFormat->LineAlignment = StringAlignment::Center;
	g->DrawString("(Near, Center)", drawFont, Brushes::Black, rect, drawFormat);
	// 中央中央寄せ
	drawFormat->Alignment = StringAlignment::Center;
	drawFormat->LineAlignment = StringAlignment::Center;
	g->DrawString("(Center, Center)", drawFont, Brushes::Black, rect, drawFormat);
	// 右中央寄せ
	drawFormat->Alignment = StringAlignment::Far;
	drawFormat->LineAlignment = StringAlignment::Center;
	g->DrawString("(Far, Center)", drawFont, Brushes::Black, rect, drawFormat);

	// 左下寄せ
	drawFormat->Alignment = StringAlignment::Near;
	drawFormat->LineAlignment = StringAlignment::Far;
	g->DrawString("(Near, Far)", drawFont, Brushes::Black, rect, drawFormat);
	// 中央下寄せ
	drawFormat->Alignment = StringAlignment::Center;
	drawFormat->LineAlignment = StringAlignment::Far;
	g->DrawString("(Center, Far)", drawFont, Brushes::Black, rect, drawFormat);
	// 右下寄せ
	drawFormat->Alignment = StringAlignment::Far;
	drawFormat->LineAlignment = StringAlignment::Far;
	g->DrawString("(Far, Far)", drawFont, Brushes::Black, rect, drawFormat);

}

【C++/CLI】画像データの拡大縮小

画像表示の拡大縮小については画像の拡大縮小表示(高機能版)として紹介しましたが、今回は画像データのそのものを拡大縮小する方法を紹介します。

 

画像データを拡大縮小するにはSystem.Drawing.Bitmapクラスの以下のコンストラクタを用いる事で可能となります。

 

Bitmap (
    Image^ original,
    int width,
    int height
)

 

概要

指定したサイズを使用して、指定した既存のイメージで Bitmap クラスの新しいインスタンスを初期化します。

 

パラメータ

original 新しい Bitmap の作成元の Image
width 新しい Bitmap の幅 (ピクセル単位)
height 新しい Bitmap の高さ (ピクセル単位)

 

処理例

//元の画像
Bitmap^ src = gcnew Bitmap("test.bmp");
//縮小画像(1/2に縮小)
Bitmap^ dstSmall = gcnew Bitmap(src, src->Width / 2, src->Height / 2);
//画像の保存
dstSmall->Save("SmallImage.bmp", Imaging::ImageFormat::Bmp);
//拡大画像(2倍に拡大)
Bitmap^ dstBig = gcnew Bitmap(src, src->Width * 2, src->Height * 2);
//画像の保存
dstBig->Save("BigImage.bmp", Imaging::ImageFormat::Bmp);

 

処理結果

元の画像(test.bmp)

 

縮小画像(SmallImage.bmp)

 

拡大画像(BigImage.bmp)

 

補間モード(InterpolationMode)を指定して保存したい場合は、いったんGraphicsを通して画像を描画することで、補間モードを設定します。

 

//元の画像
Bitmap^ src = gcnew Bitmap("test.bmp");
//拡大画像データの確保
Bitmap^ dst = gcnew Bitmap(src->Width * 2, src->Height * 2);
//Graphicsオブジェクトの確保
Graphics^ g = Graphics::FromImage(dst);
//補間モードの設定
g->InterpolationMode = Drawing2D::InterpolationMode::HighQualityBicubic;
//拡大してGraphicsへ描画
g->DrawImage(src, 0, 0, src->Width * 2, src->Height * 2);
//画像の保存
dst->Save("BigImage.bmp", Imaging::ImageFormat::Bmp);

ただし、この方法では元の画像データ(src)がモノクロデータ(8Bit)の場合、上記のプログラムのままではR,G,Bの値が等しい24Bitのカラー画像として保存されてしまいます。
モノクロ画像データとして保存したい場合は、カラー画像からモノクロ画像に変換するしかなさそうです。

【C++/CLI】画像の拡大縮小表示(高機能版)

画像の拡大縮小表示(簡易版)のページでは、簡単に画像の拡大縮小表示する方法を紹介しましたが、画像サイズが大きいときなど、不都合な場合があるのですが、その時にはDrawImageメソッドを使って画像を拡大縮小表示します。

Win32APIのStretchDIBits関数に相当するのが、.NETではGraphicsクラスのDrawImageメソッドになります。
DrawImageメソッドには実に30個ものオーバーロードがあり、その分だけ高機能になっているのですが、詳細はMSDNに任せるとして、これまで慣れ親しんできたStretchDIBitsに近いメソッドを紹介します。

 

DrawImageの構文は以下の通り

void DrawImage (
	Image^ image,
	Rectangle destRect,
	Rectangle srcRect,
	GraphicsUnit srcUnit
)
image 描画するImageオブジェクト
通常はBitmapオブジェクトを渡す
destRect 描画先(ピクチャボックスなど)の描画領域
srcRect 描画するImageオブジェクトの領域
srcUnit srcRect パラメータで使用する単位を指定する GraphicsUnit 列挙体のメンバ
通常はGraphicsUnit::Pixelと指定すればOK

 

また、Win32APIのSetStretchBltModeのように伸縮モードを設定するには、.NETでは
GraphicsオブジェクトのInterpolationModeプロパティで設定します。

 

Default 規定の補間モード
High 高品質補間
Low 低品質補間
NearestNeighbor 最近傍補間
Bilinear 双一次補間
Bicubic 双三次補間
HighQualityBilinear 高品質双一次補間
HighQualityBicubic 高品質双三次補間

 

画像処理のプログラムでは画素の1つ1つが良くわかるNearestNeighborがオススメです。

 

ということで、画像を等倍で表示する場合は、srcRectとdestRectを同じサイズに指定します。

 

画像を n倍に拡大する場合は、srcRectをdestRectの1/n倍のサイズに指定します。

 

 

この処理のサンプルプログラムは以下に示しておきます。
ただし、エラー処理などはまるで無視しています。

 

 private: System::Void Form1_Load(System::Object^  sender, System::EventArgs^  e) {

     //PictureBoxと同じ大きさのBitmapクラスを作成する。
     Bitmap^ bmpPicBox = gcnew Bitmap(pictureBox1->Width, pictureBox1->Height);
     //空のBitmapをPictureBoxのImageに指定する。
     pictureBox1->Image = bmpPicBox;
     //Graphicsクラスの作成(空のピクチャボックスからGraphicsを作成する)
     Graphics^g = Graphics::FromImage(pictureBox1->Image);

     //伸縮モードをNearestNeighborに設定
     g->InterpolationMode = Drawing2D::InterpolationMode::NearestNeighbor;

     //描画するビットマップ
     Bitmap^ bmp = gcnew Bitmap("c:\\test.bmp");
     //画像の描画
     int DrawScale = 5;
     g->DrawImage(bmp,
      System::Drawing::Rectangle(0, 0, pictureBox1->Width, pictureBox1->Height),
      System::Drawing::Rectangle(140, 100, pictureBox1->Width / DrawScale, pictureBox1->Height / DrawScale),
      GraphicsUnit::Pixel);
    }

 

実行画面

 

【C++/CLI】Graphicsオブジェクトの作成

.NETではピクチャボックスに画像や線、文字などを描画するには、Graphicsオブジェクトを作成し、このGraphicsオブジェクトに対して描画を行います。

 

Graphicsオブジェクトを作成する方法は3つ。
1.Imageオブジェクトから作成(オススメ!)
2.CreateGraphicsメソッドを使う方法(使わないことをオススメ!)
3.PaintイベントのPaintEventArgsから取得する方法

 

Graphiscオブジェクトを作成したら、画像の描画は

g->DrawImage(bmp, 0, 0);

線の描画は

g->DrawLine(Pens::Blue,0, 0, 100, 100);

というような具合で。

 

Imageオブジェクトから作成

(コード例)

//PictureBoxと同じ大きさのBitmapクラスを作成する。
Bitmap^ bmpPicBox = gcnew Bitmap(pictureBox1->Width, pictureBox1->Height);
//空のBitmapをPictureBoxのImageに指定する。
pictureBox1->Image = bmpPicBox;
//Graphicsオブジェクトの作成(FromImageを使う)
Graphics^ g = Graphics::FromImage(pictureBox1->Image);

 

上記コードをフォームのResizeイベントなどで処理を行い、作成したGraphicsオブジェクトを使い回せばよいかと思います。
この手法だと再描画は勝手にやってくれる上に、CreateGraphicsを使うよりも
格段に描画が速くなります。
.NET(GDI+)による描画が遅い!と思っている方は、まずはこの方法をお試し下さい。
(でも個人的には、それでも遅く感じます)

 

CreateGraphicsメソッドを使う

(コード例)

//Graphicsオブジェクトの作成(CreateGraphicsを使う)
Graphics^ g = pictureBox1->CreateGraphics();

 

この手法だと簡単ですが描画がかなり遅くなります。
しかも再描画してくれません...
説明が簡単なので、使っちゃう場合もありますが。

 

PaintイベントのPaintEventArgsから取得する

(コード例)

private: System::Void pictureBox1_Paint(System::Object^  sender, System::Windows::Forms::PaintEventArgs^  e) {
//PaintEventArgsから取得
Graphics^ g = e->Graphics;
}

この方法が一番高速ですが、ちょっと扱いにくい。

 

ImageオブジェクトからGraphicsオブジェクトを作成する方法とCreateGraphisメソッドを使った方法の処理時間の比較などを「モノクロ画像の上に線などを描画」のページで紹介しています。
もしよろしければご参照下さい。

【C++/CLI】モノクロ画像の上に線などを描画

モノクロ画像の上に線などを描画する方法をまとめました。

 

【目標】

  • モノクロ/カラー区別なく画像の上に描画できること
  • 高速に描画できること
  • 再描画すること

     

    作成したサンプルプログラムはこんな感じ↓です。

     

    (サンプルプログラムの使い方)
    各ボタンをクリックするとファイルを開くダイアログボックスが開きますので、ビットマップファイルを指定して下さい。
    (ダウンロード)MonoImageDisp2005.zip(Visual Studio 2005 Express Edition版)

     

    準備

    線の描画は条件を同じにするため、以下の関数を使い回しました。

    void DrawGraphics(Graphics^ g){
    	//ギザギザ模様を画像の上に描画
    
    	int i, j;
    
    	//ギザギザの線の描画
        for (j = 0; j < 256; j+= 3){
    	    for (i = 0; i < 25; i++){ g->DrawLine(Pens::Blue, i * 10, j, i * 10 + 5, j - 5);
    		    g->DrawLine(Pens::Blue, i * 10 + 5, j - 5, i * 10 + 10, j);
    	    }
        }
    }

    よくあるサンプルプログラム

    private: System::Void btnBitmap_Click(System::Object^  sender, System::EventArgs^  e) {
    		 //ビットマップファイルからBitmapクラスを作成し、ピクチャボックスへ渡す方法
    		 //モノクロ画像だとエラーになります。
    
    	     try{
    			//描画時間計測用
    		    Diagnostics::Stopwatch^ sw = gcnew Diagnostics::Stopwatch;
    
    			//ピクチャボックスの画像クリア
    			pictureBox1->Image = nullptr;
    
    			//画像ファイル名の取得
    			String^ FileName = GetImageFilename();
    			//Bitmapクラスの作成
    			Bitmap^ bmp = gcnew Bitmap(FileName);
    			//PictureBoxへBitmapを表示する。
    			pictureBox1->Image = bmp;
    
    			//Graphicsクラスの作成
    			//※モノクロ画像ファイルを開くと、ここでエラーになります。
    			Graphics^ g = Graphics::FromImage(bmp);
    			//画像の上にギザギザ模様の描画
    			sw->Start();
    			DrawGraphics(g);
    			sw->Stop();
    			//描画時間表示
    			MessageBox::Show("描画時間 = " + sw->ElapsedMilliseconds.ToString() + " msec");
    
    		 }catch(Exception^ err){
    			//エラーの表示
    			MessageBox::Show(err->Message);
    		 }
    	 }

    このプログラムではカラー画像では問題ないのですが、8ビットモノクロ画像ファイルを開くとFromImageメソッドでエラーとなります。

     

    CreateGraphicsを使ってGraphicsクラスを作成する方法

    private: System::Void btnCreateGraphics_Click(System::Object^  sender, System::EventArgs^  e) {
    		 //CreateGraphicsを使ってGraphicsクラスを作成し描画する。
    		 //モノクロ画像の上にも描画できますが、描画時間がかなり遅くなります。
    		 //描画したギザギザ模様は再描画されません。
    
    		 try{
    			//描画時間計測用
    		    Diagnostics::Stopwatch^ sw = gcnew Diagnostics::Stopwatch;
    
    			//ピクチャボックスの画像クリア
    			pictureBox1->Image = nullptr;
    
    			//Graphicsクラスの作成(CreateGraphicsを使う)
    			Graphics^ g = pictureBox1->CreateGraphics();
    
    			//画像ファイル名の取得
    			String^ FileName = GetImageFilename();
    			//Bitmapクラスの作成
    			Bitmap^ bmp = gcnew Bitmap(FileName);
    			//PictureBoxへBitmapを表示する。
    			pictureBox1->Image = bmp;
    			pictureBox1->Refresh();
    
    			//画像の上にギザギザ模様の描画
    			sw->Start();
    			DrawGraphics(g);
    			sw->Stop();
    			//描画時間表示
    			MessageBox::Show("描画時間 = " + sw->ElapsedMilliseconds.ToString() + " msec");
    
    		 }catch(Exception^ err){
    			//エラーの表示
    			MessageBox::Show(err->Message);
    		 }
    	 }

    このプログラムでは、モノクロ画像ファイルを開いても、モノクロ画像の上に線を描画することができます。
    ただし、遅い!!!
    再描画もされません。

    PictureBoxと同じ大きさのBitmapクラスを作成し、ピクチャボックスのImageへ。画像データはDrawImageで描画

    /private: System::Void btnDrawImage_Click(System::Object^  sender, System::EventArgs^  e) {
    		 //ピクチャボックス表示用と画像データ用と別のBitmapクラスを作成し、
    		 //ピクチャボックスにはDrawImageを使って画像を描画する。
    		 //モノクロ画像でも表示可能
    		 //再描画可能、CreateGraphicsを使うより高速
    
    		 try{
    			//描画時間計測用
    		    Diagnostics::Stopwatch^ sw = gcnew Diagnostics::Stopwatch;
    
    			//ピクチャボックスの画像クリア
    			//pictureBox1->Image = nullptr;
    
    			//画像ファイル名の取得
    			String^ FileName = GetImageFilename();
    			//Bitmapクラスの作成
    			Bitmap^ bmpFileImage = gcnew Bitmap(FileName);
    
    			//PictureBoxの大きさを画像の大きさに合わせる
    			pictureBox1->Width  = bmpFileImage->Width;
    			pictureBox1->Height = bmpFileImage->Height;
    
    			//PictureBoxと同じ大きさのBitmapクラスを作成する。
    			Bitmap^ bmpPicBox = gcnew Bitmap(pictureBox1->Width, pictureBox1->Height);
    			//空のBitmapをPictureBoxのImageに指定する。
    			pictureBox1->Image = bmpPicBox;
    			//Graphicsクラスの作成(CreateGraphicsを使う)
    			Graphics^ g = Graphics::FromImage(pictureBox1->Image);
    
    			//PictureBoxへBitmapを描画する。
    			g->DrawImage(bmpFileImage, 0, 0);
    			pictureBox1->Invalidate();
    
    			//画像の上にギザギザ模様の描画
    			sw->Start();
    			DrawGraphics(g);
    			sw->Stop();
    			//描画時間表示
    			MessageBox::Show("描画時間 = " + sw->ElapsedMilliseconds.ToString() + " msec");
    
    		 }catch(Exception^ err){
    			//エラーの表示
    			MessageBox::Show(err->Message);
    		 }
    	 }

    最初にピクチャボックスと同じ大きさのBitmapクラスを作成し、ピクチャボックスのImageクラスへ渡す。ピクチャボックスのImageからFromImageメソッドでGraphicsクラスを作成。
    このようにすると、モノクロ画像の上にも線を描画することができ、再描画も勝手にしてくれます。
    しかも、CreateGraphicsを使ってGraphicsクラスを作成した場合よりも、描画速度は10倍くらい速くなります。

    【C++/CLI】VB.NET固有の関数を使用する方法

    .NET Framework ではVisual Basic.NETであっても、ただのVisualBasicという名前空間であるだけなので、C#やC++/CLIからVB.NET固有の関数を使うことが可能です。

     

    以下、VB固有の関数の代表格のInputBox関数の使用方法を紹介します。

     

    まず、VisualBasicの名前空間を参照できるように参照設定を行います。

     

    プロジェクトの名前の部分で右ボタンをクリックし、参照を選択します。

     

     

    次に新しい参照の追加ボタンをクリックします。

     

     

    すると、使用可能な.NETの名前空間を表示されるので、この中からMicrosoft.VisualBaicを選択しOKボタンをクリックします。

     

     

    すると、現在参照設定している名前空間の一覧にMicrosoft.VisualBasicの名前か追加されます。

     

     

    これで準備は完了です。

     

    実際にInputBoxを呼び出すのは、こんな感じ↓です。

     

    String^ str = Microsoft::VisualBasic::Interaction::InputBox(“入力文字?”, “タイトル”, “初期値”, -1, -1);

     

    とすると、このよう↓に表示されます。

     

     

    C++からVBの関数が呼べてしまうのは不思議な感じもしますが、他にも使いたいVBの関数があったので、今回わざわざ記事にしてみました。

    【C++/CLI】大文字/小文字、全角/半角、ひらがな/カタカナ変換

    大文字/小文字の変換をするには System.String クラスToLowerToUpper メソッドを用います。

     

    【コード例】

    String^ str;
    
    //大文字→小文字変換
    str = "ImagingSolution";    // → imagingsolution
    str = str->ToLower();
    //小文字→大文字変換
    str = "Imagingsolution";    // → IMAGINGSOLUTION
    str = str->ToUpper();

    さらにVBの関数を用いると全角/半角、ひらがな/カタカナの変換も行う事が出来ます。
    VBの関数をC++/CLIから使う方法は VB.NET固有の関数を使用する方法 を参照願います。

     

    【コード例】

    using namespace Microsoft::VisualBasic;
    
    String^ str;
    
    //大文字→小文字変換
    str = "ImagingSolution";    //→imagingsolution
    str = Strings::StrConv(str, VbStrConv::Lowercase, 0);
    //小文字→大文字変換
    str = "Imagingsolution";    //→IMAGINGSOLUTION
    str = Strings::StrConv(str, VbStrConv::Uppercase, 0);
    
    //全角→半角変換
    str = "画像処理ソリューション";    //→画像処理ソリューション
    str = Strings::StrConv(str, VbStrConv::Narrow, 0);
    //半角→全角変換
    str = "画像処理ソリューション";            //→画像処理ソリューション
    str = Strings::StrConv(str, VbStrConv::Wide, 0);
    
    //ひらがな→カタカナ
    str = "画像処理そりゅーしょん";    //→画像処理ソリューション
    str = Strings::StrConv(str, VbStrConv::Katakana, 0);
    //カタカナ→ひらがな
    str = "画像処理ソリューション";    //→画像処理そりゅーしょん
    str = Strings::StrConv(str, VbStrConv::Hiragana, 0);
    

    (参考)
    http://msdn.microsoft.com/ja-jp/library/7wtc81z6%28v=VS.80%29.aspx
    http://msdn.microsoft.com/ja-jp/library/microsoft.visualbasic.vbstrconv.aspx

    【C++/CLI】ファイルパス(フルパス)からファイル名、拡張子、ディレクトリの取得

    ファイルを開くダイアログボックスなどから取得したファイル名(フルパス)からファイル名や拡張子、
    ディレクトリなどを取得する場合には

    System::IOクラスのGet×××メソッド

    を用います。

     

    以下、サンプルプログラムです。

    //ファイル名(フルパス)
    String^ FullPath = "c:\\Dir1\\Dir2\\Bitmap.bmp";
    String ^FileName, ^Extension, ^Directory;
    
    //ファイル名の取得(拡張子を含む)        → "Bitmap.bmp"
    FileName = IO::Path::GetFileName(FullPath);
    //ファイル名の取得(拡張子を含まない) → "Bitmap"
    FileName = IO::Path::GetFileNameWithoutExtension(FullPath);
    //拡張子の取得                         → ".bmp"  ※ピリオド"."を含みます。
    Extension = IO::Path::GetExtension(FullPath);
    //ディレクトリの取得                    → "c:\Dir1\Dir2"
    Directory = IO::Path::GetDirectoryName (FullPath);
    //相対パスから絶対パスを取得            → "c:\Dir1\Dir2\Bitmap.bmp"
    FullPath = IO::Path::GetFullPath("Bitmap.bmp");

     

    相対パスから絶対パスを取得する場合、指定したファイル名がカレントディレクトリに無い場合は
    カレントディレクトリにファイル名が追加された文字列が返されます。

    【C++/CLI】数値の書式(フォーマット)指定文字列

    数値の表示桁数などの設定にはFormatメソッドかToStringメソッドなどで可能ですが、ここではToStringメソッドによる設定方法を紹介します。

     

    【コード例】
    int Val = 123;
    String^ Txt = Val.ToString(“D5”);

     

    とすると
    Txt = “00123”
    となります。

     

    以下、主なフォーマットの設定例です。

     

    標準書式指定文字列

    書式 書式設定文字列 データ型 出力
    10進数 D Int32 123 123
    10進数(桁数設定) D5 Int32 123 00123
    固定小数点 F Double 123.456 123.46
    固定小数点(桁数設定) F6 Double 123.456 123.456000
    パーセント P Double 0.12345 12.35%
    パーセント(桁数設定) P3 Double 0.123456 12.346%
    16進数(小文字) x Int32 123456 1e240
    16進数(大文字) X Int32 123456 1E240
    通貨 C Double 1234567 \1,234,567
    数字 N Double 123456 123,456.00
    数字(桁数設定) N1 Double 12345678 123,456.8

    詳細は下記ページを参照願います。

    http://msdn.microsoft.com/ja-jp/library/241ad66z(VS.80).aspx

     

    カスタム数値書式指定文字列

    書式 書式設定文字列 データ型 出力
    ゼロプレースホルダ 00000 Double 123 00123
    000.000 Double 12.3456 012.346
    000.000 Double 123.45 123.450
    桁プレースホルダ ##### Double 123 123
    #.# Double 1.2 1.2
    #.# Double 1 1
    #.# Double 123.456 123.5
    #,# Double 123456789 123,456,789
    (00)####-#### Double 312345678 (03)1234-5678
    正、負、0別表示 +#;-#;±0 Double 123 +123
    +#;-#;±0 Double -123 -123
    +#;-#;±0 Double 0 ±0
    #;(#) Double 123 123
    #;(#) Double -123 (123)

    詳細は下記ページを参照願います

    http://msdn.microsoft.com/ja-jp/library/7x5bacwt(VS.80).aspx

     

    【右寄せ】
    int Val = 123;
    String^ Txt = Val.ToString()->PadLeft(5);
    とすると
    Txt = ”  123″
    となります。

     

    【左寄せ】
    int Val = 123;
    String^ Txt = Val.ToString()->PadRight(5);
    とすると
    Txt = “123  ”
    となります。

     

    右寄せ/左寄せは、少し紛らわしいですが、左側を穴埋めして右寄せ(PadLeft)、右側を穴埋めして左寄せ(PadRight)となります。
    カッコ内の数値が文字数になります。

     

    【C++/CLI】文字列の分割(System.String.Splitメソッド)

    文字列を特定の文字で分割し、Stringの配列に格納するには

    System.String.Splitメソッド

    を用います。
    CSVファイルを読み込む時などはカンマ(,)で区切るので、このSplitメソッドを使います。

     

    このメソッドの定義は

    array^ Split (
        array<String^>^ separator,
        StringSplitOptions options
    )

     

    パラメータ

    separator

    分割する文字列をString配列で指定します。

    options

    StringSplitOptions::Nodeを指定すると分割した文字列が空の場合でもそのまま返します。
    StringSplitOptions::RemoveEmptyEntriesを指定すると分割した文字列が空の場合、除去されます

     

    【サンプルプログラム】

    String^ SrcText = "A,B;C,,E,F";
    
    //分割する文字列の配列
    array<String^>^ SepString ={",", ";"};
    
    //文字列の分割
    array<String^>^ TextArr1 = SrcText->Split(SepString, StringSplitOptions::RemoveEmptyEntries);
    array<String^>^ TextArr2 = SrcText->Split(SepString, StringSplitOptions::None);

    このコードを実行し、TestArr1とTestArr2の中身をウォッチで確認すると、以下の通りとなります。

     

     

    区切り文字が1つの場合は

    array<String^>^ TextArr3 = SrcText->Split(‘,’);

    のように区切ることもできます。
    ただし、この場合はシングルクォーテーション( )なので注意して下さい。

    【C++/CLI】改行文字

    メッセージボックスなどの文字列を改行させる時には

    System.Environment.NewLineプロパティ

    を用います。

    以下、サンプルと実行例です。

     

    【サンプルプログラム】

    private: System::Void button1_Click(System::Object^  sender, System::EventArgs^  e) {
             button1->Text = "1行目の文字列" + Environment::NewLine +
                                     "2行目の文字列";
             label1->Text  = "1行目の文字列" + Environment::NewLine +
                                    "2行目の文字列";
    
             MessageBox::Show("ボタンとラベルの文字列が" + Environment::NewLine +
                                           "変更されました。");
         }

     

    【実行例】

     

    ボタンやラベルに関しては、プロパティウィンドウで文字列を入力すると改行も繁栄されます。

    また、System.Environment.NewLineプロパティを使わずとも、\n\rを使って

     

    MessageBox::Show(“ボタンとラベルの文字列が\r\n変更されました。”);

     

    のように書くことも可能です。

     

    【C++/CLI】テキストボックスの文字列を変数へ代入

    テキストボックスの文字列はtextbox1->Textのようにすると取得できますが、この文字列を数値へ変換するには各型に用意されているParseメソッドを使います。

     

    (例)
    int Val= int::Parse(textbox1->Text);

     

    文字列変換する型は必要に応じて変えてください。
    ただし、この場合、テキストボックスに数値ではない文字列が入力された場合、エラーとなるので、エラー処理を行う場合はTryParseメソッドを用います。

    (例)
    int Val;
    bool result = int::TryParse(textbox1->Text, Val );

     

    テキストボックスの文字列がint 型として判断された場合、変数(上の例ではVal)に値が代入され、戻り値(result)にtrueが戻されます。
    失敗した場合は戻り値がfalseとなります。

     

    このエラー処理を用いると、数値としてByte型の値(0~255)が欲しいときなど便利です。

     

    (例)
    Byte Val ;
    bool result = Byte::TryParse(textbox1->Text, Val );

     

    詳細はmsdn2の「Int32.TryParse メソッド 」に記載されています。

    【C++/CLI】 nullチェック

    今回は、あまり教える程理解できていないので、メモ的な意味で...

     

    C言語ではメモリを確保したか?を確認するのにポインタがNULLかどうか確認しますが、C++では『^』で表されるハンドル型というC言語のポインタにも似た型が登場し、このハンドル型で示されるオブジェクトが確保されたのか?(gcnewされたのか?)を確認するのにはnullptrを用います。

     

    (コード例)

    System::Drawing::Graphics^ g;
    if (g == nullptr) MessageBox::Show("gは未定義です");	//MessageBoxが表示される
    g = CreateGraphics();
    if (g == nullptr) MessageBox::Show("gは未定義です");	//MessageBoxが表示されない

    しかし、System::Drawing::Colorクラスでは

    System::Drawing::Color^ c;

    とも

    System::Drawing::Color c;

    とも書けるのです。

     

    このとき

    System::Drawing::Color c;
    if (c == nullptr) MessageBox::Show("cは未定義です");

    と書くと

     

    error C2446: ‘==’ : ‘nullptr’ 型から ‘System::Drawing::Color’ 型への変換ができません。
    この変換を実行可能なユーザー定義変換演算子がないか、または演算子を呼び出せません。
    error C2088: ‘==’ : class に対して正しくありません。

     

    というエラーメッセージが表示され怒られてしまします。

     

    正解は

    System::Drawing::Color c;
    if (c == System::Drawing::Color::Empty) MessageBox::Show("cは未定義です");

    のように書きます。

     

    そんなnullチェックをするのに、nullptrで比較してしまいそうなクラスをまとめておこうと思います。

    System::Drawing::Colorクラス System::Drawing::Color::Empty
    System::IntPtrクラス System::IntPtr::Zero

     

    上記表はおいおい追記しようと思いますが、こんなのもあるよ!と思った方はこのページ下部よりコメント頂けると助かります。

    よろしくお願い致します。