首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 自訂 ListBox:OwnerDraw 實作圖示、多色文字與完整集合封裝

     編輯:關於C#

前言

標準 ListBox 僅支援單一字型顏色、無圖示。透過 DrawMode.OwnerDrawFixed 並繼承 ListBox,可實作每項獨立圖示、前景色、字型,並封裝型別安全的集合類別。本文完整示範 ListBoxEx、ListBoxExItem、ListBoxExItemCollection 實作細節。

核心項目類別:ListBoxExItem


using System;
using System.Drawing;
using System.Windows.Forms;

public class ListBoxExItem
{
    public string Text { get; set; }
    public int ImageIndex { get; set; } = -1;
    public Color ForeColor { get; set; } = SystemColors.WindowText;
    public Font Font { get; set; } = null; // null 使用 ListBox 預設字型
    public object Tag { get; set; } // 攜帶任意資料

    public ListBoxExItem(string text) : this(text, -1, SystemColors.WindowText) { }
    public ListBoxExItem(string text, int imageIndex) : this(text, imageIndex, SystemColors.WindowText) { }
    public ListBoxExItem(string text, int imageIndex, Color foreColor)
    {
        Text = text;
        ImageIndex = imageIndex;
        ForeColor = foreColor;
    }

    public override string ToString() => Text; // 設計工具顯示用
}

型別安全集合:ListBoxExItemCollection


public class ListBoxExItemCollection : IList, ICollection, IEnumerable
{
    private readonly ListBox.ObjectCollection _innerList;
    private readonly ListBoxEx _owner;

    public ListBoxExItemCollection(ListBoxEx owner)
    {
        _owner = owner;
        _innerList = owner.Items; // 繼承自 ListBox 的 ObjectCollection
    }

    // IList 實作:轉型為 ListBoxExItem
    public ListBoxExItem this[int index]
    {
        get => _innerList[index] as ListBoxExItem;
        set => _innerList[index] = value;
    }

    object IList.this[int index] { get => this[index]; set => this[index] = (ListBoxExItem)value; }

    public int Add(ListBoxExItem item) => _innerList.Add(item);
    public void Insert(int index, ListBoxExItem item) => _innerList.Insert(index, item);
    public void Remove(ListBoxExItem item) => _innerList.Remove(item);
    public void RemoveAt(int index) => _innerList.RemoveAt(index);
    public void Clear() => _innerList.Clear();
    public bool Contains(ListBoxExItem item) => _innerList.Contains(item);
    public int IndexOf(ListBoxExItem item) => _innerList.IndexOf(item);
    public void CopyTo(Array array, int index) => _innerList.CopyTo(array, index);
    public IEnumerator GetEnumerator() => _innerList.GetEnumerator();

    // ICollection
    public int Count => _innerList.Count;
    public bool IsSynchronized => _innerList.IsSynchronized;
    public object SyncRoot => _innerList.SyncRoot;
    public bool IsFixedSize => _innerList.IsFixedSize;
    public bool IsReadOnly => _innerList.IsReadOnly;

    // 給 ObjectCollection 用的轉型
    int IList.Add(object value) => Add((ListBoxExItem)value);
    bool IList.Contains(object value) => Contains((ListBoxExItem)value);
    int IList.IndexOf(object value) => IndexOf((ListBoxExItem)value);
    void IList.Insert(int index, object value) => Insert(index, (ListBoxExItem)value);
    void IList.Remove(object value) => Remove((ListBoxExItem)value);
}

選取項集合:SelectedListBoxExItemCollection


public class SelectedListBoxExItemCollection : IList, ICollection, IEnumerable
{
    private readonly ListBox.SelectedObjectCollection _innerList;

    public SelectedListBoxExItemCollection(ListBox.SelectedObjectCollection innerList)
    {
        _innerList = innerList;
    }

    public ListBoxExItem this[int index] => _innerList[index] as ListBoxExItem;
    object IList.this[int index] { get => this[index]; set => throw new NotSupportedException(); }

    public int Count => _innerList.Count;
    public bool IsSynchronized => _innerList.IsSynchronized;
    public object SyncRoot => _innerList.SyncRoot;
    public bool IsFixedSize => true;
    public bool IsReadOnly => true;

    public bool Contains(ListBoxExItem item) => _innerList.Contains(item);
    public int IndexOf(ListBoxExItem item) => _innerList.IndexOf(item);
    public void CopyTo(Array array, int index) => _innerList.CopyTo(array, index);
    public IEnumerator GetEnumerator() => _innerList.Cast().GetEnumerator();

    // 唯讀集合,不支援修改
    int IList.Add(object value) => throw new NotSupportedException();
    void IList.Clear() => throw new NotSupportedException();
    void IList.Insert(int index, object value) => throw new NotSupportedException();
    void IList.Remove(object value) => throw new NotSupportedException();
    void IList.RemoveAt(int index) => throw new NotSupportedException();
}

主控制項:ListBoxEx


public class ListBoxEx : ListBox
{
    private ImageList _imageList;
    private ListBoxExItemCollection _itemsCollection;

    public ListBoxEx()
    {
        // 關鍵:啟用 OwnerDraw
        DrawMode = DrawMode.OwnerDrawFixed;
        ItemHeight = 24; // 固定項目高度

        _itemsCollection = new ListBoxExItemCollection(this);
    }

    // 覆寫 Items 屬性回傳型別安全集合
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
    public new ListBoxExItemCollection Items => _itemsCollection;

    // 型別安全的 SelectedItem
    public new ListBoxExItem SelectedItem => base.SelectedItem as ListBoxExItem;

    // 型別安全的 SelectedItems
    public new SelectedListBoxExItemCollection SelectedItems => 
        new SelectedListBoxExItemCollection(base.SelectedItems);

    // 圖示清單屬性
    public ImageList ImageList
    {
        get => _imageList;
        set
        {
            _imageList = value;
            Invalidate(); // 圖示變更時重繪
        }
    }

    // 核心繪製邏輯
    protected override void OnDrawItem(DrawItemEventArgs e)
    {
        e.DrawBackground(); // 繪製背景 (含選取反白)

        if (e.Index < 0 || e.Index >= Items.Count)
        {
            base.OnDrawItem(e);
            return;
        }

        var item = Items[e.Index];
        Rectangle bounds = e.Bounds;
        int x = bounds.Left;
        int y = bounds.Top;

        // 1. 繪製圖示
        if (_imageList != null && item.ImageIndex >= 0 && item.ImageIndex < _imageList.Images.Count)
        {
            int imgSize = Math.Min(_imageList.ImageSize.Width, bounds.Height);
            _imageList.Draw(e.Graphics, x, y, imgSize, imgSize, item.ImageIndex);
            x += imgSize + 2; // 圖示與文字間距
        }

        // 2. 繪製文字
        using var brush = new SolidBrush(item.ForeColor);
        using var font = item.Font ?? e.Font;
        var textBounds = new Rectangle(x, y, bounds.Right - x, bounds.Height);
        TextRenderer.DrawText(e.Graphics, item.Text, font, textBounds, item.ForeColor, 
            TextFormatFlags.VerticalCenter | TextFormatFlags.Left | TextFormatFlags.SingleLine | TextFormatFlags.EndEllipsis);

        e.DrawFocusRectangle(); // 繪製焦點框
        base.OnDrawItem(e);
    }

    // 支援鍵盤搜尋 (依 Text 屬性)
    protected override void OnKeyDown(KeyEventArgs e)
    {
        base.OnKeyDown(e);
        if (e.KeyCode >= Keys.A && e.KeyCode <= Keys.Z)
        {
            char c = char.ToLower((char)e.KeyCode);
            FindItemByText(c.ToString());
        }
    }

    private void FindItemByText(string prefix)
    {
        for (int i = 0; i < Items.Count; i++)
        {
            if (Items[i].Text.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
            {
                SelectedIndex = i;
                break;
            }
        }
    }
}

設計工具支援

加入 DesignerSerializationVisibility(DesignerSerializationVisibility.Content) 讓 Visual Studio 設計工具能序列化 Items 集合:


// 在表單設計器生成的程式碼中會出現:
this.listBoxEx1.Items.Add(new ListBoxExItem("項目一", 0, Color.Red));
this.listBoxEx1.Items.Add(new ListBoxExItem("項目二", 1, Color.Blue));

完整使用範例


public class DemoForm : Form
{
    private ListBoxEx _listBox;
    private ImageList _imgList;

    public DemoForm()
    {
        _imgList = new ImageList { ImageSize = new Size(16, 16), ColorDepth = ColorDepth.Depth32Bit };
        _imgList.Images.Add(Properties.Resources.IconFile);    // 索引 0
        _imgList.Images.Add(Properties.Resources.IconFolder);  // 索引 1
        _imgList.Images.Add(Properties.Resources.IconError);   // 索引 2

        _listBox = new ListBoxEx
        {
            Dock = DockStyle.Fill,
            ImageList = _imgList,
            Font = new Font("微軟正黑體", 10)
        };

        // 加入項目
        _listBox.Items.Add(new ListBoxExItem("重要文件.docx", 0, Color.DarkRed));
        _listBox.Items.Add(new ListBoxExItem("專案資料夾", 1, Color.Navy));
        _listBox.Items.Add(new ListBoxExItem("錯誤記錄.log", 2, Color.DarkOrange));
        _listBox.Items.Add(new ListBoxExItem("一般文字.txt", -1, Color.Black)); // 無圖示

        _listBox.SelectedIndexChanged += (s, e) =>
        {
            if (_listBox.SelectedItem != null)
                Text = $"已選取: {_listBox.SelectedItem.Text}";
        };

        this.Controls.Add(_listBox);
        this.Text = "ListBoxEx 示範";
        this.ClientSize = new Size(400, 300);
    }
}

進階擴充建議

  • 可變項目高度:DrawMode.OwnerDrawVariable + 覆寫 OnMeasureItem
  • 複選框:在 OnDrawItem 繪製 CheckBoxRenderer.DrawCheckBox,處理 OnMouseDown 切換狀態
  • 工具提示:ToolTip 控制項 + OnMouseMove 顯示項目 Tag 資訊
  • 虛擬模式:大量資料時實作 IVirtualList 模式,按需建立項目
  • 拖放排序:AllowDrop=true + DragDrop 事件實作項目重新排序

總結

ListBoxEx 透過 OwnerDrawFixed 實現:

  1. 型別安全:ListBoxExItem 封裝 Text/ImageIndex/ForeColor/Font/Tag
  2. 集合封裝:ListBoxExItemCollection 包裝 ObjectCollection,提供強型別存取
  3. 自訂繪製:OnDrawItem 繪製圖示 + 多色文字 + 焦點框
  4. 設計工具整合:支援屬性視窗編輯 Items 集合

此模式可直接套用至 ComboBox、ListView、TreeView 等支援 OwnerDraw 的控制項。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved