OSによってコントロールを描画してもらうのではなく、
コードを書いて、独自に描画する方法になります。
ListViewで独自に描画しようとしたところ、
Microsoft公式APIドキュメントに記載されているコードが
正常に動作しなかったため、備忘録として記載いたします。
Microsoft 公式APIドキュメント ListView.OwnerDraw
正しい表示
◯負の値の文字が赤色
◯負の値の背景が黒色(DrawSubItemで標準の背景を描画)
◯選択行の背景が栗色 (DrawItemで描画)
◯上記以外の背景はオレンジ色ー栗色のグラデーション (DrawItemで描画)

正しくない表示
◯負の値の文字が赤色
✕負の値の背景が黒(DrawSubItemで標準の背景を描画)
◯選択行の背景が栗色 (DrawItemで描画)
✕上記以外の背景はオレンジ色ー栗色のグラデーション (DrawItemで描画)
全ての行が選択状態のようです。

確認した環境は、下記になります。
・Windows 11
・Visual Studio 2022
・C#
・Windows Desktop 6 (.NET 6.0 + Windows フォーム)
修正したソースコードは下記になります。
気づいたことをインラインで記載していますので、
参考にしていただければと思います。
ListViewOwnerDraw.cs
コードを書いて、独自に描画する方法になります。
ListViewで独自に描画しようとしたところ、
Microsoft公式APIドキュメントに記載されているコードが
正常に動作しなかったため、備忘録として記載いたします。
Microsoft 公式APIドキュメント ListView.OwnerDraw
正しい表示
◯負の値の文字が赤色
◯負の値の背景が黒色(DrawSubItemで標準の背景を描画)
◯選択行の背景が栗色 (DrawItemで描画)
◯上記以外の背景はオレンジ色ー栗色のグラデーション (DrawItemで描画)

正しくない表示
◯負の値の文字が赤色
✕負の値の背景が黒(DrawSubItemで標準の背景を描画)
◯選択行の背景が栗色 (DrawItemで描画)
✕上記以外の背景はオレンジ色ー栗色のグラデーション (DrawItemで描画)
全ての行が選択状態のようです。

確認した環境は、下記になります。
・Windows 11
・Visual Studio 2022
・C#
・Windows Desktop 6 (.NET 6.0 + Windows フォーム)
修正したソースコードは下記になります。
気づいたことをインラインで記載していますので、
参考にしていただければと思います。
ListViewOwnerDraw.cs
using System.Drawing.Drawing2D; using System.Globalization; namespace ListViewOwnerDraw { public partial class ListViewOwnerDraw: Form { private ListView listView1 = new ListView();
// ▲▲▲▲ .NET 6.0用に変更 ▲▲▲▲ // .NET 6.0では、 // ContextMenu -> ContextMenuStrip に変更
// private ContextMenu contextMenu1 = new ContextMenu(); private ContextMenuStrip contextMenu1 = new ContextMenuStrip();
// ▼▼▼▼ .NET 6.0用に変更 ▼▼▼▼
public ListViewOwnerDraw() { // ListViewコントロールを初期化 listView1.BackColor = Color.Black; listView1.ForeColor = Color.White; listView1.Dock = DockStyle.Fill; listView1.View = View.Details; listView1.FullRowSelect = true; // ListViewコントロールにカラムを追加 listView1.Columns.Add("Name", 100, HorizontalAlignment.Center); listView1.Columns.Add("First", 100, HorizontalAlignment.Center); listView1.Columns.Add("Second", 100, HorizontalAlignment.Center); listView1.Columns.Add("Third", 100, HorizontalAlignment.Center); // アイテムを作成し、ListViewコントロールに追加 ListViewItem listViewItem1 = new ListViewItem(
new string[] { "One", "20", "30", "-40" }, -1); ListViewItem listViewItem2 = new ListViewItem(
new string[] { "Two", "-250", "145", "37" }, -1); ListViewItem listViewItem3 = new ListViewItem(
new string[] { "Three", "200", "800", "-1,001" }, -1); ListViewItem listViewItem4 = new ListViewItem(
new string[] { "Four", "not available", "-2", "100" }, -1); listView1.Items.AddRange(new ListViewItem[] {
listViewItem1, listViewItem2, listViewItem3, listViewItem4 });
// ▲▲▲▲ .NET 6.0用に変更 ▲▲▲▲ // .NET 6.0では、 // ContextMenu -> ContextMenuStrip に変更
// ショートカットメニューを初期化し、 // ListViewコントロールに割り当て // contextMenu1.MenuItems.Add("List", contextMenu1.Items.Add("List", null, new EventHandler(menuItemList_Click)); // contextMenu1.MenuItems.Add("Details", contextMenu1.Items.Add("Details", null, new EventHandler(menuItemDetails_Click)); // listView1.ContextMenu = contextMenu1; listView1.ContextMenuStrip = contextMenu1;
// ▼▼▼▼ .NET 6.0用に変更 ▼▼▼▼
// ListViewコントロールをOwnerDrawに設定し、 // OwnerDrawのハンドラーを追加 listView1.OwnerDraw = true; listView1.DrawItem +=
new DrawListViewItemEventHandler(listView1_DrawItem); listView1.DrawSubItem +=
new DrawListViewSubItemEventHandler(listView1_DrawSubItem); listView1.DrawColumnHeader +=
new DrawListViewColumnHeaderEventHandler(listView1_DrawColumnHeader); // MouseUpイベントを追加し、 // 項目の幅のどこをクリックしても選択できるようにする listView1.MouseUp += new MouseEventHandler(listView1_MouseUp);
// ▲▲▲▲ 参考 ▲▲▲▲ // ListViewは、Win32コントロールをラップして作られており、 // そのため、多くのバグが潜んでいます。 // 下記の、余分なDrawItemイベントが送信される現象も、その一つだと思われます。 // ▼▼▼▼ 参考 ▼▼▼▼
// マウスが各行に初めて移動したときに発生する // 余分なDrawItemイベントを補完する為に、様々なイベントを追加 listView1.MouseMove += new MouseEventHandler(listView1_MouseMove); listView1.ColumnWidthChanged +=
new ColumnWidthChangedEventHandler(listView1_ColumnWidthChanged); listView1.Invalidated += new InvalidateEventHandler(listView1_Validated); // フォームを初期化し、ListViewコントロールを追加 this.ClientSize = new Size(450, 150); this.FormBorderStyle = FormBorderStyle.FixedSingle; this.MaximizeBox = false; this.Text = "ListView OwnerDraw Example"; this.Controls.Add(listView1); } // 使用中のリソースをクリーンアップ protected override void Dispose(bool disposing) { if (disposing) { contextMenu1.Dispose(); } base.Dispose(disposing); } [STAThread] static void Main() {
// ▲▲▲▲ .NET 6.0用に変更 ▲▲▲▲ // .NET 6.0では、 // 初期化時に実行するメソッドが変更されております。
// Application.EnableVisualStyles(); ApplicationConfiguration.Initialize();
// ApplicationConfiguration.Initialize()内では、 // Application.EnableVisualStyles() // Application.SetCompatibleTextRenderingDefault(false) // Application.SetHighDpiMode(HighDpiMode.SystemAware) // が呼ばれております。 // ▼▼▼▼ .NET 6.0用に変更 ▼▼▼▼
Application.Run(new ListViewOwnerDraw()); } // ListViewコントロールをList表示に設定 private void menuItemList_Click(Object sender, EventArgs e) { listView1.View = View.List; listView1.Invalidate(); } // ListViewコントロールを詳細表示に設定 private void menuItemDetails_Click(Object sender, EventArgs e) { listView1.View = View.Details; // 各アイテムのTagをリセットして、 // MouseMoveイベントハンドラーで、回避策を再度有効にする foreach (ListViewItem item in listView1.Items) { item.Tag = null; } } // 項目のどこかがクリックされると、 // 項目を選択し、フォーカスする // // クリックは通常、親アイテムのテキスト錠で無ければならない private void listView1_MouseUp(Object sender, MouseEventArgs e) {
// ▲▲▲▲ コメント ▲▲▲▲ // 親アイテム外でも選択できるように、 // X軸は考慮せず(X=5)、Y座標だけでアイテムを特定 // ▼▼▼▼ コメント ▼▼▼▼
ListViewItem clickedItem = listView1.GetItemAt(5, e.Y); if (clickedItem != null) { clickedItem.Selected = true; clickedItem.Focused = true; } } // ListViewのアイテム全体の背景を描画 private void listView1_DrawItem(Object sender, DrawListViewItemEventArgs e) {
// ▲▲▲▲ 不具合のため、修正 ▲▲▲▲ // .NET 6.0では、 // e.Stateが常に選択状態になってしまうため、e.Item.Selectedで判断 // もしかしたら、.NET Frameworkの場合では正常に動作するのかもしれない
// if ((e.State & ListViewItemStates.Selected) != 0) { if (e.Item.Selected) {
// ▼▼▼▼ 不具合のため、修正 ▼▼▼▼
// 選択されたアイテムの、背景とフォーカスの矩形を描画 e.Graphics.FillRectangle(Brushes.Maroon, e.Bounds); e.DrawFocusRectangle(); } else { // 選択されていないアイテムの、背景を描画 using (LinearGradientBrush brush = new LinearGradientBrush( e.Bounds, Color.Orange, Color.Maroon, LinearGradientMode.Horizontal)) { e.Graphics.FillRectangle(brush, e.Bounds); } } // 詳細表示以外の表示の場合、アイテムテキストを描画 if (listView1.View != View.Details) { e.DrawText(); } } // コンテンツベースの書式を適用し、サブアイテムのテキストを描画 private void listView1_DrawSubItem(Object sender, DrawListViewSubItemEventArgs e) { TextFormatFlags flags = TextFormatFlags.Left; using (StringFormat sf = new StringFormat()) { // 列のテキスト配置を保持 // Centerまたは、Rightに設定されていない場合、 // デフォルトでLeftに設定 switch (e.Header.TextAlign) { case HorizontalAlignment.Center: sf.Alignment = StringAlignment.Center; flags = TextFormatFlags.HorizontalCenter; break; case HorizontalAlignment.Right: sf.Alignment = StringAlignment.Far; flags = TextFormatFlags.Right; break; } // 負の値を持つサブアイテムの場合、背景とテキストを描画 double subItemValue; if (e.ColumnIndex > 0 && Double.TryParse(e.SubItem.Text, NumberStyles.Currency, NumberFormatInfo.CurrentInfo, out subItemValue) && subItemValue < 0) {
// ▲▲▲▲ 不具合のため、修正 ▲▲▲▲ // .NET 6.0では、 // e.Stateが常に選択状態になってしまうため、e.Item.Selectedで判断 // もしかしたら、.NET Frameworkの場合では正常に動作するのかもしれない
// アイテムが選択されていない場合、標準の背景を描画 // グラデーションより目立たせるため // if ((e.ItemState & ListViewItemStates.Selected) == 0) { if (e.Item.Selected == false) {
// ▼▼▼▼ 不具合のため、修正 ▼▼▼▼
e.DrawBackground();
// ▲▲▲▲ コメント ▲▲▲▲ // 上記を実行することで、標準の背景を描画 (黒色) // ▼▼▼▼ コメント ▼▼▼▼
} // 赤で強調して、サブアイテムのテキストを描画 e.Graphics.DrawString(e.SubItem.Text, listView1.Font, Brushes.Red, e.Bounds, sf); return; } // 負でない値または、数値でない値を持つサブアイテムの場合、 // テキストを通常描画 e.DrawText(flags); } } // 列ヘッダーを描画 private void listView1_DrawColumnHeader(
Object sender, DrawListViewColumnHeaderEventArgs e) { using (StringFormat sf = new StringFormat()) { // 列のテキスト配置を保持 // Centerまたは、Rightに設定されていない場合、 // デフォルトでLeftに設定 switch (e.Header.TextAlign) { case HorizontalAlignment.Center: sf.Alignment = StringAlignment.Center; break; case HorizontalAlignment.Right: sf.Alignment = StringAlignment.Far; break; } // ヘッダーの背景を、通常描画 e.DrawBackground(); // ヘッダーのテキストを描画 using (Font headerFont = new Font("Helvetica", 10, FontStyle.Bold)) { e.Graphics.DrawString(e.Header.Text, headerFont, Brushes.Black, e.Bounds, sf); } } return; } // マウスが初めて移動したとき、各行がそれ事態を再描画するように強制 // ラップされたWin32コントロールによって送信される // 余分なDrawItemイベントを補正 // この問題は、ListViewが無効化されるたびに発生 private void listView1_MouseMove(Object sender, MouseEventArgs e) { ListViewItem item = listView1.GetItemAt(e.X, e.Y); if (item != null && item.Tag == null) { listView1.Invalidate(item.Bounds); item.Tag = "tagged";
// ▲▲▲▲ 参考 ▲▲▲▲ // Tagを設定することにより、Invalidate呼び出しによる再描画が、
// 何度も発生しないようにしている // ▼▼▼▼ 参考 ▼▼▼▼
} } // アイテムのTagをリセット private void listView1_Validated(Object sender, EventArgs e) { foreach (ListViewItem item in listView1.Items) { if (item == null) return; item.Tag = null;
// ▲▲▲▲ 参考 ▲▲▲▲ // 再描画が終わったら、Tagをリセットしている // ▼▼▼▼ 参考 ▼▼▼▼
} } // 列の幅が変更された場合、 // コントロール全体を強制的に再描画 private void listView1_ColumnWidthChanged(
Object sender, ColumnWidthChangedEventArgs e) { listView1.Invalidate(); } } }
コメント