简体   繁体   English

C#中的衰落控制

[英]Fading control in C#

I'm tring to build a Control derived class which supports an Opcacity property. 我想构建一个支持Opcacity属性的Control派生类。
This control could host both text and image and will beable to fade them out and in. 这个控件可以同时容纳文本和图像,并且可以将它们淡出和放入。
Here is my code: 这是我的代码:

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);
    }
}

I've putted the control on a form which has a timer on it. 我把控件放在一个有定时器的表格上。
The timer set the control's opacity from 0 to 100 and back and its working well. 计时器将控件的不透明度从0设置为100并返回并且工作正常。
The problem I'm trying to solved is that the control flickers while changing its opacity. 我试图解决的问题是控件在改变其不透明度时会闪烁。
Setting the control to ControlStyles.DoubleBuffer will make the control invisible on the form. ControlStyles.DoubleBuffer设置为ControlStyles.DoubleBuffer将使控件在窗体上不可见。

Any advice will be welcome. 欢迎任何建议。

I was unable to use both a double buffer and WS_EX_TRANSPARENT (0x20) for the transparent background. 我无法同时使用双缓冲区和WS_EX_TRANSPARENT (0x20)作为透明背景。 So I decided to implement the transparent background by copying the content of the parent control and use double buffer to prevent flicker. 所以我决定通过复制父控件的内容来实现透明背景,并使用双缓冲来防止闪烁。

The following is the final source code, tested and working: 以下是最终的源代码,测试和工作:

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);
    }
}

Note that the method CreateParams is no longer present, also I've changed the contructor. 请注意,方法CreateParams不再存在,我也改变了构造函数。

The field skipPaint is to know when not to paint in order to be able to able to tell the parent to draw itself to a bitmap during OnPaint without having infinite recursion. 字段skipPaint是知道何时不绘制,以便能够告诉父级在OnPaint期间将自己绘制到位图而不具有无限递归。

backgroundBuffer is not to implement double buffering, but to keep a copy of the contents of the parent without the control rendered. backgroundBuffer不是为了实现双缓冲,而是为了保留父对象内容的副本而不提供控件。 It is updated each paint, I know there are more efficient solutions...* yet this approach keeps it simple and shouldn't be a bottleneck unless you have too many of these controls on the same container. 它更新了每一个颜料,我知道有更有效的解决方案... *但这种方法保持简单,不应该成为瓶颈,除非你在同一个容器上有太多这些控件。

*: A better solution would be to update it each time the parent invalidates. *:更好的解决方案是每次父项无效时更新它。 Futhermore shared it among all the FadeControls in the same parent. Futhermore在同一父母的所有FadeControl中共享它。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM