繁体   English   中英

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

[英]What is the cleanest way of removing a control from a container?

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

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

虽然 memory 的消耗非常稳定,但难道释放资源还是有问题吗?

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

  1. 从所有事件中取消注册回调,
  2. 调用containerPanel.Controls.Remove(control)

谢谢!

正如您已经观察到的,这不是 memory 问题。 我的猜测是,问题很简单,您的程序需要经常刷新屏幕。 如果您批量删除并添加那些“数百个控件”,您可以尝试禁用屏幕刷新,直到完成。
您可以使用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 ,因此您还应该在将控件从其容器中删除后对其进行处置。

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() 方法!!!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM