こんにちは、C#プログラマーのさんさめです。
本記事のサンプルは書きかけです。
参考にできるところはあるかもしれませんが、
基本的にこのままでは使いにくいです。
DataGridで、
いわゆるページ内検索のような
挙動を実現したいと思いました。
この挙動には大きく2つの要件仕様が混在しています。
- 検索ワードを入れると
- ヒットしたセルに次々と移動できる
- セル内のヒットした部分はハイライトされる
また、
ページ内検索なので、
ViewModelが絡んでくる必要はありません。
極力Viewだけで完結させたいです。
今回は、
ヒットしたセルに次々と移動できる部分の実装を
お試しで作ってみました。
サンプルコード
まずは、サンプルコードだけ載せます。
繰り返しになりますが、書きかけです。
<Window
x:Class="DataGridSearch.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Width="400"
Height="400">
<DockPanel Margin="4">
<Button
Margin="4"
Click="Button_Click"
Content="検索"
DockPanel.Dock="Top" />
<Grid>
<DataGrid
x:Name="MainDataGrid"
Margin="4"
CanUserAddRows="False"
CanUserDeleteRows="False"
RowHeaderWidth="0"
SelectionUnit="CellOrRowHeader" />
<DockPanel
x:Name="SearchBox"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Visibility="Collapsed">
<Button
Click="Button_Click_1"
Content="X"
DockPanel.Dock="Right" />
<Button
Click="OnClickSearchNextButton"
Content="→"
DockPanel.Dock="Right" />
<Button
Click="OnClickSearchPrevButton"
Content="←"
DockPanel.Dock="Right" />
<TextBox x:Name="SearchTextBox" MinWidth="80" />
</DockPanel>
</Grid>
</DockPanel>
</Window>
次に、コードビハインドです。
冒頭にも述べましたが、
Viewだけで完結する機能ですので、
ViewModel層はありません。
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var people = Enumerable.Range(0, 200)
.Select(x => new Person()
{
Name = System.IO.Path.GetRandomFileName(),
Age = x,
Address = Guid.NewGuid().ToString(),
})
.ToList();
MainDataGrid.ItemsSource = people;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
SearchBox.Visibility = Visibility.Visible;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
SearchBox.Visibility = Visibility.Collapsed;
}
private int lastHitRowIndex;
private int lastHitColumnIndex = -1;
private string lastSearchWord;
private void OnClickSearchNextButton(object sender, RoutedEventArgs e)
{
var text = SearchTextBox.Text;
if (string.IsNullOrEmpty(text))
{
return;
}
if (text != lastSearchWord)
{
lastHitRowIndex = 0;
lastHitColumnIndex = -1;
}
var currentLastHitColumnIndex = lastHitColumnIndex;
// 前回ヒットが最終列だった場合のケア
if (currentLastHitColumnIndex == MainDataGrid.Columns.Count - 1)
{
lastHitRowIndex++;
currentLastHitColumnIndex = -1;
}
// DataGridのセルの中身を片っ端から確認
for (int i = lastHitRowIndex; i < MainDataGrid.Items.Count; i++)
{
var item = MainDataGrid.Items[i];
for (int j = currentLastHitColumnIndex + 1; j < MainDataGrid.Columns.Count; j++)
{
var column = MainDataGrid.Columns[j];
var cellContent = column.OnCopyingCellClipboardContent(item)?.ToString();
if (cellContent?.Contains(text) == true)
{
var cellInfo = new DataGridCellInfo(item, column);
MainDataGrid.SelectedCells.Clear();
MainDataGrid.SelectedCells.Add(cellInfo);
MainDataGrid.CurrentCell = cellInfo;
MainDataGrid.ScrollIntoView(item, column);
lastHitRowIndex = i;
lastHitColumnIndex = j;
goto searchEnd;
}
}
// 2行目以降は0からスタートするように
currentLastHitColumnIndex = -1;
}
searchEnd:
lastSearchWord = text;
}
private void OnClickSearchPrevButton(object sender, RoutedEventArgs e)
{
// 後で考える
}
}
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
public string Address { get; set; }
}
挙動の確認
動いているところを見てみましょう。
(gifアニメです)
「→」ボタンを押すたびに、
検索ワードにヒットしたセルに移り変わっています。
フォーカスしていない時の
選択セルのハイライトが弱いので分かりづらいですね。
また、全体でいくつヒットしたのか分からないので
どこまで移動できるのか不安があります。
実装の解説
一番のキモはやはり、
セルの値を取得する部分でしょう。
var cellContent = column.OnCopyingCellClipboardContent(item)?.ToString();
OnCopyingCellClipboardContentというメソッドを使って、
セルに入力された値を取得しています。
通常のDataGridTextColumnならば、
コピー処理を呼び出すことでセルの値を取得できます。
また、仮想化をしていても
問題なく取得することができます。
引数はDataContextなので、
DataGrid.Itemsの中身を渡せばOKです。
次々にセルに移動するという部分では、
最後にヒットした行と列のインデックスを
記憶することで実現しています。
lastHitRowIndex = i;
lastHitColumnIndex = j;
同じワードで検索されたときには、
このインデックスから検索を開始することで、
次のセルに移動という挙動を実現しています。
残っているやりたい事
やりたい事だらけです。
- Ctrl+Fで表示
- 全体でいくつヒットしたか表示
- 戻る方向の検索に対応
- 最後のヒットだった時に最初に戻れるようにする
検索結果が1周した時に通知する - ヒットした部分のハイライト
- 検索エリアの見た目を整える
- 機能をビヘイビアなどに切り出し
後付けできるようにする
ページ内検索の挙動自体は、
参考資料が沢山あるので理想を設定しやすいですね。
まとめ
まとめです。
- WPFのDataGridでページ内検索のようなことがしたくなった
- セルを次々に選択する部分をお試し実装
- まだまだやりたい事だらけ
最後までお読みいただき、ありがとうございました。
シリーズ記事
DataGridページ内検索はシリーズ記事となっております。
他記事は以下のリンクからどうぞ。
- 【WPF】DataGridでページ内検索的なことがしたいその1(今ここ)
- 【WPF】DataGridでページ内検索的なことがしたいその2
関連記事
DataGridでよくやりたくなることの1つに
複数セル選択して一括入力というものがあります。
【WPF】DataGridで複数セルにまとめて入力できるようにするには
で2通りの実現方法について解説しています。
DataGridはそのまま扱うには
あまりにクセのあるコントロールです。
【WPF】DataGridをMVVMで使うなら変えておくべきプロパティ
にて、MVVMとして使う上で
必ず見ておきたいプロパティを解説しています。
コメント