注意:ここで紹介しているDataGridは、System.Windows.Forms名前空間のDataGrid(Windowsフォーム)です。System.Web.UI.WebControls名前空間のDataGrid(Webフォーム)ではありません。
System.Windows.Forms.DataGridコントロールにLinkLabelを表示するには、DataGridColumnStyleクラスから派生した新しいクラスを作成することにより実現させる方法が適当だと思いますが、問題はどのようにLinkLabelを表示するかという点でしょう。
一番簡単で分かりやすいのは、DataGridColumnStyleクラスのEditメソッドでLinkLabelコントロールを表示するという方法です。掲示板の過去ログにある方法や、ニュースグループのこちらの投稿にある方法がこれです。これは、「DataGridでComboBoxを使う」のComboBoxをLinkLabelにそのまま代えたという感じです。EditメソッドでLinkLabelを表示するため、セルがアクティブにならなければLinkLabelが表示されないという欠点があります。
この方法を使った簡単なサンプルを下に示します(かなりの手抜きですが)。ここでは、LinkLabelをクリックすると、セルに表示されている文字列をProcess.Startメソッドに渡して呼び出しています。なお、DataGridColumnStyleクラスの使用法に関しては、「DataGridの列の幅を変更する」をご覧ください。
Imports System Imports System.Data Imports System.Drawing Imports System.Windows.Forms Namespace Dobon.Samples.Forms ''' <summary> ''' DataGridにLinkLabelを表示するDataGridColumnStyle ''' </summary> Public Class DataGridLinkLabelColumn Inherits DataGridTextBoxColumn 'TextBoxの代わりに表示するLinkLabel Private _linkLabel As LinkLabel Public ReadOnly Property LinkLabel() As LinkLabel Get Return _linkLabel End Get End Property Public Sub New() _linkLabel = New LinkLabel AddHandler _linkLabel.Click, AddressOf _linkLabel_Click End Sub Protected Overloads Overrides Sub Edit( _ ByVal source As System.Windows.Forms.CurrencyManager, _ ByVal rowNum As Integer, _ ByVal bounds As System.Drawing.Rectangle, _ ByVal [readOnly] As Boolean, _ ByVal instantText As String, _ ByVal cellIsVisible As Boolean) MyBase.Edit(source, rowNum, bounds, [readOnly], _ instantText, cellIsVisible) 'TextBoxの代わりにLinkLabelを表示する TextBox.Visible = False _linkLabel.Parent = TextBox.Parent _linkLabel.Bounds = bounds _linkLabel.Text = TextBox.Text _linkLabel.Visible = True _linkLabel.BringToFront() _linkLabel.Focus() End Sub Private Sub _linkLabel_Click( _ ByVal sender As Object, ByVal e As EventArgs) 'LinkLabelがクリックされた時は表示されているTextを実行する Dim link As LinkLabel = CType(sender, LinkLabel) System.Diagnostics.Process.Start(link.Text) End Sub End Class End Namespace
using System; using System.Data; using System.Drawing; using System.Windows.Forms; namespace Dobon.Samples.Forms { /// <summary> /// DataGridにLinkLabelを表示するDataGridColumnStyle /// </summary> public class DataGridLinkLabelColumn : DataGridTextBoxColumn { //TextBoxの代わりに表示するLinkLabel private LinkLabel _linkLabel; public LinkLabel LinkLabel { get {return _linkLabel;} } public DataGridLinkLabelColumn() { _linkLabel = new LinkLabel(); _linkLabel.Click += new EventHandler(_linkLabel_Click); } protected override void Edit( CurrencyManager source, int rowNum, Rectangle bounds, bool readOnly, string instantText, bool cellIsVisible) { base.Edit(source, rowNum, bounds, readOnly, instantText, cellIsVisible); //TextBoxの代わりにLinkLabelを表示する TextBox.Visible = false; _linkLabel.Parent = TextBox.Parent; _linkLabel.Bounds = bounds; _linkLabel.Text = TextBox.Text; _linkLabel.Visible = true; _linkLabel.BringToFront(); _linkLabel.Focus(); } private void _linkLabel_Click(object sender, EventArgs e) { //LinkLabelがクリックされた時は表示されているTextを実行する LinkLabel link = (LinkLabel) sender; System.Diagnostics.Process.Start(link.Text); } } }
アクティブなセルだけでなく、すべての行にLinkLabelを表示するには、Paintメソッドで何らかの処理を行う必要があります。具体的には、必要な数だけLinkLabelコントロールを作成するか、あたかもLinkLabelコントロールのように文字列を描画するかということになるでしょう。
必要な数だけLinkLabelコントロールを作成する例が、ニュースグループのこちらに投稿されています。この方法は、LinkLabelコントロールを行ごとにHashtableに保存しておき、Paintメソッドで必要に応じて配置するというものです。行ごとにLinkLabelコントロールを作成しているため、行が多くなると、かなりメモリを消費するでしょう。
それが嫌であれば、PaintメソッドでLinkLabelのような文字列を描画すればよいということになります。ここで問題なのは、どうやってクリックされたことを知るかということです。そのためには、例えばDataGridのMouseUpイベントでHitTestInfoメソッドを使ってセル上でマウスボタンが押されたか判断するという方法があります。LinkLabelの動作とはかなり違いますが、仕方のないところでしょう。
以下にPaintメソッドで文字列を描画するDataGridColumnStyleの簡単な例を示します。ここでは、DataGridのMouseMoveイベントでマウスがセル上にあり、その列スタイルにこのDataGridColumnStyleが使われている時に、マウスカーソルをハンドカーソルに変更しています(かなりいい加減なやり方ですが)。また、DataGridのMouseDownイベントでクリックされたことにしています。
Namespace Dobon.Samples.Forms ''' <summary> ''' DataGridにLinkLabelを表示するDataGridColumnStyle ''' </summary> Public Class DataGridLinkLabelColumn2 Inherits DataGridTextBoxColumn Private _margin As New Point(1, 2) Private _visitedLinks As System.Collections.ArrayList Private _dataGrid As DataGrid Public Sub New() _visitedLinks = New System.Collections.ArrayList End Sub Protected Overloads Overrides Sub Edit( _ ByVal [source] As CurrencyManager, _ ByVal rowNum As Integer, _ ByVal bounds As Rectangle, _ ByVal [readOnly] As Boolean, _ ByVal instantText As String, _ ByVal cellIsVisible As Boolean) End Sub 'Paintメソッドをオーバーライドする Protected Overloads Overrides Sub Paint( _ ByVal g As Graphics, _ ByVal bounds As Rectangle, _ ByVal [source] As CurrencyManager, _ ByVal rowNum As Integer, _ ByVal backBrush As Brush, _ ByVal foreBrush As Brush, _ ByVal alignToRight As Boolean) '表示する文字列を取得 Dim [text] As String = _ GetColumnValueAtRow([source], rowNum).ToString() Dim sf As New StringFormat '配置を指定する Select Case Me.Alignment Case HorizontalAlignment.Left sf.Alignment = StringAlignment.Near Case HorizontalAlignment.Center sf.Alignment = StringAlignment.Center Case HorizontalAlignment.Right sf.Alignment = StringAlignment.Far End Select 'テキストの方向を指定する If alignToRight Then sf.FormatFlags = _ sf.FormatFlags Or _ StringFormatFlags.DirectionRightToLeft End If '背景を塗りつぶす g.FillRectangle(backBrush, bounds) '前景色を決める Dim textBrush As Brush If _visitedLinks.Contains([text]) Then textBrush = Brushes.Purple Else textBrush = Brushes.Blue End If 'フォントにアンダーラインをつける Dim textFont As New Font( _ DataGridTableStyle.DataGrid.Font.FontFamily, _ DataGridTableStyle.DataGrid.Font.Size, _ DataGridTableStyle.DataGrid.Font.Style _ Or FontStyle.Underline) Dim rectf As New RectangleF( _ bounds.X, bounds.Y, bounds.Width, bounds.Height) rectf.Inflate(-_margin.X, -_margin.Y) '文字列を描画する g.DrawString([text], textFont, textBrush, rectf, sf) sf.Dispose() textFont.Dispose() End Sub Protected Overrides Sub SetDataGridInColumn( _ ByVal value As DataGrid) MyBase.SetDataGridInColumn(value) If Not value.Equals(_dataGrid) Then If Not (_dataGrid Is Nothing) Then RemoveHandler _dataGrid.MouseMove, _ AddressOf DataGrid_MouseMove RemoveHandler _dataGrid.MouseDown, _ AddressOf DataGrid_MouseDown End If AddHandler value.MouseMove, AddressOf DataGrid_MouseMove AddHandler value.MouseDown, AddressOf DataGrid_MouseDown _dataGrid = value End If End Sub Protected Overloads Overrides Sub Dispose( _ ByVal disposing As Boolean) MyBase.Dispose(disposing) RemoveHandler _dataGrid.MouseMove, _ AddressOf DataGrid_MouseMove RemoveHandler _dataGrid.MouseDown, _ AddressOf DataGrid_MouseDown End Sub Private Sub DataGrid_MouseMove( _ ByVal sender As Object, ByVal e As MouseEventArgs) 'マウスがセル上にあるときは、カーソルを変更する Dim hti As DataGrid.HitTestInfo = _ DataGridTableStyle.DataGrid.HitTest(e.X, e.Y) If hti.Type = DataGrid.HitTestType.Cell AndAlso _ TypeOf DataGridTableStyle.GridColumnStyles(hti.Column) _ Is DataGridLinkLabelColumn2 Then DataGridTableStyle.DataGrid.Parent.Cursor = _ Cursors.Hand Else DataGridTableStyle.DataGrid.Parent.Cursor = _ Cursors.Default End If End Sub Private Sub DataGrid_MouseDown( _ ByVal sender As Object, ByVal e As MouseEventArgs) Dim grid As DataGrid = DataGridTableStyle.DataGrid Dim info As DataGrid.HitTestInfo = grid.HitTest(e.X, e.Y) 'マウスがセル上にあるか調べる If info.Type = DataGrid.HitTestType.Cell AndAlso info.Column = _ DataGridTableStyle.GridColumnStyles.IndexOf(Me) Then 'Process.Startを呼び出す Dim cm As CurrencyManager = _ CType(grid.BindingContext( _ grid.DataSource, grid.DataMember), CurrencyManager) Dim str As String = _ GetColumnValueAtRow(cm, info.Row).ToString() System.Diagnostics.Process.Start(str) '訪れたことを記憶する _visitedLinks.Add(str) End If End Sub End Class End Namespace
using System; using System.Data; using System.Drawing; using System.Windows.Forms; namespace Dobon.Samples.Forms { /// <summary> /// DataGridにLinkLabelを表示するDataGridColumnStyle /// </summary> public class DataGridLinkLabelColumn2 : DataGridTextBoxColumn { Point _margin = new Point(1, 2); private System.Collections.ArrayList _visitedLinks; DataGrid _dataGrid; public DataGridLinkLabelColumn2() { _visitedLinks = new System.Collections.ArrayList(); } protected override void Edit( CurrencyManager source, int rowNum, Rectangle bounds, bool readOnly, string instantText, bool cellIsVisible) { } //Paintメソッドをオーバーライドする protected override void Paint(Graphics g, Rectangle bounds, CurrencyManager source, int rowNum, Brush backBrush, Brush foreBrush, bool alignToRight) { //表示する文字列を取得 string text = GetColumnValueAtRow(source, rowNum).ToString(); StringFormat sf = new StringFormat(); //配置を指定する switch (this.Alignment) { case HorizontalAlignment.Left: sf.Alignment = StringAlignment.Near; break; case HorizontalAlignment.Center: sf.Alignment = StringAlignment.Center; break; case HorizontalAlignment.Right: sf.Alignment = StringAlignment.Far; break; } //テキストの方向を指定する if (alignToRight) sf.FormatFlags |= StringFormatFlags.DirectionRightToLeft; //背景を塗りつぶす g.FillRectangle(backBrush, bounds); //前景色を決める Brush textBrush; if (_visitedLinks.Contains(text)) textBrush = Brushes.Purple; else textBrush = Brushes.Blue; //フォントにアンダーラインをつける Font textFont = new Font(DataGridTableStyle.DataGrid.Font.FontFamily, DataGridTableStyle.DataGrid.Font.Size, DataGridTableStyle.DataGrid.Font.Style | FontStyle.Underline); RectangleF rectf = new RectangleF( bounds.X, bounds.Y, bounds.Width, bounds.Height); rectf.Inflate(-_margin.X, -_margin.Y); //文字列を描画する g.DrawString(text, textFont, textBrush, rectf, sf); sf.Dispose(); textFont.Dispose(); } protected override void SetDataGridInColumn(DataGrid value) { base.SetDataGridInColumn(value); if (!value.Equals(_dataGrid)) { if (_dataGrid != null) { _dataGrid.MouseMove -= new MouseEventHandler(DataGrid_MouseMove); _dataGrid.MouseDown -= new MouseEventHandler(DataGrid_MouseDown); } value.MouseMove += new MouseEventHandler(DataGrid_MouseMove); value.MouseDown += new MouseEventHandler(DataGrid_MouseDown); _dataGrid = value; } } protected override void Dispose(bool disposing) { base.Dispose(disposing); _dataGrid.MouseMove -= new MouseEventHandler(DataGrid_MouseMove); _dataGrid.MouseDown -= new MouseEventHandler(DataGrid_MouseDown); } private void DataGrid_MouseMove( object sender, MouseEventArgs e) { //マウスがセル上にあるときは、カーソルを変更する DataGrid.HitTestInfo hti = DataGridTableStyle.DataGrid.HitTest(e.X, e.Y); if (hti.Type == DataGrid.HitTestType.Cell && DataGridTableStyle.GridColumnStyles[hti.Column] is DataGridLinkLabelColumn2) { DataGridTableStyle.DataGrid.Parent.Cursor = Cursors.Hand; } else { DataGridTableStyle.DataGrid.Parent.Cursor = Cursors.Default; } } private void DataGrid_MouseDown(object sender, MouseEventArgs e) { DataGrid grid = DataGridTableStyle.DataGrid; DataGrid.HitTestInfo info = grid.HitTest(e.X, e.Y); //マウスがセル上にあるか調べる if (info.Type == DataGrid.HitTestType.Cell && info.Column == DataGridTableStyle.GridColumnStyles.IndexOf(this)) { //Process.Startを呼び出す CurrencyManager cm = (CurrencyManager) grid.BindingContext[ grid.DataSource, grid.DataMember]; string str = GetColumnValueAtRow(cm, info.Row).ToString(); System.Diagnostics.Process.Start(str); //訪れたことを記憶する _visitedLinks.Add(str); } } } }
これらの方法に不満があるならば、市販品を使うのがよいでしょう。LinkLabelを表示できるフリーのライブラリはほとんど見かけませんが、「Extended DataGrid」というものがあります。
(この記事は「.NETプログラミング研究 第37号」で紹介したものです。)