Translucent circle with text

2019-01-08 00:38发布

问题:

I am working on a project wherein I need to add a Circle with text in the middle. I am using the code below. But my problem is the circle is too small, when I resize it, it overlaps other control. I want to draw the circle same width as the square or how will make the back ground as transparent?

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

    using (Bitmap bitmap = new Bitmap(this.Width, this.Height))
    {
        using (Graphics graphics = Graphics.FromImage(bitmap))
        {
            graphics.SmoothingMode = SmoothingMode.HighQuality;
            graphics.Clear(this.BackColor);

            using (SolidBrush brush = new SolidBrush(this._FillColor))
            {                      
                graphics.FillEllipse(brush, 0x18 - 6, 0x18 - 6, (this.Width - 0x30) + 12, (this.Height - 0x30) + 12);
            }

            Brush FontColor = new SolidBrush(this.ForeColor);
            SizeF MS = graphics.MeasureString(Convert.ToString(Convert.ToInt32((100 / _Maximum) * _Value)), Font);
            graphics.DrawString(Convert.ToString(Convert.ToInt32((100 / _Maximum) * _Value)), Font, FontColor, Convert.ToInt32((Width / 2 - MS.Width / 2) + 2), Convert.ToInt32((Height / 2 - MS.Height / 2) + 3));
            bitmap.MakeTransparent(this.BackColor);
            e.Graphics.DrawImage(bitmap, 0, 0);

            graphics.Dispose();
            bitmap.Dispose();
        }
    }
}

回答1:

This is a Custom Control derived from a standard Label, which can be made translucent.
The interface is a colored circle which can contain a couple of numbers.

The Control exposes these custom properties:

Opacity: The level of opacity of the control BackGround [0, 255]
InnerPadding: The distance between the inner rectangle, which defines the circle bounds and the control bounds.
FontPadding: The distance between the Text and the Inner rectangle.

Transparency is obtained overriding CreateParams, then setting ExStyle |= WS_EX_TRANSPARENT;

The Control.SetStyle() method is used to modify the control behaviour, adding these ControlStyles:

ControlStyles.Opaque prevents the painting of the control BackGround, so it's not managed by the System.
ControlStyles.SupportsTransparentBackColor the control accepts Alpha values for it's BackGround color.


To see it at work, create a new Class file, substitute all the code inside with this code preserving the NameSpace and build the Project/Solution.
The new Custom Control will appear in the ToolBox.
Drop it on a Form. Modify its custom properties as needed.

A visual representation of the control:


Apparently, ScreenToGif ignores the Pixel change on full opacity.
In its opinion nothing changes, so it optimizes it showing nothing.

Note:
I didn't use TextRenderer here because of its padding. It's harder to control in this context: the vertical center position needs to be adjusted and it doesn't provide any quality enhancements.

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Globalization;
using System.Windows.Forms;

[DesignerCategory("Code")]
class RoundCenterLabel : Label, INotifyPropertyChanged
{
    internal const int WS_EX_TRANSPARENT = 0x00000020;
    internal Font m_CustomFont = null;
    internal Color m_BackGroundColor;
    internal int m_InnerPadding = 0;
    internal int m_FontPadding = 25;
    internal int m_Opacity = 128;

    public event PropertyChangedEventHandler PropertyChanged;

    public RoundCenterLabel() => InitializeComponent();

    private void InitializeComponent()
    {
        this.SetStyle(ControlStyles.Opaque |
                      ControlStyles.SupportsTransparentBackColor |
                      ControlStyles.ResizeRedraw, true);
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
        this.m_CustomFont = new Font("Segoe UI", 50, FontStyle.Regular, GraphicsUnit.Pixel);
        this.BackColor = Color.LimeGreen;
        this.ForeColor = Color.White;
    }

    private void NotifyPropertyChanged(string PropertyName)
    {
        this.Invalidate();
        this.FindForm()?.Refresh();
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
    }

    public new Font Font
    {
        get => this.m_CustomFont;
        set { this.m_CustomFont = value;
              FontAdapter(value, this.DeviceDpi);
              NotifyPropertyChanged(nameof(this.Font));
        }
    }

    public override string Text {
        get => base.Text;
        set { base.Text = value;
              NotifyPropertyChanged(nameof(this.Text));
        }
    }

    public int InnerPadding {
        get => this.m_InnerPadding;
        set { this.m_InnerPadding = CheckValue(value, 0, this.ClientRectangle.Height - 10);
              NotifyPropertyChanged(nameof(this.InnerPadding)); }
    }

    public int FontPadding {
        get => this.m_FontPadding;
        set { this.m_FontPadding = CheckValue(value, 0, this.ClientRectangle.Height - 10);
              NotifyPropertyChanged(nameof(this.FontPadding));
        }
    }

    public int Opacity {
        get => this.m_Opacity;
        set { this.m_Opacity = CheckValue(value, 0, 255);
              UpdateBackColor(this.m_BackGroundColor);
              NotifyPropertyChanged(nameof(this.Opacity));
        }
    }

    public override Color BackColor {
        get => this.m_BackGroundColor;
        set { UpdateBackColor(value);
              NotifyPropertyChanged(nameof(this.BackColor));
        }
    }

    protected override void OnLayout(LayoutEventArgs evt)
    {
        base.OnLayout(evt);
        base.AutoSize = false;
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        StringFormat format = new StringFormat(StringFormatFlags.LineLimit | StringFormatFlags.NoWrap, CultureInfo.CurrentUICulture.LCID)             {
            LineAlignment = StringAlignment.Center,
            Alignment = StringAlignment.Center
        };
        e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
        e.Graphics.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;

        using (SolidBrush CircleBrush = new SolidBrush(this.m_BackGroundColor))
        using (SolidBrush ForeBrush = new SolidBrush(this.ForeColor))
        {
            this.FontAdapter(this.m_CustomFont, e.Graphics.DpiY);
            RectangleF rect = InnerRectangle();
            e.Graphics.FillEllipse(CircleBrush, rect);
            e.Graphics.DrawString(this.Text, this.m_CustomFont, ForeBrush, rect, format);
        }
    }

    private RectangleF InnerRectangle()
    {
        Tuple<decimal, decimal> refSize = GetMinMax(this.ClientRectangle.Height, this.ClientRectangle.Width);
        SizeF size = new SizeF((float)refSize.Item1 - (this.m_InnerPadding / 2), 
                               (float)refSize.Item1 - (this.m_InnerPadding / 2));
        PointF position = new PointF((this.ClientRectangle.Width - size.Width) / 2,
                                     (this.ClientRectangle.Height - size.Height) / 2);
        return new RectangleF(position, size);
    }

    private void FontAdapter(Font font, float Dpi)
    {
        RectangleF rect = InnerRectangle();
        float FontSize = CheckValue((int)(rect.Height - this.m_FontPadding), 6, 
                                    (int)(rect.Height - this.m_FontPadding)) / (Dpi / 72.0F);
        using (Font customfont = new Font(font.FontFamily, FontSize, font.Style, GraphicsUnit.Pixel))
            this.m_CustomFont = (Font)customfont.Clone();
    }

    private void UpdateBackColor(Color color)
    {
        this.m_BackGroundColor = Color.FromArgb(this.m_Opacity, Color.FromArgb(color.R, color.G, color.B));
        base.BackColor = this.m_BackGroundColor;
    }

    private int CheckValue(int Value, int Min, int Max)
    {
        return (Value < Min) ? Min : ((Value > Max) ? Max : Value);
    }

    private Tuple<decimal, decimal> GetMinMax(ValueType Value1, ValueType Value2)
    {
        if ((Value1 is Enum) || (Value1.GetType().IsNested)) return null;
        if ((Value2 is Enum) || (Value2.GetType().IsNested)) return null;
        return new Tuple<decimal, decimal>(Math.Min(Convert.ToDecimal(Value1), Convert.ToDecimal(Value2)),
                                           Math.Max(Convert.ToDecimal(Value1), Convert.ToDecimal(Value2)));
    }

    protected override CreateParams CreateParams 
    {
        get
        {
            CreateParams parameters = base.CreateParams;
            parameters.ExStyle |= WS_EX_TRANSPARENT;
            return parameters;
        }
    }
}