在 C# 中闪烁 SlideButton(DoubleBuffer 显然不工作)

Flickering in C# doing a SlideButton (DoubleBuffer not working apparently)

我在 C# WinForms 项目中遇到了闪烁问题。

我简单做了一个SlideButton控件:这是代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
using System.ComponentModel.Design;

namespace DELTA.UI.Search
{
    [Designer("System.Windows.Forms.Design.ParentControlDesigner,System.Design", typeof(IDesigner))]
    public partial class Slide : UserControl
    {
        public Boolean IsShown
        {
            get { return button.IsOpen; }
            set
            {
                button.IsOpen = value;
            }
        }

        private int isShownHeight;

        public Slide()
        {
            InitializeComponent();
            this.SetStyle(
                System.Windows.Forms.ControlStyles.UserPaint |
                System.Windows.Forms.ControlStyles.AllPaintingInWmPaint |
                System.Windows.Forms.ControlStyles.Opaque |
                System.Windows.Forms.ControlStyles.OptimizedDoubleBuffer,
                true);
            this.DoubleBuffered = true;
            this.ResizeRedraw = true;
            isShownHeight = this.Height;
            this.Height = button.Height;
        }

        private void boton_MouseEnter(object sender, EventArgs e)
        {
            button.BackColor = Color.Gray;
            button.Cursor = Cursors.Hand;
        }

        private void boton_MouseLeave(object sender, EventArgs e)
        {
            button.BackColor = Color.White;
            button.Cursor = Cursors.Arrow;
        }

        private void button_OnSlideStateChangedEvent(bool isOpen)
        {
            Debug.WriteLine(isOpen);
            if (!timerSlide.Enabled)
            {
                timerSlide.Start();
            }
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);

            using (Brush b = new SolidBrush(this.BackColor))
                e.Graphics.FillRectangle(b, 0, 0, this.Width, this.Height);

            int rectWidth = this.Width / 10;
            e.Graphics.FillRectangle(Brushes.LightGray, this.Width / 2 - rectWidth / 2, this.Height - 3, rectWidth, 3);
        }

        private void timerSlide_Tick(object sender, EventArgs e)
        {
            if (IsShown && this.Height + button.Height < isShownHeight)
            {
                int crecimiento = (isShownHeight - button.Height) / 35;
                this.Height += this.Height + crecimiento >= isShownHeight ? isShownHeight - this.Height : crecimiento;
            }
            else if (!IsShown && this.Height > button.Height)
            {
                int decrecimiento = (isShownHeight - button.Height) / 35;
                this.Height -= this.Height - decrecimiento <= button.Height ? this.Height - button.Height : decrecimiento;
            }
            else
            {
                timerSlide.Stop();
            }
        }

        private void Slide_Resize(object sender, EventArgs e)
        {
            //this.Invalidate(new Rectangle(0, this.Height - 12, this.Width, 12));
        }
    }
}

如您所见,我所做的唯一绘制操作是绘制控件底部的一个小矩形。这些控件仅包含一个带有 isOpen 标志的自定义按钮,该标志在单击时发生变化并触发 OnSlideStateChangedEvent 事件。按钮的代码是:

using DELTA.Util;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace DELTA.UI.Search
{
    class SlideButton : Button
    {
        private Boolean isOpen = true;
        public Boolean IsOpen
        {
            get { return isOpen; }
            set
            {
                isOpen = value;

                /* Refresh the button with the new appearence */
                this.Refresh();

                /* Call the event */
                if (OnSlideStateChangedEvent != null)
                    OnSlideStateChangedEvent(isOpen);
            }
        }

        public SlideButton()
            : base()
        {
            this.BackColor = Color.White;
            if (!DesignMode)
                IsOpen = false;

            this.DoubleBuffered = true;
        }

        /// <summary>
        /// Event for the Slide
        /// </summary>
        [Category("ClickCustomEvents")]
        [Description("Fired when this is clicked")]
        public event OnSlideStateChanged OnSlideStateChangedEvent;
        /// <summary>
        /// Delegate for the Change of state
        /// </summary>
        /// <param name="isOpen"></param>
        public delegate void OnSlideStateChanged(Boolean isOpen);

        /// <summary>
        /// Override to avoid the default OnClick
        /// </summary>
        /// <param name="e"></param>
        protected override void OnClick(EventArgs e)
        {
            IsOpen = !isOpen;
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            // TODO Use the isOpen value to paint it with a filled rectagle or 2 borders

            /* SETTINGS -> for open/close states of the button */
            Color topColor = Color.LightGray;
            Color otherSidesColor = Color.LightGray;
            Color backgroud = Color.LightGray;
            SingletonImages arrow = SingletonImages.ARROW_DOWN;
            Rectangle arrowPosition = new Rectangle(this.Width - 30, this.Height - 26, 20, 20);

            if (isOpen)
            {
                topColor = Color.LightGray;
                otherSidesColor = Color.Transparent;
                backgroud = Color.White;
                arrow = SingletonImages.ARROW_UP;
            }

            /* PAINT ZONE */
            /* Background */
            using (SolidBrush brush = new SolidBrush(backgroud))
                e.Graphics.FillRectangle(brush, new Rectangle(0, 0, this.Width, this.Height));

            /* Border */
            ControlPaint.DrawBorder(e.Graphics, this.ClientRectangle,
            otherSidesColor, 2, ButtonBorderStyle.Solid,
            topColor, 2, ButtonBorderStyle.Solid,
            otherSidesColor, 2, ButtonBorderStyle.Solid,
            otherSidesColor, 2, ButtonBorderStyle.Solid);

            /* Text -> Padding = 5 (This can be changed to use the padding property of the control if need) */
            e.Graphics.DrawString(this.Text, this.Font, Brushes.Black, new Point(10, 8));

            /* Arrow */
            e.Graphics.DrawImage(arrow.Image, arrowPosition);
        }
    }
}

SingletonImages 只是一个 "enum" 来保存图像,以便只有一个实例。

我将 SlideButton class 的 DoubleBuffered 属性 更改为 true 但这是不必要的,因为 SlideButton 没有 flickr。闪烁的是幻灯片class。如果您需要更多信息,请询问您想要什么。任何提示?

有一天我 运行 也遇到了这个问题。最简单(也许不是最优雅)的解决方案是删除 OnPaintBackground() 并将其添加到正常的 OnPaint() 方法中,否则每次控件无效时背景都会绘制到最后一个 Gfx 上闪烁:

    public ctor()
    {
        // this does just work with OptimizedDoubleBuffer set to true
        this.SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
    }

    protected override void OnPaintBackground(PaintEventArgs e)
    {
        // do NOT paint the background here but in OnPaint() to prevent flickering!
        //base.OnPaintBackground(e);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        // do the background and the base stuff first (if required)
        base.OnPaintBackground(e);
        base.OnPaint(e);

        // ... custom paint code goes here ...
    }

你可以试试看,祝你好运。