簡體   English   中英

向flowlayoutpanel添加和刪除自定義控件時,C#winforms可能發生內存泄漏(taskmanager中的許多User對象)

[英]C# winforms possible memoryleak when adding and removing custom controls to flowlayoutpanel (many User objects in taskmanager)

我有一個中等大小的Winforms應用程序(dotNET4.0),在該應用程序上我正在從flowlayoutpanel動態添加和刪除自定義控件。
根據用戶的選擇,這些控件的數量會有所不同。

除了我注意到一些內存泄漏之外,這一切工作正常。 我通過監視Taskmanager中的“ USER Objects”編號來檢查它。 當我將自定義控件添加到flowlayoutpanel時,該數字增加,但是在放置這些控件時並沒有完全降低。

實際上:USER對象的數量下降了很多(比方說,從100%減少到10%:因此90%已正確處理,剩下10%的內存)...

結論:放置用戶控件后,一個或多個對象仍保留在內存中。 我的猜測是代表還是圖像,但是我一無所知……可能是靜態的嗎?

所以我的實際問題是:我在哪里不能從用戶控件中正確釋放內存,我該如何解決呢? 你們能幫忙嗎?

在此先謝謝了!

這是我的用戶控件:
請注意,除了_ Costlinereportdata對象之外,所有內容都可以處理。
(由於它已鏈接並在其他部分使用,因此應繼續使用)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Classes.CustomControls
{
public partial class CostLineReport : UserControl, IDisposable
{
    #region CONSTANTS
    public static int pnlWidth = 870;
    public static int pnlHeightBase = 112;
    public static int pnlHeightExtended = 415;
    public static int pnlHeightCollapsed = 30;
    public static Color ColorNeutral = Color.FromArgb(176, 196, 222);
    public static Color ColorApprove = Color.FromArgb(173, 255, 47);
    public static Color ColorDisapprove = Color.FromArgb(255, 99, 71);
    public static Image ImageApprove = Image.FromFile(@"Images\tableAdd.png");
    public static Image ImageDisapprove = Image.FromFile(@"Images\tableDelete.png");
    public static Image ImageDetail = Image.FromFile(@"Images\tableDetail.png");
    public static Image ImageCollapse = Image.FromFile(@"Images\compile-warning.png");
    public static Image ImageViewRecords = Image.FromFile(@"Images\table.png");
    public static Image ImageCalculationInclude = Image.FromFile(@"Images\add.png");
    public static Image ImageCalculationExclude = Image.FromFile(@"Images\delete.png");
    #endregion

    #region FIELDS
    private CostLineReportData _Costlinereportdata;
    private ToolTip ttpApprove;
    private ToolTip ttpDisapprove;
    private ToolTip ttpCollapse;
    private ToolTip ttpDetail;
    private ToolTip ttpViewRecords;
    private ToolTip ttpCalculation;
    #endregion

    #region CTORS
    public CostLineReport(CostLineReportData line)
    {
        InitializeComponent();
        //
        this._Costlinereportdata = line;
        //
        this.picApprove.Click += new EventHandler(Approve);
        this.picDisapprove.Click += new EventHandler(Disapprove);
        this.picDetail.Click += new EventHandler(ResizeControl);
        this.picCollapse.Click += new EventHandler(CollapseControl);
        this.picViewRecords.Click += new EventHandler(ShowRecords);
        this.picCalculation.Click += new EventHandler(SwitchCalculateState);
        //
        this.rtMainData.Text = _Costlinereportdata.Maintext;
        this.rtDetail.Text = _Costlinereportdata.Detailtext; ;
        this.lblTitle.Text = _Costlinereportdata.Title;
        //
        ttpApprove = new ToolTip();
        ttpDisapprove = new ToolTip();
        ttpCollapse = new ToolTip();
        ttpDetail = new ToolTip();
        ttpViewRecords = new ToolTip();
        ttpCalculation = new ToolTip();
        ttpApprove.SetToolTip(this.picApprove, "Approve this line");
        ttpDisapprove.SetToolTip(this.picDisapprove, "Disapprove this line");
        ttpCollapse.SetToolTip(this.picCollapse, "Collapse this line");
        ttpDetail.SetToolTip(this.picDetail, "Show detail");
        ttpViewRecords.SetToolTip(this.picViewRecords, "View associated recordset");
        ttpCalculation.SetToolTip(this.picCalculation, "Include/Exclude from calculation");
        //
        this.picApprove.Image = CostLineReport.ImageApprove;
        this.picDisapprove.Image = CostLineReport.ImageDisapprove;
        this.picDetail.Image = CostLineReport.ImageDetail;
        this.picCollapse.Image = CostLineReport.ImageCollapse;
        this.picViewRecords.Image = CostLineReport.ImageViewRecords;
        this.picCalculation.Image = CostLineReport.ImageCalculationExclude;
        //
        Recolor();
    }
    #endregion

    #region PROPERTIES
    public RichTextBox MainTextBox
    { get { return this.rtMainData; } }
    public RichTextBox DetailTextBox
    { get { return this.rtDetail; } }
    public Label TitleLabel
    { get { return this.lblTitle; } }
    public PictureBox CalculateControl
    { get { return this.picCalculation; } }
    #endregion

    #region METHODS
    private void Approve(object o, EventArgs e)
    {
        _Costlinereportdata.Approve();
        Recolor();
    }
    private void Disapprove(object o, EventArgs e)
    {
        _Costlinereportdata.Disapprove();
        Recolor();
    }
    private void ResizeControl(object o, EventArgs e)
    {
        _Costlinereportdata.SwitchSize();
        switch(_Costlinereportdata.Viewstate)
        {
            case ViewState.Base:
                this.Height = CostLineReport.pnlHeightBase;
                break;
            case ViewState.Extended:
                this.Height = CostLineReport.pnlHeightExtended;
                break;
        }
    }
    private void CollapseControl(object o, EventArgs e)
    {
        _Costlinereportdata.Collapse();
        if (_Costlinereportdata.Collapsed)
            this.Height = CostLineReport.pnlHeightCollapsed;
        else
            this.Height = CostLineReport.pnlHeightBase;
    }
    private void Recolor()
    {
        switch (_Costlinereportdata.Approvalstate)
        {
            case ApprovalState.Approved:
                foreach (Control c in pnlColorIndicator.Controls)
                {
                    if (c is PictureBox)
                        ((PictureBox)c).BackColor = CostLineReport.ColorApprove;
                }
                pnlColorIndicator.BackColor = CostLineReport.ColorApprove;
                break;
            case ApprovalState.Disapproved:
                foreach (Control c in pnlColorIndicator.Controls)
                {
                    if (c is PictureBox)
                        ((PictureBox)c).BackColor = CostLineReport.ColorDisapprove;
                }
                pnlColorIndicator.BackColor = CostLineReport.ColorDisapprove;
                break;
            case ApprovalState.Neutral:
                foreach (Control c in pnlColorIndicator.Controls)
                {
                    if (c is PictureBox)
                        ((PictureBox)c).BackColor = CostLineReport.ColorNeutral;
                }
                pnlColorIndicator.BackColor = CostLineReport.ColorNeutral;
                break;
        }
    }
    private void ShowRecords(object sender, EventArgs e)
    {
        if (this._Costlinereportdata.Costline.LocalData != null)
        {
            using (Forms.frmCostlineRecords f = new Forms.frmCostlineRecords(this._Costlinereportdata.Costline.LocalData))
            {
                f.ShowDialog();
            }
        }
        else
            MessageBox.Show("This line has no records associated to it. The detailed list cannot be shown.",
                            "Can't show form",
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Information);
    }
    private void SwitchCalculateState(object sender, EventArgs e)
    {
        if (this._Costlinereportdata.Calculationstate == CalculationState.Included)
        {
            this._Costlinereportdata.Calculationstate = CalculationState.Excluded;
            this.picCalculation.Image = CostLineReport.ImageCalculationExclude;
        }
        else
        {
            this._Costlinereportdata.Calculationstate = CalculationState.Included;
            this.picCalculation.Image = CostLineReport.ImageCalculationInclude;
        }
    }
    public void SetCalculateState(CalculationState st)
    {
        switch (st)
        {
            case CalculationState.Included:
                this._Costlinereportdata.Calculationstate = CalculationState.Excluded;
                break;
            case CalculationState.Excluded:
                this._Costlinereportdata.Calculationstate = CalculationState.Included;
                break;
        }
        this.SwitchCalculateState(this, null);
    }
    #endregion

    #region INTERFACE_IMEPLEMENTS
    void IDisposable.Dispose()
    {
        this._Costlinereportdata = null;
        this.picApprove.Image.Dispose();
        this.picCalculation.Image.Dispose();
        this.picCollapse.Image.Dispose();
        this.picDetail.Image.Dispose();
        this.picDisapprove.Image.Dispose();
        this.picViewRecords.Image.Dispose();
        this.rtDetail.Dispose();
        this.rtMainData.Dispose();
        this.lblDivider.Dispose();
        this.lblDivider2.Dispose();
        this.lblDivider3.Dispose();
        this.lblDivider4.Dispose();
        this.lblTextDivider.Dispose();
        this.lblTitle.Dispose();
        this.picApprove.Dispose();
        this.picCalculation.Dispose();
        this.picCollapse.Dispose();
        this.picDetail.Dispose();
        this.picDisapprove.Dispose();
        this.picViewRecords.Dispose();
        this.pnlColorIndicator.Dispose();
        ttpApprove.Dispose();
        ttpDisapprove.Dispose();
        ttpCollapse.Dispose();
        ttpDetail.Dispose();
        ttpViewRecords.Dispose();
        ttpCalculation.Dispose();
        base.Dispose(true);
    }
    #endregion
}
}

這是我的用戶控件的內容(就Winform控件而言)

CostLineReport - System.Windows.Forms.UserControl
-lblDivider - System.Windows.Forms.Label
-lblDivider2 - System.Windows.Forms.Label
-lblDivider3 - System.Windows.Forms.Label
-lblDivider4 - System.Windows.Forms.Label
-lblTextDivider - System.Windows.Forms.Label
-lblTitle - System.Windows.Forms.Label
-lblTopDivider - System.Windows.Forms.Label
-picApprove - System.Windows.Forms.PictureBox
-picCalculation - System.Windows.Forms.PictureBox
-picCollapse - System.Windows.Forms.PictureBox
-picDetail - System.Windows.Forms.PictureBox
-picDisapprove - System.Windows.Forms.PictureBox
-picViewRecords - System.Windows.Forms.PictureBox
-pnlColorIndicator - System.Windows.Forms.Panel
-rtDetail - System.Windows.Forms.RichTextBox
-rtMaindata - System.Windows.Forms.RichTextBox

這是我清除flowlayoutpanel所有內容的方式:

while (pnlCenterRightControls.Controls.Count > 0)
{
    pnlCenterRightControls.Controls[0].Dispose();
}
GC.Collect();

這就是我添加控件的方式

我添加了此塊,因為這還會向用戶控件中的一個內部控件添加一個Delegate(事件)方法。

foreach (Classes.CustomControls.CostLineReportData c in SelectedCostlineReports)
    {
        Classes.CustomControls.CostLineReport r = c.ToControl();
        r.CalculateControl.Click += delegate(object o, EventArgs e) { GetCostCalculation(); };
        this.pnlCenterRightControls.Controls.Add(r);
    }

編輯解決方案
對於那些您感興趣的人,我將代碼更新為以下內容:

從flowlayoutpanel中刪除控件
我也在這里刪除活動的訂閱者

while (pnlCenterRightControls.Controls.Count > 0)
{
    foreach (Control contr in pnlCenterRightControls.Controls)
    {
        Classes.CustomControls.CostLineReport clrp = contr as Classes.CustomControls.CostLineReport;
        clrp.CalculateControl.Click -= GetCostCalculation;
        clrp.Dispose();
    }
}

處理我的物品
(雖然不處理我的靜態圖像)

    new public void Dispose()
    {
        this.Dispose(true);
    }
    /// <summary> 
    /// Clean up any resources being used.
    /// </summary>
    /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
    protected override void Dispose(bool disposing)
    {
        this.picApprove.Click -= Approve;
        this.picDisapprove.Click -= Disapprove;
        this.picDetail.Click -= ResizeControl;
        this.picCollapse.Click -= CollapseControl;
        this.picViewRecords.Click -= ShowRecords;
        this.picCalculation.Click -= SwitchCalculateState;
        this._Costlinereportdata = null;
        this.rtDetail.Dispose();
        this.rtMainData.Dispose();
        this.lblDivider.Dispose();
        this.lblDivider2.Dispose();
        this.lblDivider3.Dispose();
        this.lblDivider4.Dispose();
        this.lblTextDivider.Dispose();
        this.lblTitle.Dispose();
        this.lblToplDivider.Dispose();
        this.picApprove.Dispose();
        this.picCalculation.Dispose();
        this.picCollapse.Dispose();
        this.picDetail.Dispose();
        this.picDisapprove.Dispose();
        this.picViewRecords.Dispose();
        this.pnlColorIndicator.Dispose();
        ttpApprove.Dispose();
        ttpDisapprove.Dispose();
        ttpCollapse.Dispose();
        ttpDetail.Dispose();
        ttpViewRecords.Dispose();
        ttpCalculation.Dispose();
        if (disposing && (components != null))
        {
            components.Dispose();
        }
        base.Dispose(disposing);
    }

如果沒有可用於通過探查器運行的代碼,很難說,但是我注意到的一件事是您不是-=您的事件。 事件的任何處理程序仍將包含對您的對象的引用,因此將使它們不符合垃圾收集的條件。

編輯:

有兩類事件需要擔心,即您內部在控件中的事件(您將需要在dispose方法中手動將其刪除)和那些引用您所控制的外部事件或它們的孩子。

我見過的任何自動方法都存在問題或無法正常工作,通常我認為最好手動進行。 有關自動刪除事件的更多信息,請參見此答案。

如何從控件中刪除所有事件處理程序

從外觀上看,您還應該處理所有這些對象:

ImageApprove.Dispose;
ImageDisapprove.Dispose;
ImageDetail.Dispose;
ImageCollapse.Dispose;
ImageViewRecords.Dispose;
ImageCalculationInclude.Dispose;
ImageCalculationExclude.Dispose;

C#是垃圾收集的,因此在代碼中包含這么多Dispose毫無意義。

通常,您不必關心設計者添加的任何內容,而只需關心您自己的代碼(應該期望生成的代碼是正確的)。

另外,由於UserControl已經實現了protected override void Dispose(bool disposing)您應該真正重寫該方法,而不是嘗試重新實現接口。

我什至不了解您的代碼如何工作……因為您的代碼可能多次調用Dispose在同一對象上。 我猜預定義的控件會忽略第二個調用,否則您將有一個例外,因為對象已經被處置。

設計者添加的任何控件將被自動處理。

在內部對象上調用Dispose絕對是完全廢話。 每個對象應處置其自己的對象。 總是。 期。

picApprove.Image.Dispose();    // BAD CODE!!!

顯然,當您手動添加控件並手動連接事件處理程序時,通常必須同時刪除兩者。 盡管您的語法可能等效,但是我建議您在添加刪除事件時使用相同的簡短語法(+ =代替-=除外)。 這樣,更容易確保它們都匹配。 采用:

r.CalculateControl.Click += GetCostCalculation;

代替:

r.CalculateControl.Click += 
    delegate(object o, EventArgs e) { GetCostCalculation(); };

又為什么要為設計人員添加的控件手動添加事件處理程序? 我看不到在設計器中初始化控件的一部分以及在構造函數中初始化控件的任何優勢。 如果其他人維護該代碼並認為該事件未正確連接,則只會增加回歸錯誤的風險。

new public void Dispose()是另一種完全沒有意義的東西。

最后,我發現您的代碼還有許多其他問題...它沒有遵循許多好的設計規范。 我建議您學習SOLID設計模式。

一些不良做法的例子:

  • 返回內部控件的公共屬性。 通常沒有必要,因為在這些控件上運行的代碼應該位於此類中。 您也可以搜索有關TDA原理的信息。
  • UI和業務代碼之間的緊密耦合。
  • Recolor功能中的重復代碼(DRY原理)。
  • 不遵守.NET約定的命名法,尤其是對於事件處理程序函數名稱的命名法。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM