.NETプログラミング研究 第50号 †
.NET質問箱 †
「.NET質問箱」では、「どぼん!のプログラミング掲示板」に書き込まれた.NETプログラミングに関する投稿を基に、さらに考察を加え、Q&A形式にまとめて紹介します。
DataGridの列の幅を文字列の幅に合わせて自動的に調節するには? †
【質問】
System.Windows.Forms.DataGridの列の幅を、その列にあるすべてのセルとヘッダの文字列の最大幅となるように自動調節したいのですが、どのようにすればよいのでしょうか?
【回答】
DataGridの列のすべてのセルの文字列(及びヘッダのHeaderText)の長さをGraphics.MeasureStringメソッドで調べ、一番長いものに列のWidthを設定するという方法になるでしょう。
この方法を使った簡単なコードを以下に紹介します。ここでは、DataGridのDataSourceにDataTableが設定されているものとします。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| | Public Shared Sub AutoSizeColumnWidth( _
ByVal grid As DataGrid, ByVal column As Integer)
Dim g As Graphics = Graphics.FromHwnd(grid.Handle)
Dim sf As New StringFormat(StringFormat.GenericTypographic)
Dim dt As DataTable = CType(grid.DataSource, DataTable)
Dim rowsCount As Integer = dt.Rows.Count
Dim maxWidth As Single = 0
Dim i As Integer
For i = 0 To rowsCount - 1
Dim [text] As String = grid(i, column).ToString()
maxWidth = Math.Max(g.MeasureString( _
[text], grid.Font, grid.Width, sf).Width, maxWidth)
Next i
Dim cs As DataGridColumnStyle = _
grid.TableStyles(dt.TableName).GridColumnStyles(column)
maxWidth = Math.Max(g.MeasureString( _
cs.HeaderText, grid.Font, grid.Width, sf).Width, maxWidth)
g.Dispose()
cs.Width = CInt(maxWidth) + 8
End Sub
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| | public static void AutoSizeColumnWidth(DataGrid grid, int column)
{
Graphics g = Graphics.FromHwnd(grid.Handle);
StringFormat sf =
new StringFormat(StringFormat.GenericTypographic);
DataTable dt = ((DataTable) grid.DataSource);
int rowsCount = dt.Rows.Count;
float maxWidth = 0;
for (int i = 0; i < rowsCount; i++)
{
string text = grid[i, column].ToString();
maxWidth = Math.Max(g.MeasureString(
text, grid.Font, grid.Width, sf).Width, maxWidth);
}
DataGridColumnStyle cs =
grid.TableStyles[dt.TableName].GridColumnStyles[column];
maxWidth = Math.Max(g.MeasureString(
cs.HeaderText, grid.Font, grid.Width, sf).Width, maxWidth);
g.Dispose();
cs.Width = (int) maxWidth + 8;
}
|
この方法は次のページでも紹介されていますので、参考にしてください。
しかしこの方法には欠点があります。それは、セル内に表示される文字列をToStringで取得している点です。実際に表示される文字列はToStringで取得できる文字列と同じになるとは限りません。しかし残念ながら、実際に表示される文字列を取得するのはとても難しいです。例えばDataGridTextBoxColumnが設定されている場合は、DataGridTextBoxColumnのGetTextメソッドで取得できますが、このメソッドがprivateのため、通常はアクセスできません。
よって実際に表示される文字列を取得するには、「隠蔽されている非パブリックメンバを呼び出す」のようにリフレクションを使ってGetTextメソッドにアクセスするか、自分で列スタイルを作成するかなどの方法になりそうです。(自分でGetTextと同じことをするコードを書く方法もありますが、全く同じになる保障はありません。)
○この記事の基になった掲示板のスレッド
タブコントロールのタブを選択できないようにするには? †
【質問】
TabControl内のTabPageのEnabledプロパティをFalseにしてもそのタブは選択できてしまいます。指定したタブが選択できないようにするには、どうしたらいいのでしょうか?
【回答】
最も簡単なのは、TabControlのSelectedIndexChangedイベントで選択したくないタブが選択された時に、別のタブを選択するという方法です。
例えば次のようにすれば2番目のTabPageは選択できなくなります。
1
2
3
4
5
6
7
8
9
10
11
12
| | Private _tabIndex As Integer = -1
Private Sub TabControl1_SelectedIndexChanged( _
ByVal sender As Object, ByVal e As System.EventArgs) _
Handles TabControl1.SelectedIndexChanged
If TabControl1.SelectedIndex = 1 Then
TabControl1.SelectedIndex = _tabIndex
Else
_tabIndex = TabControl1.SelectedIndex
End If
End Sub
|
1
2
3
4
5
6
7
8
9
10
11
12
| | private int _tabIndex = -1;
private void TabControl1_SelectedIndexChanged(
object sender, EventArgs e)
{
if (TabControl1.SelectedIndex == 1)
TabControl1.SelectedIndex = _tabIndex;
else
_tabIndex = TabControl1.SelectedIndex;
}
|
この方法では選択させたくないタブが一瞬表示されるかもしれません。これが嫌だという場合は、さらに面倒な方法を使わなければなりません。この方法については、「プログラミング道掲示板」の No8361 のスレッド、または、次に示すニュースグループ「microsoft.public.dotnet.framework.windowsforms」への投稿を参考にしてください。
○この記事の基になった掲示板のスレッド
埋め込まれたWAVのリソースファイルを再生するには? †
【質問】
アセンブリにリソースとして埋め込んだWAVファイルを再生したいのですが、どのような方法がありますか?
【回答】
まず、埋め込まれたWAVファイルを一時ファイルに保存し、再生するという方法があります。これは、例えば次のようになります。(この例では、作成した一時ファイルを削除していません。実際には適当なタイミングで削除すべきです。)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
| | Imports System.Runtime.InteropServices
Imports System.Resources
Class MainClass
Private Const SND_ASYNC As Integer = &H1
Private Const SND_MEMORY As Integer = &H4
Private Const SND_FILENAME As Integer = &H20000
<DllImport("winmm.dll")> _
Private Shared Function PlaySound( _
ByVal pszSound As String, _
ByVal hmod As IntPtr, _
ByVal fdwSound As Integer) As Boolean
End Function
Public Shared Sub Main()
Dim asm As System.Reflection.Assembly = _
System.Reflection.Assembly.GetExecutingAssembly()
Dim resourceName As String = asm.GetName().Name + ".test.wav"
Dim strm As System.IO.Stream = _
asm.GetManifestResourceStream(resourceName)
Dim buffer() As Byte = New Byte(strm.Length) {}
strm.Read(buffer, 0, CInt(buffer.Length))
strm.Close()
Dim tempName As String = System.IO.Path.GetTempFileName()
Dim fs As New System.IO.FileStream( _
tempName, System.IO.FileMode.Create)
fs.Write(buffer, 0, CInt(buffer.Length))
fs.Close()
PlaySound(tempName, IntPtr.Zero, SND_FILENAME)
End Sub
End Class
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
| | using System.Runtime.InteropServices;
using System.Resources;
class MainClass
{
const int SND_ASYNC = 0x1;
const int SND_MEMORY = 0x4;
const int SND_FILENAME = 0x20000;
[DllImport("winmm.dll")]
private static extern bool PlaySound(
string pszSound, IntPtr hmod, uint fdwSound);
public static void Main()
{
System.Reflection.Assembly asm =
System.Reflection.Assembly.GetExecutingAssembly();
string resourceName = asm.GetName().Name + ".test.wav";
System.IO.Stream strm =
asm.GetManifestResourceStream(resourceName);
byte[] buffer = new Byte[strm.Length];
strm.Read(buffer, 0, (int) buffer.Length);
strm.Close();
string tempName = System.IO.Path.GetTempFileName();
System.IO.FileStream fs =
new System.IO.FileStream(
tempName, System.IO.FileMode.Create);
fs.Write(buffer, 0, (int) buffer.Length);
fs.Close();
PlaySound(tempName, IntPtr.Zero, SND_FILENAME);
}
}
|
PlaySound(もしくはsndPlaySound)関数を使ってWAVを再生する場合は、一時ファイルを作成する必要はありません。次のようにメモリ内に読み込まれたデータをそのまま再生することができます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
| | Imports System.Runtime.InteropServices
Imports System.Resources
Class MainClass
Private Const SND_ASYNC As Integer = &H1
Private Const SND_MEMORY As Integer = &H4
Private Const SND_FILENAME As Integer = &H20000
<DllImport("winmm.dll")> _
Private Shared Function PlaySound( _
ByVal pszSound() As Byte, _
ByVal hmod As IntPtr, _
ByVal fdwSound As Integer) As Boolean
End Function
Public Shared Sub Main()
Dim asm As System.Reflection.Assembly = _
System.Reflection.Assembly.GetExecutingAssembly()
Dim resourceName As String = asm.GetName().Name + ".test.wav"
Dim strm As System.IO.Stream = _
asm.GetManifestResourceStream(resourceName)
Dim buffer() As Byte = New Byte(strm.Length) {}
strm.Read(buffer, 0, CInt(buffer.Length))
strm.Close()
PlaySound(buffer, IntPtr.Zero, SND_MEMORY)
End Sub
End Class
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| | using System.Runtime.InteropServices;
using System.Resources;
class MainClass
{
const int SND_ASYNC = 0x1;
const int SND_MEMORY = 0x4;
const int SND_FILENAME = 0x20000;
[DllImport("winmm.dll")]
private static extern bool PlaySound(
byte[] pszSound, IntPtr hmod, uint fdwSound);
public static void Main()
{
System.Reflection.Assembly asm =
System.Reflection.Assembly.GetExecutingAssembly();
string resourceName = asm.GetName().Name + ".test.wav";
System.IO.Stream strm =
asm.GetManifestResourceStream(resourceName);
byte[] buffer = new Byte[strm.Length];
strm.Read(buffer, 0, (int) buffer.Length);
strm.Close();
PlaySound(buffer, IntPtr.Zero, SND_MEMORY);
}
}
|
補足:上記の例では音を再生するのにPlaySound関数を使用しましたが、代わりにsndPlaySound関数を使用すると、Windows XP(Professional Edition)ではうまく行かないようです。sndPlaySound関数についてMSDNでは「この関数は、PlaySound関数のサブセットであり、Windowsの以前のバージョンとの互換性のために残されています。」と書かれており、PlaySound関数を使った方がよいでしょう。
○この記事の基になった掲示板のスレッド
モードレスウィンドウが閉じられた時に結果を取得するには? †
【質問】
モーダルダイアログとしてフォームを表示したときは、DialogResultプロパティで結果を取得することができますが、モードレスで表示したときはできません。モードレスウィンドウが閉じられた時に結果を取得するにはどのようにすればよいのでしょうか?
【回答】
ここでは、Form1からForm2をモードレスで表示し、Form1でForm2が閉じた時の結果を取得するようにします。
まず、Form1にForm2のClosedイベントハンドラを作成することによりForm2が閉じられたことを知り、この時Form2の結果を取得するようにします。次の例では、Button1をクリックすることにより、Form2を表示しています。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| | Private Sub Button1_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Dim f2 As New Form2
AddHandler f2.Closed, AddressOf Form2_Closed
f2.Show()
End Sub
Private Sub Form2_Closed(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim f2 As Form2 = CType(sender, Form2)
Console.WriteLine(f2.DialogResult)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| | private void Button1_Click(object sender, System.EventArgs e)
{
Form2 f2 = new Form2();
f2.Closed += new EventHandler(Form2_Closed);
f2.Show();
}
private void Form2_Closed(object sender, EventArgs e)
{
Form2 f = (Form2) sender;
Console.WriteLine(f.DialogResult);
}
|
Form2では、結果を設定してからフォームを閉じるようにします。Form2のButton1をクリックすることによりDialogResultをDialogResult.OKとして閉じるには、次のようにします。
1
2
3
4
5
6
7
8
| | Private Sub Button1_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) Handles Button1.Click
Me.DialogResult = DialogResult.OK
Me.Close()
End Sub
|
1
2
3
4
5
6
7
8
| | private void Button1_Click(object sender, System.EventArgs e)
{
this.DialogResult = DialogResult.OK;
this.Close();
}
|
このような方法以外に掲示板では、Codingslaveさんが、別のスレッドを作成してForm.ShowDialogメソッドを呼び出すことにより、モードレスウィンドウのようにする方法を提案されています。
○この記事の基になった掲示板のスレッド
コメント †