.NETプログラミング研究 第35号

.NET質問箱

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

ComboBoxで上下矢印キーで項目を変更できないようにするには?

注意

この記事の最新版は「ComboBoxが上下矢印キーで項目を変更できないようにする」で公開しています。

【質問】

WindowsアプリケーションのComboBoxコントロールで、マウスだけで項目を選択でき、上下矢印キーによる選択ができないようにしたいのですが、どのような方法がありますか?

【回答】

簡単な方法としては、ComboBoxのKeyDownイベントハンドラで上下矢印キーが押された時にKeyEventArgsオブジェクトのHandledプロパティをTrueにして、キー入力を無効にする方法があります。

  1
  2
  3
  4
  5
  6
  7
  8
'ComboBox1のKeyDownイベントハンドラ
Private Sub ComboBox1_KeyDown(ByVal sender As Object, _
    ByVal e As System.Windows.Forms.KeyEventArgs) _
    Handles ComboBox1.KeyDown
    If e.KeyCode = Keys.Down Or e.KeyCode = Keys.Up Then
        e.Handled = True
    End If
End Sub
  1
  2
  3
  4
  5
  6
  7
//ComboBox1のKeyDownイベントハンドラ
private void ComboBox1_KeyDown(
    object sender, System.Windows.Forms.KeyEventArgs e)
{
    if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up)
        e.Handled = true;
}

しかし残念ながらこの方法は、.NET Framework 1.0では正常に働きません。(1.1ではこのバグが修正されています。).NET Framework 1.0では、ComboBoxの派生クラスを作成し、WndProcやPreProcessMessageメソッドをオーバーライドすることにより、上下矢印キーを無視するなどの方法を使用する必要があります。

次にPreProcessMessageメソッドをオーバーライドすることにより上下矢印キーでの操作を無効にしたComboBoxの派生クラス(MyComboBoxクラス)のサンプルを示します。このクラスを実際に使用するには、"System.Windows.Forms.ComboBox"の代わりに"MyComboBox"を使うようにします。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
''' <summary>
''' 上下矢印キーで項目を変更できないComboBox
''' </summary>
Public Class MyComboBox
    Inherits System.Windows.Forms.ComboBox
 
    Private WM_KEYDOWN As Integer = &H100
 
    Public Overrides Function PreProcessMessage( _
        ByRef msg As Message) As Boolean
        If msg.Msg = WM_KEYDOWN Then
            '上下矢印キー操作を無効にする
            Dim keyCode As Keys = _
                CType(msg.WParam.ToInt32(), Keys) And Keys.KeyCode
            If keyCode = Keys.Up Or keyCode = Keys.Down Then
                Return True
            End If
        End If
        Return MyBase.PreProcessMessage(msg)
    End Function
End Class
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
/// <summary>
/// 上下矢印キーで項目を変更できないComboBox
/// </summary>
public class MyComboBox : System.Windows.Forms.ComboBox
{
    private const int WM_KEYDOWN = 0x100;
 
    public override bool PreProcessMessage(ref Message msg)
    {
        if (msg.Msg == WM_KEYDOWN)
        {
            //上下矢印キー操作を無効にする
            Keys keyCode = (Keys)(int)msg.WParam & Keys.KeyCode;
            if (keyCode == Keys.Up || keyCode == Keys.Down)
                return true;
        }
        return base.PreProcessMessage(ref msg);
    }
}

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

PageSetupDialogのMarginsが正常に機能しない

注意

この記事の最新版は「ページ設定ダイアログのMarginsが正常に機能しない」で公開しています。

【質問】

PageSetupDialogを使ってページ設定ダイアログを表示したとき、マージン指定が正常に機能しません。例えば次のようなコードでマージンの上下左右に1インチを指定してもページ設定ダイアログでは10センチと表示され、さらに「OK」で確定後、PageSetupDialogオブジェクトのPageSettings.Marginsの値が上下左右39になってしまいます。なぜでしょうか?

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
Dim PageSetupDialog1 As New PageSetupDialog
PageSetupDialog1.Document = New System.Drawing.Printing.PrintDocument
'マージンを指定する
PageSetupDialog1.PageSettings.Margins = _
    New System.Drawing.Printing.Margins(100, 100, 100, 100)
 
'ページ設定ダイアログを表示する
If PageSetupDialog1.ShowDialog() = DialogResult.OK Then
    Console.WriteLine(PageSetupDialog1.PageSettings.Margins)
End If
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
PageSetupDialog PageSetupDialog1 = new PageSetupDialog();
PageSetupDialog1.Document =
    new System.Drawing.Printing.PrintDocument();
//マージンを指定する
PageSetupDialog1.PageSettings.Margins =
    new System.Drawing.Printing.Margins(100, 100, 100, 100);
 
//ページ設定ダイアログを表示する
if (PageSetupDialog1.ShowDialog() == DialogResult.OK)
    //指定されたマージンを表示する
    Console.WriteLine(PageSetupDialog1.PageSettings.Margins);

【回答】

これは.NET Frameworkのバグです。マイクロソフトサポート技術情報814355で紹介されています。

マイクロソフトサポート技術情報では回避法はコントロールパネルの「地域と言語のオプション」の設定を変更するということですが、それができれば苦労はありません。

今後もこのバグが修正されないという前提のもとでは、回避法として、システムでメートル法が選択されているか確認し、そうであればページ設定ダイアログを表示する前にマージンの単位を変更するという方法が考えられます。この方法はニュースグループで紹介されています。

この方法を使って問題を回避したコードの例を示します。ただし、将来このバグが修正された時には逆にバグとなってしまうことに注意してください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
Dim PageSetupDialog1 As New PageSetupDialog
PageSetupDialog1.Document = New System.Drawing.Printing.PrintDocument
'マージンを指定する
PageSetupDialog1.PageSettings.Margins = _
    New System.Drawing.Printing.Margins(100, 100, 100, 100)
 
'メートル法を使っている時は、メートルに直す
If System.Globalization.RegionInfo.CurrentRegion.IsMetric Then
    PageSetupDialog1.PageSettings.Margins.Top *= 2.54
    PageSetupDialog1.PageSettings.Margins.Bottom *= 2.54
    PageSetupDialog1.PageSettings.Margins.Left *= 2.54
    PageSetupDialog1.PageSettings.Margins.Right *= 2.54
End If
 
'ページ設定ダイアログを表示する
If PageSetupDialog1.ShowDialog() = DialogResult.OK Then
    Console.WriteLine(PageSetupDialog1.PageSettings.Margins)
ElseIf System.Globalization.RegionInfo.CurrentRegion.IsMetric Then
    'また元に戻す
    PageSetupDialog1.PageSettings.Margins.Top /= 2.54
    PageSetupDialog1.PageSettings.Margins.Bottom /= 2.54
    PageSetupDialog1.PageSettings.Margins.Left /= 2.54
    PageSetupDialog1.PageSettings.Margins.Right /= 2.54
End If
  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
 42
 43
 44
PageSetupDialog PageSetupDialog1 = new PageSetupDialog();
PageSetupDialog1.Document =
    new System.Drawing.Printing.PrintDocument();
//マージンを指定する
PageSetupDialog1.PageSettings.Margins =
    new System.Drawing.Printing.Margins(100, 100, 100, 100);
 
//メートル法を使っている時は、メートルに直す
if (System.Globalization.RegionInfo.CurrentRegion.IsMetric)
{
    PageSetupDialog1.PageSettings.Margins.Top =
        (int)Math.Round(
        PageSetupDialog1.PageSettings.Margins.Top * 2.54);
    PageSetupDialog1.PageSettings.Margins.Bottom =
        (int)Math.Round(
        PageSetupDialog1.PageSettings.Margins.Bottom * 2.54);
    PageSetupDialog1.PageSettings.Margins.Left =
        (int)Math.Round(
        PageSetupDialog1.PageSettings.Margins.Left * 2.54);
    PageSetupDialog1.PageSettings.Margins.Right =
        (int)Math.Round(
        PageSetupDialog1.PageSettings.Margins.Right * 2.54);
}
 
//ページ設定ダイアログを表示する
if (PageSetupDialog1.ShowDialog() == DialogResult.OK)
    //指定されたマージンを表示する
    Console.WriteLine(PageSetupDialog1.PageSettings.Margins);
else if (System.Globalization.RegionInfo.CurrentRegion.IsMetric)
{
    //また元に戻す
    PageSetupDialog1.PageSettings.Margins.Top =
        (int)Math.Round(
        PageSetupDialog1.PageSettings.Margins.Top / 2.54);
    PageSetupDialog1.PageSettings.Margins.Bottom =
        (int)Math.Round(
        PageSetupDialog1.PageSettings.Margins.Bottom / 2.54);
    PageSetupDialog1.PageSettings.Margins.Left =
        (int)Math.Round(
        PageSetupDialog1.PageSettings.Margins.Left / 2.54);
    PageSetupDialog1.PageSettings.Margins.Right =
        (int)Math.Round(
        PageSetupDialog1.PageSettings.Margins.Right / 2.54);
}

TreeViewのNodeの右に色の違う文字列を描画するには?

【質問】

Outlook ExpressのTreeViewでは、未読のメール数がノードの右側に青色で表示されますが、同じようなことを.NETでできませんか?

【回答】

これを実現させる方法がC# Helpで紹介されています。

この記事ではC#のコードしか紹介されていませんが、これをVB.NETのコードにしたものを掲示板でよねKENさんから投稿していただきました。

これらのコードを以下に紹介させていただきます(一部変更していますが、ほぼ同じです)。フォームにツリービューコントロールTreeView1が配置されているものとし、TreeView1のあるノードの末尾にそのノードの子ノードの数(孫ノード以降は含めない)を青色で表示しています。TreeView1のあるフォームクラスに記述してください。

  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
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
'Imports System.Runtime.InteropServices
'が宣言されているものとする
 
Public Structure NMHDR
    Public hwndFrom As IntPtr
    Public idFrom As Integer
    Public code As UInt32
End Structure
 
Public Structure NMCUSTOMDRAW
    Public hdr As NMHDR
    Public dwDrawStage As Integer
    Public hdc As Integer
    Public x1 As Integer
    Public y1 As Integer
    Public x2 As Integer
    Public y2 As Integer
    Public dwItemSpec As Integer
    Public uItemState As Integer
    Public lItemlParam As Integer
End Structure
 
Private Const WM_NOTIFY As Integer = &H4E
Private ReadOnly NM_CUSTOMDRAW As UInt32 = _
    Convert.ToUInt32(4294967284)
Private Const CDDS_ITEMPREPAINT As Integer = 65537
Private Const CDRF_NOTIFYSUBITEMDRAW As Integer = 32
 
Protected Overrides Sub WndProc(ByRef m As Message)
    Dim lp2 As NMCUSTOMDRAW
    Dim lp As NMHDR
 
    If (m.Msg = WM_NOTIFY) Then
        lp = Marshal.PtrToStructure(m.LParam, lp.GetType())
 
        If (lp.code.CompareTo(NM_CUSTOMDRAW) = 0) Then
            lp2 = Marshal.PtrToStructure(m.LParam, lp2.GetType())
 
            If (lp2.dwDrawStage = CDDS_ITEMPREPAINT) Then
                MyBase.WndProc(m)
                TreeViewPaint(m)
                m.Result = IntPtr.Zero
                Return
            Else
                m.Result = New IntPtr(CDRF_NOTIFYSUBITEMDRAW)
            End If
        End If
    End If
    MyBase.WndProc(m)
End Sub
 
Public Sub TreeViewPaint(ByRef m As Message)
    Dim lp2 As NMCUSTOMDRAW
    Dim g As Graphics
    Dim node As TreeNode
    Dim strText As String
    Dim x1, y1 As Long
 
    lp2 = Marshal.PtrToStructure(m.LParam, lp2.GetType())
 
    g = System.Drawing.Graphics.FromHwnd(Me.TreeView1.Handle)
    node = TreeView1.GetNodeAt(lp2.x1 + 1, lp2.y1 + 1)
 
    x1 = lp2.x1 + node.Bounds.X + node.Bounds.Width + 5
    y1 = lp2.y1
    strText = "( " + node.Nodes.Count.ToString() + " )"
 
    'Nodeの子ノード数をNodeの後ろに青で表示する
    g.DrawString(strText, TreeView1.Font, _
        System.Drawing.Brushes.Blue, x1, y1, _
        System.Drawing.StringFormat.GenericTypographic)
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
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
private unsafe struct NMHDR
{
    public uint hwndFrom;
    public uint idFrom;
    public uint code;
}
 
private unsafe struct  NMCUSTOMDRAW
{
    public NMHDR hdr;
    public uint dwDrawStage;
    public uint hdc;
    public uint x1;
    public uint y1;
    public uint x2;
    public uint y2;
    public uint dwItemSpec;
    public uint uItemState;
    public uint lItemlParam;
}
 
private const uint WM_NOTIFY = 0x4E;
private const uint NM_CUSTOMDRAW = 4294967284;
private const  uint CDDS_ITEMPREPAINT = 65537;
private const uint CDRF_NOTIFYSUBITEMDRAW = 32;
 
protected unsafe override void WndProc(ref Message m)
{
    NMCUSTOMDRAW *lp2;
    NMHDR *lp;
 
    if (m.Msg == WM_NOTIFY)
    {
        lp = (NMHDR*) m.LParam.ToPointer();
        if (lp->code == NM_CUSTOMDRAW)
        {
            lp2 = (NMCUSTOMDRAW*)m.LParam.ToPointer();
            if (lp2->dwDrawStage == CDDS_ITEMPREPAINT)
            {
                base.WndProc(ref m);
                TreeViewPaint(ref m);
                m.Result =(IntPtr)0;
                return;
            }
            else
            {
                m.Result =(IntPtr)CDRF_NOTIFYSUBITEMDRAW;
            }
        }
    }
    base.WndProc(ref m);
}
 
private unsafe void TreeViewPaint(ref Message m)
{
    NMCUSTOMDRAW *lp2;
    System.Drawing.Graphics g;
    System.Windows.Forms.TreeNode node;
    string strText;
    long x1, y1;
 
    lp2 = (NMCUSTOMDRAW*)m.LParam.ToPointer();
 
    g = System.Drawing.Graphics.FromHwnd(TreeView1.Handle);
 
    node = TreeView1.GetNodeAt((int)lp2->x1 + 1, (int)lp2->y1 + 1);
 
    x1 = lp2->x1 + node.Bounds.X + node.Bounds.Width + 5;
    y1 = lp2->y1;
    strText = "( " + node.Nodes.Count.ToString() + " )";
 
    //Nodeの子ノード数をNodeの後ろに青で表示する
    g.DrawString(strText, TreeView1.Font,
        System.Drawing.Brushes.Blue,
        x1, y1,
        System.Drawing.StringFormat.GenericTypographic);
}

ただしこの方法では、ノードの末尾に文字列を描画しているだけなので、水平スクロールバーが表示される時(あるいは表示されるべき時)にこの文字列の長さは考慮されないため、スクロールさせても表示されなくなる「可能性があります。

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

コンピュータ雑学

最近巷では「トリビアの泉」や「うんちく王」の影響でか、雑学ブームであるらしいです。生ぬるい雑学を得意げに披露されると非常に腹立たしいものですが、ここではこのような人に話すと嫌われるであろうコンピュータに関する雑学を紹介していこうかなと考えています(今回限りという可能性も大いにありますが)。.NETとは関係ありませんが、息抜きにどうぞ。

「C言語」の「C」の意味は?

C言語は、1973年頃AT&Tベル研究所でデニス・リッチー氏らにより、UNIXのために開発されたプログラミング言語です。同AT&Tベル研究所のケン・トンプソンらにより開発されたB言語の後続という意味で、「C言語」と名づけられましたので、「C」は何かの略ということではありません。

それではB言語の前にA言語があったのかというと、そうではありませんが、B言語の元をたどると「ALGOL 60」となるため(そのまえにBCPL、CPLがありますが)、うまい具合に「ABC」と並びます。

そうなると次は「D」ですが、「D言語」は現在開発中で、そこそこ知られています。

さらに調べてみたところ、E、F、G(National Instruments Labviewのgraphical programming language)、Hと見つかりました。Iに関しては見つかりませんでしたが、ちゃんと探せばあるかもしれません。

コメント



ページ情報
  • カテゴリ : .NET
  • 作成日 : 2004-06-16 (水) 06:00:00
  • 作成者 : DOBON!
  • 最終編集日 : 2010-03-21 (日) 02:20:32
  • 最終編集者 : DOBON!
[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]