c# - C#中的淡入淡出控制

标签 c# controls opacity fading

我正在尝试构建一个支持 Opcacity 属性的 Control 派生类。
此控件可以承载文本和图像,并且能够淡出和淡入它们。
这是我的代码:

internal class FadeControl : Control
{
    private int opacity = 100;

    public FadeControl()
    {
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
    }

    public int Opacity
    {
        get
        {
            return opacity;
        }
        set
        {
            if (value > 100) opacity = 100;
            else if (value < 1) opacity = 1;
            else opacity = value;

            if (Parent != null)
                Parent.Invalidate(Bounds, true);
        }
    }

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

    protected override void OnPaintBackground(PaintEventArgs e)
    {
        //do nothing
    }

    protected override void OnMove(EventArgs e)
    {
        RecreateHandle();
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        using (Graphics g = e.Graphics)
        {
            Rectangle bounds = new Rectangle(0, 0, Width - 1, Height - 1);
            int alpha = (opacity * 255) / 100;

            using (Brush bckColor = new SolidBrush(Color.FromArgb(alpha, BackColor)))
            {
                if (BackColor != Color.Transparent)
                    g.FillRectangle(bckColor, bounds);
            }

            ColorMatrix colorMatrix = new ColorMatrix();
            colorMatrix.Matrix33 = (float)alpha / 255;
            ImageAttributes imageAttr = new ImageAttributes();
            imageAttr.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

            if (BackgroundImage != null)
                g.DrawImage(BackgroundImage, bounds, 0, 0, Width, Height, GraphicsUnit.Pixel, imageAttr);

            if (Text != string.Empty)
            {
                using (Brush txtBrush = new SolidBrush(Color.FromArgb(alpha, ForeColor)))
                {
                    g.DrawString(Text, Font, txtBrush, 5, 5);
                }
            }
        }
    }

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

        base.OnBackColorChanged(e);
    }

    protected override void OnParentBackColorChanged(EventArgs e)
    {
        Invalidate();

        base.OnParentBackColorChanged(e);
    }
}

我已将控件放在一个带有计时器的窗体上。
计时器将控件的不透明度从 0 设置为 100 并返回,并且运行良好。
我试图解决的问题是控件在更改其不透明度时会闪烁。
将控件设置为 ControlStyles.DoubleBuffer 将使控件在窗体上不可见。

我们欢迎任何建议。

最佳答案

我无法同时使用双缓冲区和 WS_EX_TRANSPARENT (0x20) 作为透明背景。所以我决定通过复制父控件的内容来实现透明背景,并使用双缓冲区来防止闪烁。

以下是最终的源代码,经过测试可以正常工作:

using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;

internal class FadeControl : Control
{
    private int opacity = 100;
    private Bitmap backgroundBuffer;
    private bool skipPaint;

    public FadeControl()
    {
        SetStyle(ControlStyles.SupportsTransparentBackColor, true);
        SetStyle(ControlStyles.ResizeRedraw, true);
        SetStyle(ControlStyles.DoubleBuffer |
                 ControlStyles.AllPaintingInWmPaint |
                 ControlStyles.UserPaint, true);
    }

    public int Opacity
    {
        get
        {
            return opacity;
        }
        set
        {
            if (value > 100) opacity = 100;
            else if (value < 1) opacity = 1;
            else opacity = value;

            if (Parent != null)
                Parent.Invalidate(Bounds, true);
        }
    }

    protected override void OnPaintBackground(PaintEventArgs e)
    {
        //do nothig
    }

    protected override void OnMove(EventArgs e)
    {
        RecreateHandle();
    }

    private void CreateBackgroundBuffer(Control parent)
    {
        int offsetX;
        int offsetY;
        GetOffsets(out offsetX, out offsetY, parent);
        backgroundBuffer = new Bitmap(Width + offsetX, Height + offsetY);
    }

    protected override void OnResize(EventArgs e)
    {
        var parent = Parent;
        if (parent != null)
        {
            CreateBackgroundBuffer(parent);
        }
        base.OnResize(e);
    }

    private void GetOffsets(out int offsetX, out int offsetY, Control parent)
    {
        var parentPosition = parent.PointToScreen(Point.Empty);
        offsetY = Top + parentPosition.Y - parent.Top;
        offsetX = Left + parentPosition.X - parent.Left;
    }

    private void UpdateBackgroundBuffer(int offsetX, int offsetY, Control parent)
    {
        if (backgroundBuffer == null)
        {
            CreateBackgroundBuffer(parent);
        }
        Rectangle parentBounds = new Rectangle(0, 0, Width + offsetX, Height + offsetY);
        skipPaint = true;
        parent.DrawToBitmap(backgroundBuffer, parentBounds);
        skipPaint = false;
    }

    private void DrawBackground(Graphics graphics, Rectangle bounds)
    {
        int offsetX;
        int offsetY;
        var parent = Parent;
        GetOffsets(out offsetX, out offsetY, parent);
        UpdateBackgroundBuffer(offsetX, offsetY, parent);
        graphics.DrawImage(backgroundBuffer, bounds, offsetX, offsetY, Width, Height, GraphicsUnit.Pixel);
    }

    private void Draw(Graphics graphics)
    {
        Rectangle bounds = new Rectangle(0, 0, Width, Height);
        DrawBackground(graphics, bounds);

        int alpha = (opacity * 255) / 100;

        using (Brush bckColor = new SolidBrush(Color.FromArgb(alpha, BackColor)))
        {
            if (BackColor != Color.Transparent)
            {
                graphics.FillRectangle(bckColor, bounds);
            }
        }

        ColorMatrix colorMatrix = new ColorMatrix();
        colorMatrix.Matrix33 = (float)alpha / 255;
        ImageAttributes imageAttr = new ImageAttributes();
        imageAttr.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

        if (BackgroundImage != null)
        {
            graphics.DrawImage(BackgroundImage, bounds, 0, 0, Width, Height, GraphicsUnit.Pixel, imageAttr);
        }

        if (Text != string.Empty)
        {
            using (Brush txtBrush = new SolidBrush(Color.FromArgb(alpha, ForeColor)))
            {
                graphics.DrawString(Text, Font, txtBrush, 5, 5);
            }
        }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        if (!skipPaint)
        {
            Graphics graphics = e.Graphics;
            Draw(graphics);
        }
    }

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

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

请注意,CreateParams 方法不再存在,我也更改了构造函数。

字段 skipPaint 是为了能够告诉父级在 OnPaint 期间将自己绘制到位图而无需无限递归来知道何时不绘制.

backgroundBuffer 不是实现双缓冲,而是在没有呈现控件的情况下保留父级内容的副本。它会在每次绘制时更新,我知道有更有效的解决方案...* 但这种方法保持简单并且不应该成为瓶颈,除非您在同一个容器上有太多这样的控件。

*:更好的解决方案是每次父级失效时更新它。此外,在同一父级中的所有 FadeControl 之间共享它。

关于c# - C#中的淡入淡出控制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15501501/

相关文章:

c# - Silverlight 中的条件样式?

c# - 如何要求 HTTPS 但不重定向

c# - 防止 XmlSerializer 格式化输出

c# - 批处理文件获取语法错误

vb.net 删除大量动态创建的按钮

javascript - Mapbox GL设置符号图层的不透明度

c# - 同时播放两个声音c#

slider - "Previous-Next"按钮而不是 Mathematica 中的 slider ?

javascript - 添加不透明度悬停过渡会使其他图像闪烁

android - 在 Android 上使用 OpenGL ES 2.0 的 Sprite 不透明度