C#如何强制Panel1和Panel2的Paint事件同时完成?
Posted
技术标签:
【中文标题】C#如何强制Panel1和Panel2的Paint事件同时完成?【英文标题】:C# How to force Paint event of Panel1 and Panel2 complete at the same time? 【发布时间】:2016-05-29 08:52:54 【问题描述】:我有一个 SplitContainer(确切地说是一个 NonFlickerSplitContainer),我将它的两个面板都视为一个单独的画布来进行绘画。我使用 Graphics.DrawImage 方法分别在面板上绘制位图。我先刷新 Panel1,然后刷新 Panel2,这会导致垂直/水平撕裂 - Panel1 的绘画结束,然后 Panel2 的绘画开始 - 这就是原因。我的问题的解决方案是什么?我使用 splitContainer 作为具有前后功能的“位图视频流”的输出。也许我可以以某种方式冻结 UI,直到 Panel2_Paint 结束?
private void splitContainer_Panel1_Paint(object sender, PaintEventArgs e)
if (frameA != null)
if (ORIGINAL_SIZE_SET)
e.Graphics.DrawImage(frameA, 0, 0);
else
e.Graphics.DrawImage(frameA, 0, 0, ClientSize.Width, ClientSize.Height);
private void splitContainer_Panel2_Paint(object sender, PaintEventArgs e)
if (frameB != null)
//...
if (ORIGINAL_SIZE_SET)
e.Graphics.DrawImage(frameB, x, y);
else
e.Graphics.DrawImage(frameB, x, y, ClientSize.Width, ClientSize.Height);
private Bitmap frameA = null;
private Bitmap frameB = null;
private void RefreshOutput(bool refreshClipA = true, bool refreshClipB = true)
if (refreshClipA)
frameA = GetVideoFrame(...);
//...
if (refreshClipB)
frameB = GetVideoFrame(...);
//...
if (refreshClipA)
splitContainer.Panel1.Refresh();
if (refreshClipB)
splitContainer.Panel2.Refresh();
【问题讨论】:
我希望有一种方法可以冻结拆分容器,直到我说它可以再次恢复。不过,我认为没有这样的方法。这样做的肮脏方法可能是在 Panel1 单元上用前一个位图覆盖一个图片框, Panel2_Paint 结束,对吧?考虑一下是不是太肮脏和效率低下? 您可能无法强制两个面板同时绘制。我现在正在使用单个面板进行绘画。我将部分位图绘制在面板上。我将 Mouse_Click 视为先前的 Splitter_Moved 事件。这是一个更有效的解决方案。如果您遇到与我相同的问题,请首先放弃 SplitContainer。我没有写它,但在我问这个问题之前我已经找到了一种同时绘画的方法——尽管不要使用它,因为它绝对是一种非常肮脏和故障的方法。我在 Panel1 和 Panel2 上停靠了两个 PictureBox,并使用了它的 Image 属性。 【参考方案1】:也许我可以在 Panel2_Paint 结束之前冻结 UI?
看看:https://msdn.microsoft.com/en-us/library/system.windows.forms.control.suspendlayout(v=vs.110).aspx
在您的控件上调用 SuspendLayout(),执行所有图形操作,然后调用 ResumeLayout() 一次更新所有内容。该文档中有一个示例。
如果您的绘图操作花费的时间太长,那么在 ResumeLayout() 之后,临时图形工件可能会开始出现在“冻结”区域中。
【讨论】:
我认为布局方法与绘画无关。它们与布局(大小、位置等)有关。诚然,暂停布局将为中间布局节省一些绘制时间,如果您正在更改与布局相关的项目,但我认为这不是 OP 所说的。 @Damien_The_Unbeliever,哦,对不起。我认为这会阻止所有影响外观的操作,包括光栅绘制区域。 不幸的是,暂停表单或 splicontainer 的布局不是解决方案。已经检查了很多次了。【参考方案2】:查看SplitContainer.Invalidate(bool invalidate Children)
的文档。
来自链接:
使控件的特定区域无效并导致向控件发送绘制消息。 (可选)使分配给控件的子控件无效。
因此,不要单独使每个面板无效,只需调用此方法一次,它应该会执行您想要的操作。或者只修改你的代码:
if (refreshClipA && refreshClipB)
splitContainer.Invalidate(true);
else
if (refreshClipA)
splitContainer.Panel1.Refresh();
else if (refreshClipB)
splitContainer.Panel2.Refresh();
基本上我正在做的是如果他们都需要重新粉刷,让splitContainer
处理它,否则单独检查每个并在需要时进行粉刷。
从@DonBoitnott 的评论开始,不要使用Invalidate(true)
,而是使用文档中的Refresh()
:
强制控件使其客户区无效并立即重绘自身和所有子控件。
所以只需将splitContainer.Invalidate(true)
更改为splitContainer.Refresh()
。
【讨论】:
也一直在玩 Invalidate。与单独刷新相比,它似乎没有什么区别。此外,它还破坏了我基于 backgroundworker 的播放功能 @kagetoki 您不能使用线程来解决问题,因为这一切都必须在 UI 线程上完成,所以唯一的方法是让父控件处理任何绘图或直播用它。如果您想同时制作两个视频播放器,那么使用面板和位图可能不是正确的选择 @kagetoki 或者,如果您知道只有一小部分图像发生了变化,您可以只重新绘制面板的一个区域 仅供参考,区别在于Invalidate
:“发送绘制消息”和Refresh
:“强制控件使其客户区无效并立即重绘自身”之间的区别。您无法控制控件何时响应重绘的请求。
@kagetoki 我确实看到了。如果我要回答这个问题,我建议您放弃 splitcontainer 并渲染到单个表面,这样您就始终处于单个 Paint 事件中,您只需将其区域化并相应地进行绘制。【参考方案3】:
我经历过确保两个单独的 panel.Paint() 事件同时完成是不可能的,至少在 WinForms 项目中是不可能的。唯一对我有用的解决方案是 DonBoitnott 建议的。我现在使用单个面板并模拟拆分容器行为。
如果我要回答这个问题,我建议您放弃拆分容器并渲染到单个表面,这样您就始终处于单个 Paint 事件中,您只需将其区域化并相应地绘制。 – DonBoitnott 2016 年 2 月 17 日 17:51
【讨论】:
以上是关于C#如何强制Panel1和Panel2的Paint事件同时完成?的主要内容,如果未能解决你的问题,请参考以下文章
winform 窗体加载的问题,C#里不同Panel中窗体的调用
如何调整 SplitContainer 中 Panel1 和 Panel2 之间的空间?