はじめに
WinFormsで長年運用されている業務システムにおいて、「1レコードを複数行で表示したい」「セル内で複雑な装飾をしたい」といった要望に直面したことはないでしょうか?
WinForms標準のDataGridViewでこれらを実現しようとすると、オーナー描画(OnPaint)の泥沼にハマることになります。
本記事では、WinFormsの基盤を活かしつつ、表示が複雑なグリッド部分だけを**WPF(ElementHost)**へ差し替えることで、開発効率とユーザー体験を劇的に向上させる手法を紹介します。
注意
この記事の作成には生成AIを使用しています。
1. 課題:WinForms標準グリッドの限界
例えば、以下のような明細表示を求められた場合、WinFormsでは実装の難易度が跳ね上がります。
- 1行に複数の項目を2段、3段に分けて表示する
- 明細の状態(ステータス)ごとに背景色や文字色を細かく制御する
- 大量データ(数万件)を扱いながら、スムーズなスクロールを実現する
これをWPFのListViewとDataTemplateに任せることで、宣言的に(XAMLで)美しく記述できるようになります。
2. 実装:WinFormsからWPFをホストする
全体の構造は以下のようになります。WinForms プロジェクトが WPF のライブラリを参照し、ElementHost を介して View と ViewModel を結合します。
WinForms側のコード
デザイナー任せにせず、コンストラクタでViewModelと共に注入するのが疎結合に保つコツです。
public partial class HybridForm : Form
{
private DetailListViewModel _viewModel;
public HybridForm(DataEntity entity)
{
InitializeComponent();
// 1. ViewModelの生成
_viewModel = new DetailListViewModel();
// 2. WPFコントロールの生成と注入
// DataContextを介してデータを渡す
var wpfControl = new WpfDetailControl { DataContext = _viewModel };
elementHost.Child = wpfControl;
// 3. データの詰め替え(WinFormsエンティティ -> WPF ViewModel)
BindData(entity);
}
private void BindData(DataEntity entity)
{
_viewModel.Items.Clear();
foreach (var detail in entity.Details)
{
_viewModel.Items.Add(new ItemViewModel {
Code = detail.Code,
Name = detail.Name,
Value = detail.Value,
// ... 他の項目
});
}
}
}
データフローをシーケンスで見ると、WinForms がデータの「詰め替え役」として動いていることが分かります。
3. 表現力のキモ:XAMLによる多段レイアウト
WPFなら、1レコードを複数行に跨いで表示するのもGridの定義次第です。
WpfDetailControl.xaml (抜粋)
<ListView ItemsSource="{Binding Items}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid Height="44">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="120" /> <!-- コード用 -->
<ColumnDefinition Width="*" /> <!-- 名称用 -->
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="22" />
<RowDefinition Height="22" />
</Grid.RowDefinitions>
<!-- 1段目:識別コードや日付など -->
<Border Grid.Row="0" Grid.Column="0" Style="{StaticResource CellBorderStyle}">
<TextBlock Text="{Binding Code}" />
</Border>
<!-- 2段目:名称(ColumnSpanで横いっぱいに表示) -->
<Border Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Style="{StaticResource CellBorderStyle}">
<TextBlock Text="{Binding Name}" FontWeight="Bold" />
</Border>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
4. 高速化のロジック:仮想化とスクロール制御
大量の明細をストレスなく表示するために、WPFの「UI仮想化」を最大限に引き出します。
仮想化の設定
ListViewに以下のプロパティを設定することで、画面に見えている範囲の要素だけを生成し、メモリ消費と描画負荷を抑えます。
<ListView
ItemsSource="{Binding Items}"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
VirtualizingStackPanel.ScrollUnit="Pixel"> <!-- ここが重要! -->
</ListView>
-
IsVirtualizing="True": 仮想化を有効化。これがないとデータ件数に比例してパフォーマンスが低下します。 -
VirtualizationMode="Recycling": 生成したアイテムコンテナを再利用します。スクロール時のメモリ確保(GC)を減らすための必須設定です。 -
ScrollUnit="Pixel": ここがUX向上の最大のポイントです。 デフォルトの「項目単位(1行ずつカクカク動く)」ではなく「ピクセル単位」でスクロールさせることで、非常に滑らかでモダンな操作感になります。
5. デザインのTips:罫線を綺麗に見せる「負のマージン」
WPFでBorderを並べると、隣り合う境界線が重なって太く見える問題があります。業務アプリらしい精緻な0.5px〜1pxの線を実現するには、負のマージンを活用します。
<Style x:Key="CellBorderStyle" TargetType="Border">
<Setter Property="BorderBrush" Value="Gray" />
<Setter Property="BorderThickness" Value="0.5" />
<!-- 上と左に負のマージンを設定して、隣のセルの線とわざと重ね合わせる -->
<Setter Property="Margin" Value="-0.5,-0.5,0,0" />
</Style>
このわずかな工夫で、高解像度ディスプレイでもボヤけない、非常にシャープなグリッドが完成します。
まとめ:なぜこの構成が「強い」のか
従来の WinForms 単体構成と比較して、ハイブリッド化には明確なメリットがあります。
- 段階的移行が可能: 画面全体を書き換えるコストをかけず、ボトルネックとなっている「明細」だけをモダン化できる。
-
デザイナーとロジックの分離: XAML(表示)とViewModel(データ)が分かれるため、複雑な表示ロジックがWinFormsのコードビハインド(
Form.cs)を汚さない。 -
レスポンシブな追従: WinForms側の
ElementHostのAnchorを上下左右に設定するだけで、フォームのリサイズに完璧に追従します。
「WinFormsは古い」と諦める前に、ElementHostという架け橋を使って、WPFの強力な表現力を取り入れてみてはいかがでしょうか。