Show Transparent Loading Spinner above other Controls
Asked Answered
C

3

5

I am working in a spinner control. I want the control to support transparent backcolor. When the arc is drawn, there is a blank space in the middle, I want that space to be truly transparent, so that I could put another control behind it and it would not be covered by the spinner.

I tried overriding the CreateParams void.
Also I set the style to support TransparentColor.
Tried overriding OnPaintBackground void, but I cannot achieve the real transparent backcolor.

So, what can you suggest me to do?

Crazed answered 13/5, 2016 at 22:38 Comment(11)
You can see a transparent spinner in this post. Why does the designer slowing when two custom controls has timer added? The same technique is described hereto make a transparent picture box and label. How to make two transparent layer with c#?Uropygium
@Reza Aghaei Saw both posts but I could not get a true transparency. I also duplicated the SpinningCircles control from the first post you linked. But that one doesn't achieve a real transparent backcolor.Crazed
At least for the second link which I shared, you can see the screenshot which contains transparent picture boxes and transparent labels. What's the problem exactly?Uropygium
When using it in the designer or at debugging time all controls behind it get covered by a gray solid color (the form's backcolorCrazed
I checked it again and It works properly in both designer and run-time. Probably you have set a color as BackColor of it. You should set Transparent as BackColor.Uropygium
Y es I'm sure I have set the control's backcolor to Transparent (in the designer and in the code)Crazed
I'm talking about TranparentPictureBox and TransparentLabel which I shared. I don't have any idea about your code. But at least, I'm sure if you understand an apply the approach, you can have a transparent spinner simply. Hope you find those answers helpful :)Uropygium
Also when creating a spinner using an arc, you can simply exclude the center region from the region of your control. For example, take a look at this example How to create a User Control with rounded corners?Uropygium
By the way, I also made the spinning circle control work properly by some small changes in the linked code.Uropygium
Oh ok so let me make another approach with the second link code. Then I will tell you the resultsCrazed
I posted 2 good options for showing transparent spinner above other controls. Let me know if you have any question about the answer :)Uropygium
U
24

To make a transparent layer, you should override painting of the control and draw the control in this order, First draw all controls in the same container which are under your control (based on z-index) on a bitmap. Then draw that bitmap on graphics of your control. At last draw content of your control. Also the BackColor of your control should be Color.Transparent.

Also as another option to make transparent layer, you can exclude some regions from your control when drawing.

In the following samples I used the first technique and created 2 controls. A spinning circles transparent control. and a transparent picturebox control.

In both samples I used a delay between loading rows to showing a spinner make sense.

Sample 1 - Using a SpinningCircles Control

SpinningCircles control draws circles and supports transparency. The control doesn't animate at design-time, but it animates at run-time. Also it doesn't consume resources when it is not visible.

enter image description here

Sample 2 - Using a TransparentPictureBox Control and a transparent animated gif TransparentPictureBox control supports transparency, so I used an animated gif as its image and as you can see, the gif is showing correctly.

enter image description here

Sample 1 Code - SpinningCircles

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
public class SpinningCircles : Control
{
    int increment = 1;
    int radius = 4;
    int n = 8;
    int next = 0;
    Timer timer;
    public SpinningCircles()
    {
        timer = new Timer();
        this.Size = new Size(100, 100);
        timer.Tick += (s, e) => this.Invalidate();
        if (!DesignMode)
            timer.Enabled = true;
        SetStyle(ControlStyles.AllPaintingInWmPaint |
                 ControlStyles.OptimizedDoubleBuffer |
                 ControlStyles.ResizeRedraw | ControlStyles.UserPaint |
                 ControlStyles.SupportsTransparentBackColor, true);
        BackColor = Color.Transparent;
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        if (Parent != null && this.BackColor == Color.Transparent)
        {
            using (var bmp = new Bitmap(Parent.Width, Parent.Height))
            {
                Parent.Controls.Cast<Control>()
                      .Where(c => Parent.Controls.GetChildIndex(c) > Parent.Controls.GetChildIndex(this))
                      .Where(c => c.Bounds.IntersectsWith(this.Bounds))
                      .OrderByDescending(c => Parent.Controls.GetChildIndex(c))
                      .ToList()
                      .ForEach(c => c.DrawToBitmap(bmp, c.Bounds));

                e.Graphics.DrawImage(bmp, -Left, -Top);
            }
        }
        e.Graphics.SmoothingMode = SmoothingMode.HighQuality;
        int length = Math.Min(Width, Height);
        PointF center = new PointF(length / 2, length / 2);
        int bigRadius = length / 2 - radius - (n - 1) * increment;
        float unitAngle = 360 / n;
        if (!DesignMode)
            next++;
        next = next >= n ? 0 : next;
        int a = 0;
        for (int i = next; i < next + n; i++)
        {
            int factor = i % n;
            float c1X = center.X + (float)(bigRadius * Math.Cos(unitAngle * factor * Math.PI / 180));
            float c1Y = center.Y + (float)(bigRadius * Math.Sin(unitAngle * factor * Math.PI / 180));
            int currRad = radius + a * increment;
            PointF c1 = new PointF(c1X - currRad, c1Y - currRad);
            e.Graphics.FillEllipse(Brushes.Black, c1.X, c1.Y, 2 * currRad, 2 * currRad);
            using (Pen pen = new Pen(Color.White, 2))
                e.Graphics.DrawEllipse(pen, c1.X, c1.Y, 2 * currRad, 2 * currRad);
            a++;
        }
    }
    protected override void OnVisibleChanged(EventArgs e)
    {
        timer.Enabled = Visible;
        base.OnVisibleChanged(e);
    }
}

Sample 2 Code - TransparentPictureBox Code

using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
class TransparentPictureBox : PictureBox
{
    public TransparentPictureBox()
    {
        this.BackColor = Color.Transparent;
    }
    protected override void OnPaint(PaintEventArgs e)
    {
        if (Parent != null && this.BackColor == Color.Transparent)
        {
            using (var bmp = new Bitmap(Parent.Width, Parent.Height))
            {
                Parent.Controls.Cast<Control>()
                      .Where(c => Parent.Controls.GetChildIndex(c) > Parent.Controls.GetChildIndex(this))
                      .Where(c => c.Bounds.IntersectsWith(this.Bounds))
                      .OrderByDescending(c => Parent.Controls.GetChildIndex(c))
                      .ToList()
                      .ForEach(c => c.DrawToBitmap(bmp, c.Bounds));

                e.Graphics.DrawImage(bmp, -Left, -Top);
            }
        }
        base.OnPaint(e);
    }
}
Uropygium answered 27/5, 2016 at 0:53 Comment(3)
It's pretty old question, but it would be great if you can verify the answer or let me know if you have any problem using the solutions :)Uropygium
How to call it ?Promethium
@Promethium Just have an instance of the control positioned properly on the form, then show it (and update it) and hide it whenever you need. For example take a look at this post.Uropygium
E
2

I slightly changed Reza's code (SpinningCircles) to add a semi-transparent background. I wanted to share it with you. (NOTE: The spinner length was fixed to 100 and should be added as a component property)

public partial class WorkingPanel : UserControl
{
    #region Constants
    private static readonly Int32 kSpinnerLength = 100;
    #endregion

    #region Fields
    private Int32 increment = 1;
    private Int32 radius = 4;
    private Int32 n = 8;
    private Int32 next = 0;
    private Timer timer = null;
    #endregion

    #region Constructor
    public WorkingPanel()
    {
        this.Size = new Size(100, 100);

        timer = new Timer();
        timer.Tick += (s, e) => this.Invalidate();

        if (!DesignMode)
            timer.Enabled = true;

        SetStyle(ControlStyles.AllPaintingInWmPaint |
                 ControlStyles.OptimizedDoubleBuffer |
                 ControlStyles.ResizeRedraw | ControlStyles.UserPaint |
                 ControlStyles.SupportsTransparentBackColor, true);

        BackColor = Color.Transparent;
    }
    #endregion

    #region Methods (Protected - Override)
    protected override void OnPaint(PaintEventArgs e)
    {
        if (null != Parent && (this.BackColor.A != 255 || this.BackColor == Color.Transparent))
        {
            using (var bmp = new Bitmap(Parent.Width, Parent.Height))
            {
                Parent.Controls.Cast<Control>()
                      .Where(c => Parent.Controls.GetChildIndex(c) > Parent.Controls.GetChildIndex(this))
                      .Where(c => c.Bounds.IntersectsWith(this.Bounds))
                      .OrderByDescending(c => Parent.Controls.GetChildIndex(c))
                      .ToList()
                      .ForEach(c => c.DrawToBitmap(bmp, c.Bounds));

                e.Graphics.DrawImage(bmp, -Left, -Top);

                if (this.BackColor != Color.Transparent)
                    e.Graphics.FillRectangle(new SolidBrush(this.BackColor), new Rectangle(0, 0, Width, Height));
            }
        }

        e.Graphics.SmoothingMode = SmoothingMode.HighQuality;

        Int32 length = kSpinnerLength;
        PointF center = new PointF(Width / 2, Height / 2);
        Int32 bigRadius = length / 2 - radius - (n - 1) * increment;
        float unitAngle = 360 / n;

        if (!DesignMode)
            next++;

        next = next >= n ? 0 : next;
        Int32 a = 0;
        for (Int32 i = next; i < next + n; i++)
        {
            Int32 factor = i % n;
            float c1X = center.X + (float)(bigRadius * Math.Cos(unitAngle * factor * Math.PI / 180));
            float c1Y = center.Y + (float)(bigRadius * Math.Sin(unitAngle * factor * Math.PI / 180));
            Int32 currRad = radius + a * increment;
            PointF c1 = new PointF(c1X - currRad, c1Y - currRad);

            e.Graphics.FillEllipse(Brushes.White, c1.X, c1.Y, 2 * currRad, 2 * currRad);

            using (Pen pen = new Pen(Color.White, 2))
                e.Graphics.DrawEllipse(pen, c1.X, c1.Y, 2 * currRad, 2 * currRad);

            a++;
        }
    }

    protected override void OnVisibleChanged(EventArgs e)
    {
        timer.Enabled = Visible;

        base.OnVisibleChanged(e);
    }
    #endregion
}
Ecdysiast answered 5/4, 2018 at 17:27 Comment(2)
How do you have implemented it? Creating the class and writing the following code does not show it: using(var F = new SpinningCircle()) { F.Show(); System.Threading.Thread.Sleep(4000); }Address
I just added the WorkingPanel to the form and voila. After that you can set the anchors or dock properties to fill the whole form.Ecdysiast
W
1

Modification from CrApHeR's code to add Designer Properties

preview

How to use

Add this class to your project

using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;

[DesignerCategory("Code")]
public class Spinner : Control
{
    private int next = 0;
    private Timer timer = new Timer();

    public Spinner()
    {
        SetStyle(ControlStyles.UserPaint, true);
        SetStyle(ControlStyles.ResizeRedraw, true);
        SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);

        Size = new Size(100, 100);
        BackColor = Color.Transparent;

        timer.Tick += (s, e) => Invalidate();
        if (!DesignMode)
        {
            timer.Enabled = true;
        }
    }

    [Browsable(true)]
    [Category("Appearance")]
    public int NodeCount { get; set; } = 8;

    [Browsable(true)]
    [Category("Appearance")]
    public int NodeRadius { get; set; } = 4;

    [Browsable(true)]
    [Category("Appearance")]
    public float NodeResizeRatio { get; set; } = 1.0f;

    [Browsable(true)]
    [Category("Appearance")]
    public Color NodeFillColor { get; set; } = Color.Black;

    [Browsable(true)]
    [Category("Appearance")]
    public Color NodeBorderColor { get; set; } = Color.White;

    [Browsable(true)]
    [Category("Appearance")]
    public int NodeBorderSize { get; set; } = 2;

    [Browsable(true)]
    [Category("Appearance")]
    public int SpinnerRadius { get; set; } = 100;

    protected override void OnPaint(PaintEventArgs e)
    {
        if (null != Parent && (BackColor.A != 255 || BackColor == Color.Transparent))
        {
            using (Bitmap bmp = new Bitmap(Parent.Width, Parent.Height))
            {
                foreach (Control control in GetIntersectingControls(Parent))
                {
                    control.DrawToBitmap(bmp, control.Bounds);
                }

                e.Graphics.DrawImage(bmp, -Left, -Top);

                if (BackColor != Color.Transparent)
                {
                    using (Brush brush = new SolidBrush(BackColor))
                    {
                        e.Graphics.FillRectangle(brush, 0, 0, Width, Height);
                    }
                }
            }
        }

        e.Graphics.SmoothingMode = SmoothingMode.HighQuality;

        PointF center = new PointF(Width / 2, Height / 2);
        int bigRadius = (int)(SpinnerRadius / 2 - NodeRadius - (NodeCount - 1) * NodeResizeRatio);
        float unitAngle = 360 / NodeCount;

        if (!DesignMode)
        {
            next++;
        }

        next = next >= NodeCount ? 0 : next;

        for (int i = next, a = 0; i < next + NodeCount; i++, a++)
        {
            int factor = i % NodeCount;
            float c1X = center.X + (float)(bigRadius * Math.Cos(unitAngle * factor * Math.PI / 180));
            float c1Y = center.Y + (float)(bigRadius * Math.Sin(unitAngle * factor * Math.PI / 180));
            int currRad = (int)(NodeRadius + a * NodeResizeRatio);
            PointF c1 = new PointF(c1X - currRad, c1Y - currRad);

            using (Brush brush = new SolidBrush(NodeFillColor))
            {
                e.Graphics.FillEllipse(brush, c1.X, c1.Y, 2 * currRad, 2 * currRad);
            }

            using (Pen pen = new Pen(Color.White, NodeBorderSize))
            {
                e.Graphics.DrawEllipse(pen, c1.X, c1.Y, 2 * currRad, 2 * currRad);
            }
        }
    }

    protected override void OnVisibleChanged(EventArgs e)
    {
        timer.Enabled = Visible;
        base.OnVisibleChanged(e);
    }

    private IOrderedEnumerable<Control> GetIntersectingControls(Control parent)
    {
        return parent.Controls.Cast<Control>()
            .Where(c => parent.Controls.GetChildIndex(c) > parent.Controls.GetChildIndex(this))
            .Where(c => c.Bounds.IntersectsWith(Bounds))
            .OrderByDescending(c => parent.Controls.GetChildIndex(c));
    }
}

Drag the Spinner control from the Toolbox to your Form

toolbox

Alternatively, you can add it at runtime with this code

Spinner spinner = new Spinner();
spinner.Location = new Point(50, 50);
Controls.Add(spinner);

Then, you can change its properties from the Designer Properties category Appearance

properties

The main properties you may want to customize are:

  • NodeCount (default: 8)
  • NodeRadius (default: 4)
  • NodeResizeRatio (default: 1.0f)
  • NodeFillColor (default: Black)
  • NodeBorderColor (default: White)
  • NodeBorderSize (default: 2)
  • SpinnerRadius (default: 100)
  • BackColor (default: Transparent) (supports alpha transparency)
    BackColor = Color.FromArgb(50, Color.Blue.R, Color.Blue.G, Color.Blue.B))
    
Woermer answered 25/12, 2021 at 16:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.