从容器中删除控件的最干净的方法是什么

本文关键字:方法 是什么 控件 删除 | 更新日期: 2023-09-27 17:57:58

我遇到了WinForms性能问题,这可能与我动态添加然后删除数百个控件有关。

编辑{应用程序显示一个时间线,该时间线由表示历史事件的控件组成。控件的添加、删除或移动取决于您跳转到的时间。性能问题不仅发生在添加和删除控件期间(我可以接受),甚至发生在我跳转到没有历史事件的时间之后(意味着当前没有显示控件)。在跳转到时间线上没有事件的时间后,GUI中的一些活动仍然需要很长时间才能完成,例如打开菜单或打开对话框。奇怪的是,其他GUI活动(如按下按钮)不会停滞}

尽管内存消耗非常稳定,但是否仍然存在释放资源的问题?

为了删除控件,我做了两件事:

  1. 注销所有事件的回调
  2. 呼叫containerPanel.Controls.Remove(control)

谢谢!

从容器中删除控件的最干净的方法是什么

正如您已经观察到的,这不是内存问题。我的猜测是,问题很简单,你的程序需要经常刷新屏幕。如果你在一批中删除和添加"数百个控件",你可以尝试禁用屏幕刷新,直到完成为止
您可以使用SuspendLayoutResumeLayout:执行此操作

SuspendLayout();
for(...)
    AddControl(...);
ResumeLayout();

SuspendLayout();
for(...)
    RemoveControl(...);
ResumeLayout();

您可能会因为GC压力而遇到问题,也就是说,由于创建了许多对象然后释放了这些对象,垃圾收集器经常在运行。当GC运行时,所有线程都停止在它们的轨道上(几乎),应用程序看起来像是冻结的

我不认为你的删除代码有任何错误,但也许你可以缓存控件?你能告诉我们更多关于你的场景吗?

-编辑-

根据您的场景,我建议通过删除控件和添加新控件来回避整个问题,如果可能的话,重新使用视图中已经存在的控件,但在视图更改时切换它们的数据上下文(将它们绑定到不同的数据)。在wpf中,这种方法的通用名称是UI虚拟化,但它可以应用于任何UI框架,至少在原则上是

解决这个问题的另一种方法可能是为时间线中的所有位置设置空的占位符控件,您可以立即滚动到这些位置,然后在从磁盘或任何地方加载内容时将其添加到中。这样就不必影响整个时间线的布局,只需填充用户正在查看的特定时段即可。如果所有时间线事件控件的大小都相同,那么整个时间线的布局将完全不受影响,这将更加有效)

一次删除一个控件并不是WinForms的设计初衷。

ControlCollection.Remove的每次调用都会导致对ArrayList.RemoveAt的调用。如果您要删除集合中的最后一个项目,这还不错。如果您要从集合的中间移除一个项目,Array.Copy将被调用来向下洗牌ArrayList的内部数组中该元素之后的所有项目,以填补空白。

你可以尝试以下几种方法:

删除所有控件,然后重新添加要保留的控件

ArrayList l = new ArrayList();
foreach (Control c in Controls){
    if (ShouldKeepControl(c))
        l.Add(c);
    else
        UnhookEvents(c);
}
SuspendLayout();
Controls.Clear();
Controls.AddRange((Control[])l.ToArray(typeof(Control)));
ResumeLayout();

从倒数第一删除

/* Example assumes your controls are in the best possible
   order for this technique. If they were mostly at the end
   with a few in the middle a modified version of this
   could still work. */
int i = Controls.Count - 1;
bool stillRemoving = true;
SuspendLayout();
while (stillRemoving && i >= 0){
    Control c = Controls[i];
    if (ShouldRemoveControl(c)){
        UnhookEvents(c);
        Controls.RemoveAt(i);
        i--;
    }else{
        stillRemoving = false;
    }
}
ResumeLayout();

这两种方法的有效性将取决于删除一批控件后保留的控件数量以及控件在集合中的顺序。

由于Control实现了IDisposable,您还应该在将控件从其容器中移除后对其进行Dispose。

containerPanel.Controls.Remove(control);
control.Dispose();

当对WinForm应用程序的UI进行数百次小更新时,UI线程一次又一次地重新绘制界面时,可能会出现性能问题。如果更新是从后台线程推送的,则尤其会发生这种情况。

如果这是问题所在,它可能会使UI在一段时间内完全不可用。解决方案是以一种在完成所有挂起的更新之前不会重新绘制UI的方式进行更新。

好的,这看起来很有趣,但对我来说,唯一对我有效的解决方案是

  For i = 0 To 3 ' just to repeat it !!
            For Each con In splitContainer.Panel2.Controls
                splitContainer.Panel2.Controls.Remove(con)
                con.Dispose()
                'con.Visible = False
            Next
        Next
  • 使用suspendLayout()和resumeLayout(!)方法