winforms 如何设置所有者描述控件的初始颜色

hi3rlvi2  于 2022-11-16  发布在  其他
关注(0)|答案(1)|浏览(163)

场景

具有包含Panel衍生控件的Windows Form Form衍生表单:

窗体的背景颜色设置为黑色:

public MyForm()
{
    InitializeComponent();

    base.BackColor = Color.Black;
}

并且面板控制被配置为双缓冲等,如hereherehere所述:

public MyPanel()
{
    base.DoubleBuffered = true;

    SetStyle(ControlStyles.AllPaintingInWmPaint, true);
    SetStyle(ControlStyles.ResizeRedraw, true);
    SetStyle(ControlStyles.UserPaint, true);
    SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

    UpdateStyles();
}

实际绘制是在MyPanel中的这些覆盖内完成的:

protected override void OnPaintBackground(PaintEventArgs e)
{
    e.Graphics.Clear(Color.Black);
}

protected override void OnPaint(PaintEventArgs e)
{
    e.Graphics.Clear(Color.Black);
}

错误行为

有时,当窗体 * 最初 * 显示时,我的所有者绘制的面板很快就被绘制为白色,然后我自己的绘制代码实际上被调用:

调用我的绘图代码后,一切都正确绘制,我再也无法重现白色背景:

即使调整窗口和面板的大小也不会使其闪烁成白色。

执行错误的行为

如果在窗体的Shown事件处理程序中设置sleep,则可以强制面板初始化为白色:

private void MyForm_Shown(object sender, EventArgs e)
{
    Thread.Sleep(1000);
}

在调用我的所有者绘制的绘制代码之前,面板显示为白色1000毫秒。
我的问题
当我有一个所有者绘制/自定义绘制的面板时,我如何避免最初的白色显示?
我的目标是让控件从一开始就“知道”它的初始背景色(在我的示例中是黑色),而不是在它最初显示之后。
一些想法
我试过玩各种各样的东西,包括CreateParams property,但没有明显的成功。
我最初的想法是提供一些initial background color through the WNDCLASSEX structure,但在挖掘了Reference Source之后,我仍然不知道这是否可能,是否会有帮助。

完整代码

为了安全起见,以下是我的全部代码。
MyForm.cs:

public partial class MyForm : Form
{
    public MyForm()
    {
        InitializeComponent();

        base.BackColor = Color.Black;
    }

    private void MyForm_Shown(object sender, EventArgs e)
    {
        Thread.Sleep(1000);
    }
}

MyPanel.cs:

public class MyPanel : Panel
{
    public MyPanel()
    {
        base.DoubleBuffered = true;

        SetStyle(ControlStyles.AllPaintingInWmPaint, true);
        SetStyle(ControlStyles.ResizeRedraw, true);
        SetStyle(ControlStyles.UserPaint, true);
        SetStyle(ControlStyles.OptimizedDoubleBuffer, true);

        UpdateStyles();
    }

    protected override void OnPaintBackground(PaintEventArgs e)
    {
        e.Graphics.Clear(Color.Black);
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        e.Graphics.Clear(Color.Black);
    }
}
nwnhqdif

nwnhqdif1#

我有一个自定义控件,几乎完全是自绘制的,它是在PropertyGrid的下拉机制中动态添加到窗体中的。
如果我没看错的话,你的问题基本上是同一个问题。
我设置了BackColor,很好。但是设置了DoubleBuffer,它似乎只是忽略了它一点。
考虑到所有现有的关于这个问题的评论,我能够有这个解决方案,并希望这些细节能帮助其他人做出自己的。

问题1

除非我每隔OnPaint重新绘制整个控件,否则控件会闪烁。
如果正确的绘制只尝试绘制与e.ClipRectangle相交的东西,那么它会实时闪烁,就像鼠标移动时无效的效果一样。尝试绘制整个东西,没有问题。
我实时观察跟踪输出,观察我所有的绘制和无效打印,从来没有一个时间应该介绍闪烁自己,对自己直接。

问题2

如果我打开DoubleBuffered,那么它会闪烁得很厉害,因为每次打开下拉菜单时都会显示控件。从白色背景只持续100-200毫秒,至少,然后一步就突然变成黑色和渲染前景。

问题3

我实际上从来不需要双缓冲区。问题1和2都总是与WM_ERASEBKGND有关。
实际的原始闪烁似乎是由WM_ERASEBKGND非常短暂地明显地敲打我已经画好的东西引起的,就在我再次画它之前。在我的情况下并不真的需要实际的双缓冲。由于某种原因,当我盲目地画整个列表时,可能是时间不同,在可以看到它之前就在擦除上画了。
尽管如此,如果我打开DoubleBuffered,通过打开AllPaintingInWmPaint删除WM_ERASEBKGND,那么初始背景将不会被绘制,直到我认为双缓冲和绘制过程的工作方式,所有的方式通过第一次。

问题4

如果我让WM_ERASEBKGND“只是发生”,那么它仍然是双重绘制,我不知道它是否或何时可能最终为其他人闪烁。
如果我只打开SetStyle(OptimizedDoubleBuffer,那么我现在知道我会让初始背景绘制,而不会在显示时闪烁。但我也知道我会使用双缓冲区在显示后的整个控件生命周期中屏蔽WM_ERASEBKGND
所以......
我做了这样的事情:

第一部分

如果控件的用户发现需要双缓冲来执行可能闪烁的操作,请创建一种方法,让他们轻松地启用它,而无需强制AllPaintingInWmPaint。例如,如果他们希望使用Paint或DrawXXX事件,并执行动画或与鼠标移动相关的操作。

bool _isDoubleBuffer;
    [Category("Behavior")]
    public virtual bool DoubleBuffer
    {
        get { return _isDoubleBuffer; } // dont care about real SetStyle state
        set
        {
            if (value != DoubleBuffer)
            {
                _isDoubleBuffer = value;

                SetStyle(ControlStyles.OptimizedDoubleBuffer, value);
            }
        }
    }

第二部分

自行管理WM_ERASEBKGND,因为选择是1)总是关闭AllPaintingInWmPaint,并且没有显示背景绘制,或者2)违反双缓冲区的预期,它将总是屏蔽WM_ERASEBKGND。

protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_ERASEBKGND:
                if (_hasPaintForeground && _isDoubleBuffer)
                    return;
        }
        base.WndProc(ref m);
    }

第三部分

现在您可以自己决定AllPaintingInWmPaint的含义。
在这种情况下,我们希望初始消息像正常一样处理。当我们确信.Net和DoubleBuffer端终于开始了,看到我们第一次真实的的绘制发生时,然后在此期间关闭WM_ERASEBKGND。

bool _hasPaintForeground;
    protected override void OnPaint(PaintEventArgs e)
    {
        // your paint code here, if any
        
        base.OnPaint(e);
        
        if (!_hasPaintForeground) // read cheaper than write every time
        {
            _hasPaintForeground = true;
        }
    }

第四部分

在我的例子中,我最初也使用了OnBackground draw,如果你在OnPaint中自己不透明地绘制每个元素,它就可以工作。这允许我不用长时间使用双缓冲区,直到我开始跟随剪辑并改变计时,这样我也开始看到其他WM_ERASEBKGND副作用和问题。

protected override void OnPaintBackground(PaintEventArgs e)
    {                                   // Paint default background until first time paint foreground.
        if (!_hasPaintForeground)       //   This will kill background image but stop background flicker
        {                               //     and eliminate a lot of code, and need for double buffer.

            base.OnPaintBackground(e);
        }                               //   PaintBackground is needed on first show so no white background 
    }

我可能不需要这个部分了,但如果我的DoubleBuffer是关闭的,那么我会。只要我总是画不透明的OnPaint覆盖整个绘图剪辑区。

附录:

除此之外...
单独的文本渲染问题。
如果我只渲染250 x 42,比如两行和两个文本渲染,这都发生在一个OnPaint中,用Diagnostics.Trace.WriteLine验证,那么每次文本渲染至少一个监视器帧。使它看起来像文本在闪烁。只是2x绘制背景单色,然后2x绘制每行的文本。
然而,如果我试图绘制整个客户端区域,比如250 x 512,或者其他什么,比如17行,即使e.剪辑正是这两行,因为我是使它无效的人,那么文本没有闪烁,0闪烁。
这可能是因为时间问题或其他副作用。但这是17次而不是两次,至少有一行在呈现文本之前闪烁整个背景的文本,而且从来没有发生过。如果我试图只呈现剪辑区域中的行,每次都会发生。
Net或Windows都有一些问题。我试过g.DrawStringTextRenderer.DrawText,它们都能做到。如果画2,闪烁,如果试图画17,不闪烁。每次都是这样。

  • 也许与在鼠标指针附近绘制文本有关,当OnPaint回来得太快时?
  • 也许如果我画了足够多的东西或者OnPaint需要更长的时间才能返回,它无论如何都在做双缓冲?

所以......

幸好我用原来的问题做了这个练习。
我可以选择每次只呈现整个客户端,但如果没有上面的示例代码,我将永远无法以“正确的方式”完成它。

相关问题