File: System\Windows\Forms\Controls\ComboBox\ComboBox.FlatComboAdapter.cs
Web Access
Project: src\src\System.Windows.Forms\src\System.Windows.Forms.csproj (System.Windows.Forms)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Drawing;
using System.Windows.Forms.Layout;
 
namespace System.Windows.Forms;
 
public partial class ComboBox
{
    internal class FlatComboAdapter
    {
        private Rectangle _outerBorder;
        private Rectangle _innerBorder;
        private Rectangle _innerInnerBorder;
        internal Rectangle _dropDownRect;
        private Rectangle _whiteFillRect;
        private Rectangle _clientRect;
        private readonly RightToLeft _origRightToLeft; // The combo box's RTL value when we were created
 
        private const int WhiteFillRectWidth = 5; // used for making the button look smaller than it is
 
        private const int OFFSET_2PIXELS = 2;
        protected static int s_offsetPixels = OFFSET_2PIXELS;
 
        private bool ShouldRedrawAsSmallButton { get; }
 
        public FlatComboAdapter(ComboBox comboBox, bool shouldRedrawAsSmallButton)
        {
            // adapter is re-created when ComboBox is resized, see IsValid method, thus we don't need to handle DPI changed explicitly
            s_offsetPixels = comboBox.LogicalToDeviceUnits(OFFSET_2PIXELS);
 
            _clientRect = comboBox.ClientRectangle;
            int dropDownButtonWidth = SystemInformation.GetHorizontalScrollBarArrowWidthForDpi(comboBox._deviceDpi);
            _outerBorder = new Rectangle(_clientRect.Location, new Size(_clientRect.Width - 1, _clientRect.Height - 1));
            _innerBorder = new Rectangle(_outerBorder.X + 1, _outerBorder.Y + 1, _outerBorder.Width - dropDownButtonWidth - 2, _outerBorder.Height - 2);
            _innerInnerBorder = new Rectangle(_innerBorder.X + 1, _innerBorder.Y + 1, _innerBorder.Width - 2, _innerBorder.Height - 2);
            _dropDownRect = new Rectangle(_innerBorder.Right + 1, _innerBorder.Y, dropDownButtonWidth, _innerBorder.Height + 1);
 
            ShouldRedrawAsSmallButton = shouldRedrawAsSmallButton;
 
            // fill in several pixels of the dropdown rect with white so that it looks like the combo button is thinner.
            if (shouldRedrawAsSmallButton)
            {
                _whiteFillRect = _dropDownRect;
                _whiteFillRect.Width = WhiteFillRectWidth;
                _dropDownRect.X += WhiteFillRectWidth;
                _dropDownRect.Width -= WhiteFillRectWidth;
            }
 
            _origRightToLeft = comboBox.RightToLeft;
 
            if (_origRightToLeft == RightToLeft.Yes)
            {
                _innerBorder.X = _clientRect.Width - _innerBorder.Right;
                _innerInnerBorder.X = _clientRect.Width - _innerInnerBorder.Right;
                _dropDownRect.X = _clientRect.Width - _dropDownRect.Right;
                _whiteFillRect.X = _clientRect.Width - _whiteFillRect.Right + 1;  // since we're filling, we need to move over to the next px.
            }
        }
 
        public bool IsValid(ComboBox combo)
        {
            return (combo.ClientRectangle == _clientRect && combo.RightToLeft == _origRightToLeft);
        }
 
        public virtual void DrawPopUpCombo(ComboBox comboBox, Graphics g)
        {
            if (comboBox.DropDownStyle == ComboBoxStyle.Simple)
            {
                return;
            }
 
            if (ShouldRedrawAsSmallButton)
            {
                DrawFlatCombo(comboBox, g);
            }
 
            bool rightToLeft = comboBox.RightToLeft == RightToLeft.Yes;
 
            // Draw a dark border around everything if we're in popup mode
            if ((!comboBox.Enabled) || (comboBox.FlatStyle == FlatStyle.Popup))
            {
                bool focused = comboBox.ContainsFocus || comboBox.MouseIsOver;
                Color borderPenColor = GetPopupOuterBorderColor(comboBox, focused);
 
                using var borderPen = borderPenColor.GetCachedPenScope();
                Pen innerPen = comboBox.Enabled ? borderPen : SystemPens.Control;
 
                // Draw a border around the dropdown.
                if (rightToLeft)
                {
                    g.DrawRectangle(
                        innerPen,
                        new Rectangle(_outerBorder.X, _outerBorder.Y, _dropDownRect.Width + 1, _outerBorder.Height));
                }
                else
                {
                    g.DrawRectangle(
                        innerPen,
                        new Rectangle(_dropDownRect.X, _outerBorder.Y, _outerBorder.Right - _dropDownRect.X, _outerBorder.Height));
                }
 
                // Draw a border around the whole ComboBox.
                g.DrawRectangle(borderPen, _outerBorder);
            }
        }
 
        /// <summary>
        ///  Paints over the edges of the combo box to make it appear flat.
        /// </summary>
        public virtual void DrawFlatCombo(ComboBox comboBox, Graphics g)
        {
            if (comboBox.DropDownStyle == ComboBoxStyle.Simple)
            {
                return;
            }
 
            Color outerBorderColor = GetOuterBorderColor(comboBox);
            Color innerBorderColor = GetInnerBorderColor(comboBox);
            bool rightToLeft = comboBox.RightToLeft == RightToLeft.Yes;
 
            // Draw the drop down
            DrawFlatComboDropDown(comboBox, g, _dropDownRect);
 
            // When we are disabled there is one line of color that seems to eek through if BackColor is set
            // so let's erase it.
            if (!LayoutUtils.IsZeroWidthOrHeight(_whiteFillRect))
            {
                // Fill in two more pixels with white so it looks smaller.
                using var b = innerBorderColor.GetCachedSolidBrushScope();
                g.FillRectangle(b, _whiteFillRect);
            }
 
            // Draw the outer border
 
            using var outerBorderPen = outerBorderColor.GetCachedPenScope();
            g.DrawRectangle(outerBorderPen, _outerBorder);
            if (rightToLeft)
            {
                g.DrawRectangle(
                    outerBorderPen,
                    new Rectangle(_outerBorder.X, _outerBorder.Y, _dropDownRect.Width + 1, _outerBorder.Height));
            }
            else
            {
                g.DrawRectangle(
                    outerBorderPen,
                    new Rectangle(_dropDownRect.X, _outerBorder.Y, _outerBorder.Right - _dropDownRect.X, _outerBorder.Height));
            }
 
            // Draw the inner border
            using var innerBorderPen = innerBorderColor.GetCachedPenScope();
            g.DrawRectangle(innerBorderPen, _innerBorder);
            g.DrawRectangle(innerBorderPen, _innerInnerBorder);
        }
 
        /// <summary>
        ///  Paints over the edges of the combo box to make it appear flat.
        /// </summary>
        protected virtual void DrawFlatComboDropDown(ComboBox comboBox, Graphics g, Rectangle dropDownRect)
        {
            g.FillRectangle(SystemBrushes.Control, dropDownRect);
 
            Brush brush = (comboBox.Enabled) ? SystemBrushes.ControlText : SystemBrushes.ControlDark;
 
            Point middle = new(dropDownRect.Left + dropDownRect.Width / 2, dropDownRect.Top + dropDownRect.Height / 2);
            if (_origRightToLeft == RightToLeft.Yes)
            {
                // if the width is odd - favor pushing it over one pixel left.
                middle.X -= (dropDownRect.Width % 2);
            }
            else
            {
                // if the width is odd - favor pushing it over one pixel right.
                middle.X += (dropDownRect.Width % 2);
            }
 
            g.FillPolygon(
                brush,
                (ReadOnlySpan<Point>)
                [
                    new(middle.X - s_offsetPixels, middle.Y - 1),
                    new(middle.X + s_offsetPixels + 1, middle.Y - 1),
                    new(middle.X, middle.Y + s_offsetPixels)
                ]);
        }
 
        protected virtual Color GetOuterBorderColor(ComboBox comboBox)
            => comboBox.Enabled ? SystemColors.Window : SystemColors.ControlDark;
 
        protected virtual Color GetPopupOuterBorderColor(ComboBox comboBox, bool focused)
        {
            if (!comboBox.Enabled)
            {
                return SystemColors.ControlDark;
            }
 
            return focused ? SystemColors.ControlDark : SystemColors.Window;
        }
 
        protected virtual Color GetInnerBorderColor(ComboBox comboBox)
            => comboBox.Enabled ? comboBox.BackColor : SystemColors.Control;
    }
}