如何使自定义 ComboBox (OwnerDrawFixed) 看起来像标准 ComboBox 一样 3D?

发布于 2024-11-04 20:26:46 字数 2543 浏览 0 评论 0原文

我正在制作一个自定义 ComboBox,继承自 Winforms 的标准 ComboBox。对于我的自定义 ComboBox,我将 DrawMode 设置为 OwnerDrawFixed,将 DropDownStyle 设置为 DropDownList。然后我编写自己的 OnDrawItem 方法。但我最终是这样的:

标准与自定义组合框

如何使我的自定义组合框看起来像标准组合框?


更新1:ButtonRenderer

在四处搜索后,我发现了 <代码>ButtonRenderer类。它提供了一个 DrawButton 静态/共享方法——顾名思义——绘制正确的 3D 按钮。我现在正在尝试。


更新 2:什么会覆盖我的控件?

我尝试使用我能想到的各种对象的 Graphics 属性,但总是失败。最后,我尝试了表单的图形,显然有东西覆盖了我的按钮。

代码如下:

Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
  Dim TextToDraw As String = _DefaultText
  __Brush_Window.Color = Color.FromKnownColor(KnownColor.Window)
  __Brush_Disabled.Color = Color.FromKnownColor(KnownColor.GrayText)
  __Brush_Enabled.Color = Color.FromKnownColor(KnownColor.WindowText)
  If e.Index >= 0 Then
    TextToDraw = _DataSource.ItemText(e.Index)
  End If
  If TextToDraw.StartsWith("---") Then TextToDraw = StrDup(3, ChrW(&H2500)) ' U+2500 is "Box Drawing Light Horizontal"
  If (e.State And DrawItemState.ComboBoxEdit) > 0 Then
    'ButtonRenderer.DrawButton(e.Graphics, e.Bounds, VisualStyles.PushButtonState.Default)
  Else
    e.DrawBackground()
  End If
  With e
    If _IsEnabled(.Index) Then
      .Graphics.DrawString(TextToDraw, Me.Font, __Brush_Enabled, .Bounds.X, .Bounds.Y)
    Else
      '.Graphics.FillRectangle(__Brush_Window, .Bounds)
      .Graphics.DrawString(TextToDraw, Me.Font, __Brush_Disabled, .Bounds.X, .Bounds.Y)
    End If
  End With
  TextToDraw = Nothing
  ButtonRenderer.DrawButton(Me.Parent.CreateGraphics, Me.ClientRectangle, VisualStyles.PushButtonState.Default)

  'MyBase.OnDrawItem(e)
End Sub

结果如下:

Overwriting ButtonRenderer

Me.Parent.CreateGraphics 替换为 < code>e.Graphics 给了我这个:

Clipped ButtonRenderer

并执行上述操作 + 替换 Me。 ClientRectanglee.Bounds 给了我这个:

Shrunk ButtonRenderer

谁能指点我我必须将谁的 Graphics 用于 ButtonRenderer.DrawButton 方法?

PS:蓝色边框是由于我使用 PushButtonState.Default 而不是 PushButtonState.Normal


我找到了答案! (见下文)

I am making a custom ComboBox, inherited from Winforms' standard ComboBox. For my custom ComboBox, I set DrawMode to OwnerDrawFixed and DropDownStyle to DropDownList. Then I write my own OnDrawItem method. But I ended up like this:

Standard vs Custom ComboBoxes

How do I make my Custom ComboBox to look like the Standard one?


Update 1: ButtonRenderer

After searching all around, I found the ButtonRenderer class. It provides a DrawButton static/shared method which -- as the name implies -- draws the proper 3D button. I'm experimenting with it now.


Update 2: What overwrites my control?

I tried using the Graphics properties of various objects I can think of, but I always fail. Finally, I tried the Graphics of the form, and apparently something is overwriting my button.

Here's the code:

Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
  Dim TextToDraw As String = _DefaultText
  __Brush_Window.Color = Color.FromKnownColor(KnownColor.Window)
  __Brush_Disabled.Color = Color.FromKnownColor(KnownColor.GrayText)
  __Brush_Enabled.Color = Color.FromKnownColor(KnownColor.WindowText)
  If e.Index >= 0 Then
    TextToDraw = _DataSource.ItemText(e.Index)
  End If
  If TextToDraw.StartsWith("---") Then TextToDraw = StrDup(3, ChrW(&H2500)) ' U+2500 is "Box Drawing Light Horizontal"
  If (e.State And DrawItemState.ComboBoxEdit) > 0 Then
    'ButtonRenderer.DrawButton(e.Graphics, e.Bounds, VisualStyles.PushButtonState.Default)
  Else
    e.DrawBackground()
  End If
  With e
    If _IsEnabled(.Index) Then
      .Graphics.DrawString(TextToDraw, Me.Font, __Brush_Enabled, .Bounds.X, .Bounds.Y)
    Else
      '.Graphics.FillRectangle(__Brush_Window, .Bounds)
      .Graphics.DrawString(TextToDraw, Me.Font, __Brush_Disabled, .Bounds.X, .Bounds.Y)
    End If
  End With
  TextToDraw = Nothing
  ButtonRenderer.DrawButton(Me.Parent.CreateGraphics, Me.ClientRectangle, VisualStyles.PushButtonState.Default)

  'MyBase.OnDrawItem(e)
End Sub

And here's the result:

Overwritten ButtonRenderer

Replacing Me.Parent.CreateGraphics with e.Graphics got me this:

Clipped ButtonRenderer

And doing the above + replacing Me.ClientRectangle with e.Bounds got me this:

Shrunk ButtonRenderer

Can anyone point me whose Graphics I must use for the ButtonRenderer.DrawButton method?

PS: The bluish border is due to my using PushButtonState.Default instead of PushButtonState.Normal


I Found An Answer! (see below)

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

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

发布评论

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

评论(2

疯了 2024-11-11 20:26:46

我忘记了在哪里找到答案...当我记得时我会编辑这个答案。

但显然,我需要设置 Systems.Windows.Forms.ControlStyles 标志。特别是 ControlStyles.UserPaint 标志。

所以,我的 New() 现在看起来像这样:

Private _ButtonArea as New Rectangle

Public Sub New()
  ' This call is required by the designer.
  InitializeComponent()
  ' Add any initialization after the InitializeComponent() call.
  MyBase.SetStyle(ControlStyles.Opaque Or ControlStyles.UserPaint, True)
  MyBase.DrawMode = Windows.Forms.DrawMode.OwnerDrawFixed
  MyBase.DropDownStyle = ComboBoxStyle.DropDownList
  ' Cache the button's modified ClientRectangle (see Note)
  With _ButtonArea
    .X = Me.ClientRectangle.X - 1
    .Y = Me.ClientRectangle.Y - 1
    .Width = Me.ClientRectangle.Width + 2
    .Height = Me.ClientRectangle.Height + 2
  End With
End Sub

现在我可以挂接到 OnPaint 事件:

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
  If Me.DroppedDown Then
    ButtonRenderer.DrawButton(Me.CreateGraphics, _ButtonArea, VisualStyles.PushButtonState.Pressed)
  Else
    ButtonRenderer.DrawButton(Me.CreateGraphics, _ButtonArea, VisualStyles.PushButtonState.Normal)
  End If
  MyBase.OnPaint(e)
End Sub

注意: 是的,_ButtonArea 矩形必须向各个方向(上、下、左、右)放大 1 像素,否则会有 1 像素的“周长” ' 在显示垃圾的 ButtonRenderer 周围。让我疯狂了一段时间,直到我读到我必须放大控件的ButtonRenderer 的矩形。

I forgot where I found the answer... I'll edit this answer when I remember.

But apparently, I need to set the Systems.Windows.Forms.ControlStyles flags. Especially the ControlStyles.UserPaint flag.

So, my New() now looks like this:

Private _ButtonArea as New Rectangle

Public Sub New()
  ' This call is required by the designer.
  InitializeComponent()
  ' Add any initialization after the InitializeComponent() call.
  MyBase.SetStyle(ControlStyles.Opaque Or ControlStyles.UserPaint, True)
  MyBase.DrawMode = Windows.Forms.DrawMode.OwnerDrawFixed
  MyBase.DropDownStyle = ComboBoxStyle.DropDownList
  ' Cache the button's modified ClientRectangle (see Note)
  With _ButtonArea
    .X = Me.ClientRectangle.X - 1
    .Y = Me.ClientRectangle.Y - 1
    .Width = Me.ClientRectangle.Width + 2
    .Height = Me.ClientRectangle.Height + 2
  End With
End Sub

And now I can hook into the OnPaint event:

Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
  If Me.DroppedDown Then
    ButtonRenderer.DrawButton(Me.CreateGraphics, _ButtonArea, VisualStyles.PushButtonState.Pressed)
  Else
    ButtonRenderer.DrawButton(Me.CreateGraphics, _ButtonArea, VisualStyles.PushButtonState.Normal)
  End If
  MyBase.OnPaint(e)
End Sub

Note: Yes, the _ButtonArea rectangle must be enlarged by 1 pixel to all directions (up, down, left, right), or else there will be a 1-pixel 'perimeter' around the ButtonRenderer that shows garbage. Made me crazy for awhile until I read that I must enlarge the Control's rect for ButtonRenderer.

你对谁都笑 2024-11-11 20:26:46

我自己也遇到了这个问题,回复pepoluan 让我开始了。我仍然认为为了获得外观和行为类似于具有 DropDownStyle=DropDownList 的标准 ComboBox 的 ComboBox,还缺少一些东西。

下拉箭头
我们还需要绘制 DropDownArrow。我尝试了 ComboBoxRenderer,但它在下拉箭头区域周围绘制了一个黑色边框,因此不起作用。

我的最终解决方案是简单地绘制一个类似的箭头并将其渲染到 OnPaint 方法中的按钮上。

热门商品行为
我们还需要确保我们的 ComboBox 具有与标准 ComboBox 类似的热门项目行为。我不知道有什么简单可靠的方法可以知道鼠标何时不再位于控件之上。因此,我建议使用计时器在每次滴答时检查鼠标是否仍在控件上。

编辑
刚刚添加了一个 KeyUp 事件处理程序,以确保使用键盘进行选择时控件能够正确更新。还对文本的呈现位置进行了细微的修正,以确保它更类似于香草组合框的文本定位。

下面是我定制的ComboBox的完整代码。它允许您在每个项目上显示图像,并且始终以 DropDownList 样式呈现,但希望它应该很容易将代码适应您自己的解决方案。

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

namespace CustomControls
{
    /// <summary>
    /// This is a special ComboBox that each item may conatins an image.
    /// </summary>
    public class ImageComboBox : ComboBox
    {
        private static readonly Size arrowSize = new Size(18, 20);


        private bool itemIsHot;

        /* Since properties such as SelectedIndex and SelectedItems may change when the mouser is hovering over items in the drop down list
         * we need a property that will store the item that has been selected by comitted selection so we know what to draw as the selected item.*/
        private object comittedSelection;

        private readonly ImgHolder dropDownArrow = ImgHolder.Create(ImageComboBox.DropDownArrow());

        private Timer hotItemTimer;

        public Font SelectedItemFont { get; set; }

        public Padding ImageMargin { get; set; }

        //
        // Summary:
        //     Gets or sets the path of the property to use as the image for the items
        //     in the System.Windows.Forms.ListControl.
        //
        // Returns:
        //     A System.String representing a single property name of the System.Windows.Forms.ListControl.DataSource
        //     property value, or a hierarchy of period-delimited property names that resolves
        //     to a property name of the final data-bound object. The default is an empty string
        //     ("").
        //
        // Exceptions:
        //   T:System.ArgumentException:
        //     The specified property path cannot be resolved through the object specified by
        //     the System.Windows.Forms.ListControl.DataSource property.
        [DefaultValue("")]
        [Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        public string ImageMember { get; set; }


        public ImageComboBox()
        {
            base.SetStyle(ControlStyles.Opaque | ControlStyles.UserPaint, true);

            //All the elements in the control are drawn manually.
            base.DrawMode = DrawMode.OwnerDrawFixed;

            //Specifies that the list is displayed by clicking the down arrow and that the text portion is not editable. 
            //This means that the user cannot enter a new value. 
            //Only values already in the list can be selected.
            this.DropDownStyle = ComboBoxStyle.DropDownList;

            //using DrawItem event we need to draw item
            this.DrawItem += this.ComboBoxDrawItemEvent;

            this.hotItemTimer = new Timer();
            this.hotItemTimer.Interval = 250;
            this.hotItemTimer.Tick += this.HotItemTimer_Tick;

            this.MouseEnter += this.ImageComboBox_MouseEnter;

            this.KeyUp += this.ImageComboBox_KeyUp;

            this.SelectedItemFont = this.Font;
            this.ImageMargin = new Padding(4, 4, 5, 4);

            this.SelectionChangeCommitted += this.ImageComboBox_SelectionChangeCommitted;
            this.SelectedIndexChanged += this.ImageComboBox_SelectedIndexChanged;
        }


        private static Image DropDownArrow()
        {
            var arrow = new Bitmap(8, 4, PixelFormat.Format32bppArgb);

            using (Graphics g = Graphics.FromImage(arrow))
            {
                g.CompositingQuality = CompositingQuality.HighQuality;

                g.FillPolygon(Brushes.Black, ImageComboBox.CreateArrowHeadPoints());
            }

            return arrow;
        }

        private static PointF[] CreateArrowHeadPoints()
        {
            return new PointF[4] { new PointF(0, 0), new PointF(7F, 0), new PointF(3.5F, 3.5F), new PointF(0, 0) };
        }

        private static void DrawComboBoxItem(Graphics g, string text, Image image, Rectangle itemArea, int itemHeight, int itemWidth, Padding imageMargin
            , Brush brush, Font font)
        {
            if (image != null)
            {
                // recalculate margins so image is always approximately vertically centered
                int extraImageMargin = itemHeight - image.Height;

                int imageMarginTop = Math.Max(imageMargin.Top, extraImageMargin / 2);
                int imageMarginBotttom = Math.Max(imageMargin.Bottom, extraImageMargin / 2);

                g.DrawImage(image, itemArea.X + imageMargin.Left, itemArea.Y + imageMarginTop, itemHeight, itemHeight - (imageMarginBotttom
                    + imageMarginTop));
            }

            const double TEXT_MARGIN_TOP_PROPORTION = 1.1;
            const double TEXT_MARGIN_BOTTOM_PROPORTION = 2 - TEXT_MARGIN_TOP_PROPORTION;

            int textMarginTop = (int)Math.Round((TEXT_MARGIN_TOP_PROPORTION * itemHeight - g.MeasureString(text, font).Height) / 2.0, 0);
            int textMarginBottom = (int)Math.Round((TEXT_MARGIN_BOTTOM_PROPORTION * itemHeight - g.MeasureString(text, font).Height) / 2.0, 0);

            //we need to draw the item as string because we made drawmode to ownervariable
            g.DrawString(text, font, brush, new RectangleF(itemArea.X + itemHeight + imageMargin.Left + imageMargin.Right, itemArea.Y + textMarginTop
                , itemWidth, itemHeight - textMarginBottom));
        }


        private string GetDistplayText(object item)
        {
            if (this.DisplayMember == string.Empty) { return item.ToString(); }
            else
            {
                var display = item.GetType().GetProperty(this.DisplayMember).GetValue(item).ToString();

                return display ?? item.ToString();
            }

        }

        private Image GetImage(object item)
        {
            if (this.ImageMember == string.Empty) { return null; }
            else { return item.GetType().GetProperty(this.ImageMember).GetValue(item) as Image; }

        }

        private void ImageComboBox_SelectionChangeCommitted(object sender, EventArgs e)
        {
            this.comittedSelection = this.Items[this.SelectedIndex];
        }

        private void HotItemTimer_Tick(object sender, EventArgs e)
        {
            if (!this.RectangleToScreen(this.ClientRectangle).Contains(Cursor.Position)) { this.TurnOffHotItem(); }
        }

        private void ImageComboBox_KeyUp(object sender, KeyEventArgs e)
        {
            this.Invalidate();
        }

        private void ImageComboBox_MouseEnter(object sender, EventArgs e)
        {
            this.TurnOnHotItem();
        }

        private void ImageComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (!this.DroppedDown)
            {
                if (this.SelectedIndex > -1) { this.comittedSelection = this.Items[this.SelectedIndex]; }
                else { this.comittedSelection = null; }

            }
        }

        private void TurnOnHotItem()
        {
            this.itemIsHot = true;
            this.hotItemTimer.Enabled = true;
        }

        private void TurnOffHotItem()
        {
            this.itemIsHot = false;
            this.hotItemTimer.Enabled = false;
            this.Invalidate(this.ClientRectangle);
        }

        /// <summary>
        /// Draws overridden items.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ComboBoxDrawItemEvent(object sender, DrawItemEventArgs e)
        {
            //Draw backgroud of the item
            e.DrawBackground();
            if (e.Index != -1)
            {
                Brush brush;

                if (e.State.HasFlag(DrawItemState.Focus) || e.State.HasFlag(DrawItemState.Selected)) { brush = Brushes.White; }
                else { brush = Brushes.Black; }

                object item = this.Items[e.Index];

                ImageComboBox.DrawComboBoxItem(e.Graphics, this.GetDistplayText(item), this.GetImage(item), e.Bounds, this.ItemHeight, this.DropDownWidth
                    , new Padding(0, 1, 5, 1), brush, this.Font);
            }

        }

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

        // define the area of the control where we will write the text
        var topTextRectangle = new Rectangle(e.ClipRectangle.X - 1, e.ClipRectangle.Y - 1, e.ClipRectangle.Width + 2, e.ClipRectangle.Height + 2);

        using (var controlImage = new Bitmap(e.ClipRectangle.Width, e.ClipRectangle.Height, PixelFormat.Format32bppArgb))
        {
            using (Graphics ctrlG = Graphics.FromImage(controlImage))
            {
                /* Render the control. We use ButtonRenderer and not ComboBoxRenderer because we want the control to appear with the DropDownList style. */
                if (this.DroppedDown) { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Pressed); }
                else if (this.itemIsHot) { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Hot); }
                else { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Normal); }


                // Draw item, if any has been selected
                if (this.comittedSelection != null)
                {
                    ImageComboBox.DrawComboBoxItem(ctrlG, this.GetDistplayText(this.comittedSelection), this.GetImage(this.comittedSelection)
                        , topTextRectangle, this.Height, this.Width - ImageComboBox.arrowSize.Width, this.ImageMargin, Brushes.Black, this.SelectedItemFont);
                }


                /* Now we need to draw the arrow. If we use ComboBoxRenderer for this job, it will display a distinct border around the dropDownArrow and we don't want that. As an alternative we define the area where the arrow should be drawn, and then procede to draw it. */
                var dropDownButtonArea = new RectangleF(topTextRectangle.X + topTextRectangle.Width - (ImageComboBox.arrowSize.Width
                            + this.dropDownArrow.Image.Width) / 2.0F, topTextRectangle.Y + topTextRectangle.Height - (topTextRectangle.Height
                            + this.dropDownArrow.Image.Height) / 2.0F, this.dropDownArrow.Image.Width, this.dropDownArrow.Image.Height);

                ctrlG.DrawImage(this.dropDownArrow.Image, dropDownButtonArea);

            }

            if (this.Enabled) { e.Graphics.DrawImage(controlImage, 0, 0); }
            else { ControlPaint.DrawImageDisabled(e.Graphics, controlImage, 0, 0, Color.Transparent); }

        }
        }
    }

internal struct ImgHolder
    {
        internal Image Image
        {
            get
            {
                return this._image ?? new Bitmap(1, 1); ;
            }
        }
        private Image _image;

        internal ImgHolder(Bitmap data)
        {
            _image = data;
        }
        internal ImgHolder(Image data)
        {
            _image = data;
        }

        internal static ImgHolder Create(Image data)
        {
            return new ImgHolder(data);
        }
        internal static ImgHolder Create(Bitmap data)
        {
            return new ImgHolder(data);
        }
    }

}

I had this problem myself and the reply by pepoluan got me started. I still think a few things are missing in order to get a ComboBox with looks and behavior similar to the standard ComboBox with DropDownStyle=DropDownList though.

DropDownArrow
We also need to draw the DropDownArrow. I played around with the ComboBoxRenderer, but it draws a dark border around the area of the drop down arrow so that didn't work.

My final solution was to simply draw a similar arrow and render it onto the button in the OnPaint method.

Hot Item Behavior
We also need to ensure our ComboBox has a hot item behavior similar to the standard ComboBox. I don't know of any simple and reliable method to know when a mouse is no longer above the control. Therefore I suggest using a Timer that checks at each tick whether the mouse is still over the control.

Edit
Just added a KeyUp event handler to make sure the control would update correctly when a selection was made using the keyboard. Also made a minor correction of where the text was rendered, to ensure it is more similar to the vanilla combobox' text positioning.

Below is the full code of my customized ComboBox. It allows you to display images on each item and is always rendered as in the DropDownList style, but hopefully it should be easy to accommodate the code to your own solution.

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;

namespace CustomControls
{
    /// <summary>
    /// This is a special ComboBox that each item may conatins an image.
    /// </summary>
    public class ImageComboBox : ComboBox
    {
        private static readonly Size arrowSize = new Size(18, 20);


        private bool itemIsHot;

        /* Since properties such as SelectedIndex and SelectedItems may change when the mouser is hovering over items in the drop down list
         * we need a property that will store the item that has been selected by comitted selection so we know what to draw as the selected item.*/
        private object comittedSelection;

        private readonly ImgHolder dropDownArrow = ImgHolder.Create(ImageComboBox.DropDownArrow());

        private Timer hotItemTimer;

        public Font SelectedItemFont { get; set; }

        public Padding ImageMargin { get; set; }

        //
        // Summary:
        //     Gets or sets the path of the property to use as the image for the items
        //     in the System.Windows.Forms.ListControl.
        //
        // Returns:
        //     A System.String representing a single property name of the System.Windows.Forms.ListControl.DataSource
        //     property value, or a hierarchy of period-delimited property names that resolves
        //     to a property name of the final data-bound object. The default is an empty string
        //     ("").
        //
        // Exceptions:
        //   T:System.ArgumentException:
        //     The specified property path cannot be resolved through the object specified by
        //     the System.Windows.Forms.ListControl.DataSource property.
        [DefaultValue("")]
        [Editor("System.Windows.Forms.Design.DataMemberFieldEditor, System.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
        public string ImageMember { get; set; }


        public ImageComboBox()
        {
            base.SetStyle(ControlStyles.Opaque | ControlStyles.UserPaint, true);

            //All the elements in the control are drawn manually.
            base.DrawMode = DrawMode.OwnerDrawFixed;

            //Specifies that the list is displayed by clicking the down arrow and that the text portion is not editable. 
            //This means that the user cannot enter a new value. 
            //Only values already in the list can be selected.
            this.DropDownStyle = ComboBoxStyle.DropDownList;

            //using DrawItem event we need to draw item
            this.DrawItem += this.ComboBoxDrawItemEvent;

            this.hotItemTimer = new Timer();
            this.hotItemTimer.Interval = 250;
            this.hotItemTimer.Tick += this.HotItemTimer_Tick;

            this.MouseEnter += this.ImageComboBox_MouseEnter;

            this.KeyUp += this.ImageComboBox_KeyUp;

            this.SelectedItemFont = this.Font;
            this.ImageMargin = new Padding(4, 4, 5, 4);

            this.SelectionChangeCommitted += this.ImageComboBox_SelectionChangeCommitted;
            this.SelectedIndexChanged += this.ImageComboBox_SelectedIndexChanged;
        }


        private static Image DropDownArrow()
        {
            var arrow = new Bitmap(8, 4, PixelFormat.Format32bppArgb);

            using (Graphics g = Graphics.FromImage(arrow))
            {
                g.CompositingQuality = CompositingQuality.HighQuality;

                g.FillPolygon(Brushes.Black, ImageComboBox.CreateArrowHeadPoints());
            }

            return arrow;
        }

        private static PointF[] CreateArrowHeadPoints()
        {
            return new PointF[4] { new PointF(0, 0), new PointF(7F, 0), new PointF(3.5F, 3.5F), new PointF(0, 0) };
        }

        private static void DrawComboBoxItem(Graphics g, string text, Image image, Rectangle itemArea, int itemHeight, int itemWidth, Padding imageMargin
            , Brush brush, Font font)
        {
            if (image != null)
            {
                // recalculate margins so image is always approximately vertically centered
                int extraImageMargin = itemHeight - image.Height;

                int imageMarginTop = Math.Max(imageMargin.Top, extraImageMargin / 2);
                int imageMarginBotttom = Math.Max(imageMargin.Bottom, extraImageMargin / 2);

                g.DrawImage(image, itemArea.X + imageMargin.Left, itemArea.Y + imageMarginTop, itemHeight, itemHeight - (imageMarginBotttom
                    + imageMarginTop));
            }

            const double TEXT_MARGIN_TOP_PROPORTION = 1.1;
            const double TEXT_MARGIN_BOTTOM_PROPORTION = 2 - TEXT_MARGIN_TOP_PROPORTION;

            int textMarginTop = (int)Math.Round((TEXT_MARGIN_TOP_PROPORTION * itemHeight - g.MeasureString(text, font).Height) / 2.0, 0);
            int textMarginBottom = (int)Math.Round((TEXT_MARGIN_BOTTOM_PROPORTION * itemHeight - g.MeasureString(text, font).Height) / 2.0, 0);

            //we need to draw the item as string because we made drawmode to ownervariable
            g.DrawString(text, font, brush, new RectangleF(itemArea.X + itemHeight + imageMargin.Left + imageMargin.Right, itemArea.Y + textMarginTop
                , itemWidth, itemHeight - textMarginBottom));
        }


        private string GetDistplayText(object item)
        {
            if (this.DisplayMember == string.Empty) { return item.ToString(); }
            else
            {
                var display = item.GetType().GetProperty(this.DisplayMember).GetValue(item).ToString();

                return display ?? item.ToString();
            }

        }

        private Image GetImage(object item)
        {
            if (this.ImageMember == string.Empty) { return null; }
            else { return item.GetType().GetProperty(this.ImageMember).GetValue(item) as Image; }

        }

        private void ImageComboBox_SelectionChangeCommitted(object sender, EventArgs e)
        {
            this.comittedSelection = this.Items[this.SelectedIndex];
        }

        private void HotItemTimer_Tick(object sender, EventArgs e)
        {
            if (!this.RectangleToScreen(this.ClientRectangle).Contains(Cursor.Position)) { this.TurnOffHotItem(); }
        }

        private void ImageComboBox_KeyUp(object sender, KeyEventArgs e)
        {
            this.Invalidate();
        }

        private void ImageComboBox_MouseEnter(object sender, EventArgs e)
        {
            this.TurnOnHotItem();
        }

        private void ImageComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (!this.DroppedDown)
            {
                if (this.SelectedIndex > -1) { this.comittedSelection = this.Items[this.SelectedIndex]; }
                else { this.comittedSelection = null; }

            }
        }

        private void TurnOnHotItem()
        {
            this.itemIsHot = true;
            this.hotItemTimer.Enabled = true;
        }

        private void TurnOffHotItem()
        {
            this.itemIsHot = false;
            this.hotItemTimer.Enabled = false;
            this.Invalidate(this.ClientRectangle);
        }

        /// <summary>
        /// Draws overridden items.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ComboBoxDrawItemEvent(object sender, DrawItemEventArgs e)
        {
            //Draw backgroud of the item
            e.DrawBackground();
            if (e.Index != -1)
            {
                Brush brush;

                if (e.State.HasFlag(DrawItemState.Focus) || e.State.HasFlag(DrawItemState.Selected)) { brush = Brushes.White; }
                else { brush = Brushes.Black; }

                object item = this.Items[e.Index];

                ImageComboBox.DrawComboBoxItem(e.Graphics, this.GetDistplayText(item), this.GetImage(item), e.Bounds, this.ItemHeight, this.DropDownWidth
                    , new Padding(0, 1, 5, 1), brush, this.Font);
            }

        }

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

        // define the area of the control where we will write the text
        var topTextRectangle = new Rectangle(e.ClipRectangle.X - 1, e.ClipRectangle.Y - 1, e.ClipRectangle.Width + 2, e.ClipRectangle.Height + 2);

        using (var controlImage = new Bitmap(e.ClipRectangle.Width, e.ClipRectangle.Height, PixelFormat.Format32bppArgb))
        {
            using (Graphics ctrlG = Graphics.FromImage(controlImage))
            {
                /* Render the control. We use ButtonRenderer and not ComboBoxRenderer because we want the control to appear with the DropDownList style. */
                if (this.DroppedDown) { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Pressed); }
                else if (this.itemIsHot) { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Hot); }
                else { ButtonRenderer.DrawButton(ctrlG, topTextRectangle, PushButtonState.Normal); }


                // Draw item, if any has been selected
                if (this.comittedSelection != null)
                {
                    ImageComboBox.DrawComboBoxItem(ctrlG, this.GetDistplayText(this.comittedSelection), this.GetImage(this.comittedSelection)
                        , topTextRectangle, this.Height, this.Width - ImageComboBox.arrowSize.Width, this.ImageMargin, Brushes.Black, this.SelectedItemFont);
                }


                /* Now we need to draw the arrow. If we use ComboBoxRenderer for this job, it will display a distinct border around the dropDownArrow and we don't want that. As an alternative we define the area where the arrow should be drawn, and then procede to draw it. */
                var dropDownButtonArea = new RectangleF(topTextRectangle.X + topTextRectangle.Width - (ImageComboBox.arrowSize.Width
                            + this.dropDownArrow.Image.Width) / 2.0F, topTextRectangle.Y + topTextRectangle.Height - (topTextRectangle.Height
                            + this.dropDownArrow.Image.Height) / 2.0F, this.dropDownArrow.Image.Width, this.dropDownArrow.Image.Height);

                ctrlG.DrawImage(this.dropDownArrow.Image, dropDownButtonArea);

            }

            if (this.Enabled) { e.Graphics.DrawImage(controlImage, 0, 0); }
            else { ControlPaint.DrawImageDisabled(e.Graphics, controlImage, 0, 0, Color.Transparent); }

        }
        }
    }

internal struct ImgHolder
    {
        internal Image Image
        {
            get
            {
                return this._image ?? new Bitmap(1, 1); ;
            }
        }
        private Image _image;

        internal ImgHolder(Bitmap data)
        {
            _image = data;
        }
        internal ImgHolder(Image data)
        {
            _image = data;
        }

        internal static ImgHolder Create(Image data)
        {
            return new ImgHolder(data);
        }
        internal static ImgHolder Create(Bitmap data)
        {
            return new ImgHolder(data);
        }
    }

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