Translucent circular Control with text
Asked Answered
E

1

5

I am working on a project wherein I need to add a Control with the shape of a Circle with some text in the middle.
My problem is the circle is too small, when I resize it, it overlaps other controls. I want to draw the circle same width as the square.
Otherwise. how can I make the Control's background transparent?

enter image description here

I am using the code below:

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();
        }
    }
}
Emancipate answered 18/7, 2018 at 8:3 Comment(9)
Seems like you're making the circle too small. Have you tried increasing values of graphics.FillEllipse?Chopper
Your circle is exactly as big as I expect it to be based on your code. Your problem is that you're drawing it at 18, 18 with the approximate size 41x41, but you want it to be 0, 0 with approximate size 77x77. It seems to me that all you need is graphics.FillEllipse(brush, e.ClipRectangle); without all the extra Bitmap stuff, etc.Pointillism
@john Thanks, that works fine. But i did not remove the Bitmap thing, when i remove that, the circle is not showing.Emancipate
I meant that why don't you just use e.Graphics rather than creating a bitmap, creating an instance of Graphics for the bitmap, drawing everything onto that before finally using e.Graphics to draw it to the control?Pointillism
Another issue, If I resize the circle, the font is not resizing. How will I do it?Emancipate
In winforms there is no such thing as 'a circle'. There are only controls with surfaces consisting of pixels. And bitmaps.We have no idea what you want to do. So there really is not advice what to do best. To place a text centered in a bitmap you should: use TextRenderer.DrawText not Graphics.DrawString b) draw into a rectangle c) use centered StringFormat.Grappling
Also: You can use graphics.Clear(Color.Transparent) to start with a transparent bitmap instead of using the notorious MakeTransparent, which will miss all anti-aliased pixels.. But most important you need to know what you really want to achieve. If you do: TELL USGrappling
@TaW, ok thanks. I am doing a custom control public class CircularLabel : Control Then I use the code above to Paint it. The text is already centered and the Circle is now okay. The remaining issue is when I resize the Circle, the font is not resizing.Emancipate
OK, so all you need it to change the Font. You will have to find some formula to determine the new Height, then write: using (Font font = new Font(Font.FontFamily, newHeight)) TextRenderer.DrawText(e.Graphics,text, font, ... ); Do avoid graphics.DrawString for TextRendererGrappling
U
12

This is a Custom Control derived from Control, 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 behavior, adding these ControlStyles:

ControlStyles.Opaque: prevents the painting of a Control's BackGround, so it's not managed by the System. Combined with CreateParams to set the Control's Extended Style to WS_EX_TRANSPARENT, the Control becomes completely transparent.

ControlStyles.SupportsTransparentBackColor the control accepts Alpha values for it's BackGround color. Without also setting ControlStyles.UserPaint it won't be used to simulate transparency. We're doing that ourselves with other means.


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:

WinForms Translucent Label

Note and disclaimer:

  • This is a prototype Control, the custom Designer is missing (cannot post that here, too much code, also connected to a framework).
    As presented here, it can be used to completely overlap other Controls in a Form or other containers. Partial overlapping is not handled in this simplified implementation.
  • The Font is hard-coded to Segoe UI, since this Font has a base-line that simplifies the position of the text in the middle of the circular area.
    Other Fonts have a different base-line, which requires more complex handling.
    See: TextBox with dotted lines for typing for the base math.

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Text;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Windows.Forms;

[DesignerCategory("Code")]
public class RoundCenterLabel : Label, INotifyPropertyChanged, ISupportInitialize 
{
    private const int WS_EX_TRANSPARENT = 0x00000020;
    private bool IsInitializing = false;
    private Point MouseDownLocation = Point.Empty;
    private readonly int fontPadding = 4;
    private Font m_CustomFont = null;
    private Color m_BackGroundColor;
    private int m_InnerPadding = 0;
    private int m_FontPadding = 25;
    private int m_Opacity = 128;

    public event PropertyChangedEventHandler PropertyChanged;

    public RoundCenterLabel() => InitializeComponent();

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

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

    public new Font Font
    {
        get => m_CustomFont;
        set { 
            m_CustomFont = value;
            if (IsInitializing) return;
            FontAdapter(value, DeviceDpi);
            NotifyPropertyChanged();
        }
    }

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

    public int InnerPadding {
        get => m_InnerPadding;
        set {
            if (IsInitializing) return;
            m_InnerPadding = ValidateRange(value, 0, ClientRectangle.Height - 10);
            NotifyPropertyChanged(); }
    }

    public int FontPadding {
        get => m_FontPadding;
        set {
            if (IsInitializing) return;
            m_FontPadding = ValidateRange(value, 0, ClientRectangle.Height - 10);
            NotifyPropertyChanged();
        }
    }

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

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

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

    private void NotifyPropertyChanged([CallerMemberName] string PropertyName = null)
    {
        InvalidateParent();
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
    }

    protected override void OnMouseDown(MouseEventArgs e)
    {
        base.OnMouseDown(e);
        MouseDownLocation = e.Location;
    }

    protected override void OnMouseMove(MouseEventArgs e)
    {
        base.OnMouseMove(e);
        if (e.Button == MouseButtons.Left) {
            var loc = new Point(Left + (e.X - MouseDownLocation.X), Top + (e.Y - MouseDownLocation.Y));
            InvalidateParent();
            BeginInvoke(new Action(() => Location = loc));
        }
    }

    private void InvalidateParent()
    {
        Parent?.Invalidate(Bounds, true);
        Invalidate();
    }

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

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

    public void BeginInit() => IsInitializing = true;

    public void EndInit()
    {
        IsInitializing = false;
        Font = new Font("Segoe UI", 50, FontStyle.Regular, GraphicsUnit.Pixel);
        FontPadding = m_FontPadding;
        InnerPadding = m_InnerPadding;
    }

    private RectangleF InnerRectangle()
    {
        (float Min, _) = GetMinMax(ClientRectangle.Height, ClientRectangle.Width);
        var size = new SizeF(Min - (m_InnerPadding / 2), Min - (m_InnerPadding / 2));
        var position = new PointF((ClientRectangle.Width - size.Width) / 2,
                                  (ClientRectangle.Height - size.Height) / 2);
        return new RectangleF(position, size);
    }

    private void FontAdapter(Font font, float dpi)
    {
        RectangleF rect = InnerRectangle();
        float fontSize = ValidateRange(
            (int)(rect.Height - m_FontPadding), 6, 
            (int)(rect.Height - m_FontPadding)) / (dpi / 72.0F) - fontPadding;

        m_CustomFont.Dispose();
        m_CustomFont = new Font(font.FontFamily, fontSize, font.Style, GraphicsUnit.Pixel);
    }

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

    private int ValidateRange(int Value, int Min, int Max) 
        => Math.Max(Math.Min(Value, Max), Min); // (Value < Min) ? Min : ((Value > Max) ? Max : Value);

    private (float, float) GetMinMax(float Value1, float Value2) 
        => (Math.Min(Value1, Value2), Math.Max(Value1, Value2));
}
Upstretched answered 20/7, 2018 at 5:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.