この記事は2019年05月22日に投稿しました。
目次
エッセンシャルWPF:Windows Presentation Foundation (Programmer's SELECTION)
- 作者: Chris Anderson,星睦
- 出版社/メーカー: 翔泳社
- 発売日: 2007/10/31
- メディア: 大型本
- 購入: 6人 クリック: 128回
- この商品を含むブログ (32件) を見る
1. はじめに
こんにちは、iOSのエディタアプリPWEditorの開発者の二俣です。
今回は業務で使用しているWPFのDataGridでヘッダーをセル結合する方法についてです。
2. WPFのDataGridでヘッダーをセル結合する
WPFのDataGridでヘッダーをセル結合する方法ですが、ヘッダー行のみ表示するDataGridとデータ行のみ表示するDataGridの2つを使って実現します。
ヘッダー行の1つのカラムの列幅と、データ行の2つまたは複数のカラム幅の合計サイズを合わせることで、疑似的にセル結合したように表示します。
そのため以下の制限があります。
- カラム幅は固定
- ソート不可
またこのままだと横スクロールした場合、ばらばらにスクロールしてしまうので、横スクロールを同期させます。
実葬例
MainWindow.xaml
<Window x:Class="WPFDataGridColumnHeaderSpan.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:WPFDataGridColumnHeaderSpan" mc:Ignorable="d" Title="MainWindow" Height="450" Width="200" ContentRendered="Window_ContentRendered"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="auto" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <!-- ヘッダ行のデータグリッド --> <DataGrid Grid.Row="0" Grid.Column="0" Name="dataGridHeader" AutoGenerateColumns="False" CanUserResizeColumns="False" CanUserReorderColumns="False" IsReadOnly="True" HeadersVisibility="Column" HorizontalScrollBarVisibility="Hidden"> <DataGrid.Columns> <DataGridTextColumn Width="200" Header="名前" /> <DataGridTextColumn Width="40" Header="Data1" /> <DataGridTextColumn Width="40" Header="Data2" /> </DataGrid.Columns> </DataGrid> <!-- データ行のデータグリッド --> <DataGrid Grid.Row="1" Grid.Column="0" Name="dataGridData" AutoGenerateColumns="False" CanUserResizeColumns="False" HeadersVisibility="None"> <DataGrid.Columns> <DataGridTextColumn Width="200" x:Name="Name" Binding="{Binding Name}" /> <DataGridTextColumn Width="20" x:Name="Data1" Binding="{Binding Data1}" /> <DataGridTextColumn Width="20" x:Name="Data2" Binding="{Binding Data2}" /> <DataGridTextColumn Width="20" x:Name="Data3" Binding="{Binding Data3}" /> <DataGridTextColumn Width="20" x:Name="Data4" Binding="{Binding Data4}" /> </DataGrid.Columns> </DataGrid> </Grid> </Window>
MainWindow.xaml.cs
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; namespace WPFDataGridColumnHeaderSpan { /** * @brief サンプルデータ */ class SampleData { public string Name { get; set; } public string Data1 { get; set; } public string Data2 { get; set; } public string Data3 { get; set; } public string Data4 { get; set; } } /// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { //! スクロール同期処理 private DataGridScrollSynchronizer ScrollSynchronizer; /** * @brief コンストラクタ */ public MainWindow() { InitializeComponent(); // ヘッダとするデータグリッドのダミーデータを設定します。 // データ行が無いとスクロールしません。 var headerList = new List<SampleData>(); headerList.Add(new SampleData() { Name = "" }); dataGridHeader.ItemsSource = headerList; // データ行となるデータグリッドにデータを設定します。 var dataList = new List<SampleData>(); dataList.Add(new SampleData() { Name = "名前1" }); dataList.Add(new SampleData() { Name = "名前2" }); dataList.Add(new SampleData() { Name = "名前3" }); dataGridData.ItemsSource = dataList; } /** * @brief ウィンドウが描画された後に呼び出されます。 * * @param [in] sender ウィンドウ * @param [in] e イベント */ private void Window_ContentRendered(object sender, EventArgs e) { // ヘッダーとなるDataGridのデータ行の高さを0に設定して、非表示にします。 var row = GetDataGridRow(dataGridHeader, 0); row.Height = 0; // 各データグリッドのスクロール同期を設定します。 var dataGridList = new List<DataGrid>(); dataGridList.Add(dataGridHeader); dataGridList.Add(dataGridData); ScrollSynchronizer = new DataGridScrollSynchronizer(dataGridList, SynchronizeDirection.Horizontal); } // 内部メソッド(詳細は省略します) /** * @brief データグリッドの行オブジェクトを取得します。 * * @param [in] dataGrid データグリッド * @param [in] rowIndex 行番号(0起算) * @return データグリッドの行オブジェクト */ private DataGridRow GetDataGridRow(DataGrid dataGrid, int rowIndex) { var generator = dataGridHeader.ItemContainerGenerator; var row = generator.ContainerFromIndex(rowIndex) as DataGridRow; if (row == null) { dataGridHeader.UpdateLayout(); var item = dataGridHeader.Items[rowIndex]; dataGridHeader.ScrollIntoView(item); row = generator.ContainerFromIndex(rowIndex) as DataGridRow; } return row; } } }
DataGridScrollSynchronizer.cs
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace WPFDataGridColumnHeaderSpan { /** * @brief スクロールの同期する方向 */ [Flags] public enum SynchronizeDirection { //! 水平方向 Horizontal = 0x01, //! 垂直方向 Vertical = 0x02, //! 両方 Both = 0x03, } /** * @brief データグリッドスクロール同期クラス */ public class DataGridScrollSynchronizer { //! スクロールビューワーリスト private List<ScrollViewer> ScrollViewerList; //! スクロール方向 private SynchronizeDirection Direction { get; set; } /** * @brier コンストラクタ * * @param [in] dataGridList 同期するデータグリッドリスト * @param [in] direction 同期するスクロール方向 */ public DataGridScrollSynchronizer(List<DataGrid> dataGridList, SynchronizeDirection direction = SynchronizeDirection.Both) { ScrollViewerList = new List<ScrollViewer>(); // データグリッド数を取得する。 int dataGridNum = dataGridList.Count; // 同期するデータグリッド数が1以下の場合、何もしない。 if (dataGridNum < 2) { return; } // データグリッド数分繰り返す。 for (int i = 0; i < dataGridNum; ++i) { // データグリッドのスクロールビューワーを取得する。 var dataGrid = dataGridList[i]; var scrollViewer = GetScrollViewer(dataGrid); // スクロールビューワーにイベントハンドラを設定する。 scrollViewer.ScrollChanged += ScrollChanged; // スクロールビューワーを識別するためタグを設定する。 scrollViewer.Tag = i; // スクロールビューワーリストに保存する。 ScrollViewerList.Add(scrollViewer); } // スクロール方向を保存する。 Direction = direction; } /** * @brief スクロールビューワーを取得する。 * * @param [in] element エレメント * @return スクロールビューワー * 取得できない場合、nullを返却する。 */ private ScrollViewer GetScrollViewer(FrameworkElement element) { // 引数elementのビジュアルオブジェクト数分繰り返す。 var childrenNum = VisualTreeHelper.GetChildrenCount(element); for (int i = 0; i < childrenNum; ++i) { // ビジュアルオブジェクトを取得する。 var child = VisualTreeHelper.GetChild(element, i) as FrameworkElement; // ビジュアルオブジェクトが取得できない場合 if (child == null) { // 次を取得する。 continue; } // 取得したビジュアルオブジェクトがスクロールビューワーの場合 if (child is ScrollViewer) { // 取得したスクロールビューワーを返却する。 return child as ScrollViewer; } // 次のビジュアルオブジェクトを取得する。 child = GetScrollViewer(child); if (child != null) { return child as ScrollViewer; } } return null; } /** * @brief スクロールされた時に呼び出される。 * * @param [in] sender スクロールビューワー * @param [in] e スクロールチェンジイベント */ private void ScrollChanged(object sender, ScrollChangedEventArgs e) { var srcScrollViewer = sender as ScrollViewer; // 同期するスクロール方向が水平方向の場合 if (Direction.HasFlag(SynchronizeDirection.Horizontal)) { // スクロールするオフセットを取得する。 var offset = srcScrollViewer.HorizontalOffset; // スクロールビューワー数分繰り返す。 foreach (var dstScrollVierwer in ScrollViewerList) { // スクロールしたスクロールビューワーは無視する。 if (dstScrollVierwer.Tag == srcScrollViewer.Tag) { continue; } // 同期するスクロールビューワーをスクロールする。 dstScrollVierwer.ScrollToHorizontalOffset(offset); } } // 同期するスクロール方向が垂直方向の場合 if (Direction.HasFlag(SynchronizeDirection.Vertical)) { // スクロールするオフセットを取得する。 var offset = srcScrollViewer.VerticalOffset; // スクロールビューワー数分繰り返す。 foreach (var dstScrollVierwer in ScrollViewerList) { // スクロールしたスクロールビューワーは無視する。 if (dstScrollVierwer.Tag == srcScrollViewer.Tag) { continue; } // 同期するスクロールビューワーをスクロールする。 dstScrollVierwer.ScrollToVerticalOffset(offset); } } } } }
3. おわりに
いろいろ調べましたが、DataGrid自体でセル結合する方法はわかりませんでした。
DataGrid自体でセル結合する方法がわかったらまた紹介したいと思います。
実戦で役立つ C#プログラミングのイディオム/定石&パターン
- 作者: 出井秀行
- 出版社/メーカー: 技術評論社
- 発売日: 2017/02/18
- メディア: 大型本
- この商品を含むブログ (1件) を見る
紹介している一部の記事のコードはGitlabで公開しています。
興味のある方は覗いてみてください。
私が勤務しているニューラルでは、主に組み込み系ソフトの開発を行っております。
弊社製品のハイブリッドOS Bi-OSは高い技術力を評価されており、特に制御系や通信系を得意としています。
私自身はiOSモバイルアプリやウィンドウズアプリを得意としております。
ソフトウェア開発に関して相談などございましたら、お気軽にご連絡ください。
また一緒に働きたい技術者の方も随時募集中です。
興味がありましたらご連絡ください。
EMAIL : info-nr@newral.co.jp / m-futamata@newral.co.jp
TEL : 042-523-3663
FAX : 042-540-1688