简体   繁体   English

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

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

I'm having a WinForms performance issue that might be related to the fact that I dynamically add and then remove hundreds of controls.我遇到了一个 WinForms 性能问题,这可能与我动态添加然后删除数百个控件的事实有关。

EDIT { The application displays a timeline which consists of controls representing historical events. EDIT {应用程序显示一个时间线,该时间线由代表历史事件的控件组成。 Controls are added, removed or moved, depending on the time you jump to.根据您跳转到的时间,添加、删除或移动控件。 The performance issues are not only during the addition and removal of controls (this I can live with), but even after I jump to a time with no historical events (meaning no controls are currently displayed).性能问题不仅存在于添加和删除控件期间(我可以忍受),甚至在我跳转到没有历史事件的时间之后(这意味着当前没有显示控件)。 After jumping around and getting to a time where there are no events on the timeline, some activities in the GUI still take a long amount of time to complete, such as opening menus or opening dialog boxes.在跳转到时间轴上没有事件的时间之后,GUI 中的某些活动仍然需要很长时间才能完成,例如打开菜单或打开对话框。 The strange thing is that other GUI activities, such as pressing buttons, do not stall.奇怪的是,其他 GUI 活动,例如按下按钮,并没有停止。 } }

Although the memory consumption is perfectly stable, can it still be that there is an issue with freeing resources?虽然 memory 的消耗非常稳定,但难道释放资源还是有问题吗?

In order to remove a control, I do two things:为了删除控件,我做了两件事:

  1. Unregister callbacks from all events,从所有事件中取消注册回调,
  2. Call containerPanel.Controls.Remove(control) .调用containerPanel.Controls.Remove(control)

Thanks!谢谢!

As you already observed, it isn't a memory problem.正如您已经观察到的,这不是 memory 问题。 My guess is, that the problem is the simple fact, that your program needs to refresh the screen that often.我的猜测是,问题很简单,您的程序需要经常刷新屏幕。 If you remove and add those "hundreds of controls" in one batch, you can try to disable screen refresh until you are done.如果您批量删除并添加那些“数百个控件”,您可以尝试禁用屏幕刷新,直到完成。
You can do this using SuspendLayout and ResumeLayout :您可以使用SuspendLayoutResumeLayout做到这一点:

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

and

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

You might have trouble due to GC pressure, that is that the garbage collector is running often due to many objects beeing created and then freed.由于 GC 压力,您可能会遇到麻烦,即垃圾收集器经常运行,因为创建并释放了许多对象。 when the GC runs all threads are stopped in their tracks (almost) and the app looks like its freezing当 GC 运行时,所有线程都停止在它们的轨道上(几乎),并且应用程序看起来像是在冻结

i dont think you're doing anything wrong with your removal code, but perhaps you can cache the controls somehow?我认为您的删除代码没有任何问题,但也许您可以以某种方式缓存控件? can you tell us a bit more about you scenario?你能告诉我们更多关于你的场景吗?

-edit- -编辑-

Based on your scenario, i'd suggest sidestepping the whole issue with removing controls and adding new ones and if possible reusing the controls that are already in the view, but switching out their data contexts (binding them to diffrent data) when the view changes.根据您的情况,我建议通过删除控件和添加新控件来回避整个问题,并在可能的情况下重用视图中已经存在的控件,但在视图更改时切换它们的数据上下文(将它们绑定到不同的数据) . In wpf a common name for this approach is UI-virtualization but it can be applied to any ui framework, at least in principle在 wpf 中,这种方法的通用名称是UI 虚拟化,但它可以应用于任何 ui 框架,至少在原则上是这样

Another way around the problem might be to have empty place holder controls for the for all the positions in the timeline that you can scroll to immediately and then add content to as its loaded from disk or whereever.解决该问题的另一种方法可能是为时间轴中的所有位置设置空的占位符控件,您可以立即滚动到这些位置,然后将内容添加到从磁盘或其他任何位置加载的内容。 That way you would not have to affect the layout of the whole time line, you'd just fill in the particular slot the user is viewing.这样您就不必影响整个时间线的布局,您只需填写用户正在查看的特定时段。 This would be even more effective if all the time-line-event-controls are all the same size, then the layout of the entire timeline would be completley unaffected)如果所有时间线事件控件的大小都相同,这将更加有效,那么整个时间线的布局将完全不受影响)

Removing lots of controls one at a time is really not something that WinForms is designed to do well.一次删除大量控件确实不是 WinForms 旨在做好的事情。

Each call to ControlCollection.Remove results in a call to ArrayList.RemoveAt .ControlCollection.Remove的每次调用都会导致对ArrayList.RemoveAt的调用。 If you are removing the last item in the collection this not too bad.如果您要删除集合中的最后一项,这还不错。 If you are removing an item from the middle of the collection Array.Copy will get called to shuffle all of the items after that element in the ArrayList 's internal array down to fill the empty spot.如果您要从集合的中间删除一个项目Array.Copy将被调用以随机排列ArrayList内部数组中该元素之后的所有项目以填充空白点。

There are a couple of approaches you could try:您可以尝试以下几种方法:

Remove all the controls then add back the ones you want to keep删除所有控件,然后添加回您要保留的控件

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();

Remove last to first从最后到第一个删除

/* 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();

The effectiveness of either approach will depend on how many controls you are keeping after removing a batch of controls and the order of the controls in the collection.任一方法的有效性将取决于您在删除一批控件后保留的控件数量以及集合中控件的顺序。

Since Control implements IDisposable you should also Dispose the control after removing it from its container.由于Control实现了IDisposable ,因此您还应该在将控件从其容器中删除后对其进行处置。

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

When doing hundreds of small updates to the UI of a WinForm app there might be performance issues when the UI thread over and over again redraws the interface.在对 WinForm 应用程序的 UI 进行数百次小更新时,当 UI 线程一遍又一遍地重绘界面时,可能会出现性能问题。 This especially occurs if the updates are pushed from a background thread.如果更新是从后台线程推送的,则尤其会发生这种情况。

If this is the problem it can render the UI totally unusable for a while.如果这是问题所在,它可能会使 UI 在一段时间内完全无法使用。 The solution is to make the updates in a way that the UI doesn't redraw until all of the pending updates are done.解决方案是以一种 UI 不会重绘的方式进行更新,直到完成所有挂起的更新。

Okay, this look funny but for me, the only solution which works fine for me was好的,这看起来很有趣,但对我来说,唯一对我有用的解决方案是

  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
  • using suspendLayout() and resumeLayout() methods !!!使用 suspendLayout() 和 resumeLayout() 方法!!!

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

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