ListBox更改特定项目预彩

发布于 2025-01-25 14:06:56 字数 10890 浏览 5 评论 0 原文

我正在努力为ListBox创建自定义功能。 我需要我的列表框来更改特定(“标记”)项目的预彩。 不要与选定的项目混淆。

所需的功能: 假设ListBox集合包含几个文件名。
当我双击一个项目时;该项目索引和对象存储在两个变量(索引和对象)中。
然后,这些变量将用于设置项目预彩(当未选择列表框项目时)。
剩余的项目和项目矩形应使用默认属性绘制 (在这种情况下,它们具有自己的颜色属性,可以进一步自定义)。

我的问题:

  • 在负载
  • 字符串时不绘制“怪异字符”
  • 选择项目时, ;我正在绘制“标记”项目。

我真的很困惑。 MSDN文档并不清楚如何实现这一目标;也不是如何&当绘画事件发生时。

我一直在弄乱几种选择。但是,删除了所有内容,并回到了当前的代码,试图了解逻辑和行为。

第一次尝试代码(原始问题代码):

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Custom_Controls.Controls
{
    internal class MyPlaylist : ListBox
    {
        public MyPlaylist()
        {
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

            // Enable ListBox Customized Design
            DrawMode = DrawMode.OwnerDrawVariable;
            //SelectionMode = SelectionMode.MultiExtended;
        }

        #region <Custom Properties>
        private int markedIndex = -1;
        public int MarkedIndex
        {
            get { return markedIndex; }
            set { markedIndex = value; Invalidate(); }
        }

        private object markedItem = string.Empty;
        public object MarkedItem
        {
            get { return markedItem; }
            set
            {
                markedItem = value;
                Invalidate();
            }
        }

        private Color markedItemForeColor = Color.Red;
        public Color MarkedItemForeColor
        {
            get { return markedItemForeColor; }
            set { markedItemForeColor = value; Invalidate(); }
        }


        private Color markedItemBackColor = Color.DimGray;
        public Color MarkedItemBackColor
        {
            get { return markedItemBackColor; }
            set { markedItemBackColor = value; Invalidate(); }
        }


        private Color selectionBackColor = Color.DeepSkyBlue;
        public Color SelectionBackColor
        {
            get { return selectionBackColor; }
            set { selectionBackColor = value; Invalidate(); }
        }


        private Color selectionForeColor = Color.White;
        public Color SelectionForeColor
        {
            get { return selectionForeColor; }
            set { selectionForeColor = value; Invalidate(); }
        }
        #endregion


        
    protected override void OnDrawItem(DrawItemEventArgs e) // When Selected?
    {
        e.DrawBackground();
        e.DrawFocusRectangle();

        //// Improve Graphic Quality and Pixel Precision
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
        e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

        
        using (var defaultForeBrush = new SolidBrush(Color.White))
        using (var markForeBrush = new SolidBrush(markedItemForeColor))
        {
            // Iterate over all the items
            for (int i = 0; i < Items.Count; i++)
            {
                var item = Items[i];

                // Draw "Marked" Item
                if (i == markedIndex)
                { 
                    e.Graphics.DrawString(Items[markedIndex].ToString(), e.Font, markForeBrush, e.Bounds, StringFormat.GenericDefault); 
                }

                // Draw Remaining Items
                else
                {
                    e.Graphics.DrawString(item.ToString(), e.Font, markForeBrush, e.Bounds, StringFormat.GenericDefault);
                }

                // Draw Selection Rectangle
                // ...
            }
        }
    }        



        #region <Overriden Events>
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
        }

        protected override void OnDoubleClick(EventArgs e)
        {
            base.OnDoubleClick(e);

            SetMarkedItem();
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            base.OnMouseDoubleClick(e);

            SetMarkedItem();
        }


        #region <Methods>
        private void SetMarkedItem()
        {
            markedIndex = SelectedIndex;
            markedItem = SelectedItem;
        } 
        #endregion
    }
}

我的第二次尝试使用Jimi的帮助(当前代码)

更改:

  • 我已经评论了Pinvoke LB_枚举wndproc,因为我无法使它起作用。
  • 为了保持简单性:我删除了三元运营商(但是我喜欢Jimi的代码与它们交替使用颜色的方式)。
  • SetMarker()已恢复为以前的版本。标记的项目从未以这种方式绘制。
  • 自定义属性没有为刷子提供其价值;因此,它们被暂时删除。

当前问题: 确切需要重新完成WNDProc以清除图纸(标记的项目);也许要重新刷新控件,以便尽快更新标记。

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Custom_Controls.Controls
{
    internal class MyPlaylist : ListBox
    {
        #region <Constructor>
        public MyPlaylist()
        {
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

            DrawMode = DrawMode.OwnerDrawVariable;

            BackColor = Color.FromArgb(255, 25, 25, 25);
            ForeColor = Color.White;
            BorderStyle = BorderStyle.FixedSingle;
        }
        #endregion


        #region <Fields>
        //private const int LB_RESETCONTENT = 0x0184;
        //private const int LB_DELETESTRING = 0x0182;

        //TextFormatFlags flags = TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.LeftAndRightPadding | TextFormatFlags.VerticalCenter;
        #endregion


        #region <Custom Properties>
        // Tip: Always Verify that the new Values are Different from the Old Ones

        private int markedIndex = -1;
        public int MarkedIndex
        {
            get { return markedIndex; }
            set
            {
                if (value != markedIndex)
                {
                    markedIndex = value;
                    Invalidate();
                }
            }
        }

        // Read-only: just return the marked Item, set it using the Index only
        public object MarkedItem
        {
            get { return Items[markedIndex]; }
        }
        #endregion


        #region <Overriden Events>
        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            if (Items.Count == 0) return;

            // Draw Selection:
            if (e.State.HasFlag(DrawItemState.Focus) || e.State.HasFlag(DrawItemState.Selected))
            {
                using (var brush = new SolidBrush(Color.FromArgb(255, 52, 52, 52)))
                {
                    // Background Rectangle
                    e.Graphics.FillRectangle(brush, e.Bounds);

                    // Item Text : Marked Item
                    if (e.Index == markedIndex)
                    {
                        TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, Color.Red, flags);
                    }
                    
                    // Other Items (Except Marked)
                    else
                    {
                        TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, Color.White, flags);
                    }
                }
            }

            // Draw Unselected:
            else
            {
                using (var brush = new SolidBrush(BackColor))
                using (var markedBrush = new SolidBrush(Color.Khaki))
                {
                    e.Graphics.FillRectangle(brush, e.Bounds);
                    TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, Color.White, flags);
                }

                // Draw (Unselected) Marked Item
                if (markedIndex > -1 && e.Index == markedIndex)
                {
                    TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, Color.Red, flags);
                }
            }

            e.DrawFocusRectangle();

            base.OnDrawItem(e);
        }

        // Set the Height of the Item (Width: only if needed).
        // This is the Standard Value (Modify as required)
        protected override void OnMeasureItem(MeasureItemEventArgs e)
        {
            if (Items.Count > 0)
            {
                e.ItemHeight = Font.Height + 4; // 4 = Text vs Item Rectangle Margin
            }

            base.OnMeasureItem(e);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
        }

        protected override void OnDoubleClick(EventArgs e)
        {
            base.OnDoubleClick(e);

            SetMarkedItem();
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            base.OnMouseDoubleClick(e);

            if (e.Button == MouseButtons.Left)
            {
                SetMarkedItem();
            }
        }
        #endregion

        #region <Methods>
        /// <summary>
        /// WndProc is Overridden in order to Intercept the LB_RESETCONTENT (sent when the ObjectCollection is cleared);<br/> 
        /// and the LB_DELETESTRING (sent when an Item is removed).
        /// This is done to reset the marked Item when the list is cleared or the marked Item is removed (otherwise an Item will remain marked when it shouldn't).
        /// </summary>
        /// <param name="m"></param>
        //protected override void WndProc(ref Message m)
        //{
        //    switch (m.Msg)
        //    {
        //        // List Cleared
        //        case LB_RESETCONTENT:
        //            markedIndex = -1;
        //            break;

        //        // Item Deleted
        //        case LB_DELETESTRING:
        //            if (markedIndex == m.WParam.ToInt32())
        //            {
        //            markedIndex = -1;
        //            }
        //            break;
        //    }
        //}

        private void SetMarkedItem()    // Current Block
        {
            markedIndex = SelectedIndex;
        }

        // Previous Code Line (By Jimmy; using Ternary Operator) <----------------------------------------
        //private void SetMarkedItem() => MarkedIndex = markedIndex == SelectedIndex ? -1 : SelectedIndex;
        #endregion
    }
}

有用的相关内容

listbox:listbox:pinvoke lb_(enums lb_(enums)

如何将多行文本添加到列表框项目

I'm struggling in creating customized functionality for a listbox.
I need my ListBox to change the Forecolor of a specific ("marked") item.
Not to be confused with Selected Item.

The Functionality Required:
Let's say the ListBox Collection Contains Several File Names.
When I double click an Item; that item index and object are stored into two variables (index and object).
These variables would then be used to set the Item ForeColor (when Listbox Item is Unselected).
Remaining Items and Item Rectangle should be drawn with default properties
(in this case they have their own color properties to allow further customization).

My problems:

  • Not Painting upon Load
  • String is Drawn with "Weird Characters"
  • When Selecting an Item; I'm drawing the "Marked" Item.

I'm really confused. MSDN documentation is not very clear how to achieve this; nor how & when the DrawItem Event Occurs.

I've been messing with several alternatives; however deleted everything and got back to the current code attempting to understand the logic and behaviour.

1st Attempt code (Original Question Code):

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Custom_Controls.Controls
{
    internal class MyPlaylist : ListBox
    {
        public MyPlaylist()
        {
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

            // Enable ListBox Customized Design
            DrawMode = DrawMode.OwnerDrawVariable;
            //SelectionMode = SelectionMode.MultiExtended;
        }

        #region <Custom Properties>
        private int markedIndex = -1;
        public int MarkedIndex
        {
            get { return markedIndex; }
            set { markedIndex = value; Invalidate(); }
        }

        private object markedItem = string.Empty;
        public object MarkedItem
        {
            get { return markedItem; }
            set
            {
                markedItem = value;
                Invalidate();
            }
        }

        private Color markedItemForeColor = Color.Red;
        public Color MarkedItemForeColor
        {
            get { return markedItemForeColor; }
            set { markedItemForeColor = value; Invalidate(); }
        }


        private Color markedItemBackColor = Color.DimGray;
        public Color MarkedItemBackColor
        {
            get { return markedItemBackColor; }
            set { markedItemBackColor = value; Invalidate(); }
        }


        private Color selectionBackColor = Color.DeepSkyBlue;
        public Color SelectionBackColor
        {
            get { return selectionBackColor; }
            set { selectionBackColor = value; Invalidate(); }
        }


        private Color selectionForeColor = Color.White;
        public Color SelectionForeColor
        {
            get { return selectionForeColor; }
            set { selectionForeColor = value; Invalidate(); }
        }
        #endregion


        
    protected override void OnDrawItem(DrawItemEventArgs e) // When Selected?
    {
        e.DrawBackground();
        e.DrawFocusRectangle();

        //// Improve Graphic Quality and Pixel Precision
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
        e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;

        
        using (var defaultForeBrush = new SolidBrush(Color.White))
        using (var markForeBrush = new SolidBrush(markedItemForeColor))
        {
            // Iterate over all the items
            for (int i = 0; i < Items.Count; i++)
            {
                var item = Items[i];

                // Draw "Marked" Item
                if (i == markedIndex)
                { 
                    e.Graphics.DrawString(Items[markedIndex].ToString(), e.Font, markForeBrush, e.Bounds, StringFormat.GenericDefault); 
                }

                // Draw Remaining Items
                else
                {
                    e.Graphics.DrawString(item.ToString(), e.Font, markForeBrush, e.Bounds, StringFormat.GenericDefault);
                }

                // Draw Selection Rectangle
                // ...
            }
        }
    }        



        #region <Overriden Events>
        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
        }

        protected override void OnDoubleClick(EventArgs e)
        {
            base.OnDoubleClick(e);

            SetMarkedItem();
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            base.OnMouseDoubleClick(e);

            SetMarkedItem();
        }


        #region <Methods>
        private void SetMarkedItem()
        {
            markedIndex = SelectedIndex;
            markedItem = SelectedItem;
        } 
        #endregion
    }
}

My 2nd Attempt using Jimi's Help (Current Code)

Changes:

  • I've Commented the Pinvoke LB_ Enums and WndProc as I was not able to make it work.
  • In order to keep simplicity: I removed the ternary operators (however I loved the way Jimi's code would alternate the colors with them).
  • SetMarker() was reverted to previous version. The Marked Item was never Drawn that way.
  • Custom Properties were not providing their value to the Brushes; therefore they were temporarily removed.

Current Issues:
WndProc definitly needs to be reimplemented to clear the Drawing (Marked Item); and perhaps to Redraw the Control so it Updates the Marker ASAP.

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Custom_Controls.Controls
{
    internal class MyPlaylist : ListBox
    {
        #region <Constructor>
        public MyPlaylist()
        {
            SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

            DrawMode = DrawMode.OwnerDrawVariable;

            BackColor = Color.FromArgb(255, 25, 25, 25);
            ForeColor = Color.White;
            BorderStyle = BorderStyle.FixedSingle;
        }
        #endregion


        #region <Fields>
        //private const int LB_RESETCONTENT = 0x0184;
        //private const int LB_DELETESTRING = 0x0182;

        //TextFormatFlags flags = TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.LeftAndRightPadding | TextFormatFlags.VerticalCenter;
        #endregion


        #region <Custom Properties>
        // Tip: Always Verify that the new Values are Different from the Old Ones

        private int markedIndex = -1;
        public int MarkedIndex
        {
            get { return markedIndex; }
            set
            {
                if (value != markedIndex)
                {
                    markedIndex = value;
                    Invalidate();
                }
            }
        }

        // Read-only: just return the marked Item, set it using the Index only
        public object MarkedItem
        {
            get { return Items[markedIndex]; }
        }
        #endregion


        #region <Overriden Events>
        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            if (Items.Count == 0) return;

            // Draw Selection:
            if (e.State.HasFlag(DrawItemState.Focus) || e.State.HasFlag(DrawItemState.Selected))
            {
                using (var brush = new SolidBrush(Color.FromArgb(255, 52, 52, 52)))
                {
                    // Background Rectangle
                    e.Graphics.FillRectangle(brush, e.Bounds);

                    // Item Text : Marked Item
                    if (e.Index == markedIndex)
                    {
                        TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, Color.Red, flags);
                    }
                    
                    // Other Items (Except Marked)
                    else
                    {
                        TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, Color.White, flags);
                    }
                }
            }

            // Draw Unselected:
            else
            {
                using (var brush = new SolidBrush(BackColor))
                using (var markedBrush = new SolidBrush(Color.Khaki))
                {
                    e.Graphics.FillRectangle(brush, e.Bounds);
                    TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, Color.White, flags);
                }

                // Draw (Unselected) Marked Item
                if (markedIndex > -1 && e.Index == markedIndex)
                {
                    TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, Color.Red, flags);
                }
            }

            e.DrawFocusRectangle();

            base.OnDrawItem(e);
        }

        // Set the Height of the Item (Width: only if needed).
        // This is the Standard Value (Modify as required)
        protected override void OnMeasureItem(MeasureItemEventArgs e)
        {
            if (Items.Count > 0)
            {
                e.ItemHeight = Font.Height + 4; // 4 = Text vs Item Rectangle Margin
            }

            base.OnMeasureItem(e);
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
        }

        protected override void OnDoubleClick(EventArgs e)
        {
            base.OnDoubleClick(e);

            SetMarkedItem();
        }

        protected override void OnMouseDoubleClick(MouseEventArgs e)
        {
            base.OnMouseDoubleClick(e);

            if (e.Button == MouseButtons.Left)
            {
                SetMarkedItem();
            }
        }
        #endregion

        #region <Methods>
        /// <summary>
        /// WndProc is Overridden in order to Intercept the LB_RESETCONTENT (sent when the ObjectCollection is cleared);<br/> 
        /// and the LB_DELETESTRING (sent when an Item is removed).
        /// This is done to reset the marked Item when the list is cleared or the marked Item is removed (otherwise an Item will remain marked when it shouldn't).
        /// </summary>
        /// <param name="m"></param>
        //protected override void WndProc(ref Message m)
        //{
        //    switch (m.Msg)
        //    {
        //        // List Cleared
        //        case LB_RESETCONTENT:
        //            markedIndex = -1;
        //            break;

        //        // Item Deleted
        //        case LB_DELETESTRING:
        //            if (markedIndex == m.WParam.ToInt32())
        //            {
        //            markedIndex = -1;
        //            }
        //            break;
        //    }
        //}

        private void SetMarkedItem()    // Current Block
        {
            markedIndex = SelectedIndex;
        }

        // Previous Code Line (By Jimmy; using Ternary Operator) <----------------------------------------
        //private void SetMarkedItem() => MarkedIndex = markedIndex == SelectedIndex ? -1 : SelectedIndex;
        #endregion
    }
}

Helpful Related Content

ListBox : Pinvoke LB_(Enums)

How to add multiline Text to a ListBox item

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

飞烟轻若梦 2025-02-01 14:06:56

该示例类包含使列表作为标准列表框起作用所需的调整,但使用问题中描述的增强功能
另请参见代码中的评论。

  • graphics.drawString():这将为渲染列表项目提供更多天然。无需使用抗异化。
  • onMeasureItem 也被压倒了为项目提供自定义高度(可选的宽度 - 严格要求)。它设置为 ListBox.font.height + 4 (pretty Standard);根据需要进行修改。
  • OnDrawItem()可以纠正以处理自定义选择颜色和标记项目的颜色。请注意,此方法是每项一次调用一次的,因此您不必每次循环整个集合,只需用正确的颜色绘制当前项目即可。
  • setMarkedItem()被修改以切换标记项目的状态,以防双击它两次。
  • wndproc 被覆盖为 Intercept lb_resetContent (当清除 Objectcollection 时发送)和 lb_deletestring (删除项目时发送)。这样做是为了在清除列表或删除标记的项目时重置标记的项目(否则,在不应该的情况下将保持标记)。
  • 设置属性值时,请始终验证新值不等于旧值,然后再调用 invalidate()(或任何其他方法 - 或属性设置程序)无缘无故。
  • 一些小更改,请参阅代码。

public class MyPlaylist : ListBox {

    private const int LB_DELETESTRING = 0x0182;
    private const int LB_RESETCONTENT = 0x0184;

    TextFormatFlags flags = TextFormatFlags.PreserveGraphicsClipping |
                            TextFormatFlags.LeftAndRightPadding |
                            TextFormatFlags.VerticalCenter;

    public MyPlaylist()
    {
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        DrawMode = DrawMode.OwnerDrawVariable;
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg) {
            // List cleared
            case LB_RESETCONTENT:
                markedIndex = -1;
                break;
            // Item deleted
            case LB_DELETESTRING:
                if (markedIndex == m.WParam.ToInt32()) {
                    markedIndex = -1;
                }
                break;
        }
        base.WndProc(ref m);
    }

    private int markedIndex = -1;
    public int MarkedIndex {
        get => markedIndex;
        set {
            if (value != markedIndex) {
                markedIndex = value;
                Invalidate();
            }
        }
    }

    // Read-only: just return the marked Item, set it using the Index only
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public object MarkedItem {
        get => Items[markedIndex];
    }

    // Always verify that the new value is different from the old one
    private Color markedItemForeColor = Color.Orange;
    public Color MarkedItemForeColor {
        get => markedItemForeColor;
        set { 
            if (value != markedItemForeColor) {
                markedItemForeColor = value;
                Invalidate();
            }
        }
    }

    private Color markedItemBackColor = Color.DimGray;
    public Color MarkedItemBackColor {
        get => markedItemBackColor;
        set { 
            if (value != markedItemBackColor) {
                markedItemBackColor = value;
                Invalidate();
            }
        }
    }

    private Color selectionBackColor = Color.DeepSkyBlue;
    public Color SelectionBackColor {
        get => selectionBackColor;
        set { 
            if (value != selectionBackColor) {
                selectionBackColor = value;
                Invalidate();
            }
        }
    }

    private Color selectionForeColor = Color.White;
    public Color SelectionForeColor {
        get => selectionForeColor;
        set { 
            if (value != selectionForeColor) {
                selectionForeColor = value;
                Invalidate();
            }
        }
    }

    // Use TextRenderer to draw the Items - no anti-aliasing needed
    protected override void OnDrawItem(DrawItemEventArgs e)
    {
        if (Items.Count == 0) return;
        if (e.State.HasFlag(DrawItemState.Focus) || e.State.HasFlag(DrawItemState.Selected)) {
            using (var brush = new SolidBrush(selectionBackColor)) {
                e.Graphics.FillRectangle(brush, e.Bounds);
            }
            TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, selectionForeColor, flags);
        }
        else {
            var color = markedIndex != -1 && markedIndex == e.Index ? markedItemBackColor : BackColor;
            using (var brush = new SolidBrush(color)) {
                e.Graphics.FillRectangle(brush, e.Bounds);
            }
            var foreColor = markedIndex == e.Index ? markedItemForeColor : ForeColor;
            TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, foreColor, flags);
        }
        e.DrawFocusRectangle();
        base.OnDrawItem(e);
    }

    // Set the Height (the Width only if needed) of the Item
    // This is the standard value, modify as required
    protected override void OnMeasureItem(MeasureItemEventArgs e)
    {
        if (Items.Count > 0) {
            e.ItemHeight = Font.Height + 4;
        }
        base.OnMeasureItem(e);
    }

    protected override void OnMouseDoubleClick(MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left) {
            SetMarkedItem();
        }
        base.OnMouseDoubleClick(e);
    }

    private void SetMarkedItem() => MarkedIndex = markedIndex == SelectedIndex ? -1 : SelectedIndex;
}

This sample class contains the adjustments needed to make the List work as the standard ListBox, but with the enhancements described in the question.
See also the comments in code.

  • TextRenderer.DrawText() replaces Graphics.DrawString(): this will give a more natural aspect to the rendered list items. No need to use anti-aliasing.
  • OnMeasureItem is also overridden, to provide a custom Height (optionally the Width - when strictly required) for the Items. It's set to ListBox.Font.Height + 4 (pretty standard); modify as needed.
  • OnDrawItem() is corrected to handle both the custom Selection colors and the marked Item's colors. Note that this method is called once per Item, so you don't have to loop the entire collection each time, just paint the current Item with correct colors.
  • SetMarkedItem() is modified to toggle the state of a marked Item, in case you double-click it twice.
  • WndProc is overridden to intercept LB_RESETCONTENT (sent when the ObjectCollection is cleared) and LB_DELETESTRING (sent when an Item is removed). This is done to reset the marked Item when the list is cleared or the marked Item is removed (otherwise an Item will remain marked when it shouldn't).
  • When setting a Property value, always verify that the new value is not equal to the old one, before you call Invalidate() (or any other method - or a Property setter) for no reason.
  • A few minor changes, see the code.

public class MyPlaylist : ListBox {

    private const int LB_DELETESTRING = 0x0182;
    private const int LB_RESETCONTENT = 0x0184;

    TextFormatFlags flags = TextFormatFlags.PreserveGraphicsClipping |
                            TextFormatFlags.LeftAndRightPadding |
                            TextFormatFlags.VerticalCenter;

    public MyPlaylist()
    {
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        DrawMode = DrawMode.OwnerDrawVariable;
    }

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg) {
            // List cleared
            case LB_RESETCONTENT:
                markedIndex = -1;
                break;
            // Item deleted
            case LB_DELETESTRING:
                if (markedIndex == m.WParam.ToInt32()) {
                    markedIndex = -1;
                }
                break;
        }
        base.WndProc(ref m);
    }

    private int markedIndex = -1;
    public int MarkedIndex {
        get => markedIndex;
        set {
            if (value != markedIndex) {
                markedIndex = value;
                Invalidate();
            }
        }
    }

    // Read-only: just return the marked Item, set it using the Index only
    [Browsable(false)]
    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
    public object MarkedItem {
        get => Items[markedIndex];
    }

    // Always verify that the new value is different from the old one
    private Color markedItemForeColor = Color.Orange;
    public Color MarkedItemForeColor {
        get => markedItemForeColor;
        set { 
            if (value != markedItemForeColor) {
                markedItemForeColor = value;
                Invalidate();
            }
        }
    }

    private Color markedItemBackColor = Color.DimGray;
    public Color MarkedItemBackColor {
        get => markedItemBackColor;
        set { 
            if (value != markedItemBackColor) {
                markedItemBackColor = value;
                Invalidate();
            }
        }
    }

    private Color selectionBackColor = Color.DeepSkyBlue;
    public Color SelectionBackColor {
        get => selectionBackColor;
        set { 
            if (value != selectionBackColor) {
                selectionBackColor = value;
                Invalidate();
            }
        }
    }

    private Color selectionForeColor = Color.White;
    public Color SelectionForeColor {
        get => selectionForeColor;
        set { 
            if (value != selectionForeColor) {
                selectionForeColor = value;
                Invalidate();
            }
        }
    }

    // Use TextRenderer to draw the Items - no anti-aliasing needed
    protected override void OnDrawItem(DrawItemEventArgs e)
    {
        if (Items.Count == 0) return;
        if (e.State.HasFlag(DrawItemState.Focus) || e.State.HasFlag(DrawItemState.Selected)) {
            using (var brush = new SolidBrush(selectionBackColor)) {
                e.Graphics.FillRectangle(brush, e.Bounds);
            }
            TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, selectionForeColor, flags);
        }
        else {
            var color = markedIndex != -1 && markedIndex == e.Index ? markedItemBackColor : BackColor;
            using (var brush = new SolidBrush(color)) {
                e.Graphics.FillRectangle(brush, e.Bounds);
            }
            var foreColor = markedIndex == e.Index ? markedItemForeColor : ForeColor;
            TextRenderer.DrawText(e.Graphics, GetItemText(Items[e.Index]), Font, e.Bounds, foreColor, flags);
        }
        e.DrawFocusRectangle();
        base.OnDrawItem(e);
    }

    // Set the Height (the Width only if needed) of the Item
    // This is the standard value, modify as required
    protected override void OnMeasureItem(MeasureItemEventArgs e)
    {
        if (Items.Count > 0) {
            e.ItemHeight = Font.Height + 4;
        }
        base.OnMeasureItem(e);
    }

    protected override void OnMouseDoubleClick(MouseEventArgs e)
    {
        if (e.Button == MouseButtons.Left) {
            SetMarkedItem();
        }
        base.OnMouseDoubleClick(e);
    }

    private void SetMarkedItem() => MarkedIndex = markedIndex == SelectedIndex ? -1 : SelectedIndex;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文