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

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

#contents

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

**お知らせ [#k62d31be]

過去に発行した「.NETプログラミング研究」のタイトルの一覧を閲覧できるようにしてほしいというご要望があったため、以下のURLから閲覧できるようにしました。

-[[.NETプログラミング研究 バックナンバー>http://dobon.net/vb/melma/dotnet.cgi]]
-[[.NETプログラミング研究 バックナンバー>https://dobon.net/vb/melma/dotnet.cgi]]

バックナンバーのメニュー部分を切り出して列挙して表示しているだけのものですが、ご利用いただければ幸いです。

**.NET質問箱 [#h26a52f5]

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

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

***文字列の計算式の計算結果を取得するには? [#d2a14585]

#column(注意){{
この記事の最新版は「[[文字列の計算式の計算結果を取得する>http://dobon.net/vb/dotnet/programing/eval.html]]」で公開しています。
この記事の最新版は「[[文字列の計算式の計算結果を取得する>https://dobon.net/vb/dotnet/programing/eval.html]]」で公開しています。
}}

''【質問】''

例えば、"(1+6)*5/(7-4)"のような計算式を表す文字列から、その計算結果を取得するにはどのようにすればよいのでしょうか?

''【回答】''

これには、いろいろな方法が考えられます。

正攻法で行けば、計算式を解析し、計算するコードを自分で書くということになります。そのために参考になりそうなコードが、例えば、「The Code Project」で紹介されている「A Math Expression Evaluator」です。この「A Math Expression Evaluator」は簡単な計算はもちろん、cos、sin、logなどにも対応しています。(ただし、コードはVB.NETのみです。)

-[[A Math Expression Evaluator>http://www.codeproject.com/vb/net/math_expression_evaluator.asp]]

さらに、掲示板でArAyさんが紹介されたMSDN Japanの「アルゴリズム入門:第1章 Visual C# による文字列処理入門」も参考になります。

-[[アルゴリズム入門:第1章 Visual C# による文字列処理入門>http://www.microsoft.com/japan/msdn/academic/Articles/Algorithm/01/]]

このように自分でコードを書く以外の方法も様々あります。

まず、JScript.NETのEvalを使う方法があります。この方法は、GotDotNet Message Boardsの「String to Numeric」のスレッドでenderminhさんが紹介しています。

-[[GotDotNet Message Boards - String to Numeric>http://www.gotdotnet.com/community/messageboard/Thread.aspx?id=212303]]

この方法によると、まず参照に「Microsoft.Jscript」と「Microsoft.Vsa」を追加し、次のようなコードにより、計算を実行します。

#code(vbnet){{
'計算式
Dim exp As String = "(1+6)*5/(7-4)"

Dim ve As Microsoft.JScript.Vsa.VsaEngine = _
    Microsoft.JScript.Vsa.VsaEngine.CreateEngine()
Dim result As Double = _
    CDbl(Microsoft.JScript.Eval.JScriptEvaluate(exp, ve))

'結果を表示
Console.WriteLine(result)
}}

#code(csharp){{
//計算式
string exp = "(1+6)*5/(7-4)";

Microsoft.JScript.Vsa.VsaEngine ve =
    Microsoft.JScript.Vsa.VsaEngine.CreateEngine();
double result =
    (double) Microsoft.JScript.Eval.JScriptEvaluate(
        exp, ve);

//結果を表示
Console.WriteLine(result);
}}

また、JScriptCodeProviderにより、Evalメソッドを実行する方法が「An Eval Function for C# using JScript.NET (JavaScript)」に紹介されています。

-[[An Eval Function for C# using JScript.NET (JavaScript)>http://www.odetocode.com/Code/80.aspx]]

この方法によると、次のようなコードがかけます。

#code(vbnet){{
'Imports System.Reflection
'Imports System.CodeDom.Compiler
'が宣言されているものとする

'計算式
Dim exp As String = "(1+6)*5/(7-4)"

'計算するためのコード
Dim [source] As String = _
    "package Evaluator {" + vbCrLf + _
    "class Evaluator {" + vbCrLf + _
    "public function Eval(expr : String) : String {" + vbCrLf + _
    "return eval(expr);} } }"

'コンパイルするための準備
Dim cp = New Microsoft.JScript.JScriptCodeProvider
Dim icc As ICodeCompiler = cp.CreateCompiler()
Dim cps As New CompilerParameters
Dim cres As CompilerResults
'メモリ内で出力を生成する
cps.GenerateInMemory = True
'コンパイルする
cres = icc.CompileAssemblyFromSource(cps, [source])

'コンパイルしたアセンブリを取得
Dim asm As [Assembly] = cres.CompiledAssembly
'クラスのTypeを取得
Dim t As Type = asm.GetType("Evaluator.Evaluator")
'インスタンスの作成
Dim eval As Object = Activator.CreateInstance(t)
'Evalメソッドを実行し、結果を取得
Dim result As String = CStr(t.InvokeMember("Eval", _
    BindingFlags.InvokeMethod, Nothing, eval, New Object() {exp}))

'結果を表示
Console.WriteLine(result)
}}

#code(csharp){{
//using System.Reflection;
//using System.CodeDom.Compiler;
//が宣言されているものとする

//計算式
string exp = "(1+6)*5/(7-4)";

//計算するためのコード
string source =
@"package Evaluator
{
    class Evaluator
    {
        public function Eval(expr : String) : String 
        { 
            return eval(expr); 
        }
    }
}";

//コンパイルするための準備
CodeDomProvider cp = new Microsoft.JScript.JScriptCodeProvider();
ICodeCompiler icc = cp.CreateCompiler();
CompilerParameters cps = new CompilerParameters();
CompilerResults cres;
//メモリ内で出力を生成する
cps.GenerateInMemory = true;
//コンパイルする
cres = icc.CompileAssemblyFromSource(cps, source);

//コンパイルしたアセンブリを取得
Assembly asm = cres.CompiledAssembly;
//クラスのTypeを取得
Type t = asm.GetType("Evaluator.Evaluator");
//インスタンスの作成
object eval = Activator.CreateInstance(t);
//Evalメソッドを実行し、結果を取得
string result = (string) t.InvokeMember("Eval",
    BindingFlags.InvokeMethod,
    null,
    eval,
    new object[] {exp});

//結果を表示
Console.WriteLine(result);
}}

さらに、掲示板でピラルクさんが提案されたように、CSharpCodeProviderを使って、計算式が含まれるコードの文字列をコンパイルし、実行する方法もあります。この方法は「The Code Project」などでもいくつか紹介されています。

-[[Runtime Compilation (A .NET eval statement)>http://www.codeproject.com/dotnet/evaluator.asp]]
-[[Evaluating Mathematical Expressions by Compiling C# Code at Runtime>http://www.codeproject.com/csharp/matheval.asp]]

具体的なコードは、例えば、次のようなものです。

#code(vbnet){{
'Imports System.Reflection
'Imports System.CodeDom.Compiler
'が宣言されているものとする

'計算するためのコード
Dim [source] As String = _
    "public class MainClass {" + vbCrLf + _
    "public static double EVal() {" + vbCrLf + _
    "return (1d+6d)*5d/(7d-4d);" + vbCrLf + _
    "} }"

'コンパイルするための準備
Dim cp = New Microsoft.CSharp.CSharpCodeProvider
Dim icc As ICodeCompiler = cp.CreateCompiler()
Dim cps As New CompilerParameters
Dim cres As CompilerResults
'メモリ内で出力を生成する
cps.GenerateInMemory = True
'コンパイルする
cres = icc.CompileAssemblyFromSource(cps, [source])

'コンパイルしたアセンブリを取得
Dim asm As [Assembly] = cres.CompiledAssembly
'MainClassクラスのTypeを取得
Dim t As Type = asm.GetType("MainClass")
'EValメソッドを実行し、結果を取得
Dim d As Double = CDbl(t.InvokeMember("EVal", _
    BindingFlags.InvokeMethod, Nothing, Nothing, Nothing))

'結果を表示
Console.WriteLine(d)
}}

#code(csharp){{
//using System.CodeDom.Compiler;
//using System.Reflection;
//が宣言されているものとする

//計算するためのコード
string source = @"
public class MainClass
{
    public static double EVal()
    {
        return (1d+6d)*5d/(7d-4d);
    }
}";

//コンパイルするための準備
CodeDomProvider cp = new Microsoft.CSharp.CSharpCodeProvider();
ICodeCompiler icc = cp.CreateCompiler();
CompilerParameters cps = new CompilerParameters();
CompilerResults cres;
//メモリ内で出力を生成する
cps.GenerateInMemory = true;
//コンパイルする
cres = icc.CompileAssemblyFromSource(cps, source);

//コンパイルしたアセンブリを取得
Assembly asm = cres.CompiledAssembly;
//MainClassクラスのTypeを取得
Type t = asm.GetType("MainClass");
//EValメソッドを実行し、結果を取得
double d = (double) t.InvokeMember("EVal",
    BindingFlags.InvokeMethod,
    null,
    null,
    null);

//結果を表示
Console.WriteLine(d);
}}

前のJScriptCodeProviderを使った方法と比べると、この方法は計算式をコードに含めなければならず、計算式が変わるたびにコンパイルが必要になりますし、メモリのアセンブリをどのように解放するかという問題もあります(*1)。そのため、実用としては困難かもしれません。

(*1)この問題に関しては、次のページに詳しいです。

-[[Dynamically executing code in .Net>http://www.west-wind.com/presentations/dynamicCode/DynamicCode.htm]]

Microsoft Script Controlが使用できるならば、VBScriptやJSCriptのEval関数を使う方法もあります。

#code(vbnet){{
'計算式
Dim exp As String = "(1+6)*5/(7-4)"

Dim t As Type = _
    Type.GetTypeFromProgID("MSScriptControl.ScriptControl")
Dim obj As Object = Activator.CreateInstance(t)
t.InvokeMember("Language", _
    System.Reflection.BindingFlags.SetProperty, _
    Nothing, _
    obj, _
    New Object() {"vbscript"})
'Eval関数で計算を実行して結果を取得
Dim result As Double = CDbl( _
    t.InvokeMember("Eval", _
        System.Reflection.BindingFlags.InvokeMethod, _
        Nothing, _
        obj, _
        New Object() {exp}))

'結果を表示
Console.WriteLine(result)
}}

#code(csharp){{
//計算式
string exp = "(1+6)*5/(7-4)";

Type t = Type.GetTypeFromProgID("MSScriptControl.ScriptControl");
object obj = Activator.CreateInstance(t);
t.InvokeMember("Language",
    System.Reflection.BindingFlags.SetProperty,
    null,
    obj,
    new object[] {"vbscript"});
//Eval関数で計算を実行して結果を取得
double result = (double) t.InvokeMember("Eval",
    System.Reflection.BindingFlags.InvokeMethod,
    null,
    obj,
    new object[] {exp});

//結果を表示
Console.WriteLine(result);
}}

これ以外にも、DataTable.Computeメソッドを使う方法など、まだまだありそうですが、きりが無いので、この辺で終わりにします。(面白い方法がありましたら、ご連絡ください。)

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

-[[C#で変数の中身を実行する: 投稿者(敬称略) ArAy, ピラルク, 管理人>http://dobon.net/vb/bbs/log3-1/462.html]]
-[[文字列で表現した数式を計算: 投稿者(敬称略) nissa.com, ArAy>http://dobon.net/vb/bbs/log3-5/2483.html]]
-[[C#で変数の中身を実行する: 投稿者(敬称略) ArAy, ピラルク, 管理人>https://dobon.net/vb/bbs/log3-1/462.html]]
-[[文字列で表現した数式を計算: 投稿者(敬称略) nissa.com, ArAy>https://dobon.net/vb/bbs/log3-5/2483.html]]

***TabControlのTabPageを非表示にするには? [#of915b3e]

#column(注意){{
この記事の最新版は「[[TabControlのTabPageを非表示にする>http://dobon.net/vb/dotnet/control/tabpagehide.html]]」で公開しています。
この記事の最新版は「[[TabControlのTabPageを非表示にする>https://dobon.net/vb/dotnet/control/tabpagehide.html]]」で公開しています。
}}

''【質問】''

TabControlのTabPageにはVisibleプロパティが見つかりません。TabPageを非表示にするにはどのようにすればよいのでしょうか?

''【回答】''

TabPageを非表示にするには、TabControl.TabPagesプロパティのRemoveまたはRemoveAtメソッドを使って削除するしかないようです。よって一時的にTabPageを非表示にするには、非表示にするTabPageオブジェクトを保持しておき、Removeメソッドで削除し、再び表示するときは、TabControl.TabPagesプロパティのAddメソッドで追加します。

TabControlのTabPageを隠し、再び表示させるためのコードを以下に示します。まずは、次のようなTabPageManagerクラスを書きます。

#code(vbnet){{
Public Class TabPageManager
    Private Class TabPageInfo
        Public TabPage As TabPage
        Public Visible As Boolean

        Public Sub New(ByVal page As TabPage, ByVal v As Boolean)
            TabPage = page
            Visible = v
        End Sub
    End Class

    Private _tabPageInfos As TabPageInfo() = Nothing
    Private _tabControl As TabControl = Nothing

    ''' <summary>
    ''' TabPageManagerクラスのインスタンスを作成する
    ''' </summary>
    ''' <param name="crl">基になるTabControlオブジェクト</param>
    Public Sub New(ByVal crl As TabControl)
        _tabControl = crl
        _tabPageInfos = _
            New TabPageInfo(_tabControl.TabPages.Count - 1) {}
        Dim i As Integer
        For i = 0 To _tabControl.TabPages.Count - 1
            _tabPageInfos(i) = _
                New TabPageInfo(_tabControl.TabPages(i), True)
        Next i
    End Sub

    ''' <summary>
    ''' TabPageの表示・非表示を変更する
    ''' </summary>
    ''' <param name="index">変更するTabPageのIndex番号</param>
    ''' <param name="v">表示するときはTrue。
    ''' 非表示にするときはFalse。</param>
    Public Sub ChangeTabPageVisible( _
        ByVal index As Integer, ByVal v As Boolean)
        If _tabPageInfos(index).Visible = v Then
            Return
        End If
        _tabPageInfos(index).Visible = v
        _tabControl.SuspendLayout()
        _tabControl.TabPages.Clear()
        Dim i As Integer
        For i = 0 To _tabPageInfos.Length - 1
            If _tabPageInfos(i).Visible Then
                _tabControl.TabPages.Add(_tabPageInfos(i).TabPage)
            End If
        Next i
        _tabControl.ResumeLayout()
    End Sub
End Class
}}

#code(csharp){{
public class TabPageManager
{
    private class TabPageInfo
    {
        public TabPage TabPage;
        public bool Visible;
        public TabPageInfo(TabPage page, bool v)
        {
            TabPage = page;
            Visible = v;
        }
    }
    private TabPageInfo[] _tabPageInfos = null;
    private TabControl _tabControl = null;

    /// <summary>
    /// TabPageManagerクラスのインスタンスを作成する
    /// </summary>
    /// <param name="crl">基になるTabControlオブジェクト</param>
    public TabPageManager(TabControl crl)
    {
        _tabControl = crl;
        _tabPageInfos = new TabPageInfo[_tabControl.TabPages.Count];
        for(int i = 0; i < _tabControl.TabPages.Count; i++)
            _tabPageInfos[i] =
                new TabPageInfo(_tabControl.TabPages[i], true);
    }

    /// <summary>
    /// TabPageの表示・非表示を変更する
    /// </summary>
    /// <param name="index">変更するTabPageのIndex番号</param>
    /// <param name="v">表示するときはTrue。
    /// 非表示にするときはFalse。</param>
    public void ChangeTabPageVisible(int index, bool v)
    {
        if (_tabPageInfos[index].Visible == v)
            return;

        _tabPageInfos[index].Visible = v;
        _tabControl.SuspendLayout();
        _tabControl.TabPages.Clear();
        for(int i = 0; i < _tabPageInfos.Length; i++)
        {
            if (_tabPageInfos[i].Visible)
                _tabControl.TabPages.Add(_tabPageInfos[i].TabPage);
        }
        _tabControl.ResumeLayout();
    }
}
}}

次に、このTabPageManagerクラスを使ってTabControlのTabPageを隠し、再び表示させるコードを示します。フォーム(Form1)にTabControl(TabControl1)が配置されており、TabControl1にいくつかのTabPageが追加されている時、Button1のクリックによりTabControl1の一番先頭のTabPageを隠し、さらに、Button2のクリックにより再びこのTabPageを表示させるようにしています。

#code(vbnet){{
Private _tabPageManager As TabPageManager

'フォームのLoadイベントハンドラ
Private Sub Form1_Load(ByVal sender As Object, _
        ByVal e As EventArgs) Handles MyBase.Load
    'TabPageManagerオブジェクトの作成
    _tabPageManager = New TabPageManager(TabControl1)
End Sub

'Button1のClickイベントハンドラ
Private Sub Button1_Click(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles Button1.Click
    '0番目のTabPageを非表示にする
    _tabPageManager.ChangeTabPageVisible(0, False)
End Sub

'Button2のClickイベントハンドラ
Private Sub Button2_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Button2.Click
    '0番目のTabPageを表示する
    _tabPageManager.ChangeTabPageVisible(0, True)
End Sub
}}

#code(csharp){{
TabPageManager _tabPageManager = null

//フォームのLoadイベントハンドラ
private void Form1_Load(object sender, System.EventArgs e)
{
    //TabPageManagerオブジェクトの作成
    _tabPageManager = new TabPageManager(TabControl1);
}

//Button1のClickイベントハンドラ
private void Button1_Click(object sender, System.EventArgs e)
{
    //0番目のTabPageを非表示にする
    _tabPageManager.ChangeTabPageVisible(0, false);
}

//Button2のClickイベントハンドラ
private void button2_Click(object sender, System.EventArgs e)
{
    //0番目のTabPageを表示する
    _tabPageManager.ChangeTabPageVisible(0, true);
}
}}

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

-[[TabControlのタブを非表示にしたい: 投稿者(敬称略) どらごら, よねKEN>http://dobon.net/vb/bbs/log3-1/607.html]]
-[[TabControlのタブを非表示にしたい: 投稿者(敬称略) どらごら, よねKEN>https://dobon.net/vb/bbs/log3-1/607.html]]

***DataGridで複数行選択できないようにし、セルがアクティブにならならず、行全体が選択されるようにするには? [#z24ae80d]

#column(注意){{
この記事の最新版は「[[DataGridで複数行選択できないようにし、セルがアクティブにならならず、行全体が選択されるようにする>http://dobon.net/vb/dotnet/datagrid/singleselect.html]]」で公開しています。
この記事の最新版は「[[DataGridで複数行選択できないようにし、セルがアクティブにならならず、行全体が選択されるようにする>https://dobon.net/vb/dotnet/datagrid/singleselect.html]]」で公開しています。
}}

''【質問】''

Windowsアプリケーションにおいて、ListViewコントロールのMultiSelectプロパティをFalseにした時のように、DataGridコントロールで複数行選択することができなく、さらに、セルをクリックした時にセルがアクティブになることなく、そのセルの行全体が選択されるようにしたいのですが、どのようにすればよいでしょうか?

''【回答】''

まず、DataGridで一つの行だけを選択できるようにする方法を考えてみましょう。これに関しては、「Windows Forms FAQ」の「How can I make my DataGrid support a single select mode, and not the default multiselect mode?」や「TheScarms .NET Code Library」の「Make the DataGrid support single select vs multiselect mode.」でその方法が紹介されています。

-[[Windows Forms FAQ: 5.37 How can I make my DataGrid support a single select mode, and not the default multiselect mode?>http://www.syncfusion.com/faq/winforms/search/839.asp]]
-[[TheScarms .NET Code Library: Make the DataGrid support single select vs multiselect mode.>http://www.thescarms.com/dotNet/SingleSelect.asp]]

これらで紹介されている方法は、DataGridクラスのOnMouseMoveをオーバーライドして、マウスドラッグによる選択が無効になるようにし、さらにOnMouseDownをオーバーライドして、前に選択した行の選択を取り消すという方法です。

しかしこのやり方では、キーボードによる複数行の選択を全く考慮しておらず、不完全です。(マウスの部分も不完全な点が多々ありますが。)

そこで以下に示すコードでは、OnMouseMoveとOnMouseDownをオーバーライドする以外に、ProcessCmdKeyをオーバーライドして、Shiftキーと上矢印または下矢印キーが押された時、ShiftキーとCtrlキーとHomeまたはEndキーが押された時、CtrlキーとAキーが押された時のそれぞれの場合を無効にしています。

#code(vbnet){{
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Namespace Dobon.Samples.Forms
    Public Class MyDataGrid
        Inherits DataGrid

        Private lastRowSelected As Integer = -1
        Private rowSelecting As Boolean = False

        Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
            If rowSelecting = False Or _
               (e.Button And MouseButtons.Left) <> MouseButtons.Left Then
                MyBase.OnMouseMove(e)
            End If
        End Sub

        Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
            rowSelecting = False
            Dim info As HitTestInfo = Me.HitTest(e.X, e.Y)

            If info.Type = HitTestType.Cell Or _
                info.Type = HitTestType.RowHeader Then
                '選択されている行をリセットする
                If lastRowSelected <> -1 Then
                    Dim cm As CurrencyManager = _
                        CType(BindingContext(DataSource), _
                            CurrencyManager)
                    If lastRowSelected < cm.Count Then
                        Me.UnSelect(lastRowSelected)
                    Else
                        Me.ResetSelection()
                    End If
                End If
                If info.Type = HitTestType.Cell Then
                    'セル上の時
                    lastRowSelected = -1
                    MyBase.OnMouseDown(e)
                Else If info.Type = HitTestType.RowHeader Then
                    '行ヘッダ上の時
                    If (Control.ModifierKeys And Keys.Shift) = 0 Then
                        MyBase.OnMouseDown(e)
                    Else
                        CurrentCell = _
                            New DataGridCell(info.Row, info.Column)
                    End If
                    Me.Select(info.Row)
                    lastRowSelected = info.Row
                    rowSelecting = True
                End If
            Else
                MyBase.OnMouseDown(e)
            End If
        End Sub

        Protected Overrides Function ProcessCmdKey( _
            ByRef msg As Message, ByVal keyData As Keys) As Boolean
            Const WM_KEYDOWN As Integer = &H100
            Const WM_KEYUP As Integer = &H101

            If msg.Msg = WM_KEYDOWN Or msg.Msg = WM_KEYUP Then
                Dim keyCode As Keys = _
                    CType(CInt(keyData), Keys) And Keys.KeyCode
                'Shift+Up,Downキーでの複数行選択を防止
                If (keyData And Keys.Shift) = Keys.Shift And _
                    (keyCode = Keys.Up Or keyCode = Keys.Down) Then
                    Return True
                End If 'Shift+Ctrl+Home,Endキーでの複数行選択を防止
                If (keyData And Keys.Shift) = Keys.Shift And _
                    (keyData And Keys.Control) = Keys.Control And _
                    (keyCode = Keys.Home Or keyCode = Keys.End) Then
                    Return True
                End If
                'Ctrl+Aキーでの複数行選択を防止
                If (keyData And Keys.Control) = Keys.Control And _
                    keyCode = Keys.A Then
                    Return True
                End If
            End If
            Return MyBase.ProcessCmdKey(msg, keyData)
        End Function
    End Class
End Namespace
}}

#code(csharp){{
using System;
using System.Drawing;
using System.Windows.Forms;

namespace Dobon.Samples.Forms
{
    public class MyDataGrid : DataGrid
    {
        private int lastRowSelected = -1;
        private bool rowSelecting = false;

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (rowSelecting == false || _
                (e.Button & MouseButtons.Left) != MouseButtons.Left)
                base.OnMouseMove(e);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            rowSelecting = false;
            HitTestInfo info = this.HitTest(e.X, e.Y);

            if (info.Type == HitTestType.Cell ||
                info.Type == HitTestType.RowHeader)
            {
                //選択されている行をリセットする
                if (lastRowSelected != -1)
                {
                    CurrencyManager cm =
                        (CurrencyManager) BindingContext[DataSource];
                    if (lastRowSelected < cm.Count)
                        this.UnSelect(lastRowSelected);
                    else
                        this.ResetSelection();
                }

                if (info.Type == HitTestType.Cell)
                {
                    //セル上の時
                    lastRowSelected = -1;
                    base.OnMouseDown(e);
                }
                else if (info.Type == HitTestType.RowHeader)
                {
                    //行ヘッダ上の時
                    if ((Control.ModifierKeys & Keys.Shift) == 0)
                        base.OnMouseDown(e);
                    else
                        CurrentCell =
                            new DataGridCell(info.Row, info.Column);
                    this.Select(info.Row);
                    lastRowSelected = info.Row;
                    rowSelecting = true;
                }
            }
            else
            {
                base.OnMouseDown(e);
            }
        }

        protected override bool ProcessCmdKey(
            ref Message msg, Keys keyData)
        {
            const int WM_KEYDOWN = 0x100;
            const int WM_KEYUP = 0x101;

            if (msg.Msg == WM_KEYDOWN || msg.Msg == WM_KEYUP)
            {
                Keys keyCode = (Keys)(int)keyData & Keys.KeyCode;
                //Shift+Up,Downキーでの複数行選択を防止
                if ((keyData & Keys.Shift) == Keys.Shift &&
                    (keyCode == Keys.Up || keyCode == Keys.Down))
                    return true;
                //Shift+Ctrl+Home,Endキーでの複数行選択を防止
                if ((keyData & Keys.Shift) == Keys.Shift &&
                    (keyData & Keys.Control) == Keys.Control &&
                    (keyCode == Keys.Home || keyCode == Keys.End))
                    return true;
                //Ctrl+Aキーでの複数行選択を防止
                if ((keyData & Keys.Control) == Keys.Control &&
                    keyCode == Keys.A)
                    return true;
            }

            return base.ProcessCmdKey(ref msg, keyData);
        }
    }
}
}}

次にセルがアクティブにならないようにする方法を考えます。この方法も、「Windows Forms FAQ」で紹介されています。

-[[Windows Forms FAQ: 5.41 How can I make my grid never have an active edit cell and always select whole rows (as in a browser-type grid)?>http://www.syncfusion.com/faq/winforms/search/856.asp]]
-[[Windows Forms FAQ: 5.83 How can I prevent all the cells in my DataGrid from being edited without deriving GridColumnStyle?>http://www.syncfusion.com/faq/winforms/search/1008.asp]]

「5.41」は、GridColumnStyleを使い、GridColumnStyleのEditメソッドをオーバーライドし、編集できないようにする方法です。「5.83」は、GridColumnStyleを使わずに、DataGrid.Controlsからスクロールバー以外のコントロールを削除するという方法です。

ここでは、「5.83」の方法を採用し、DataGridのOnControlAddedメソッドをオーバーライドして、スクロールバー以外のコントロールが追加された時は、これを削除するようにします。(ただしこの方法ではDataGridBoolColumnによるチェックボックスはアクティブになってしまうようです。)

#code(vbnet){{
Protected Overrides Sub OnControlAdded( _
    ByVal e As ControlEventArgs)
    MyBase.OnControlAdded(e)
    If Not TypeOf e.Control Is VScrollBar And _
        Not TypeOf e.Control Is HScrollBar Then
        Me.Controls.Remove(e.Control)
    End If
End Sub
}}

#code(csharp){{
protected override void OnControlAdded(ControlEventArgs e)
{
    base.OnControlAdded(e);
    if (!(e.Control is VScrollBar) && !(e.Control is HScrollBar))
        this.Controls.Remove(e.Control);
}
}}

セルをクリックした時に行全体が選択されるようにするには、これまた「Windows Forms FAQ」にあるように、DataGridのMouseUpイベントで行を選択するようにすればよいでしょう。

-[[Windows Forms FAQ: 5.11 How can I select the entire row when the user clicks on a cell in the row>http://www.syncfusion.com/faq/winforms/search/689.asp]]

クリックでも行が選択されるようにするには、CurrentCellChangedイベントなど、適当なイベントを使ってください。ここでは、OnMouseDownメソッドで行を選択するようにします。

以上の考察により書かれたDataGridクラスの派生クラス(MyDataGridクラス)のコードを以下に示します。

#code(vbnet){{
Imports System
Imports System.Drawing
Imports System.Windows.Forms

Namespace Dobon.Samples.Forms
    Public Class MyDataGrid
        Inherits DataGrid

        Private lastRowSelected As Integer = -1
        Private rowSelecting As Boolean = False

        Protected Overrides Sub OnMouseMove(ByVal e As MouseEventArgs)
            If rowSelecting = False Or _
               (e.Button And MouseButtons.Left) <> MouseButtons.Left Then
                MyBase.OnMouseMove(e)
            End If
        End Sub

        Protected Overrides Sub OnMouseDown(ByVal e As MouseEventArgs)
            rowSelecting = False
            Dim info As HitTestInfo = Me.HitTest(e.X, e.Y)

            If info.Type = HitTestType.Cell Or _
                info.Type = HitTestType.RowHeader Then
                '選択されている行をリセットする
                If lastRowSelected <> -1 Then
                    Dim cm As CurrencyManager = _
                        CType(BindingContext(DataSource), _
                            CurrencyManager)
                    If lastRowSelected < cm.Count Then
                        Me.UnSelect(lastRowSelected)
                    Else
                        Me.ResetSelection()
                    End If
                End If
                If info.Type = HitTestType.Cell Then
                    'セル上の時
                    lastRowSelected = -1
                    MyBase.OnMouseDown(e)
                    Me.Select(info.Row)
                Else If info.Type = HitTestType.RowHeader Then
                    '行ヘッダ上の時
                    If (Control.ModifierKeys And Keys.Shift) = 0 Then
                        MyBase.OnMouseDown(e)
                    Else
                        CurrentCell = _
                            New DataGridCell(info.Row, info.Column)
                    End If
                    Me.Select(info.Row)
                    lastRowSelected = info.Row
                    rowSelecting = True
                End If
            Else
                MyBase.OnMouseDown(e)
            End If
        End Sub

        Protected Overrides Function ProcessCmdKey( _
            ByRef msg As Message, ByVal keyData As Keys) As Boolean
            Const WM_KEYDOWN As Integer = &H100
            Const WM_KEYUP As Integer = &H101

            If msg.Msg = WM_KEYDOWN Or msg.Msg = WM_KEYUP Then
                Dim keyCode As Keys = _
                    CType(CInt(keyData), Keys) And Keys.KeyCode
                'Shift+Up,Downキーでの複数行選択を防止
                If (keyData And Keys.Shift) = Keys.Shift And _
                    (keyCode = Keys.Up Or keyCode = Keys.Down) Then
                    Return True
                End If
                'Shift+Ctrl+Home,Endキーでの複数行選択を防止
                If (keyData And Keys.Shift) = Keys.Shift And _
                    (keyData And Keys.Control) = Keys.Control And _
                    (keyCode = Keys.Home Or keyCode = Keys.End) Then
                    Return True
                End If
                'Ctrl+Aキーでの複数行選択を防止
                If (keyData And Keys.Control) = Keys.Control And _
                    keyCode = Keys.A Then
                    Return True
                End If
            End If
            Return MyBase.ProcessCmdKey(msg, keyData)
        End Function

        Protected Overrides Sub OnControlAdded( _
            ByVal e As ControlEventArgs)
            MyBase.OnControlAdded(e)
            If Not TypeOf e.Control Is VScrollBar And _
                Not TypeOf e.Control Is HScrollBar Then
                Me.Controls.Remove(e.Control)
            End If
        End Sub
    End Class
End Namespace
}}

#code(csharp){{
using System;
using System.Drawing;
using System.Windows.Forms;

namespace Dobon.Samples.Forms
{
    public class MyDataGrid : DataGrid
    {
        private int lastRowSelected = -1;
        private bool rowSelecting = false;

        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (rowSelecting == false || _
                (e.Button & MouseButtons.Left) != MouseButtons.Left)
                base.OnMouseMove(e);
        }

        protected override void OnMouseDown(MouseEventArgs e)
        {
            rowSelecting = false;
            HitTestInfo info = this.HitTest(e.X, e.Y);

            if (info.Type == HitTestType.Cell ||
                info.Type == HitTestType.RowHeader)
            {
                //選択されている行をリセットする
                if (lastRowSelected != -1)
                {
                    CurrencyManager cm =
                        (CurrencyManager) BindingContext[DataSource];
                    if (lastRowSelected < cm.Count)
                        this.UnSelect(lastRowSelected);
                    else
                        this.ResetSelection();
                }

                if (info.Type == HitTestType.Cell)
                {
                    //セル上の時
                    lastRowSelected = -1;
                    base.OnMouseDown(e);
                    this.Select(info.Row);
                }
                else if (info.Type == HitTestType.RowHeader)
                {
                    //行ヘッダ上の時
                    if ((Control.ModifierKeys & Keys.Shift) == 0)
                        base.OnMouseDown(e);
                    else
                        CurrentCell =
                            new DataGridCell(info.Row, info.Column);
                    this.Select(info.Row);
                    lastRowSelected = info.Row;
                    rowSelecting = true;
                }
            }
            else
            {
                base.OnMouseDown(e);
            }
        }

        protected override bool ProcessCmdKey(
            ref Message msg, Keys keyData)
        {
            const int WM_KEYDOWN = 0x100;
            const int WM_KEYUP = 0x101;

            if (msg.Msg == WM_KEYDOWN || msg.Msg == WM_KEYUP)
            {
                Keys keyCode = (Keys)(int)keyData & Keys.KeyCode;
                //Shift+Up,Downキーでの複数行選択を防止
                if ((keyData & Keys.Shift) == Keys.Shift &&
                    (keyCode == Keys.Up || keyCode == Keys.Down))
                    return true;
                //Shift+Ctrl+Home,Endキーでの複数行選択を防止
                if ((keyData & Keys.Shift) == Keys.Shift &&
                    (keyData & Keys.Control) == Keys.Control &&
                    (keyCode == Keys.Home || keyCode == Keys.End))
                    return true;
                //Ctrl+Aキーでの複数行選択を防止
                if ((keyData & Keys.Control) == Keys.Control &&
                    keyCode == Keys.A)
                    return true;
            }

            return base.ProcessCmdKey(ref msg, keyData);
        }

        protected override void OnControlAdded(ControlEventArgs e)
        {
            base.OnControlAdded(e);
            if (!(e.Control is VScrollBar) &&
                !(e.Control is HScrollBar))
                this.Controls.Remove(e.Control);
        }
    }
}
}}

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

-[[NO TITLE: 投稿者(敬称略) しびっく, ピラルク, 管理人>http://dobon.net/vb/bbs/log3-2/618.html]]
-[[NO TITLE: 投稿者(敬称略) しびっく, ピラルク, 管理人>https://dobon.net/vb/bbs/log3-2/618.html]]

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

//これより下は編集しないでください
#pageinfo([[:Category/.NET]],2004-06-01 (火) 06:00:00,DOBON!,2010-03-21 (日) 02:13:57,DOBON!)

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