File: System\Windows\Forms\Controls\Buttons\ButtonInternal\RadioButtonBaseAdapter.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 RadioButtonBaseAdapter : CheckableControlBaseAdapter
{
    internal RadioButtonBaseAdapter(ButtonBase control) : base(control) { }
 
    protected new RadioButton Control => (RadioButton)base.Control;
 
    protected void DrawCheckFlat(
        PaintEventArgs e,
        LayoutData layout,
        Color checkColor,
        Color checkBackground,
        Color checkBorder)
    {
        DrawCheckBackgroundFlat(e, layout.CheckBounds, checkBorder, checkBackground);
        DrawCheckOnly(e, layout, checkColor, disabledColors: true);
    }
 
    protected void DrawCheckBackground3DLite(
        PaintEventArgs e,
        Rectangle bounds,
        Color checkBackground,
        ColorData colors,
        bool disabledColors)
    {
        Graphics g = e.GraphicsInternal;
 
        Color field = checkBackground;
        if (!Control.Enabled && disabledColors)
        {
            field = SystemColors.Control;
        }
 
        using var fieldBrush = field.GetCachedSolidBrushScope();
        using var dark = colors.ButtonShadow.GetCachedPenScope();
        using var light = colors.ButtonFace.GetCachedPenScope();
        using var lightLight = colors.Highlight.GetCachedPenScope();
 
        bounds.Width--;
        bounds.Height--;
 
        // Fall a little short of SW, NW, NE, SE because corners come out nasty
        g.DrawPie(dark, bounds, 135 + 1, 90 - 2);
        g.DrawPie(dark, bounds, 225 + 1, 90 - 2);
        g.DrawPie(lightLight, bounds, 315 + 1, 90 - 2);
        g.DrawPie(lightLight, bounds, 45 + 1, 90 - 2);
        bounds.Inflate(-1, -1);
        g.FillEllipse(fieldBrush, bounds);
        g.DrawEllipse(light, bounds);
    }
 
    protected void DrawCheckBackgroundFlat(PaintEventArgs e, Rectangle bounds, Color borderColor, Color checkBackground)
    {
        Color field = checkBackground;
        Color border = borderColor;
 
        if (!Control.Enabled)
        {
            // If we are not in HighContrast mode OR we opted into the legacy behavior
            if (!SystemInformation.HighContrast)
            {
                border = ControlPaint.ContrastControlDark;
            }
 
            // Otherwise we are in HighContrast mode
            field = SystemColors.Control;
        }
 
        double scale = GetDpiScaleRatio();
 
        using DeviceContextHdcScope hdc = new(e);
        using CreatePenScope borderPen = new(border);
        using CreateBrushScope fieldBrush = new(field);
 
        if (scale > 1.1)
        {
            // In high DPI mode when we draw an ellipse as three rectangles, the quality of ellipse is poor. Draw
            // it directly as an ellipse.
            bounds.Width--;
            bounds.Height--;
            hdc.DrawAndFillEllipse(borderPen, fieldBrush, bounds);
            bounds.Inflate(-1, -1);
        }
        else
        {
            DrawAndFillEllipse(hdc, borderPen, fieldBrush, bounds);
        }
    }
 
    // Helper method to overcome the poor GDI ellipse drawing routine
    private static void DrawAndFillEllipse(HDC hdc, HPEN borderPen, HBRUSH fieldBrush, Rectangle bounds)
    {
        Debug.Assert(!hdc.IsNull, "Calling DrawAndFillEllipse with null wg");
        if (hdc.IsNull)
        {
            return;
        }
 
        hdc.FillRectangle(fieldBrush, new(bounds.X + 2, bounds.Y + 2, 8, 8));
        hdc.FillRectangle(fieldBrush, new(bounds.X + 4, bounds.Y + 1, 4, 10));
        hdc.FillRectangle(fieldBrush, new(bounds.X + 1, bounds.Y + 4, 10, 4));
 
        hdc.DrawLine(borderPen, new(bounds.X + 4, bounds.Y + 0), new(bounds.X + 8, bounds.Y + 0));
        hdc.DrawLine(borderPen, new(bounds.X + 4, bounds.Y + 11), new(bounds.X + 8, bounds.Y + 11));
 
        hdc.DrawLine(borderPen, new(bounds.X + 2, bounds.Y + 1), new(bounds.X + 4, bounds.Y + 1));
        hdc.DrawLine(borderPen, new(bounds.X + 8, bounds.Y + 1), new(bounds.X + 10, bounds.Y + 1));
 
        hdc.DrawLine(borderPen, new(bounds.X + 2, bounds.Y + 10), new(bounds.X + 4, bounds.Y + 10));
        hdc.DrawLine(borderPen, new(bounds.X + 8, bounds.Y + 10), new(bounds.X + 10, bounds.Y + 10));
 
        hdc.DrawLine(borderPen, new(bounds.X + 0, bounds.Y + 4), new(bounds.X + 0, bounds.Y + 8));
        hdc.DrawLine(borderPen, new(bounds.X + 11, bounds.Y + 4), new(bounds.X + 11, bounds.Y + 8));
 
        hdc.DrawLine(borderPen, new(bounds.X + 1, bounds.Y + 2), new(bounds.X + 1, bounds.Y + 4));
        hdc.DrawLine(borderPen, new(bounds.X + 1, bounds.Y + 8), new(bounds.X + 1, bounds.Y + 10));
 
        hdc.DrawLine(borderPen, new(bounds.X + 10, bounds.Y + 2), new(bounds.X + 10, bounds.Y + 4));
        hdc.DrawLine(borderPen, new(bounds.X + 10, bounds.Y + 8), new(bounds.X + 10, bounds.Y + 10));
    }
 
    private static int GetScaledNumber(int n, double scale)
    {
        return (int)(n * scale);
    }
 
    protected void DrawCheckOnly(PaintEventArgs e, LayoutData layout, Color checkColor, bool disabledColors)
    {
        if (!Control.Checked)
        {
            return;
        }
 
        if (!Control.Enabled && disabledColors)
        {
            checkColor = SystemColors.ControlDark;
        }
 
        double scale = GetDpiScaleRatio();
        using DeviceContextHdcScope hdc = new(e);
        using CreateBrushScope brush = new(checkColor);
 
        // Circle drawing doesn't work at this size
        int offset = 5;
 
        Rectangle vCross = new(
            layout.CheckBounds.X + GetScaledNumber(offset, scale),
            layout.CheckBounds.Y + GetScaledNumber(offset - 1, scale),
            GetScaledNumber(2, scale),
            GetScaledNumber(4, scale));
 
        hdc.FillRectangle(vCross, brush);
 
        Rectangle hCross = new(
            layout.CheckBounds.X + GetScaledNumber(offset - 1, scale),
            layout.CheckBounds.Y + GetScaledNumber(offset, scale),
            GetScaledNumber(4, scale), GetScaledNumber(2, scale));
 
        hdc.FillRectangle(hCross, brush);
    }
 
    protected ButtonState GetState()
    {
        ButtonState style = default;
 
        if (Control.Checked)
        {
            style |= ButtonState.Checked;
        }
        else
        {
            style |= ButtonState.Normal;
        }
 
        if (!Control.Enabled)
        {
            style |= ButtonState.Inactive;
        }
 
        if (Control.MouseIsDown)
        {
            style |= ButtonState.Pushed;
        }
 
        return style;
    }
 
    protected void DrawCheckBox(PaintEventArgs e, LayoutData layout)
    {
        Rectangle check = layout.CheckBounds;
        if (!Application.RenderWithVisualStyles)
        {
            check.X--;      // compensate for Windows drawing slightly offset to right
        }
 
        ButtonState style = GetState();
 
        if (Application.RenderWithVisualStyles)
        {
            using DeviceContextHdcScope hdc = new(e);
            RadioButtonRenderer.DrawRadioButtonWithVisualStyles(
                hdc,
                new Point(check.Left, check.Top),
                RadioButtonRenderer.ConvertFromButtonState(style, Control.MouseIsOver),
                Control.HWNDInternal);
        }
        else
        {
            ControlPaint.DrawRadioButton(e.GraphicsInternal, check, style);
        }
    }
 
    protected void AdjustFocusRectangle(LayoutData layout)
    {
        if (string.IsNullOrEmpty(Control.Text))
        {
            // When a RadioButton 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 around the circle area.
            // Otherwise, it should encircle all the available space next to the box (like it's done in WPF and ComCtl32).
            layout.Focus = Control.AutoSize ? layout.CheckBounds : layout.Field;
        }
    }
 
    internal override LayoutOptions CommonLayout()
    {
        LayoutOptions layout = base.CommonLayout();
        layout.CheckAlign = Control.CheckAlign;
 
        return layout;
    }
}