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事件同时完成?的主要内容,如果未能解决你的问题,请参考以下文章

c# 两个panel

c# 两个panel的问题

winform 窗体加载的问题,C#里不同Panel中窗体的调用

如何调整 SplitContainer 中 Panel1 和 Panel2 之间的空间?

c#中关于splitContainer控件的用法:如何做到不清除控件就能在panel2中打开一个窗体呢?

C# WinForm splitContainer 问题