缓慢处理UserControl

时间:2012-05-22 09:45:47

标签: c# .net winforms devexpress

我的表单上有两个控件:一个列表框,其中包含一个工作列表和一个面板,用作显示有关其工作的详细信息(卡片)的容器。 当用户点击工作人员的姓名时,我会在面板上显示卡片。卡是一个用户控件,具有一些相当简单的UI(2个组框,3个文本框和几个标签)和简单的逻辑(设置标签的前景色)。

卡片是在运行时创建的。之前的卡片已从面板中移除,新的卡片将被添加 - 每个工人的卡片数量为1到4.这里很有趣。 一切正常,直到约。第五次点击工人。似乎GC启动并且需要大约两秒钟(0.3s x先前移除的卡的数量)用于旧卡(先前移除的)以便处理并且显示新的卡。如果之间的工作之间的移动工作很好,那么在那一点上会变得非常缓慢。 经过一番探索后,我找到了问题,在我的usedcontrol的Dispose方法中。致电base.Dispose()大约需要0.3秒。

这是我的代码:

private void ShowCards(List<Work> workItems) {
  var y = 5;
  panelControl1.SuspendLayout();
  panelControl1.Controls.Clear();

  foreach (var work in workItems) {
    var card = new Components.WorkDisplayControl(work);
    card.Top = y;
    card.Left = 10;

    y += card.Height + 5;

    panelControl1.Controls.Add(card);
  }

  panelControl1.ResumeLayout(true);
  Application.DoEvents();
}

到目前为止我尝试过:

  • 隐藏卡而不是处理 - 移动时它的工作速度更快 工人之间,但在结束表格时支付罚款
  • 隐藏牌并有一个单独的线程来处理它们 - 没有改变
  • 测试,添加10张卡并立即处理 - 慢
  • 测试添加10张卡片并立即将它们置于构造函数中 - 快速!
  • 用“正常”替换DevExpress控件 - 无变化
  • 在更换工作人员时手动处理旧卡而不是删除它们 - 工人之间的每次移动都会变慢:for (var ix = panelControl1.Controls.Count - 1; ix >= 0; --ix) { panelControl1.Controls[ix].Dispose();}
  • 描述它 - 这就是我在Dispose中找到问题的方式。我可以追溯到Control.DestroyHandle
  • 在我控制的Controls.Clear()方法中调用Dispose - 超级奇怪的行为和异常
  • 从我的usercontrol中删除了所有控件 - 更快一点,但仍然很慢
  • 在删除和添加卡片时隐藏panelControl1 - 无需更改
  • 关闭背景GC - 无变化
  • 使用AddRange
  • 添加卡片

由于从构造函数调用时相同的功能运行得很快,我相信理由必须在(控件)句柄中的某处。

我只是找不到这种奇怪行为的原因。我很感激任何想法......

更新:在研究GC和Control.Dispose之间的连接时,我发现this excellent answer

2 个答案:

答案 0 :(得分:1)

在[DevExpress的支持不成功]之后,我做了一些测试并玩了代码,最后找到了解决方案。

诀窍是在处置之前清除UserControl上的控件。

可以修改UC上的Dispose方法(此解决方案在某些情况下有效,但并非在所有情况下都有效)或者隐藏UC而不是将其从面板中移除并清除其Controls

解决方案1:

protected override void Dispose(bool disposing) {
  if (disposing && (components != null)) {
    components.Dispose();
  }

  Controls.Clear(); // <--- Add this line

  base.Dispose(disposing); 
  }

解决方案2:

向UC添加新方法:

public void ClearControls() {
  Controls.Clear();
}

并在我的原始问题中替换此行

panelControl1.Controls.Clear();

用这个:

for (var ii = panelControl1.Controls.Count - 1; ii >= 0; --ii) { 
  var wdc = panelControl1.Controls[ii] as Components.WorkDisplayControl;
  wdc.Visible = false;
  wdc.ClearControls();
}

它的工作速度(至少)快20倍,这已经足够了。

答案 1 :(得分:0)

问题的原因既与DevExpress也不与标准控件有关。但是,它与创建和销毁控制句柄有关。改善你的卡片#39;实现,在可能的情况下避免这些操作。我建议你使用缓存卡:

void ShowCards(List<Work> workItems) {
    cardsPanel.SuspendLayout();
    CacheCards(cardsPanel.Controls);
    int y = 5;
    foreach(var work in workItems) {
        var card = GetCardFromCache(work);
        card.Top = y;
        card.Left = 10;
        y += card.Height + 5;
        cardsPanel.Controls.Add(card);
    }
    cardsPanel.ResumeLayout(true);
}
//
Stack<WorkDisplayControl> cache;
void CacheCards(Control.ControlCollection controls) {
    if(cache == null)
        cache = new Stack<WorkDisplayControl>();
    foreach(WorkDisplayControl wdc in controls)
        cache.Push(wdc);
    controls.Clear();
}
WorkDisplayControl GetCardFromCache(Work data) {
    WorkDisplayControl result = (cache.Count > 0) ?
        cache.Pop() : new WorkDisplayControl();
    result.InitData(data);
    return result;
}

优化卡片的下一步是减少使用的控制手柄的总数。由于您使用的是DevExpress控件,因此最适合您的选项是XtraLayoutControl。使用XtraLayoutControl可以显着减少控制句柄的总数。它只为您所描述的布局创建4个手柄(3个编辑器,在几个组框内有标签),而不是使用标准控件时的8个手柄。 XtraLayoutControl不会为编辑器的标签,组,选项卡创建句柄。 另请参阅XtraGrid LayoutView - 它提供了使用网格数据绑定架构和卡片的好处。没有任何额外编码的布局虚拟化..