Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How many controls can I create and show in a Windows Forms form?

Tags:

c#

winforms

I'm developing a C# Windows Forms application. I know that too many controls makes a terrible user experience, and I would keep the number of controls visible at the same time small (for example, as suggested in Upper (reasonable) limit to number of user control instances), but some of controls may be hidden (i.e. using tabs or layers).

Is there an absolute maximum number of controls I can show on a form, and if there is one, what is it?

like image 692
wshcdr Avatar asked Oct 16 '25 03:10

wshcdr


1 Answers

Actually, there is a limit, though not hard coded and configurable - and its default value is 10,000 (why?).

Each control is creating a user-object in the operating system, and the default maximum number of active user-objects per process in Windows is 10,000 - so once you will try to add the 10,001 control to the form you should get an exception of type System.ComponentModel.Win32Exception with the message:

Error creating window handle.

Of course, no user would want to see a form with 10,000 controls, so unless you have a leak somewhere, this should never happen. (And of course, the only reason I know about it is just because I've had this leak in the past - I've had user controls listening to events from a static class and didn't unhook them in the Dispose method, so even after they where cleared from the screen, they were still alive...)

When you get this exception, look at the Processes tab of Task manager. Click the View menu, inside click Select Columns, mark the checkbox of USER Objects (it's not visible by default; that fact probably cost me a couple of hours when attempting to understand my leak) - and then sort by that column. If you see your application at the top, with 10,000 user objects - then you know your application have reached the maximum number of controls - which means you have a leak you need to fix.

Please note that even if you remove the controls from the form, if they have some other reference they will not get disposed and you will, eventually, get this error if your application is running enough time.

If anyone is interested, here is the code I've used to re-create the error (including designer code)

using System;
using System.Collections.Generic;
using System.Windows.Forms;

namespace UserObjectsLeak
{
    public partial class FrmUserObjectsLeak : Form
    {
        // This is used to keep references of the labels being added dynamically.
        static readonly List<Label> Labels = new List<Label>();

        public FrmUserObjectsLeak()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            for (var i = 0; i < 11000; i++)
            {
                var label = new Label()
                {
                    Text = i.ToString(),
                    Width = 50
                };
                Labels.Add(label);
                try
                {
                    panel1.Controls.Add(label);
                }
                catch (System.ComponentModel.Win32Exception ex)
                {
                    lblException.Text = ex.ToString();
                    return;
                }

                lblControlsCount.Text = (i).ToString();

                // Quick and dirty just to show the progress...
                Application.DoEvents();

                if (i % 500 == 0)
                {
                    // Remove all labels from the panel,
                    // keep only the reference in the list.
                    panel1.Controls.Clear();
                }
            }
        }

        private void btnClear_Click(object sender, EventArgs e)
        {
            panel1.Controls.Clear();
            Labels.Clear();
            lblControlsCount.Text = "";
            lblException.Text = "";
        }

        #region Designer code

        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <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)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.label1 = new System.Windows.Forms.Label();
            this.label2 = new System.Windows.Forms.Label();
            this.btnStart = new System.Windows.Forms.Button();
            this.lblControlsCount = new System.Windows.Forms.Label();
            this.btnClear = new System.Windows.Forms.Button();
            this.panel1 = new System.Windows.Forms.FlowLayoutPanel();
            this.lblException = new System.Windows.Forms.Label();
            this.SuspendLayout();
            // 
            // label1
            // 
            this.label1.AutoSize = true;
            this.label1.Location = new System.Drawing.Point(15, 17);
            this.label1.Name = "label1";
            this.label1.Size = new System.Drawing.Size(191, 13);
            this.label1.TabIndex = 0;
            this.label1.Text = "Click the button to start adding controls";
            // 
            // label2
            // 
            this.label2.AutoSize = true;
            this.label2.Location = new System.Drawing.Point(12, 95);
            this.label2.Name = "label2";
            this.label2.Size = new System.Drawing.Size(77, 13);
            this.label2.TabIndex = 1;
            this.label2.Text = "controls count:";
            // 
            // btnStart
            // 
            this.btnStart.Location = new System.Drawing.Point(12, 49);
            this.btnStart.Name = "btnStart";
            this.btnStart.Size = new System.Drawing.Size(75, 23);
            this.btnStart.TabIndex = 2;
            this.btnStart.Text = "Start";
            this.btnStart.UseVisualStyleBackColor = true;
            this.btnStart.Click += new System.EventHandler(this.btnStart_Click);
            // 
            // lblControlsCount
            // 
            this.lblControlsCount.AutoSize = true;
            this.lblControlsCount.Location = new System.Drawing.Point(95, 95);
            this.lblControlsCount.Name = "lblControlsCount";
            this.lblControlsCount.Size = new System.Drawing.Size(0, 13);
            this.lblControlsCount.TabIndex = 3;
            // 
            // btnClear
            // 
            this.btnClear.Location = new System.Drawing.Point(98, 49);
            this.btnClear.Name = "btnClear";
            this.btnClear.Size = new System.Drawing.Size(75, 23);
            this.btnClear.TabIndex = 5;
            this.btnClear.Text = "Clear";
            this.btnClear.UseVisualStyleBackColor = true;
            this.btnClear.Click += new System.EventHandler(this.btnClear_Click);
            // 
            // panel1
            // 
            this.panel1.FlowDirection = System.Windows.Forms.FlowDirection.TopDown;
            this.panel1.Location = new System.Drawing.Point(226, 17);
            this.panel1.Name = "panel1";
            this.panel1.Size = new System.Drawing.Size(200, 148);
            this.panel1.TabIndex = 6;
            // 
            // lblException
            // 
            this.lblException.AutoSize = true;
            this.lblException.Location = new System.Drawing.Point(15, 179);
            this.lblException.Name = "lblException";
            this.lblException.Size = new System.Drawing.Size(0, 13);
            this.lblException.TabIndex = 7;
            // 
            // frmUserObjectsLeak
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(452, 308);
            this.Controls.Add(this.lblException);
            this.Controls.Add(this.panel1);
            this.Controls.Add(this.btnClear);
            this.Controls.Add(this.lblControlsCount);
            this.Controls.Add(this.btnStart);
            this.Controls.Add(this.label2);
            this.Controls.Add(this.label1);
            this.Name = "FrmUserObjectsLeak";
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
            this.Text = "User Objects Leak Demo";
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.Label label1;
        private System.Windows.Forms.Label label2;
        private System.Windows.Forms.Button btnStart;
        private System.Windows.Forms.Label lblControlsCount;
        private System.Windows.Forms.Button btnClear;
        private System.Windows.Forms.FlowLayoutPanel panel1;
        private System.Windows.Forms.Label lblException;

        #endregion Designer code
    }
}
like image 70
Zohar Peled Avatar answered Oct 17 '25 18:10

Zohar Peled