File: System\Windows\Forms\Controls\Buttons\ButtonInternal\CheckBoxBaseAdapter.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;
 
namespace System.Windows.Forms.ButtonInternal;
 
internal abstract class CheckBoxBaseAdapter : CheckableControlBaseAdapter
{
    protected const int FlatCheckSize = 11;
 
    [ThreadStatic]
    private static Bitmap? t_checkImageChecked;
    [ThreadStatic]
    private static Color t_checkImageCheckedBackColor;
 
    [ThreadStatic]
    private static Bitmap? t_checkImageIndeterminate;
    [ThreadStatic]
    private static Color t_checkImageIndeterminateBackColor;
 
    internal CheckBoxBaseAdapter(ButtonBase control) : base(control)
    {
    }
 
    protected new CheckBox Control => (CheckBox)base.Control;
 
    protected void DrawCheckFlat(
        PaintEventArgs e,
        LayoutData layout,
        Color checkColor,
        Color checkBackground,
        Color checkBorder,
        ColorData colors)
    {
        Rectangle bounds = layout.CheckBounds;
 
        // Removed subtracting one for Width and Height. In VS 2003 (Everett) we needed to do this, as we were using
        // GDI+ to draw the border. Now that we are using GDI, this is unnecessary.
 
        if (!layout.Options.DotNetOneButtonCompat)
        {
            bounds.Width--;
            bounds.Height--;
        }
 
        using (DeviceContextHdcScope hdc = new(e))
        {
            using CreatePenScope hpen = new(checkBorder);
            hdc.DrawRectangle(bounds, hpen);
 
            // Now subtract, since the rest of the code is like Everett.
            if (layout.Options.DotNetOneButtonCompat)
            {
                bounds.Width--;
                bounds.Height--;
            }
 
            bounds.Inflate(-1, -1);
        }
 
        if (Control.CheckState == CheckState.Indeterminate)
        {
            bounds.Width++;
            bounds.Height++;
            DrawDitheredFill(e.Graphics, colors.ButtonFace, checkBackground, bounds);
        }
        else
        {
            using DeviceContextHdcScope hdc = new(e);
            using CreateBrushScope hbrush = new(checkBackground);
 
            // Even though we are using GDI here as opposed to GDI+ in VS 2003 (Everett), we still need to add 1.
            bounds.Width++;
            bounds.Height++;
            hdc.FillRectangle(bounds, hbrush);
        }
 
        DrawCheckOnly(e, layout, colors, checkColor);
    }
 
    internal static void DrawCheckBackground(
        bool controlEnabled,
        CheckState controlCheckState,
        IDeviceContext deviceContext,
        Rectangle bounds,
        Color checkBackground,
        bool disabledColors)
    {
        using DeviceContextHdcScope hdc = deviceContext.ToHdcScope();
 
        Color color;
 
        if (!controlEnabled && disabledColors)
        {
            color = SystemColors.Control;
        }
        else if (controlCheckState == CheckState.Indeterminate && checkBackground == SystemColors.Window && disabledColors)
        {
            Color comboColor = SystemInformation.HighContrast ? SystemColors.ControlDark : SystemColors.Control;
            color = Color.FromArgb(
                (byte)((comboColor.R + SystemColors.Window.R) / 2),
                (byte)((comboColor.G + SystemColors.Window.G) / 2),
                (byte)((comboColor.B + SystemColors.Window.B) / 2));
        }
        else
        {
            color = checkBackground;
        }
 
        using CreateBrushScope hbrush = new(color);
 
        RECT rect = bounds;
        PInvoke.FillRect(hdc, rect, hbrush);
    }
 
    protected void DrawCheckBackground(
        PaintEventArgs e,
        Rectangle bounds,
        Color checkBackground,
        bool disabledColors,
        ColorData colors)
    {
        // Area behind check
 
        if (Control.CheckState == CheckState.Indeterminate)
        {
            DrawDitheredFill(e.GraphicsInternal, colors.ButtonFace, checkBackground, bounds);
        }
        else
        {
            DrawCheckBackground(Control.Enabled, Control.CheckState, e, bounds, checkBackground, disabledColors);
        }
    }
 
    protected void DrawCheckOnly(PaintEventArgs e, LayoutData layout, ColorData colors, Color checkColor) =>
        DrawCheckOnly(
            FlatCheckSize,
            Control.Checked,
            Control.Enabled,
            Control.CheckState,
            e.GraphicsInternal,
            layout,
            colors,
            checkColor);
 
    internal static void DrawCheckOnly(
        int checkSize,
        bool controlChecked,
        bool controlEnabled,
        CheckState controlCheckState,
        Graphics g,
        LayoutData layout,
        ColorData colors,
        Color checkColor)
    {
        if (!controlChecked)
        {
            return;
        }
 
        if (!controlEnabled)
        {
            checkColor = colors.ButtonShadow;
        }
        else if (controlCheckState == CheckState.Indeterminate)
        {
            checkColor = SystemInformation.HighContrast ? colors.Highlight : colors.ButtonShadow;
        }
 
        Rectangle fullSize = layout.CheckBounds;
 
        if (fullSize.Width == checkSize)
        {
            fullSize.Width++;
            fullSize.Height++;
        }
 
        fullSize.Width++;
 
        fullSize.Height++;
        Bitmap checkImage;
 
        if (controlCheckState == CheckState.Checked)
        {
            checkImage = GetCheckBoxImage(checkColor, fullSize, ref t_checkImageCheckedBackColor, ref t_checkImageChecked);
        }
        else
        {
            Debug.Assert(
                controlCheckState == CheckState.Indeterminate,
                "we want to paint the check box only if the item is checked or indeterminate");
            checkImage = GetCheckBoxImage(checkColor, fullSize, ref t_checkImageIndeterminateBackColor, ref t_checkImageIndeterminate);
        }
 
        fullSize.Y -= layout.Options.DotNetOneButtonCompat ? 1 : 2;
 
        ControlPaint.DrawImageColorized(g, checkImage, fullSize, checkColor);
    }
 
    internal static Rectangle DrawPopupBorder(Graphics g, Rectangle r, ColorData colors)
    {
        using DeviceContextHdcScope hdc = new(g);
        return DrawPopupBorder(hdc, r, colors);
    }
 
    internal static Rectangle DrawPopupBorder(PaintEventArgs e, Rectangle r, ColorData colors)
    {
        using DeviceContextHdcScope hdc = new(e);
        return DrawPopupBorder(hdc, r, colors);
    }
 
    internal static Rectangle DrawPopupBorder(HDC hdc, Rectangle r, ColorData colors)
    {
        using CreatePenScope high = new(colors.Highlight);
        using CreatePenScope shadow = new(colors.ButtonShadow);
        using CreatePenScope face = new(colors.ButtonFace);
 
        hdc.DrawLine(high, r.Right - 1, r.Top, r.Right - 1, r.Bottom);
        hdc.DrawLine(high, r.Left, r.Bottom - 1, r.Right, r.Bottom - 1);
 
        hdc.DrawLine(shadow, r.Left, r.Top, r.Left, r.Bottom);
        hdc.DrawLine(shadow, r.Left, r.Top, r.Right - 1, r.Top);
 
        hdc.DrawLine(face, r.Right - 2, r.Top + 1, r.Right - 2, r.Bottom - 1);
        hdc.DrawLine(face, r.Left + 1, r.Bottom - 2, r.Right - 1, r.Bottom - 2);
 
        r.Inflate(-1, -1);
        return r;
    }
 
    protected ButtonState GetState()
    {
        ButtonState style = 0;
 
        if (Control.CheckState == CheckState.Unchecked)
        {
            style |= ButtonState.Normal;
        }
        else
        {
            style |= ButtonState.Checked;
        }
 
        if (!Control.Enabled)
        {
            style |= ButtonState.Inactive;
        }
 
        if (Control.MouseIsDown)
        {
            style |= ButtonState.Pushed;
        }
 
        return style;
    }
 
    protected void DrawCheckBox(PaintEventArgs e, LayoutData layout)
    {
        ButtonState style = GetState();
 
        if (Control.CheckState == CheckState.Indeterminate)
        {
            if (Application.RenderWithVisualStyles)
            {
                CheckBoxRenderer.DrawCheckBoxWithVisualStyles(
                    e,
                    new Point(layout.CheckBounds.Left, layout.CheckBounds.Top),
                    CheckBoxRenderer.ConvertFromButtonState(style, isMixed: true, Control.MouseIsOver),
                    Control.HWNDInternal);
            }
            else
            {
                ControlPaint.DrawMixedCheckBox(e.GraphicsInternal, layout.CheckBounds, style);
            }
        }
        else
        {
            if (Application.RenderWithVisualStyles)
            {
                CheckBoxRenderer.DrawCheckBoxWithVisualStyles(
                    e,
                    new Point(layout.CheckBounds.Left, layout.CheckBounds.Top),
                    CheckBoxRenderer.ConvertFromButtonState(style, isMixed: false, Control.MouseIsOver),
                    Control.HWNDInternal);
            }
            else
            {
                ControlPaint.DrawCheckBox(e.GraphicsInternal, layout.CheckBounds, style);
            }
        }
    }
 
    private static Bitmap GetCheckBoxImage(Color checkColor, Rectangle fullSize, ref Color cacheCheckColor, ref Bitmap? cacheCheckImage)
    {
        if (cacheCheckImage is not null
            && cacheCheckColor.Equals(checkColor)
            && cacheCheckImage.Width == fullSize.Width
            && cacheCheckImage.Height == fullSize.Height)
        {
            return cacheCheckImage;
        }
 
        cacheCheckImage?.Dispose();
 
        // We draw the checkmark slightly off center to eliminate 3-D border artifacts and compensate below
        RECT rcCheck = new Rectangle(0, 0, fullSize.Width, fullSize.Height);
        Bitmap bitmap = new(fullSize.Width, fullSize.Height);
 
        using (Graphics offscreen = Graphics.FromImage(bitmap))
        {
            offscreen.Clear(Color.Transparent);
            using DeviceContextHdcScope hdc = new(offscreen, applyGraphicsState: false);
            PInvoke.DrawFrameControl(
                hdc,
                ref rcCheck,
                DFC_TYPE.DFC_MENU,
                DFCS_STATE.DFCS_MENUCHECK);
        }
 
        bitmap.MakeTransparent();
        cacheCheckImage = bitmap;
        cacheCheckColor = checkColor;
 
        return cacheCheckImage;
    }
 
    protected void AdjustFocusRectangle(LayoutData layout)
    {
        if (string.IsNullOrEmpty(Control.Text))
        {
            // When a CheckBox has no text, AutoSize sets the size to zero and thus there's no place around which
            // to draw the focus rectangle. So, when AutoSize == true we want the focus rectangle to be rendered
            // inside the box. Otherwise, it should encircle all the available space next to the box (like it's
            // done in WPF and ComCtl32).
            layout.Focus = Control.AutoSize ? Rectangle.Inflate(layout.CheckBounds, -2, -2) : layout.Field;
        }
    }
 
    internal override LayoutOptions CommonLayout()
    {
        LayoutOptions layout = base.CommonLayout();
        layout.CheckAlign = Control.CheckAlign;
        layout.TextOffset = false;
        layout.ShadowedText = !Control.Enabled;
        layout.LayoutRTL = Control.RightToLeft == RightToLeft.Yes;
 
        return layout;
    }
}