DOBON.NET プログラミング道: .NET Framework, VB.NET, C#, Visual Basic, Visual Studio, インストーラ, ...

DataGridにLinkLabelを表示する

注意:ここで紹介しているDataGridは、System.Windows.Forms名前空間のDataGrid(Windowsフォーム)です。System.Web.UI.WebControls名前空間のDataGrid(Webフォーム)ではありません。

System.Windows.Forms.DataGridコントロールにLinkLabelを表示するには、DataGridColumnStyleクラスから派生した新しいクラスを作成することにより実現させる方法が適当だと思いますが、問題はどのようにLinkLabelを表示するかという点でしょう。

アクティブなセルのみLinkLabelとなるDataGridColumnStyle

一番簡単で分かりやすいのは、DataGridColumnStyleクラスのEditメソッドでLinkLabelコントロールを表示するという方法です。掲示板の過去ログにある方法や、ニュースグループのこちらの投稿にある方法がこれです。これは、「DataGridでComboBoxを使う」のComboBoxをLinkLabelにそのまま代えたという感じです。EditメソッドでLinkLabelを表示するため、セルがアクティブにならなければLinkLabelが表示されないという欠点があります。

この方法を使った簡単なサンプルを下に示します(かなりの手抜きですが)。ここでは、LinkLabelをクリックすると、セルに表示されている文字列をProcess.Startメソッドに渡して呼び出しています。なお、DataGridColumnStyleクラスの使用法に関しては、「DataGridの列の幅を変更する」をご覧ください。

VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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のDataGridColumnStyle

アクティブなセルだけでなく、すべての行にLinkLabelを表示するには、Paintメソッドで何らかの処理を行う必要があります。具体的には、必要な数だけLinkLabelコントロールを作成するか、あたかもLinkLabelコントロールのように文字列を描画するかということになるでしょう。

必要な数だけLinkLabelコントロールを作成する例が、ニュースグループのこちらに投稿されています。この方法は、LinkLabelコントロールを行ごとにHashtableに保存しておき、Paintメソッドで必要に応じて配置するというものです。行ごとにLinkLabelコントロールを作成しているため、行が多くなると、かなりメモリを消費するでしょう。

それが嫌であれば、PaintメソッドでLinkLabelのような文字列を描画すればよいということになります。ここで問題なのは、どうやってクリックされたことを知るかということです。そのためには、例えばDataGridのMouseUpイベントでHitTestInfoメソッドを使ってセル上でマウスボタンが押されたか判断するという方法があります。LinkLabelの動作とはかなり違いますが、仕方のないところでしょう。

以下にPaintメソッドで文字列を描画するDataGridColumnStyleの簡単な例を示します。ここでは、DataGridのMouseMoveイベントでマウスがセル上にあり、その列スタイルにこのDataGridColumnStyleが使われている時に、マウスカーソルをハンドカーソルに変更しています(かなりいい加減なやり方ですが)。また、DataGridのMouseDownイベントでクリックされたことにしています。

VB.NET
コードを隠すコードを選択
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
C#
コードを隠すコードを選択
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」というものがあります。

  • 履歴:
  • 2005/8/19 DataGridLinkLabelColumn2クラスを修正。一回のクリックでProcess.Startが2回呼び出される点と、DataGridLinkLabelColumn2を複数の列で使用した時に1つの列しかカーソルアイコンが変更しない点を修正。

注意:この記事では、基本的な事柄の説明が省略されているかもしれません。初心者の方は、特に以下の点にご注意ください。

  • このサイトで紹介されているコードの多くは、例外処理が省略されています。例外処理については、こちらをご覧ください。
  • .NET Tipsをご利用いただく際は、注意事項をお守りください。