【WPF】DataGridをユーザーコントロール化しよう

当ページのリンクには広告が含まれています。

データを一覧形式で表示する事って結構よくあります。

こんな時に登場するのがDataGridなのですが、標準機能はシンプルなので、行番号の表示やCSV読み込み、クリップボードからの貼り付け、表示データの絞り込みや検索機能など、色々と付加してあげなければなりません。

そこで、よく使う機能を一通り実装したDataGridのユーザーコントロールを作ってみました。

あくまでもサンプルなので、みなさんの用途に合わせて自由に修正して使っていただければと思います。

目次

DataGridユーザーコントロールの概要

今回のユーザーコントロールはこんな感じのデザインです。

機能としては次の通りです。

  • イベントの発生(カレントセル移動、クリック、ダブルクリック、キー入力、データ表示)
  • 表示内容をクリップボードへコピー(コンテキストメニュー及びコントロール+V)
  • クリップボードの内容(CSV)を一覧へ張り付け(コンテキストメニュー及びコントロール+C)
  • 表示内容を全選択(コンテキストメニュー及びコントロール+A)
  • 指定条件による表示内容の絞り込み表示
  • カレントセルのある列に対して文字列を検索
  • 表示内容をCSVファイルへ書き込み(コンテキストメニュー)
  • CSVファイルの内容を読み込んで表示(コンテキストメニュー)
  • CSVファイルのドラッグ&ドロップによる表示

以前の記事で紹介したCsvUtilクラスが必要

CSVファイル読み込み、ファイル出力については、「【C#】CSVの読み込みと書き込みを部品化しました!」の記事で紹介しているCsvUtilクラスを利用しています。

このユーザーコントロールと一緒に、CsvUtilもご利用ください。

ダウンロード先

今回のソースコード一式は、こちらからダウンロード可能です。

デモプログラムに張り付けていますので、実際に絞り込みや検索機能をお試しいただけます。

デモプログラムの仕様については、こちら をご覧ください。

ソースコード

では、さっそくソースコードを見ていきます。

まずはXamlから。

<UserControl x:Class="CommonUserControl.DataGridControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:CommonUserControl"
             mc:Ignorable="d" 
             d:DesignHeight="260.587" d:DesignWidth="757.526">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <DataGrid x:Name="uxDataGrid" ItemsSource="{Binding}"
                 CurrentCellChanged="uxDataGrid_CurrentCellChanged" PreviewKeyDown="uxDataGrid_PreviewKeyDown" PreviewMouseDoubleClick="uxDataGrid_PreviewMouseDoubleClick" PreviewMouseDown="uxDataGrid_PreviewMouseDown"
                 EnableColumnVirtualization="True" EnableRowVirtualization="true" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling"
                 AlternatingRowBackground="Cornsilk" >
            
            <!-- 右クリックによるコンテキストメニュー表示中の選択セルの背景色・テキスト色設定 -->
            <DataGrid.Resources>
                <!-- 選択中セルの背景色(フォーカス有り) -->
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="#0078d7"  />
                <!-- 選択中セルのテキスト色(フォーカス有り) -->
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="White"  />
                <!-- 選択中セルの背景色(フォーカス無し) -->
                <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="#0078d7" />
                <!-- 選択中セルのテキスト色(フォーカス無し) -->
                <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="White" />
            </DataGrid.Resources>
            
            <!-- コンテキストメニューの表示 -->
            <DataGrid.ContextMenu>
                <ContextMenu x:Name="uxDataGridContextMenu">
                    <MenuItem Header="コピー"  Command="{x:Static local:DataGridControl.ContextMenuCommand}" CommandParameter="Copy" />
                    <MenuItem Header="貼り付け" Command="{x:Static local:DataGridControl.ContextMenuCommand}" CommandParameter="Paste"  />
                    <MenuItem Header="全選択" Command="{x:Static local:DataGridControl.ContextMenuCommand}" CommandParameter="AllSelect"  />
                    <MenuItem Header="ファイル">
                        <MenuItem Header="読込" Command="{x:Static local:DataGridControl.ContextMenuCommand}" CommandParameter="Open"/>
                        <MenuItem Header="保存" Command="{x:Static local:DataGridControl.ContextMenuCommand}" CommandParameter="Save" />
                    </MenuItem>
                </ContextMenu>
            </DataGrid.ContextMenu>
            
        </DataGrid>

    </Grid>
</UserControl>

続けて、C#のソースコードです。

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Win32;
using CommonClass;

namespace CommonUserControl
{
    /// <summary>
    /// カレントセル変更時に発生するイベントの引数
    /// </summary>
    public class DataGridCellEventArgs : EventArgs
    {
        public int RowIndex { get; set; }
        public int ColumnIndex { get; set; }
        public string ColumnName { get; set; }
        public object Value { get; set; }
    }

    /// <summary>
    /// マウスクリック/ダブルクリック時に発生するイベントの引数
    /// </summary>
    public class DataGridMouseEventArgs : MouseButtonEventArgs 
    {
        public DataGridMouseEventArgs(MouseDevice mouse, int timestamp, MouseButton button, StylusDevice stylusDevice) : base(mouse, timestamp, button, stylusDevice) { }
        public int RowIndex { get; set; }
        public int ColumnIndex { get; set; }
        public string ColumnName { get; set; }
        public object Value { get; set; }
    }

    /// <summary>
    /// キーボード入力時に発生するイベントの引数
    /// </summary>
    public class DataGridKeyEventArgs : KeyEventArgs
    {
        public DataGridKeyEventArgs(KeyboardDevice keyboard,PresentationSource inputSource,int timestamp,Key key) : base(keyboard, inputSource, timestamp, key) { }
        public int RowIndex { get; set; }
        public int ColumnIndex { get; set; }
        public string ColumnName { get; set; }
        public object Value { get; set; }
    }


    /// <summary>
    /// DataGridControl.xaml の相互作用ロジック
    /// </summary>
    public partial class DataGridControl : UserControl
    {
        /// <summary>
        /// ファイル選択完了時のイベント定義
        /// </summary>
        public event EventHandler<DataGridCellEventArgs> CurrentCellChanged = null;
        public event EventHandler<DataGridMouseEventArgs> CellClick = null;
        public event EventHandler<DataGridMouseEventArgs> CellDoubleClick = null;
        public event EventHandler<DataGridKeyEventArgs> CellKeyDown = null;
        public event EventHandler<EventArgs> GridDataBinding = delegate { };

        //XAMLからコマンドが参照できるようにするため、RoutedCommandのインスタンスを生成
        public readonly static RoutedCommand ContextMenuCommand = new RoutedCommand("ContextMenuCommand", typeof(DataGridControl));

        /// <summary>
        /// 表示したいDataTableを指定するプロパティ
        /// </summary>
        public DataTable DataSource
        {
            get
            {
                return (DataTable)uxDataGrid.DataContext;
            }
            set
            {
                uxDataGrid.Columns.Clear();
                uxDataGrid.DataContext = null;
                uxDataGrid.DataContext = value;

                GridDataBinding(this, new EventArgs());
            }
        }

        //表示にフィルターを掛けるプロパティ
        public string Filter
        {
            get
            {
                return ((DataTable)uxDataGrid.DataContext).DefaultView.RowFilter;
            } 
            set
            {
                try
                {
                    if (uxDataGrid.DataContext != null) ((DataTable)uxDataGrid.DataContext).DefaultView.RowFilter = value;
                }
                catch { }
            } 
        }
 
        public DataGridControl()
        {
            InitializeComponent();

            //XamlからRoutedCommandが呼び出された時のイベントハンドラを定義
            this.CommandBindings.Add(new CommandBinding(ContextMenuCommand, (s, e) => { ExecContextMenu(e.Parameter.ToString()); }, (s, e) => e.CanExecute = true));

            // DataGridの行ヘッダに行Noを表示するためのイベント処理を登録
            EnableRowNum();

            //ドラッグ&ドロップを行うためのイベント処理を登録
            EnableDragDrop(uxDataGrid);

            // DataGridの動作に関する初期値を設定
            InitDataGrid(uxDataGrid);

        }

        /// <summary>
        /// DataGridの動作に関する初期値を設定
        /// </summary>
        /// <param name="dataGrid"></param>
        private void InitDataGrid(DataGrid dataGrid)
        {
            //DataGridの初期化 XAMに書いても良い
            uxDataGrid.ClipboardCopyMode = DataGridClipboardCopyMode.IncludeHeader;
            uxDataGrid.SelectionMode = DataGridSelectionMode.Extended;
            uxDataGrid.SelectionUnit = DataGridSelectionUnit.Cell;
            uxDataGrid.CanUserAddRows = false;
            uxDataGrid.CanUserDeleteRows = false;
            uxDataGrid.CanUserReorderColumns = false;
            uxDataGrid.CanUserSortColumns = false;
            uxDataGrid.CanUserResizeColumns = false;
            uxDataGrid.CanUserResizeColumns = false;
            uxDataGrid.CanUserAddRows = false;
            uxDataGrid.IsReadOnly = true;
        }

        /// <summary>
        /// コンテキストメニュー処理
        /// </summary>
        /// <param name="function"></param>
        public void ExecContextMenu(string function)
        {
            switch (function)
            {
               
                case "AllSelect": //全選択
                    System.Windows.Forms.SendKeys.SendWait("^a");
                    break;    
                case "Copy": //クリップボードへコピー
                    uxDataGrid.ClipboardCopyMode = DataGridClipboardCopyMode.IncludeHeader;
                    uxDataGrid.Focus(); System.Windows.Forms.SendKeys.SendWait("^c");
                    break;
                case "Paste": //クリップボードから張り付け
                    System.Windows.Forms.SendKeys.SendWait("^v");
                    break;
                case "Open": //CSVファイルの読み込み
                    {
                        var filename = SelectFileName(false);
                        if (System.IO.File.Exists(filename))
                        {
                            DataSource = new CsvUtil().Read(filename, true);
                        }
                    }
                    break;
                case "Save": //CSVファイルの書き込み
                    { 
                        var filename = SelectFileName(true);
                        if (filename != "")
                        {
                            new CsvUtil().Write(DataSource, filename, true);
                        }
                    }
                    break;
            }
        }

        /// <summary>
        /// ダイアログを表示し、選択されたファイル名を返す
        /// </summary>
        /// <param name="isSave"></param>
        /// <returns></returns>
        private string SelectFileName(bool isSave = false)
        {
            var dlg = (isSave) ? (FileDialog)new SaveFileDialog() : (FileDialog)new OpenFileDialog();
            dlg.Filter = "CSVファイル(*.csv)|*.csv|TSVファイル(*.tsv)|*.txv;*.tsv|全てのファイル(*.*)|*.*";
            if (dlg.ShowDialog() == true)
            {
                return dlg.FileName;
            }
            return "";
        }

        /// <summary>
        /// キー入力されたときのイベント処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uxDataGrid_PreviewKeyDown(object sender, KeyEventArgs e)
        {
            //コントロール+Vなら、クリップボードからデータを取得し、DataContextに登録
            if (((Keyboard.GetKeyStates(Key.LeftCtrl) & KeyStates.Down) == KeyStates.Down ||
                  (Keyboard.GetKeyStates(Key.RightCtrl) & KeyStates.Down) == KeyStates.Down) &&
                     e.Key == Key.V)
            {
                uxDataGrid.DataContext = CreateDataTableFromClipboard();
                GridDataBinding(this, new EventArgs());

                return;
            }

            //カレントセルが存在していれば、イベントを発生させる
            if (CellKeyDown != null && ExistCurrentCell())
            {
                (var row, var col) = GetCurrentCellIndex();
                var val = CreateDataGridCellEventArgs(row, col);
                var args = new DataGridKeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key);
                args.ColumnIndex = val.ColumnIndex;
                args.ColumnName = val.ColumnName;
                args.RowIndex = val.RowIndex;

                CellKeyDown(sender, args);
            }
        }

        /// <summary>
        /// カレントセルが変化した時のイベント処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uxDataGrid_CurrentCellChanged(object sender, EventArgs e)
        {
            if (CurrentCellChanged != null && ExistCurrentCell())
            {
                (var row,var col) = GetCurrentCellIndex();
                CurrentCellChanged(this, CreateDataGridCellEventArgs(row, col));
            }
        }

        /// <summary>
        /// セルをダブルクリックした時のイベント処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uxDataGrid_PreviewMouseDoubleClick(object sender, MouseButtonEventArgs e)
        {
            (var row, var col) = GetClickCellIndex(e.GetPosition(uxDataGrid));

            if (CellDoubleClick != null&& row >= 0 && col >= 0)
            {
                var val = CreateDataGridCellEventArgs(row, col);
                var args = new DataGridMouseEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice);
                args.ColumnIndex = val.ColumnIndex;
                args.ColumnName = val.ColumnName;
                args.RowIndex = val.RowIndex;
                args.Value = val.Value;

                CellDoubleClick(this, args);
            }
        }

        /// <summary>
        /// 現在のカレントセル位置から下方向に文字列を検索する
        /// </summary>
        /// <param name="value"></param>
        public void FindNext(string value)
        {
            if (value != "" && ExistSelectedCell())
            {
                (var row, var col) = GetSelectedCellIndex();

                for (int n = row + 1; n < uxDataGrid.Items.Count; n++)
                {
                    if (GetCellValue(n, col).ToString().Contains(value))
                    {
                        SetCurrentCell(n, col);
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// 現在のカレント位置から方向に文字列を検索する
        /// </summary>
        /// <param name="value"></param>
        public void FindBack(string value)
        {
            if (value != "" && ExistSelectedCell())
            {
                (var row, var col) = GetSelectedCellIndex();
                
                for (int n = row - 1; 0 <= n; n --)
                {
                    if (GetCellValue(n, col).ToString().Contains(value))
                    {
                        SetCurrentCell(n, col);
                        break;
                    }
                }
            }
        }
        /// <summary>
        /// セルをマウスクリックした時のイベント処理
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void uxDataGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            //DataGridが表示された初期状態でセルが存在しない場合、フォーカスが当たらずキーイベントが発生しないので
            //マウスがクリックされたタイミングでフォーカスを当てておく
            uxDataGrid.Focus();

            (var row, var col) = GetClickCellIndex(e.GetPosition(uxDataGrid));

            if (row >= 0 && col >= 0)
            {
                var val = CreateDataGridCellEventArgs(row, col);
                var args = new DataGridMouseEventArgs(e.MouseDevice, e.Timestamp, e.ChangedButton, e.StylusDevice);
                args.ColumnIndex = val.ColumnIndex;
                args.ColumnName = val.ColumnName;
                args.RowIndex = val.RowIndex;
                args.Value = val.Value;

                CellClick(this, args);
            }
        }

        /// <summary>
        /// 引数で指定された行と列を使ってDataGridCellEventArgsを生成する
        /// </summary>
        /// <param name="rowIndex"></param>
        /// <param name="columnIndex"></param>
        /// <returns></returns>
        private DataGridCellEventArgs CreateDataGridCellEventArgs(int rowIndex,int columnIndex)
        {
            var args = new DataGridCellEventArgs();
            args.ColumnIndex = columnIndex;
            args.ColumnName = uxDataGrid.Columns[columnIndex].Header.ToString();
            args.RowIndex = rowIndex;
            args.Value = GetCellValue(rowIndex, columnIndex);

            return args;
        }

        /// <summary>
        /// 行Noを表示するためのイベント処理を登録
        /// </summary>
        private void EnableRowNum()
        {
            uxDataGrid.LoadingRow += (sender, e) =>
            {
                e.Row.Header = (e.Row.GetIndex() + 1).ToString().PadLeft(10);
            };

        }

        /// <summary>
        /// ドラッグ&ドロップを行うためのイベント処理を登録
        /// </summary>
        /// <param name="dataGrid"></param>
        private void EnableDragDrop(DataGrid dataGrid)
        {
            dataGrid.AllowDrop = true;

            //ドラッグ&ドロップの処理
            dataGrid.PreviewDragOver += (sender, args) =>
            {
                args.Effects = (args.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None);
                //コントロールの種類によって、下記の記述が必要(PreviewDropが効かない場合がある) なので、念のため
                args.Handled = true;
            };

            dataGrid.PreviewDrop += (sender, args) =>
            {
                if (args.Data.GetDataPresent(DataFormats.FileDrop))
                {
                    string[] files = (string[])args.Data.GetData(DataFormats.FileDrop);

                    uxDataGrid.DataContext = new CsvUtil().Read(files[0],true);
                    GridDataBinding(this, new EventArgs());
                }
            };

        }

        /// <summary>
        /// 指定した行と列にあるセルの値を取得
        /// </summary>
        /// <param name="rowNo"></param>
        /// <param name="columnNo"></param>
        /// <returns></returns>
        public object GetCellValue(int rowNo,int columnNo)
        {
            return (uxDataGrid.Items[rowNo] as DataRowView).Row[columnNo];
        }

        /// <summary>
        /// 指定した位置にカレントセルを設定
        /// </summary>
        /// <param name="rowNo"></param>
        /// <param name="columnNo"></param>
        public void SetCurrentCell(int rowNo, int columnNo)
        {
            uxDataGrid.Focus();
            
            //カレントセルを設定
            DataGridCellInfo info = new DataGridCellInfo(uxDataGrid.Items[rowNo], uxDataGrid.Columns[columnNo]);
            uxDataGrid.CurrentCell = info;

            //カレントセルの位置までスクロール
            uxDataGrid.ScrollIntoView(uxDataGrid.Items[rowNo], uxDataGrid.Columns[columnNo]);

            //直前の選択セルを解除し、移動したセルを選択状態に設定
            uxDataGrid.SelectedCells.Clear();
            ((DataGridCell)info.Column.GetCellContent(info.Item).Parent).IsSelected = true;
        }

        /// <summary>
        /// 選択されているセルが存在するかをチェック
        /// </summary>
        /// <returns></returns>
        private bool ExistSelectedCell()
        {
            return (uxDataGrid.SelectedCells == null || uxDataGrid.SelectedCells.Count == 0) ? false : true;
        }

        /// <summary>
        /// 選択されている先頭のセルを取得
        /// </summary>
        /// <returns></returns>
        public (int rowNo, int columnNo) GetSelectedCellIndex()
        {
            return (uxDataGrid.Items.IndexOf(uxDataGrid.SelectedCells[0].Item), uxDataGrid.SelectedCells[0].Column.DisplayIndex);
        }

        /// <summary>
        /// カレントセルが存在するかをチェック
        /// </summary>
        /// <returns></returns>
        private bool ExistCurrentCell()
        {
            return (uxDataGrid.CurrentItem == null || uxDataGrid.CurrentCell == null) ? false : true;
        }

        /// <summary>
        /// 現在のカレントセルの行と列番号を取得
        /// </summary>
        /// <returns></returns>
        public (int rowIndex, int columnIndex) GetCurrentCellIndex()
        {
            return (uxDataGrid.Items.IndexOf(uxDataGrid.CurrentItem), uxDataGrid.CurrentCell.Column.DisplayIndex);
        }

        /// <summary>
        /// クリックされたセルの行番号と列番号の取得
        /// </summary>
        /// <param name="pos"></param>
        /// <returns></returns>
        private (int rowIndex, int columnIndex) GetClickCellIndex(Point pos)
        {
            DependencyObject dep = VisualTreeHelper.HitTest(uxDataGrid, pos)?.VisualHit;
            int rowIndex = -1;
            int columnIndex = -1;

            while (dep != null)
            {
                dep = VisualTreeHelper.GetParent(dep);

                while (dep != null)
                {
                    if (dep is DataGridCell)
                    {
                        columnIndex = (dep == null) ? -1 : (dep as DataGridCell).Column.DisplayIndex;
                    }
                    if (dep is DataGridRow)
                    {
                        rowIndex = (dep == null) ? -1 : (int)(dep as DataGridRow).GetIndex();
                    }

                    if (rowIndex >= 0 && columnIndex >= 0)
                    {
                        break;
                    }

                    dep = VisualTreeHelper.GetParent(dep);
                }
            }
            return (rowIndex, columnIndex);
        }

        /// <summary>
        /// クリップボードからCSVを読み込む
        /// </summary>
        /// <param name="lines"></param>
        /// <returns></returns>
        public DataTable CreateDataTableFromClipboard(bool isHeader = true)
        {
            DataTable dt = new DataTable();

            //クリップボードから文字列を取得
            IDataObject obj = Clipboard.GetDataObject();

            if (!obj.GetDataPresent(DataFormats.Text))
            {
                return dt;
            }

            //文字列を改行で分割(ダブルクォートで括られている場合は1行とみなす)
            string[] lines = split_lines(((string)obj.GetData(DataFormats.Text)));

            //区切り文字を解析
            string delimiter = (lines[0].Split(',').Length > lines[0].Split('\t').Length) ? "," : "\t";


            //クリップボード取得時、最終が空白行なので取り除くためlength-1でループ
            for (int n = 0; n < lines.Length - 1; n++)
            {
                string line = lines[n];

                string[] fields = line.Split(new string[] { delimiter }, StringSplitOptions.None);

                if (isHeader)
                {
                    isHeader = false;
                    dt.Columns.AddRange(fields.Select(i => new DataColumn(i)).ToArray());
                    continue;
                }

                //列よりデータの数が多い場合、列を追加
                int cnt = fields.Length - dt.Columns.Count;
                Enumerable.Range(0, (cnt < 0) ? 0 : cnt).Select(i => dt.Columns.Add()).ToArray();

                //DataRowに値をセット
                DataRow dr = dt.NewRow();
                Enumerable.Range(0, fields.Length).Select(i => dr[i] = omit_dq(fields[i])).ToArray();

                //DataTableに行を追加
                dt.Rows.Add(dr);
            }

            return dt;

            //前後にダブルクォートがあれば抹消
            string omit_dq(string p_str)
            {
                int l_start = (p_str.StartsWith("\"")) ? 1 : 0;
                int l_len = p_str.Length - l_start - ((p_str.EndsWith("\"")) ? 1 : 0);
                return p_str.Substring(l_start, l_len).Replace("\"\"", "\"");
            }

            //文字列を複数行に分割
            string[] split_lines(string p_str)
            {
                string l_line = p_str.Replace(Environment.NewLine, "\n");
                List<string> l_lines = new List<string>();
                StringBuilder sb = new StringBuilder();
                int cnt = 0;

                foreach (char chr in l_line)
                {
                    cnt += (chr == '\"') ? 1 : 0;

                    if (chr == '\n' && (cnt % 2) == 0)
                    {
                        l_lines.Add(sb.ToString());
                        sb.Clear();
                        continue;
                    }
                    sb.Append(chr);
                }
                if (sb.Length > 0)
                {
                    l_lines.Add(sb.ToString());
                }

                return l_lines.ToArray();

            }
        }
    }
}

使い方

使用可能なプロパティとイベントは次の通りです。

カテゴリ名前説明
プロパティDataSource表示対象のDataTable
プロパティFilter表示内容の絞り込み用フィルタ
イベントCurrentCellChangedカレントセルが変化した時に発生するイベント
イベントCellClickセルをクリックした時に発生するイベント
イベントCellDoubleClickセルをダブルクリックした時に発生するイベント
イベントCellKeyDownキー入力した時に発生するイベント
イベントGridDataBindingDataGridにDataTableの内容が表示された時に発生するイベント。
次の4つの操作のいずれかにより発生
①プログラムからDataSourceにDataTableを代入した時
②ファイルをGridにドラッグ&ドロップした時
③クリップボードからコントロール+CでGridに張り付けた時
④コンテキストメニューからCSVファイルを選択した時
メソッドExecContextMenuコンテキストメニューに割り当てられている機能を呼び出す

void ExecContextMenu(string function)

function:"AllSelect","Copy","Paste","Open","Save" から選択
メソッドFindNext現在のカレントセル位置から下方向に文字列を検索する

void FindNext(string value)

value:検索したい文字列
メソッドFindBack現在のカレントセル位置から上方向に文字列を検索する

void FindBack(string value)

value:検索したい文字列
メソッドGetCellValue指定した列と位置にあるセルの値を取得

object GetCellValue(DataGrid dataGridint rowNo,int columnNo)

rowNo:0から始まる行番号
columnNo:0から始まる列番号
メソッドSetCurrentCell指定した位置にカレントセルを設定し、その位置までスクロールする

void SetCurrentCell(int rowNo, int columnNo)

rowNo:0から始まる行番号
columnNo:0から始まる列番号
メソッドGetSelectedCellIndex選択されている先頭のセルを取得

(int rowNo, int columnNo) GetSelectedCellIndex()
メソッドGetCurrentCellIndex現在のカレントセルの行と列番号を取得

(int rowIndex, int columnIndex) GetCurrentCellIndex()
メソッドCreateDataTableFromClipboardクリップボードからCSVを読み込む

DataTable CreateDataTableFromClipboard(bool isHeader = true)

isHeader:1行目をヘッダーとして扱う場合 true

DataSourceプロパティ

表示したいDataTableは、このプロパティに代入します。

逆に表示中のDataTableを取り出すことも可能です。

このプロパティにDataTableを代入したタイミングで、DataTableのカラム名がDataGridに動的に追加され、DataTableの内容が表示されます。

Filterプロパティ

これは DataTable のDefaultView に対して Filter を渡すだけのプロパティです。

DefaultView は DataView 型なので、Filter の記述方法は DataView に準じます。

このプロパティを使わずに、 DataSource.DefaultView.RowFilter と言う風に記述しても同じことが出来ますが、少しでも記述を省略するために、あえてFilterというプロパティを作りました。

詳細は、「DataTableの基礎と使い方」の記事の目次から「データのフィルタリングとソート」へジャンプしてお読みください。 

= , <= , < , <> , > , > = , is null , like , not , and , or の演算子を使ってSQLみたいな記述が可能です。

カレント列の検索を行うFindNext,FindBackメソッド

カレントセルが無い状態だと検索機能が効かないようになっているので、検索したい列をあらかじめクリックしてください。

検索はカレントセルを基準に、FindNextで下方向、FindBack で上方向に検索します。

連続してメソッドを実行することで、次々に検索を行い、自動的にスクロールしてくれます。

そして、もうその方向にはヒットするセルが存在しない場合、カレントセルは移動しなようになっています。

「もうこれ以上見つかりません」みたいなメッセージを出すことも考えましたが、今のところはその仕様は入れていませんので、ほしい方はメッセージボックスを出すなりの修正を行って下さい。

コンテキストメニューをカスタマイズしたい場合

コンテキストメニューは、XamlとC#のExecContextMenuメソッドの2つから成り立っています。

単純に文言を変えるだけとか、いらないメニューを消したいだけなら、Xamlの修正でOKです。

もし新たにメニューを追加したい場合、XamlとC#の両方を変更します。

まず、Xamlにメニューを1つ追加し、CommandParameterに新たな機能の名前を指定します。

            <!-- コンテキストメニューの表示 -->
            <DataGrid.ContextMenu>
                <ContextMenu x:Name="uxDataGridContextMenu">
                    <MenuItem Header="コピー"  Command="{x:Static local:DataGridControl.ContextMenuCommand}" CommandParameter="Copy" />
                    <MenuItem Header="貼り付け" Command="{x:Static local:DataGridControl.ContextMenuCommand}" CommandParameter="Paste"  />
                    <MenuItem Header="全選択" Command="{x:Static local:DataGridControl.ContextMenuCommand}" CommandParameter="AllSelect"  />
                    <MenuItem Header="ファイル">
                        <MenuItem Header="読込" Command="{x:Static local:DataGridControl.ContextMenuCommand}" CommandParameter="Open"/>
                        <MenuItem Header="保存" Command="{x:Static local:DataGridControl.ContextMenuCommand}" CommandParameter="Save" />
                    </MenuItem>
                </ContextMenu>
            </DataGrid.ContextMenu>

CommandParameterで指定した機能名が、下記のfunction に渡ってきますので、Switch文に処理を記述します。

        public void ExecContextMenu(string function)
        {
            switch (function)
            {
               
                case "AllSelect": //全選択
                    System.Windows.Forms.SendKeys.SendWait("^a");
                    break;    
                case "Copy": //クリップボードへコピー
                    uxDataGrid.ClipboardCopyMode = DataGridClipboardCopyMode.IncludeHeader;
                    uxDataGrid.Focus(); System.Windows.Forms.SendKeys.SendWait("^c");
                    break;
                case "Paste": //クリップボードから張り付け
                    System.Windows.Forms.SendKeys.SendWait("^v");
                    break;
                case "Open": //CSVファイルの読み込み
                    {
                        var filename = SelectFileName(false);
                        if (System.IO.File.Exists(filename))
                        {
                            DataSource = new CsvUtil().Read(filename, true);
                        }
                    }
                    break;
                case "Save": //CSVファイルの書き込み
                    { 
                        var filename = SelectFileName(true);
                        if (filename != "")
                        {
                            new CsvUtil().Write(DataSource, filename, true);
                        }
                    }
                    break;
            }
        }

イベント発生時のEventArgs について

マウスクリック、キー入力のイベント発生時に渡されるイベント引数(EventArgs)は、元のイベント引数を継承し、新たにクリックされた行番号、列番号、列名、セルの値が追加された形になっています。

例えば、セルをクリックした時、DataGridが受け取るイベントのイベント引数は MouseButtonEventArgsですが、それを継承したDataGridMouseEventArgsを作り、そこにRowIndex,ColumnIndex,ColumnName,Value を追加しています。

    public class DataGridMouseEventArgs : MouseButtonEventArgs 
    {
        public DataGridMouselEventArgs(MouseDevice mouse, int timestamp, MouseButton button, StylusDevice stylusDevice) : base(mouse, timestamp, button, stylusDevice) { }
        public int RowIndex { get; set; }
        public int ColumnIndex { get; set; }
        public string ColumnName { get; set; }
        public object Value { get; set; }
    }

まとめ

今回は、標準のDataGridにいくつかの便利機能を加えた形で、ユーザーコントロールを作りました。

実際にこのユーザーコントロールを使う上では、例外処理など色々な考慮が必要になるとは思いますが、細かい部分は利用する方の仕様に合わせてカスタマイズして頂くか、ご自身のソースコードの参考にして頂ければと思います。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次