┏第34号━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃         .NETプログラミング研究         ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ──<メニュー>─────────────────────── ■お知らせ ■.NET質問箱 ・文字列の計算式の計算結果を取得するには? ・TabControlのTabPageを非表示にするには? ・DataGridで複数行選択できないようにし、セルがアクティブになら  ならず、行全体が選択されるようにするには? ─────────────────────────────── ─────────────────────────────── ■お知らせ ─────────────────────────────── 過去に発行した「.NETプログラミング研究」のタイトルの一覧を閲覧 できるようにしてほしいというご要望があったため、以下のURLから閲 覧できるようにしました。 [URL].NETプログラミング研究 バックナンバー http://dobon.net/vb/melma/dotnet.cgi バックナンバーのメニュー部分を切り出して列挙して表示しているだ けのものですが、ご利用いただければ幸いです。 ─────────────────────────────── ■.NET質問箱 ─────────────────────────────── 「.NET質問箱」では、「どぼん!のプログラミング掲示板」に書き込 まれた.NETプログラミングに関する投稿を基に、さらに考察を加え、 Q&A形式にまとめて紹介します。 [URL]どぼん!のプログラミング掲示板 http://dobon.net/vb/bbs.html ●文字列の計算式の計算結果を取得するには? 【質問】 例えば、"(1+6)*5/(7-4)"のような計算式を表す文字列から、その計算 結果を取得するにはどのようにすればよいのでしょうか? 【回答】 これには、いろいろな方法が考えられます。 正攻法で行けば、計算式を解析し、計算するコードを自分で書くとい うことになります。そのために参考になりそうなコードが、例えば、 「The Code Project」で紹介されている「A Math Expression Evaluator」です。この「A Math Expression Evaluator」は簡単な計 算はもちろん、cos、sin、logなどにも対応しています。(ただし、コー ドはVB.NETのみです。) [URL]A Math Expression Evaluator http://www.codeproject.com/vb/net/math_expression_evaluator.asp さらに、掲示板でArAyさんが紹介されたMSDN Japanの「アルゴリズム 入門:第1章 Visual C# による文字列処理入門」も参考になります。 [URL]アルゴリズム入門:第1章 Visual C# による文字列処理入門 http://www.microsoft.com/japan/msdn/academic/Articles /Algorithm/01/ このように自分でコードを書く以外の方法も様々あります。 まず、JScript.NETのEvalを使う方法があります。この方法は、 GotDotNet Message Boardsの「String to Numeric」のスレッドで enderminhさんが紹介しています。 [URL]GotDotNet Message Boards - String to Numeric http://www.gotdotnet.com/community/messageboard/Thread.aspx?id=212303 この方法によると、まず参照に「Microsoft.Jscript」と「Microsoft. Vsa」を追加し、次のようなコードにより、計算を実行します。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ '計算式 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) ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //計算式 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); ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ また、JScriptCodeProviderにより、Evalメソッドを実行する方法が「 An Eval Function for C# using JScript.NET (JavaScript)」に紹介 されています。 [URL]An Eval Function for C# using JScript.NET (JavaScript) http://www.odetocode.com/Code/80.aspx この方法によると、次のようなコードがかけます。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ '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) ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //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); ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ さらに、掲示板でピラルクさんが提案されたように、 CSharpCodeProviderを使って、計算式が含まれるコードの文字列をコ ンパイルし、実行する方法もあります。この方法は「The Code Project」などでもいくつか紹介されています。 [URL]Runtime Compilation (A .NET eval statement) http://www.codeproject.com/dotnet/evaluator.asp [URL]Evaluating Mathematical Expressions by Compiling C# Code at Runtime http://www.codeproject.com/csharp/matheval.asp 具体的なコードは、例えば、次のようなものです。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ '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) ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //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); ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 前のJScriptCodeProviderを使った方法と比べると、この方法は計算式 をコードに含めなければならず、計算式が変わるたびにコンパイルが 必要になりますし、メモリのアセンブリをどのように解放するかとい う問題もあります(*1)。そのため、実用としては困難かもしれません。 (*1)この問題に関しては、次のページに詳しいです。 [URL]Dynamically executing code in .Net http://www.west-wind.com/presentations/dynamicCode/DynamicCode. htm Microsoft Script Controlが使用できるならば、VBScriptやJSCriptの Eval関数を使う方法もあります。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ '計算式 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) ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //計算式 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); ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ これ以外にも、DataTable.Computeメソッドを使う方法など、まだまだ ありそうですが、きりが無いので、この辺で終わりにします。(面白 い方法がありましたら、ご連絡ください。) ○この記事の基になった掲示板のスレッド [題名] C#で変数の中身を実行する [投稿者(敬称略)] ArAy, ピラルク, 管理人 [URL] http://dobon.net/vb/bbs/log3-1/462.html [題名] 文字列で表現した数式を計算 [投稿者(敬称略)] nissa.com, ArAy [URL] http://dobon.net/vb/bbs/log3-5/2483.html ─────────────────────────────── ●TabControlのTabPageを非表示にするには? 【質問】 TabControlのTabPageにはVisibleプロパティが見つかりません。 TabPageを非表示にするにはどのようにすればよいのでしょうか? 【回答】 TabPageを非表示にするには、TabControl.TabPagesプロパティの RemoveまたはRemoveAtメソッドを使って削除するしかないようです。 よって一時的にTabPageを非表示にするには、非表示にするTabPageオ ブジェクトを保持しておき、Removeメソッドで削除し、再び表示する ときは、TabControl.TabPagesプロパティのAddメソッドで追加します。 TabControlのTabPageを隠し、再び表示させるためのコードを以下に示 します。まずは、次のようなTabPageManagerクラスを書きます。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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 ''' ''' TabPageManagerクラスのインスタンスを作成する ''' ''' 基になるTabControlオブジェクト 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 ''' ''' TabPageの表示・非表示を変更する ''' ''' 変更するTabPageのIndex番号 ''' 表示するときはTrue。 ''' 非表示にするときはFalse。 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 ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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; /// /// TabPageManagerクラスのインスタンスを作成する /// /// 基になるTabControlオブジェクト 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); } /// /// TabPageの表示・非表示を変更する /// /// 変更するTabPageのIndex番号 /// 表示するときはTrue。 /// 非表示にするときはFalse。 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(); } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 次に、このTabPageManagerクラスを使ってTabControlのTabPageを隠し、 再び表示させるコードを示します。フォーム(Form1)にTabControl (TabControl1)が配置されており、TabControl1にいくつかのTabPageが 追加されている時、Button1のクリックによりTabControl1の一番先頭 のTabPageを隠し、さらに、Button2のクリックにより再びこの TabPageを表示させるようにしています。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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 ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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); } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ○この記事の基になった掲示板のスレッド [題名] TabControlのタブを非表示にしたい [投稿者(敬称略)] どらごら, よねKEN [URL] http://dobon.net/vb/bbs/log3-1/607.html ─────────────────────────────── ●DataGridで複数行選択できないようにし、セルがアクティブになら  ならず、行全体が選択されるようにするには? 【質問】 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.」 でその方法が紹介されています。 [URL]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 [URL]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キーが押された時のそ れぞれの場合を無効にしています。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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 ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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); } } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 次にセルがアクティブにならないようにする方法を考えます。この方 法も、「Windows Forms FAQ」で紹介されています。 [URL]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 [URL]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によるチェックボックスはアクティブになってし まうようです。) ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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 ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ protected override void OnControlAdded(ControlEventArgs e) { base.OnControlAdded(e); if (!(e.Control is VScrollBar) && !(e.Control is HScrollBar)) this.Controls.Remove(e.Control); } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ セルをクリックした時に行全体が選択されるようにするには、これま た「Windows Forms FAQ」にあるように、DataGridのMouseUpイベント で行を選択するようにすればよいでしょう。 [URL]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ク ラス)のコードを以下に示します。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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 ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 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); } } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ○この記事の基になった掲示板のスレッド [題名] NO TITLE [投稿者(敬称略)] しびっく, ピラルク, 管理人 [URL] http://dobon.net/vb/bbs/log3-2/618.html =============================== ■このマガジンの購読、購読中止、バックナンバー、説明に関しては  次のページをご覧ください。  http://www.mag2.com/m/0000104516.htm ■発行人・編集人:どぼん!  (Microsoft MVP for Visual Basic, Oct 2003-Oct 2004)  http://dobon.net  dobon_info@yahoo.co.jp ■ご質問等はメールではなく、掲示板へお願いいたします。  http://dobon.net/vb/bbs.html ■上記メールアドレスへのメールは確実に読まれる保障はありません  (スパム、ウィルス対策です)。メールは下記URLのフォームメール  から送信してください。  http://dobon.net/mail.html Copyright (c) 2003 - 2004 DOBON! All rights reserved. ===============================