在C#中更改下拉列表边框颜色

6

在c#中,是否可以更改组合框下拉列表的边框颜色?

enter image description here

我想要将白色边框更改为较暗的颜色以匹配深色方案。我在 .net 文档中搜索并找到了 DropDownList.BorderStyle 属性。但是,我不确定这是否有效。我正在使用 WinForms。

这是我用于组合框的类:

public class FlattenCombo : ComboBox
{
    private Brush BorderBrush = new SolidBrush(SystemColors.WindowFrame);
    private Brush ArrowBrush = new SolidBrush(SystemColors.ControlText);
    private Brush DropButtonBrush = new SolidBrush(SystemColors.Control);

    private Color _borderColor = Color.Black;
    private ButtonBorderStyle _borderStyle = ButtonBorderStyle.Solid;
    private static int WM_PAINT = 0x000F; 

    private Color _ButtonColor = SystemColors.Control;

    public Color ButtonColor
    {
        get { return _ButtonColor; }
        set
        {
            _ButtonColor = value;
            DropButtonBrush = new SolidBrush(this.ButtonColor);
            this.Invalidate();
        }
    }

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);

        switch (m.Msg)
        {
            case 0xf:
                Graphics g = this.CreateGraphics();
                Pen p = new Pen(Color.Black);
                g.FillRectangle(BorderBrush, this.ClientRectangle);

                //Draw the background of the dropdown button
                Rectangle rect = new Rectangle(this.Width - 17, 0, 17, this.Height);
                g.FillRectangle(DropButtonBrush, rect);

                //Create the path for the arrow
                System.Drawing.Drawing2D.GraphicsPath pth = new System.Drawing.Drawing2D.GraphicsPath();
                PointF TopLeft = new PointF(this.Width - 13, (this.Height - 5) / 2);
                PointF TopRight = new PointF(this.Width - 6, (this.Height - 5) / 2);
                PointF Bottom = new PointF(this.Width - 9, (this.Height + 2) / 2);
                pth.AddLine(TopLeft, TopRight);
                pth.AddLine(TopRight, Bottom);

                g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;

                //Determine the arrow's color.
                if (this.DroppedDown)
                {
                    ArrowBrush = new SolidBrush(SystemColors.HighlightText);
                }
                else
                {
                    ArrowBrush = new SolidBrush(SystemColors.ControlText);
                }

                //Draw the arrow
                g.FillPath(ArrowBrush, pth);

                break;
            default:
                break;
        }
    }

    [Category("Appearance")]
    public Color BorderColor
    {
        get { return _borderColor; }
        set
        {
            _borderColor = value;
            Invalidate(); // causes control to be redrawn
        }
    }

    [Category("Appearance")]
    public ButtonBorderStyle BorderStyle
    {
        get { return _borderStyle; }
        set
        {
            _borderStyle = value;
            Invalidate();
        }
    }

    protected override void OnLostFocus(System.EventArgs e)
    {
        base.OnLostFocus(e);
        this.Invalidate();
    }

    protected override void OnGotFocus(System.EventArgs e)
    {
        base.OnGotFocus(e);
        this.Invalidate();
    }
    protected override void OnResize(EventArgs e)
    {
        base.OnResize(e);
        this.Invalidate();
    }
}

你完成了这个组合框控件吗?如果是,能给我吗? - Simos Sigma
2个回答

7
尽管FlatComboBoxAdapter不可用,但仍然可以捕获WM_CTLCOLORLISTBOX窗口消息并将本机GDI矩形应用于下拉边框。这需要一些工作,但确实可以完成任务。(如果您想跳过此步骤,下面有一个示例链接)
首先,如果您不熟悉WM_CTLCOLORLISTBOX窗口消息,可以这样描述它:
"在系统绘制列表框之前发送到列表框的父窗口。通过响应此消息,父窗口可以使用指定的显示设备上下文句柄设置列表框的文本和背景颜色。"
该消息常量定义如下:
const int WM_CTLCOLORLISTBOX = 0x0134;

一旦定义了消息常量,就在自定义ComboBox的重写WndProc()事件中设置它的条件:

protected override void WndProc(ref Message m)
{
    // Filter window messages
    switch (m.Msg)
    {
        // Draw a custom color border around the drop down pop-up
        case WM_CTLCOLORLISTBOX:
            base.WndProc(ref m);
            DrawNativeBorder(m.LParam);
            break;

        default: base.WndProc(ref m); break;
    }
}

DrawNativeBorder()方法是我们将使用Win API绘制矩形的地方。它接受下拉框的句柄作为参数。然而,在此之前,我们需要定义将要使用的本机方法、枚举和结构:

public enum PenStyles
{
    PS_SOLID = 0,
    PS_DASH = 1,
    PS_DOT = 2,
    PS_DASHDOT = 3,
    PS_DASHDOTDOT = 4
}

public enum ComboBoxButtonState
{
    STATE_SYSTEM_NONE = 0,
    STATE_SYSTEM_INVISIBLE = 0x00008000,
    STATE_SYSTEM_PRESSED = 0x00000008
}

[StructLayout(LayoutKind.Sequential)]
public struct COMBOBOXINFO
{
    public Int32 cbSize;
    public RECT rcItem;
    public RECT rcButton;
    public ComboBoxButtonState buttonState;
    public IntPtr hwndCombo;
    public IntPtr hwndEdit;
    public IntPtr hwndList;
}

[DllImport("user32.dll")]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

[DllImport("user32.dll")]
public static extern IntPtr GetWindowDC(IntPtr hWnd);

[DllImport("user32.dll")]
public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);

[DllImport("user32.dll")]
public static extern IntPtr SetFocus(IntPtr hWnd);

[DllImport("user32.dll")]
public static extern bool GetComboBoxInfo(IntPtr hWnd, ref COMBOBOXINFO pcbi);

[DllImport("gdi32.dll")]
public static extern int ExcludeClipRect(IntPtr hdc, int nLeftRect, int nTopRect, int nRightRect, int nBottomRect);

[DllImport("gdi32.dll")]
public static extern IntPtr CreatePen(PenStyles enPenStyle, int nWidth, int crColor);

[DllImport("gdi32.dll")]
public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hObject);

[DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

[DllImport("gdi32.dll")]
public static extern void Rectangle(IntPtr hdc, int X1, int Y1, int X2, int Y2);

public static int RGB(int R, int G, int B)
{
    return (R | (G << 8) | (B << 16));
}

[Serializable, StructLayout(LayoutKind.Sequential)]
public struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;

    public RECT(int left_, int top_, int right_, int bottom_)
    {
        Left = left_;
        Top = top_;
        Right = right_;
        Bottom = bottom_;
    }

    public override bool Equals(object obj)
    {
        if (obj == null || !(obj is RECT))
        {
            return false;
        }
        return this.Equals((RECT)obj);
    }

    public bool Equals(RECT value)
    {
        return this.Left == value.Left &&
        this.Top == value.Top &&
        this.Right == value.Right &&
        this.Bottom == value.Bottom;
    }

    public int Height
    {
        get
        {
             return Bottom - Top + 1;
        }
    }

    public int Width
    {
        get
        {
            return Right - Left + 1;
        }
    }

    public Size Size { get { return new Size(Width, Height); } }
    public Point Location { get { return new Point(Left, Top); } }
    // Handy method for converting to a System.Drawing.Rectangle
    public System.Drawing.Rectangle ToRectangle()
    {
        return System.Drawing.Rectangle.FromLTRB(Left, Top, Right, Bottom);
    }

    public static RECT FromRectangle(Rectangle rectangle)
    {
        return new RECT(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom);
    }

    public void Inflate(int width, int height)
    {
        this.Left -= width;
        this.Top -= height;
        this.Right += width;
        this.Bottom += height;
    }

    public override int GetHashCode()
    {
        return Left ^ ((Top << 13) | (Top >> 0x13))
                          ^ ((Width << 0x1a) | (Width >> 6))
                          ^ ((Height << 7) | (Height >> 0x19));
    }

    public static implicit operator Rectangle(RECT rect)
    {
        return System.Drawing.Rectangle.FromLTRB(rect.Left, rect.Top, rect.Right, rect.Bottom);
    }

    public static implicit operator RECT(Rectangle rect)
    {
        return new RECT(rect.Left, rect.Top, rect.Right, rect.Bottom);
    }
}

DrawNativeBorder()方法的定义如下:

/// <summary>
/// Non client area border drawing
/// </summary>
/// <param name="handle">The handle to the control</param>
public static void DrawNativeBorder(IntPtr handle)
{
    // Define the windows frame rectangle of the control
    RECT controlRect;
    GetWindowRect(handle, out controlRect);
    controlRect.Right -= controlRect.Left; controlRect.Bottom -= controlRect.Top;
    controlRect.Top = controlRect.Left = 0;

    // Get the device context of the control
    IntPtr dc = GetWindowDC(handle);

    // Define the client area inside the control rect so it won't be filled when drawing the border
    RECT clientRect = controlRect;
    clientRect.Left += 1;
    clientRect.Top += 1;
    clientRect.Right -= 1;
    clientRect.Bottom -= 1;
    ExcludeClipRect(dc, clientRect.Left, clientRect.Top, clientRect.Right, clientRect.Bottom);

    // Create a pen and select it
    Color borderColor = Color.Magenta;
    IntPtr border = WinAPI.CreatePen(WinAPI.PenStyles.PS_SOLID, 1, RGB(borderColor.R,
        borderColor.G, borderColor.B));

    // Draw the border rectangle
    IntPtr borderPen = SelectObject(dc, border);
    Rectangle(dc, controlRect.Left, controlRect.Top, controlRect.Right, controlRect.Bottom);
    SelectObject(dc, borderPen);
    DeleteObject(border);

    // Release the device context
    ReleaseDC(handle, dc);
    SetFocus(handle);
}

我使用品红色来清晰地显示绘画。这就是边框绘画的全部内容。然而,还有一个问题。当下拉菜单显示且鼠标未移动到下拉项上时,默认边框仍然显示。为了解决这个问题,您需要确定下拉菜单何时完全打开。然后发送自己的WM_CTLCOLORLISTBOX消息以更新边框。
我通过定时器粗略检查下拉菜单显示的时刻。我尝试了各种其他选项,但它们没有成功。老实说,如果有人有更好的解决方案可以工作,那将是很棒的。
您需要一个计时器来检查下拉菜单何时完全下降:
private Timer _dropDownCheck = new Timer();

计时器是自定义组合框中的一个字段。在InitializeComponent()之后,在自定义组合框构造函数中设置它:
_dropDownCheck.Interval = 100;
_dropDownCheck.Tick += new EventHandler(dropDownCheck_Tick);

重写自定义组合框的OnDropDown()事件,并设置计时器Tick事件:

/// <summary>
/// On drop down
/// </summary>
protected override void OnDropDown(EventArgs e)
{
    base.OnDropDown(e);

    // Start checking for the dropdown visibility
    _dropDownCheck.Start();
}

/// <summary>
/// Checks when the drop down is fully visible
/// </summary>
private void dropDownCheck_Tick(object sender, EventArgs e)
{
    // If the drop down has been fully dropped
    if (DroppedDown)
    {
        // Stop the time, send a listbox update
        _dropDownCheck.Stop();
        Message m = GetControlListBoxMessage(this.Handle);
        WndProc(ref m);
    }
}

最后,创建以下方法来获取下拉句柄并创建一个WM_CTLCOLORLISTBOX消息发送到控件:
/// <summary>
/// Creates a default WM_CTLCOLORLISTBOX message
/// </summary>
/// <param name="handle">The drop down handle</param>
/// <returns>A WM_CTLCOLORLISTBOX message</returns>
public Message GetControlListBoxMessage(IntPtr handle)
{
    // Force non-client redraw for focus border
    Message m = new Message();
    m.HWnd = handle;
    m.LParam = GetListHandle(handle);
    m.WParam = IntPtr.Zero;
    m.Msg = WM_CTLCOLORLISTBOX;
    m.Result = IntPtr.Zero;
    return m;
}

/// <summary>
/// Gets the list control of a combo box
/// </summary>
/// <param name="handle">Handle of the combo box itself</param>
/// <returns>A handle to the list</returns>
public static IntPtr GetListHandle(IntPtr handle)
{
    COMBOBOXINFO info;
    info = new COMBOBOXINFO();
    info.cbSize = System.Runtime.InteropServices.Marshal.SizeOf(info);
    return GetComboBoxInfo(handle, ref info) ? info.hwndList : IntPtr.Zero;
}

如果你还是感到困惑,那么最好的方法就是查看我提供的这个示例VS 2010自定义组合框项目中的控件:

http://www.pyxosoft.com/downloads/CustomComboBoxBorderColor.zip


请注意,保留了HTML标签。

4
我为此纠结了很久。根据您之前的提问,我看到您有从以下网址获取代码:http://www.codeproject.com/Articles/2433/Flatten-that-Combobox并设置了BackColor:
protected override void WndProc(ref Message m)
{
    base.WndProc(ref m);

    switch (m.Msg)
    {
        case 0xf:
            base.BackColor = Color.Black;

使用WndProc,这是我最接近的一次尝试,但它没有着色下拉列表的Popup/ItemSelection边框:

...

if (this.DroppedDown)
{
    ArrowBrush = new SolidBrush(SystemColors.HighlightText);

    Rectangle dropDownBounds = new Rectangle(0, 0, Width,Height + DropDownHeight );
    //ControlPaint.DrawBorder(g, dropDownBounds, _borderColor, _borderStyle);
    ControlPaint.DrawBorder(g, dropDownBounds, _borderColor,1, ButtonBorderStyle.Dotted ,Color.GreenYellow,1,ButtonBorderStyle.Solid ,Color.Gold,1,ButtonBorderStyle.Dashed,Color.HotPink,1,ButtonBorderStyle.Solid);

}

原来类FlatComboBoxAdapter需要实现这个功能,但它是私有的,建议使用WPF:ComboBox DropDown-Area Border Color 更多信息请参考:Combobox borderstyle,但即使按照LarsTech和Hans的建议(使用非客户端绘制消息),仍然无法解决问题,并且会出现严重的闪烁。
除了WPF之外,还有一种建议是重写 Combobox .Net Framework 代码:http://www.dotnetframework.org/default.aspx/FX-1434/FX-1434/1@0/untmp/whidbey/REDBITS/ndp/fx/src/WinForms/Managed/System/WinForms/ComboBox@cs/2/ComboBox@cs

1
谢谢你的回复,Jeremy。我很可能会使用WPF创建下拉框。感谢你的第二意见。 - Spencer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接