リード開発メモ

大阪のソフトウェア会社です。 技術的な事柄についてのメモとしてブログを始めます。

.NET

C# ネットワーク上のコンピュータの共有フォルダの一覧

.Net Framework には、ネットワーク上のコンピュータの共有フォルダの一覧を作成するための機能がないため、Windows API を使う必要があった。

[DllImport("Netapi32.dll", CharSet = CharSet.Unicode)]
private static extern int NetShareEnum(
	String ServerName,
	int level, 
	ref IntPtr bufPtr,
	uint prefmaxlen,
	ref int entriesread,
	ref int totalentries,
	ref int resume_handle
);

[DllImport("Netapi32.dll", SetLastError = true)]
static extern int NetApiBufferFree(IntPtr Buffer);

public enum SHARE_TYPE : uint
{
	STYPE_DISKTREE = 0,	
	STYPE_PRINTQ = 1,
	STYPE_DEVICE = 2,
	STYPE_IPC = 3,
	STYPE_CLUSTER_FS = 0x02000000,
	STYPE_CLUSTER_SOFS = 0x04000000,
	STYPE_CLUSTER_DFS = 0x08000000,
	STYPE_TEMPORARY = 0x40000000,
	STYPE_SPECIAL = 0x80000000,
}

public struct SHARE_INFO_2
{
	[MarshalAs(UnmanagedType.LPWStr)]
	public string shi2_netname;
	public SHARE_TYPE shi2_type;
	[MarshalAs(UnmanagedType.LPWStr)]
	public string shi2_remark;
	public Int32 shi2_permissions;
	public Int32 shi2_max_uses;
	public Int32 shi2_current_uses;
	[MarshalAs(UnmanagedType.LPWStr)]
	public string shi2_path;
	[MarshalAs(UnmanagedType.LPWStr)]
	public string shi2_passwd;
}

private List<string> getSharedDirectories(string servername)
{
	var sharedDirectories = new List<string>();

	int level = 2;
	uint prefmaxlen = 368880;
	int entriesread = 0;
	int totalentries = 0;
	int resume_handle = 0;
	IntPtr bufPtr = IntPtr.Zero;

	int ret = NetShareEnum(servername, level, ref bufPtr, prefmaxlen, ref entriesread, ref totalentries, ref resume_handle);
	if (ret != 0)
	{
		return sharedDirectories;
	}

	IntPtr currentPtr = bufPtr;
	int nStructSize = Marshal.SizeOf(typeof(SHARE_INFO_2));

	for (int i = 0; i < entriesread; i++)
	{
		SHARE_INFO_2 shio = (SHARE_INFO_2)Marshal.PtrToStructure(currentPtr, typeof(SHARE_INFO_2));
		if (shio.shi2_type == SHARE_TYPE.STYPE_DISKTREE)
		{
			// Disk driveのみ追加
			sharedDirectories.Add(@"\\" + path + @"\" + shio.shi2_netname + @"\");
		}
		currentPtr = new IntPtr(currentPtr.ToInt32() + nStructSize);
	}

	NetApiBufferFree(bufPtr);

	return sharedDirectories;
}

以上です。

クリスタルレポートでデータによって枠線を表示する

Crystal Reports でデータによって枠線を表示したり非表示したり、または枠線の色を変えたりしたい。

Crystal Reports には線オブジェクトやボックスオブジェクトが用意されているが、データによってスタイルを変えたい場合は、線オブジェクトやボックスオブジェクトの代わりにフィールドオブジェクトの境界線を利用する。

まず、デザイン画面で適当なフィールドオブジェクトを貼り付け大きさを合わせる。
crystral-reports-border1

次にフィールドオブジェクトで右クリックし、書式エディタの「フォント」タブの「色」で背景色と同じ色を選ぶ。ここでは白を選んだ。
crystral-reports-border2

さらに「境界線」タブの「線のスタイル」で、上下左右を「単一線」などを選ぶ。また、「色」の「境界線」の右側にあるボタンをクリックする。
crystral-reports-border3

ここでデータに応じて線の色を変えるように式を記述する。crWhiteを返すことで背景色と同じ色となり、非表示の代用とできる。
crystral-reports-border4


ここでは以下のように記述した。
if ({CrystalReportsTest2_Class1.Prop1} = "26"
 or {CrystalReportsTest2_Class1.Prop1} = "27") then
  crBlack
else
  crWhite

出力結果は以下のとおり。 crystral-reports-border5

以上。

クリスタルレポートでデータ長に応じてフォントサイズを調整する

Crystal Reposrts でデータの長さによってはお尻が途切れてしまうことがある。
crystal-reposrts-fontsize1

折り返しをしていもいい場合であれば、書式エディタで「複数行にチェック」を入れる。

折り返しができない場合はフォントサイズを調整するが、データ長に応じて動的にフォントサイズを調整することもできる。

設定したいフィールドオブジェクトで右クリックし、「オブジェクトの書式設定」をクリックする。「フォント」タブの「サイズ」の右側のボタンをクリックする。
crystal-reposrts-fontsize2

ここで式を記述する。
crystal-reposrts-fontsize3

ここでは以下のように記述した。データ長によってフォントサイズを3段階に分けた。
if (Length({CrystalReportsTest2_Class1.Prop3}) < 14) then
  18
else if (Length({CrystalReportsTest2_Class1.Prop3}) < 20) then
  14
else
  10

出力結果は以下のとおりとなる。
crystal-reposrts-fontsize4

以上。

C# コントロールの DrawToBitmap で ArgumentException が発生する

Crystal Reports では動的に読み込んだフォントを使用できないようだ。

バーコードのフォントをシステムにインストールせずに使いたかったので、動的に生成したラベルにこのバーコードフォントを設定し、ラベルの画像を取り出して、Crystal Reports にその画像を渡すようにした。

ただ、一度に何千ページも印刷しようすると、ある程度のページ数まで処理が進んだときに、ラベルの DrawToBitmap 呼び出しで ArgumentException が発生する。

たとえば以下のコードを実行すると、自分のマシンではループカウンタが1300 あたりでArgumentException が発生する。以下では label と image は Dispose を行っていないため、それが原因のようにも思うが、たとえ Dispose を呼ぶようにしても、ループカウンタ 4600 あたりでやはり同様のエラーが起こる。
private void button1_Click(object sender, EventArgs e)
{
	for (int i = 0; i < 5000; i++)
	{
		counter.Text = i.ToString();
		counter.Refresh();

		var label = new Label();
		label.Font = new Font("Arial", 20);
		label.Text = "test";

		try
		{
			Bitmap image = new Bitmap(300, 500);
			label.DrawToBitmap(image, label.ClientRectangle);
		}
		catch (Exception ex)
		{
			MessageBox.Show(ex.ToString());
			return;
		}
	}

	MessageBox.Show("complete");
}

今回は GC.Collect を呼んで、回避した。ただし、GC.Collect は以下のような使いかたをしなければ効果がなかった。
private void button1_Click(object sender, EventArgs e)
{
	for (int i = 0; i < 5000; i++)
	{
		counter.Text = i.ToString();
		counter.Refresh();

		var label = new Label();
		label.Font = new Font("Arial", 20);
		label.Text = "test";

		try
		{
			Bitmap image = new Bitmap(300, 500);
			label.DrawToBitmap(image, label.ClientRectangle);
		}
		catch (ArgumentException ex)
		{
			GC.Collect();
			GC.WaitForPendingFinalizers();
			GC.Collect();
			i--;
		}
		catch (Exception ex)
		{
			MessageBox.Show(ex.ToString());
			return;
		}
	}

	MessageBox.Show("complete");
}

.NET アプリケーションのパフォーマンスとスケーラビリティの向上 - 第 5 章 「マネージ コード パフォーマンスの向上 によれば、GC.Collect の呼び出しは避けたほうがいいが、もし GC.Collect を呼び出さなければならない特別な事情がある場合は、以下の3行をセットで書くようにしたほうがいいようだ。
System.GC.Collect(); // アクセス不可能なオブジェクトを除去
System.GC.WaitForPendingFinalizers(); // ファイナライゼーションが終わるまでスレッド待機
System.GC.Collect(); // ファイナライズされたばかりのオブジェクトに関連するメモリを開放

以上。

C# 継承したフォーム上のコントロールのサイズが変更できない

Visual Studio 2012 の C# で開発をしているが、継承したフォーム上のコントロールのサイズ変更ができないという現象が起こった。

もうすこし詳しく言うと
・マウスでドラッグしてのサイズ変更ができない。そもそもコントロールの淵にマウスポインタを持って行ってもサイズ変更用のマウスポインタにならない。
・プロパティウィンドウでの変更は可能。
・いったんコントロールをウィンドウの外に移動すると、ウィンドウからはみ出た部分だけサイズ変更用のマウスポインタになる。
といった具合。

調べてみると、継承元のフォームの WindowState プロパティが Normal 以外のとき(つまり Maximized か Minimized にしているとき)に起こるようだ。この設定が必要なら実行時に設定するのがよさそうだ。

以上。

アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

livedoor 天気