透明重叠的圆形进度条(自定义控件)

7
我遇到了一个自定义的圆形进度条控件问题。我试图将它们重叠在右下角。它具有透明背景,在WinForms中显然显示背景,但对彼此没有影响。
以下是我所看到的内容:

Overlapping Circular Progress Bars

我一直在StackOverflow上进行研究,发现有些人在使用自定义picturebox控件时遇到了问题。大多数解决方案似乎对圆形进度条控件没有影响。我尝试过的一些解决方案是。

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;

            cp.ExStyle |= 0x20;

            return cp;
        }
    }

我还有一个自定义控件的代码,可以允许透明背景。显然,正如我所说,这不会影响重叠的控件。
SetStyle(ControlStyles.SupportsTransparentBackColor, true);

stackoverflow上也有一个TransparentControl的解决方案,我看到有人在使用它。我已经创建了这个控件,但是要么不知道如何使用它,要么它在我的情况下不起作用。这是来自该控件的代码。

public class TransparentControl : Panel
{
    public bool drag = false;
    public bool enab = false;
    private int m_opacity = 100;

    private int alpha;
    public TransparentControl()
    {
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        SetStyle(ControlStyles.Opaque, true);
        this.BackColor = Color.Transparent;
    }

    public int Opacity
    {
        get
        {
            if (m_opacity > 100)
            {
                m_opacity = 100;
            }
            else if (m_opacity < 1)
            {
                m_opacity = 1;
            }
            return this.m_opacity;
        }
        set
        {
            this.m_opacity = value;
            if (this.Parent != null)
            {
                Parent.Invalidate(this.Bounds, true);
            }
        }
    }

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams cp = base.CreateParams;
            cp.ExStyle = cp.ExStyle | 0x20;
            return cp;
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        Graphics g = e.Graphics;
        Rectangle bounds = new Rectangle(0, 0, this.Width - 1, this.Height - 1);

        Color frmColor = this.Parent.BackColor;
        Brush bckColor = default(Brush);

        alpha = (m_opacity * 255) / 100;

        if (drag)
        {
            Color dragBckColor = default(Color);

            if (BackColor != Color.Transparent)
            {
                int Rb = BackColor.R * alpha / 255 + frmColor.R * (255 - alpha) / 255;
                int Gb = BackColor.G * alpha / 255 + frmColor.G * (255 - alpha) / 255;
                int Bb = BackColor.B * alpha / 255 + frmColor.B * (255 - alpha) / 255;
                dragBckColor = Color.FromArgb(Rb, Gb, Bb);
            }
            else
            {
                dragBckColor = frmColor;
            }

            alpha = 255;
            bckColor = new SolidBrush(Color.FromArgb(alpha, dragBckColor));
        }
        else
        {
            bckColor = new SolidBrush(Color.FromArgb(alpha, this.BackColor));
        }

        if (this.BackColor != Color.Transparent | drag)
        {
            g.FillRectangle(bckColor, bounds);
        }

        bckColor.Dispose();
        g.Dispose();
        base.OnPaint(e);
    }

    protected override void OnBackColorChanged(EventArgs e)
    {
        if (this.Parent != null)
        {
            Parent.Invalidate(this.Bounds, true);
        }
        base.OnBackColorChanged(e);
    }

    protected override void OnParentBackColorChanged(EventArgs e)
    {
        this.Invalidate();
        base.OnParentBackColorChanged(e);
    }
}

任何帮助都将不胜感激。这已经让我抓狂了好几个小时了。谢谢 :)
更新1:我尝试使用下面发布的示例代码片段。结果是一样的。我仍然在圆形进度条之间有一个空白区域(如图片所示)。
                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));

仍然感到困惑。 :(
更新2:我尝试在FormLoad中将前置的圆形进度条设置为使用后置圆形进度条作为其父级。但这也没有成功。它们彼此之间变得透明,但会裁剪掉任何未在后置圆形进度条范围内的顶部圆形进度条的部分。
var pts = this.PointToScreen(circularprogressbar1.Location);
pts = circularprogressbar2.PointToClient(pts);
circularprogressbar1.Parent = circularprogressbar2;
circularprogressbar1.Location = pts;

1
另一个半透明控件:带文本的半透明圆形。它是从标签控件派生而来的,也可以是面板。这是一个完整的自定义控件。您可以从工具箱中将其拖放到表单上并查看其工作原理。 - Jimi
你好 @RezaAghaei,我已经查看了你的示例并尝试将 Parent.Controls 代码实现到我的控件中。现在,两个控件似乎都没有在顶部,并且我们仍然存在透明背景擦除另一个控件一部分的间距问题。(请参见上面的图片) - frostbyte
1个回答

6

我将为您提供几条关于如何继续的建议。

首先从这个基础的透明控件 (TransparentPanel) 开始。
这个类是从 Panel 派生出来的。这是要做出的第一个选择: Panel 是继承/扩展这个任务的正确控件吗?也许是,也许不是。
例如,Panel 是一个容器。你需要这个容器的特性吗?容器意味着很多东西。它继承 ScrollableControl 并在其窗口样式中拥有 ContainerControl。它已经带了一些负担。

您可以选择使用 Label,它很轻量级。或者构建一个 UserControl。
我认为没有绝对的最佳选择。这取决于这个自定义控件的用途。您需要试一试。

注意:
要使用这里展示的代码创建旋转效果,您需要下面展示的TransparentPanel控件,如果在标准控件的表面绘制,它不会以相同的方式工作。
该控件在绘制形状时生成一种持久性,使用其他类型的画布控件则不存在(除非进行大量调整)。
class TransparentPanel : Panel
{
    internal const int WS_EX_TRANSPARENT = 0x00000020;

    public TransparentPanel() => InitializeComponent();

    protected void InitializeComponent()
    {
        this.SetStyle(ControlStyles.AllPaintingInWmPaint |
                      ControlStyles.Opaque |
                      ControlStyles.ResizeRedraw |
                      ControlStyles.SupportsTransparentBackColor |
                      ControlStyles.UserPaint, true);
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer, false);
    }

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

其他注意事项:
这里明确设置了ControlStyles.SupportsTransparentBackColorPanel 类已经支持此功能。它被指定是因为它可以在构造函数中阅读时给出这个自定义控件的用途的想法。

此外,ControlStyles.OptimizedDoubleBuffer 被设置为 false。
这可以防止系统以任何方式干扰控件的绘制。没有缓存,当无效时,自定义控件会重新绘制。容器表单最好将其DoubleBuffer 属性设置为true,但您可能希望在不设置时进行测试,以查看是否有区别。


这个自定义控件(不要和UserControl混淆)是完全透明的。它不绘制背景。但你可以在它的表面上绘制任何东西。
看一下之前发布的链接:

四种不同的方法可以得到相同的结果。选择哪一个取决于上下文/目标。

设计时建议: 当您测试自定义控件功能时,请记得始终重新构建项目。当从工具箱CustomControl拖放到Form上时,可能会出现该控件在运行项目时未更新为新更改的情况。
此外,如果您添加或删除属性,则需要删除该控件,重新构建并在Form上拖放一个新控件。
如果不这样做,有很大的机会您的修改/添加将被完全忽略,并且您将继续测试从未发挥作用的功能。

示例,使用2个重叠的自定义控件。
(使用基本的自定义TransparentPanel)

Transparent Overlapped Controls Rotate

这是用于生成这些图形的测试代码:

  • 使用之前展示的TransparentPanel类创建一个新的自定义控件。
  • 在一个测试窗体上放置两个TransparentPanel对象。
  • transparentPanel1_PainttransparentPanel2_Paint事件处理程序分配给TransparentPanel1TransparentPanel2
  • 重叠两个透明面板,确保不要错误地嵌套它们
  • 调整其余的代码(您只需要一个按钮,在这里命名为btnRotate,并将btnRotate_Click处理程序分配给它)。
private System.Windows.Forms.Timer RotateTimer = null;
private float RotationAngle1 = 90F;
private float RotationAngle2 = 0F;
public bool RotateFigures = false;

public form1()
{
    InitializeComponent();
    RotateTimer = new Timer();
    RotateTimer.Interval = 50;
    RotateTimer.Enabled = false;
    RotateTimer.Tick += new EventHandler(this.RotateTick);
}

protected void RotateTick(object sender, EventArgs e)
{
    RotationAngle1 += 10F;
    RotationAngle2 += 10F;
    transparentPanel1.Invalidate();
    transparentPanel2.Invalidate();
}

private void btnRotate_Click(object sender, EventArgs e)
{
    RotateTimer.Enabled = !RotateTimer.Enabled;
    if (RotateTimer.Enabled == false)
    {
        RotateFigures = false;
        RotationAngle1 = 90F;
        RotationAngle2 = 0F;
    }
    else
    {
        RotateFigures = true;
    }
}


private void transparentPanel1_Paint(object sender, PaintEventArgs e)
{
    if (!RotateFigures) return;
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
    e.Graphics.CompositingMode = CompositingMode.SourceOver;
    Rectangle rect = transparentPanel1.ClientRectangle;
    Rectangle rectInner = rect;

    using (Pen transpPen = new Pen(transparentPanel1.Parent.BackColor, 10))
    using (Pen penOuter = new Pen(Color.SteelBlue, 8))
    using (Pen penInner = new Pen(Color.Teal, 8))
    using (Matrix m1 = new Matrix())
    using (Matrix m2 = new Matrix())
    {
        m1.RotateAt(-RotationAngle1, new PointF(rect.Width / 2, rect.Height / 2));
        m2.RotateAt(RotationAngle1, new PointF(rect.Width / 2, rect.Height / 2));
        rect.Inflate(-(int)penOuter.Width, -(int)penOuter.Width);
        rectInner.Inflate(-(int)penOuter.Width * 3, -(int)penOuter.Width * 3);

        e.Graphics.Transform = m1;
        e.Graphics.DrawArc(transpPen, rect, -4, 94);
        e.Graphics.DrawArc(penOuter, rect, -90, 90);
        e.Graphics.ResetTransform();
        e.Graphics.Transform = m2;
        e.Graphics.DrawArc(transpPen, rectInner, 190, 100);
        e.Graphics.DrawArc(penInner, rectInner, 180, 90);
    }
}

private void transparentPanel2_Paint(object sender, PaintEventArgs e)
{
    if (!RotateFigures) return;
    e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
    e.Graphics.CompositingQuality = CompositingQuality.HighQuality;
    e.Graphics.CompositingMode = CompositingMode.SourceOver;
    Rectangle rect = transparentPanel2.ClientRectangle;
    Rectangle rectInner = rect;

    using (Pen transpPen = new Pen(transparentPanel2.Parent.BackColor, 10))
    using (Pen penOuter = new Pen(Color.Orange, 8))
    using (Pen penInner = new Pen(Color.DarkRed, 8))
    using (Matrix m1 = new Matrix())
    using (Matrix m2 = new Matrix())
    {
        m1.RotateAt(RotationAngle2, new PointF(rect.Width / 2, rect.Height / 2));
        m2.RotateAt(-RotationAngle2, new PointF(rect.Width / 2, rect.Height / 2));
        rect.Inflate(-(int)penOuter.Width, -(int)penOuter.Width);
        rectInner.Inflate(-(int)penOuter.Width * 3, -(int)penOuter.Width * 3);

        e.Graphics.Transform = m1;
        e.Graphics.DrawArc(transpPen, rect, -4, 94);
        e.Graphics.DrawArc(penOuter, rect, 0, 90);
        e.Graphics.ResetTransform();
        e.Graphics.Transform = m2;
        e.Graphics.DrawArc(transpPen, rectInner, 190, 100);
        e.Graphics.DrawArc(penInner, rectInner, 180, 90);
    }
}

谢谢@Jimi。你的解释非常详细,所以我把答案给了你。 - frostbyte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接