Контроль замирания в C#

Я пытаюсь построить Control производный класс, который поддерживает Opcacity имущество.
Этот элемент управления может содержать как текст, так и изображение, и будет способен затемнять их.
Вот мой код:

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 сделает элемент управления невидимым в форме.

Любой совет будет приветствоваться.

1 ответ

Решение

Я не смог использовать как двойной буфер, так и 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 не для реализации двойной буферизации, а для сохранения копии содержимого родительского элемента без визуализации элемента управления. Он обновляется с каждой краской, я знаю, что есть более эффективные решения...* но этот подход делает его простым и не должен быть узким местом, если у вас не слишком много этих элементов управления в одном контейнере.

*: Лучшим решением будет обновлять его каждый раз, когда родитель делает недействительным. Futhermore поделился этим между всеми FadeControls в том же родителе.

Другие вопросы по тегам