C#のDataGridで右クリックメニューを作ってみた

WPFのDataGridに、右クリックメニューを設定してみました。セルの内容に合わせて、メニューの項目が変わるやつです。

試した環境は下記です。

  • Visual Studio 2015 Express for Windows Desktop

目次

  1. DataGridを右クリック
  2. 右クリックメニューにはContextMenuクラスを使う
    1. XAMLで設定してみた
    2. コードで設定してみた

DataGridを右クリック

DataGridのセルをダブルクリックして編集状態にしてからマウスを右クリックすると、「切り取り」「コピー」「貼り付け」のメニューが表示されます。

160309-2-01

でも、単純にセルを選択して右クリックしたときに、セルの内容に合わせたメニューが表示されたら便利だと思いませんか。というわけで、試してみました。

右クリックメニューにはContextMenuクラスを使う

いわゆる右クリックメニューにはContextMenuクラスを使います。MSDNのContextMenuの概要に使い方が書いてあります。

XAMLで設定する方法と、コードで設定する方法があるようですね。

XAMLで設定してみた

まずXAMLでDataGridにContextMenuを設定してみました。

<Window x:Class="datagrid_trial.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:datagrid_trial"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="200">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition Width="80" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="50" />
            <RowDefinition Height="50" />
            <RowDefinition />
        </Grid.RowDefinitions>
        <DataGrid Name="datagrid" ItemsSource="{Binding}" Grid.Column="0" Grid.Row="0" Grid.RowSpan="4"
                  AlternationCount="2"
                  AlternatingRowBackground="AliceBlue"
                  GridLinesVisibility="Vertical"
                  VerticalGridLinesBrush="LightGray"
                  CanUserAddRows="False"
                  CanUserDeleteRows="False" SelectionUnit="Cell">
            <DataGrid.ContextMenu>
                <ContextMenu>
                    <MenuItem Name="contextmenu" Header="セルの内容を表示" Click="contextmenu_Click" />
                </ContextMenu>
            </DataGrid.ContextMenu>
        </DataGrid>
        <Button Name="buttonPoem" Content="短歌" Grid.Column="1" Grid.Row="0" Margin="5" Click="buttonPoem_Click" />
        <Button Name="buttonPoet" Content="歌人" Grid.Column="1" Grid.Row="1" Margin="5" Click="buttonPoet_Click" />
        <Button Name="buttonMarge" Content="短歌と歌人" Grid.Column="1" Grid.Row="2" Margin="5" Click="buttonMarge_Click" />
    </Grid>
</Window>

コードの方は割愛します。

<DataGrid Name="datagrid" ItemsSource="{Binding}" Grid.Column="0" Grid.Row="0" Grid.RowSpan="4"
          AlternationCount="2"
          AlternatingRowBackground="AliceBlue"
          GridLinesVisibility="Vertical"
          VerticalGridLinesBrush="LightGray"
          CanUserAddRows="False"
          CanUserDeleteRows="False" SelectionUnit="Cell">
    <DataGrid.ContextMenu>
        <ContextMenu>
            <MenuItem Name="contextmenu" Header="セルの内容を表示" Click="contextmenu_Click" />
        </ContextMenu>
    </DataGrid.ContextMenu>
</DataGrid>

こんな感じで、DataGrid要素の子としてContextMenu要素を設定し、さらにContextMenu要素の子としてMenuItemを設定する訳ですね。そして、MenuItemにClick属性を設定して、メニューがクリックされたらコマンドが実行されるようにする・・・と。わかりやすいですね。

早速動かしてみたら、問題が出ました。カラム(列)やレコード(行)のヘッダをクリックした場合にも、右クリックメニューが表示されてしまうのです。

160309-2-02

コードで設定してみた

それでは、コードで右クリックメニューの設定をしてみます。

Windowsの色々なアプリで試してみると、右クリックメニューはマウスの右ボタンを押すときではなく、押した後に離す時に表示されるようです。ということで、DataGridのMouseRightButtonUpイベントに右クリックメニューの設定をするコードを書いてみます。

private void datagrid_MouseRightButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    if (datagrid.SelectedCells.Count < 1) return; // セルが選択されていない場合は、何もしない。

    // カラムのインデックス番号を求める
    string header = datagrid.SelectedCells[0].Column.Header.ToString();
    int colindex;
    for (colindex = 0; colindex < datagrid.Columns.Count; colindex++)
    {
        if (datagrid.Columns[colindex].Header.ToString() == header) break;
    }
    // カラムのインデックス番号に対応したデータを取得する。
    ViewData d = (ViewData)datagrid.SelectedCells[0].Item;
    string str = "";
    switch (colindex)
    {
        case 0:
            str = d.Field1;
            break;
        case 1:
            str = d.Field2;
            break;
        case 2:
            str = d.Field3;
            break;
        default:
            return;
    }
    // ContextMenuを作成する。
    MenuItem menuitem = new MenuItem();
    menuitem.Header = str;
    menuitem.Click += menuitem_Click; // メニューのClickにイベントハンドラを追加する。
    ContextMenu contextmenu = new ContextMenu();
    contextmenu.Items.Add(menuitem); // MenuItemインスタンスを、ContextMenuインスタンスに追加する。
    datagrid.ContextMenu = contextmenu; // ContextMenuインスタンスを、DataGridのContextMenuに設定する。
}

private void menuitem_Click(object sender, RoutedEventArgs e)
{
    MenuItem menuitem = (MenuItem)sender; // 呼び出し元のMenuItemを取得する。
    MessageBox.Show(menuitem.Header.ToString()); // 呼び出し元のMenuItemのHeaderプロパティを表示する。
    /*
    senderに合わせて何らかの処理をする
    */
}

コードの内容

コードの中身について説明します。

if (datagrid.SelectedCells.Count < 1) return; // セルが選択されていない場合は、何もしない。

DataGridの選択されているセルの数が1未満(つまり0個)のときは、何もせずにreturnします。

// カラムのインデックス番号を求める
string header = datagrid.SelectedCells[0].Column.Header.ToString();
int colindex;
for (colindex = 0; colindex < datagrid.Columns.Count; colindex++)
{
    if (datagrid.Columns[colindex].Header.ToString() == header) break;
}
// カラムのインデックス番号に対応したデータを取得する。
ViewData d = (ViewData)datagrid.SelectedCells[0].Item;
string str = "";
switch (colindex)
{
    case 0:
        str = d.Field1;
        break;
    case 1:
        str = d.Field2;
        break;
    case 2:
        str = d.Field3;
        break;
    default:
        return;
}

DataGridの選択されたセルを取得するのに苦心しました。DataGridのカラムにはDisplayIndexというプロパティがあるのですが、これはユーザーが列の入れ替えを行うと変わってしまうのです。

そこで、選択されたセルのColumn.Headerからカラムのヘッダの文字列を取得し、forループを使ってColumnsコレクションの中からヘッダの文字列が一致するColumn要素のインデックス番号を取得します。

そして、選択されたセルを含む行オブジェクト(Item)をキャストしてインデックス番号に対応するプロパティを取得します。

そうすると、選択されたセルの内容が得られるわけです。

このやり方は、あとで表の作り方を変えたときにこのコードも変えないといけないので、あまり良い方法ではないと思います。が、他の方法が思いつかないんですよ~。頭が固いのか。もっと簡単な方法ないかなぁ。せめてColumnにNameとかTagを設定できれば・・・。

// ContextMenuを作成する。
MenuItem menuitem = new MenuItem();
menuitem.Header = str;
menuitem.Click += menuitem_Click; // メニューのClickにイベントハンドラを追加する。
ContextMenu contextmenu = new ContextMenu();
contextmenu.Items.Add(menuitem); // MenuItemインスタンスを、ContextMenuインスタンスに追加する。
datagrid.ContextMenu = contextmenu; // ContextMenuインスタンスを、DataGridのContextMenuに設定する。

ここで右クリックメニューを作っています。

やることは単純です。MenuItemインスタンスを作って、それをContextMenuインスタンスにAddして、それをDataGridのContextMenuプロパティに代入します。

メニューのClickイベントのイベントハンドラですが、MenuItem.Click +=まで入力したところでインテリセンスさんが「イベントハンドラ設定する?」と聞いてきて、イベントハンドラのメソッドの雛型も自動的に作ってくれます。

private void menuitem_Click(object sender, RoutedEventArgs e)
{
    MenuItem menuitem = (MenuItem)sender; // 呼び出し元のMenuItemを取得する。
    MessageBox.Show(menuitem.Header.ToString()); // 呼び出し元のMenuItemのHeaderプロパティを表示する。
    /*
    senderに合わせて何らかの処理をする
    */
}

イベントハンドラの引数のsenderを呼び出し元のクラスでキャストすると、呼び出したインスタンスが得られます。そのインスタンスのHeaderを取得してゴニョゴニョします。

動かしてみた

実行してみました。

「中皇命」のセルを選択して右クリックしました。

160309-2-03

で、右クリックメニューの中のメニューをクリックすると下図の様にメッセージボックスが表示されます。

160309-2-04

ちなみに、ユーザーがカラムを入れ替えた後に右クリックしても、正しく表示されます。

160309-2-05

更新日
公開日

広告