场景
具有包含Panel
衍生控件的Windows Form Form
衍生表单:
窗体的背景颜色设置为黑色:
public MyForm()
{
InitializeComponent();
base.BackColor = Color.Black;
}
并且面板控制被配置为双缓冲等,如here、here和here所述:
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);
}
}
1条答案
按热度按时间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事件,并执行动画或与鼠标移动相关的操作。第二部分
自行管理
WM_ERASEBKGND
,因为选择是1)总是关闭AllPaintingInWmPaint,并且没有显示背景绘制,或者2)违反双缓冲区的预期,它将总是屏蔽WM_ERASEBKGND。第三部分
现在您可以自己决定AllPaintingInWmPaint的含义。
在这种情况下,我们希望初始消息像正常一样处理。当我们确信.Net和DoubleBuffer端终于开始了,看到我们第一次真实的的绘制发生时,然后在此期间关闭WM_ERASEBKGND。
第四部分
在我的例子中,我最初也使用了OnBackground draw,如果你在OnPaint中自己不透明地绘制每个元素,它就可以工作。这允许我不用长时间使用双缓冲区,直到我开始跟随剪辑并改变计时,这样我也开始看到其他WM_ERASEBKGND副作用和问题。
我可能不需要这个部分了,但如果我的
DoubleBuffer
是关闭的,那么我会。只要我总是画不透明的OnPaint覆盖整个绘图剪辑区。附录:
除此之外...
单独的文本渲染问题。
如果我只渲染250 x 42,比如两行和两个文本渲染,这都发生在一个OnPaint中,用
Diagnostics.Trace.WriteLine
验证,那么每次文本渲染至少一个监视器帧。使它看起来像文本在闪烁。只是2x绘制背景单色,然后2x绘制每行的文本。然而,如果我试图绘制整个客户端区域,比如250 x 512,或者其他什么,比如17行,即使e.剪辑正是这两行,因为我是使它无效的人,那么文本没有闪烁,0闪烁。
这可能是因为时间问题或其他副作用。但这是17次而不是两次,至少有一行在呈现文本之前闪烁整个背景的文本,而且从来没有发生过。如果我试图只呈现剪辑区域中的行,每次都会发生。
Net或Windows都有一些问题。我试过
g.DrawString
和TextRenderer.DrawText
,它们都能做到。如果画2,闪烁,如果试图画17,不闪烁。每次都是这样。OnPaint
回来得太快时?所以......
幸好我用原来的问题做了这个练习。
我可以选择每次只呈现整个客户端,但如果没有上面的示例代码,我将永远无法以“正确的方式”完成它。