OSによってコントロールを描画してもらうのではなく、
コードを書いて、独自に描画する方法になります。

ListViewで独自に描画しようとしたところ、
Microsoft公式APIドキュメントに記載されているコードが
正常に動作しなかったため、備忘録として記載いたします。
Microsoft 公式APIドキュメント ListView.OwnerDraw

正しい表示
◯負の値の文字が赤色
◯負の値の背景が黒色(DrawSubItemで標準の背景を描画)
◯選択行の背景が栗色 (DrawItemで描画)
◯上記以外の背景はオレンジ色ー栗色のグラデーション (DrawItemで描画)
ListViewOwnerDraw-OK

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

確認した環境は、下記になります。
・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(); } } }