• 追加された行はこの色です。
  • 削除された行はこの色です。
#title(.NETプログラミング研究 第50号)

#navi(.NETプログラミング研究)

#contents

*.NETプログラミング研究 第50号 [#p62becca]

**.NET質問箱 [#f0aedfbf]

「.NET質問箱」では、「どぼん!のプログラミング掲示板」に書き込まれた.NETプログラミングに関する投稿を基に、さらに考察を加え、Q&A形式にまとめて紹介します。

-[[どぼん!のプログラミング掲示板>http://dobon.net/vb/bbs.html]]
-[[どぼん!のプログラミング掲示板>https://dobon.net/vb/bbs.html]]

***DataGridの列の幅を文字列の幅に合わせて自動的に調節するには? [#e424327c]

#column(注意){{
この記事の最新版は「[[DataGridの列の幅を文字列の幅に合わせて自動的に調節する>http://dobon.net/vb/dotnet/datagrid/adjustcolumn.html]]」で公開しています。
この記事の最新版は「[[DataGridの列の幅を文字列の幅に合わせて自動的に調節する>https://dobon.net/vb/dotnet/datagrid/adjustcolumn.html]]」で公開しています。
}}

''【質問】''

System.Windows.Forms.DataGridの列の幅を、その列にあるすべてのセルとヘッダの文字列の最大幅となるように自動調節したいのですが、どのようにすればよいのでしょうか?

''【回答】''

DataGridの列のすべてのセルの文字列(及びヘッダのHeaderText)の長さをGraphics.MeasureStringメソッドで調べ、一番長いものに列のWidthを設定するという方法になるでしょう。

この方法を使った簡単なコードを以下に紹介します。ここでは、DataGridのDataSourceにDataTableが設定されているものとします。

#code(vbnet){{
''' <summary>
''' DataGridの列の幅をTextの幅に合わせて変更する
''' </summary>
''' <param name="grid">対象とするDataGrid</param>
''' <param name="column">列の番号</param>
Public Shared Sub AutoSizeColumnWidth( _
    ByVal grid As DataGrid, ByVal column As Integer)
    'DataGridのGraphicsを取得
    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
}}

#code(csharp){{
/// <summary>
/// DataGridの列の幅をTextの幅に合わせて変更する
/// </summary>
/// <param name="grid">対象とするDataGrid</param>
/// <param name="column">列の番号</param>
public static void AutoSizeColumnWidth(DataGrid grid, int column)
{
    //DataGridのGraphicsを取得
    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;
}
}}

この方法は次のページでも紹介されていますので、参考にしてください。

-[[Windows Forms FAQ | 5.52 How can I autosize a column in my datagrid?>http://www.syncfusion.com/FAQ/WinForms/FAQ_c44c.asp#q877q]]
-[[microsoft.public.dotnet.languages.vb | Re: how-to autosize the width of a column in a DataGrid>http://www.msusenet.com/archive/index.php/t-244101.html]]

しかしこの方法には欠点があります。それは、セル内に表示される文字列をToStringで取得している点です。実際に表示される文字列はToStringで取得できる文字列と同じになるとは限りません。しかし残念ながら、実際に表示される文字列を取得するのはとても難しいです。例えばDataGridTextBoxColumnが設定されている場合は、DataGridTextBoxColumnのGetTextメソッドで取得できますが、このメソッドがprivateのため、通常はアクセスできません。

よって実際に表示される文字列を取得するには、「隠蔽されている非パブリックメンバを呼び出す」のようにリフレクションを使ってGetTextメソッドにアクセスするか、自分で列スタイルを作成するかなどの方法になりそうです。(自分でGetTextと同じことをするコードを書く方法もありますが、全く同じになる保障はありません。)

-[[隠蔽されている非パブリックメンバを呼び出す>http://dobon.net/vb/dotnet/programing/invokenonpublicmember.html]]
-[[隠蔽されている非パブリックメンバを呼び出す>https://dobon.net/vb/dotnet/programing/invokenonpublicmember.html]]

○この記事の基になった掲示板のスレッド

-[[DataGridの幅設定 | 投稿者(敬称略) えーぴーてー, 深山>http://dobon.net/vb/bbs/log3-4/2447.html]]
-[[DataGridの幅設定 | 投稿者(敬称略) えーぴーてー, 深山>https://dobon.net/vb/bbs/log3-4/2447.html]]

***タブコントロールのタブを選択できないようにするには? [#wff9ab18]

#column(注意){{
この記事の最新版は「[[TabControlのタブを選択できないようにする>http://dobon.net/vb/dotnet/control/tabdisabledpage.html]]」で公開しています。
この記事の最新版は「[[TabControlのタブを選択できないようにする>https://dobon.net/vb/dotnet/control/tabdisabledpage.html]]」で公開しています。
}}

''【質問】''

TabControl内のTabPageのEnabledプロパティをFalseにしてもそのタブは選択できてしまいます。指定したタブが選択できないようにするには、どうしたらいいのでしょうか?

''【回答】''

最も簡単なのは、TabControlのSelectedIndexChangedイベントで選択したくないタブが選択された時に、別のタブを選択するという方法です。

例えば次のようにすれば2番目のTabPageは選択できなくなります。

#code(vbnet){{
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
}}

#code(csharp){{
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」への投稿を参考にしてください。

-[[タブコントロールのタブの選択について>http://dobon.net/cgi-bin/vbbbs/cbbs.cgi?mode=al2&namber=8361&no=0]]
-[[タブコントロールのタブの選択について>https://dobon.net/cgi-bin/vbbbs/cbbs.cgi?mode=al2&namber=8361&no=0]]
-[[Subject:Re: Disabling A Tab Page on A TAb Control | Newsgroups:microsoft.public.dotnet.framework.windowsforms>http://groups.google.co.jp/groups?hl=ja&lr=&inlang=ja&selm=ALTDd.62632%249N2.2159158%40weber.videotron.net]]

○この記事の基になった掲示板のスレッド

-[[タブコントロールについて | 投稿者(敬称略) taro, 管理人>http://dobon.net/vb/bbs/log3-5/2141.html]]
-[[タブコントロールについて | 投稿者(敬称略) taro, 管理人>https://dobon.net/vb/bbs/log3-5/2141.html]]

***埋め込まれたWAVのリソースファイルを再生するには? [#a630ae27]

#column(注意){{
この記事の最新版は「[[埋め込まれたWAVEのリソースファイルを再生する>http://dobon.net/vb/dotnet/programing/playembeddedwave.html]]」で公開しています。
この記事の最新版は「[[埋め込まれたWAVEのリソースファイルを再生する>https://dobon.net/vb/dotnet/programing/playembeddedwave.html]]」で公開しています。
}}

''【質問】''

アセンブリにリソースとして埋め込んだWAVファイルを再生したいのですが、どのような方法がありますか?

''【回答】''

まず、埋め込まれたWAVファイルを一時ファイルに保存し、再生するという方法があります。これは、例えば次のようになります。(この例では、作成した一時ファイルを削除していません。実際には適当なタイミングで削除すべきです。)

#code(vbnet){{
Imports System.Runtime.InteropServices
Imports System.Resources

Class MainClass
    '音を鳴らすWin32 API
    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
}}

#code(csharp){{
using System.Runtime.InteropServices;
using System.Resources;

class MainClass
{
    //音を鳴らすWin32 API
    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を再生する場合は、一時ファイルを作成する必要はありません。次のようにメモリ内に読み込まれたデータをそのまま再生することができます。

#code(vbnet){{
Imports System.Runtime.InteropServices
Imports System.Resources

Class MainClass
    '音を鳴らすWin32 API
    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
}}

#code(csharp){{
using System.Runtime.InteropServices;
using System.Resources;

class MainClass
{
    //音を鳴らすWin32 API
    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関数を使った方がよいでしょう。

○この記事の基になった掲示板のスレッド

-[[実行ファイルに埋め込んだ音楽データの読み出し方法 | 投稿者(敬称略) えりんぎ, Codingslave>http://dobon.net/vb/bbs/log3-5/2424.html]]
-[[実行ファイルに埋め込んだ音楽データの読み出し方法 | 投稿者(敬称略) えりんぎ, Codingslave>https://dobon.net/vb/bbs/log3-5/2424.html]]

***モードレスウィンドウが閉じられた時に結果を取得するには? [#mb76a0b3]

#column(注意){{
この記事の最新版は「[[モードレスウィンドウが閉じられた時に結果を取得する>http://dobon.net/vb/dotnet/form/modelessformresult.html]]」で公開しています。
この記事の最新版は「[[モードレスウィンドウが閉じられた時に結果を取得する>https://dobon.net/vb/dotnet/form/modelessformresult.html]]」で公開しています。
}}

''【質問】''

モーダルダイアログとしてフォームを表示したときは、DialogResultプロパティで結果を取得することができますが、モードレスで表示したときはできません。モードレスウィンドウが閉じられた時に結果を取得するにはどのようにすればよいのでしょうか?

''【回答】''

ここでは、Form1からForm2をモードレスで表示し、Form1でForm2が閉じた時の結果を取得するようにします。

まず、Form1にForm2のClosedイベントハンドラを作成することによりForm2が閉じられたことを知り、この時Form2の結果を取得するようにします。次の例では、Button1をクリックすることにより、Form2を表示しています。

#code(vbnet){{
'Button1のクリックイベントハンドラ
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

'Form2が閉じた時
Private Sub Form2_Closed(ByVal sender As Object, _
    ByVal e As System.EventArgs)
    Dim f2 As Form2 = CType(sender, Form2)
    '結果を表示する
    Console.WriteLine(f2.DialogResult)
}}

#code(csharp){{
//Button1のクリックイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    Form2 f2 = new Form2();
    f2.Closed += new EventHandler(Form2_Closed);
    //表示する
    f2.Show();
}

//Form2が閉じた時
private void Form2_Closed(object sender, EventArgs e)
{
    Form2 f = (Form2) sender;
    //結果を表示する
    Console.WriteLine(f.DialogResult);
}
}}

Form2では、結果を設定してからフォームを閉じるようにします。Form2のButton1をクリックすることによりDialogResultをDialogResult.OKとして閉じるには、次のようにします。

#code(vbnet){{
'Button1のクリックイベントハンドラ
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
}}

#code(csharp){{
//Button1のクリックイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //結果を設定する
    this.DialogResult = DialogResult.OK;
    //閉じる
    this.Close();
}
}}

このような方法以外に掲示板では、Codingslaveさんが、別のスレッドを作成してForm.ShowDialogメソッドを呼び出すことにより、モードレスウィンドウのようにする方法を提案されています。

○この記事の基になった掲示板のスレッド

-[[モードレスダイアログの結果を待つ? | 投稿者(敬称略) yuyu, Codingslave>http://dobon.net/vb/bbs/log3-5/2491.html]]
-[[モードレスダイアログの結果を待つ? | 投稿者(敬称略) yuyu, Codingslave>https://dobon.net/vb/bbs/log3-5/2491.html]]

**コメント [#n7109a63]
#comment

//これより下は編集しないでください
#pageinfo([[:Category/.NET]],2005-01-27 (木) 06:00:00,DOBON!,2010-03-22 (月) 01:52:07,DOBON!)

[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]