0

In my C# application, I populate a FlowLayoutPanel (FLP) with a list of 324 instances of a custom User Control (UC), with the UC containing a number of text fields and a small (10x10) panel, which is filled with an ellipse. After populating the FLP and scrolling from the top to the bottom of the FLP, only the first 282 UCs have the ellipse visible, and the rest of the last 82 UCs don't. However, if I scroll right to the bottom of the FLP, and re-populate the 324 UCs into the FLP, all UCs have the ellipse visible.

However and what's more, when I debug the FLP population process (loop), and inspect any one of the 82 UCs in the Local tab, and inspect the panel where the ellipse is drawn without doing anything else, the ellipse of the inspected UC then becomes visible when I scroll down to that UC once the FLP is populated. For me this is bizarre behavior, and I hope some of the experts out there can shed light on this.

I've spent days to figure out this problem, so any help from you experts would be greatly appreciated. Following are the approaches that I have tried, but all have failed to resolve the issue:

  • Populate a PictureBox in the UC with a PNG instead of drawing an ellipse.
  • Populate a PictureBox in the UC with a PNG after the main FLP population loop.
  • Force scroll to the end of FLP after population, then back up to the top of the FLP.
  • Only populate the FLP with a subset of the 324 UCs, like suggested in this post: Fake-scrolling containers with very many controls. This partially works, but does introduce flickering when scrolling through the FLP, since UCs are continuously being created, populated and destroyed.

Below are some screenshots and code snippets. This is the code snippet to populate the FLP with the 324 UCs:

    this.Invoke(new Action(() =>
    {
        int i = 1;
        foreach (var contact in (ContactsList.ListOfContacts))
        {
            ContactsListItem cliUc = new ContactsListItem()
            {
                Phone = contact.Phone.ToString(),
                ContactName = contact.ContactName.ToString(),
                Code = contact.Code.ToString(),
                Flagged = contact.Flagged
            };

            cliUc.Colored();
            cliList.Add(cliUc);
        }

        // REFRESH FlowLayoutPanel with list of ContactListItems from above.
        this.custFlowLayout.Visible = false;
        this.custFlowLayout.SuspendLayout();

        // This may be tasked to a background thread so the flowlayoutpanel can appear sooner.
        foreach (ContactsListItem contactsListItemOld in this.custFlowLayout.Controls)
        {
            contactsListItemOld.Dispose();
        }

        //Move to catch block, when error handling.
        this.custFlowLayout.Controls.Clear();
        this.custFlowLayout.Controls.AddRange(cliList.ToArray());
        this.custFlowLayout.ResumeLayout(false);
        this.custFlowLayout.Visible = true;
    }));

This is the code snippet which draws the ellipse in the UC:

    private void panelFlagged_Paint(object sender, PaintEventArgs e)
    {
        if (_flagged)
        {
            //this.picBoxStatus.Image = Properties.Resources.status_red;
            Graphics g = panelFlagged.CreateGraphics();
            SolidBrush sb = new SolidBrush(Color.Red);
            g.FillEllipse(sb, 0, 0, 10, 10);
        }
        else
        {
            //this.picBoxStatus.Image = Properties.Resources.status_gray;
            Graphics g = panelFlagged.CreateGraphics();
            SolidBrush sb = new SolidBrush(Color.Gray);
            g.FillEllipse(sb, 0, 0, 10, 10);
        }
        return;
    }

This is the code snippet of the custom FLP and scrolling event for the FLP:

    public class CustomFlowLayout : FlowLayoutPanel
    {
        public CustomFlowLayout()
        {
            // May be related to improved performance.
            this.DoubleBuffered = true;

            this.SetStyle(ControlStyles.UserPaint, true);
            this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);
            this.SetStyle(ControlStyles.DoubleBuffer, true);

            // Set this properties of this custom FlowLayoutPanel.
            this.AutoScroll = false;
            this.AutoSize = false;
            this.WrapContents = false;
            this.FlowDirection = FlowDirection.TopDown;
        }

        protected override void OnScroll(ScrollEventArgs se)
        {
            // DoEvents eliminates flickering while scrolling the panel.
            Application.DoEvents();
            // PerformaLayout ensures all child user controls added to this Controls are visible.
            this.PerformLayout();

            // INVESTIGATE following need further investigation.
            base.OnScroll(se);
        }
    }

Screenshot shows the cutoff of the UCs which successfully display the drawn ellipse, and then rest of the 82 UCs don't:

Screenshot with 82 UCs not displaying drawing

Screenshot shows the one UC I inspected at debug time which does show the drawn ellipse, while the rest of the 82 UCs don't:

Screenshot with debugged 1 of 82 UCs displaying drawing

Edit: And now the problem is also happening on a new label I introduced into the UC (the original ellipse problem was solved by the first comment below):

Screenshot of new label not displayed text after 282 UCs

Gev
  • 1
  • 2
  • `CreateGraphics()` should be avoided. Use the graphic object from the paint parameter. – LarsTech Jan 30 '21 at 04:29
  • @LarsTech, thanks for suggestion. I changed and used the graphics object which comes with the parameter PaintEventArgs e, and still didn't work. So I tried then to do the same thing on the OnPaint event of the actual UC and it worked! It now displays all 324 ellipse drawings. For some reason, it didn't like me drawing in the panel on the UC, but rather the UC itself. However, now there's another related issue; I introduced a new label, and it won't display the text of the label, again passed the 282 UC. Any ideas please? – Gev Jan 30 '21 at 06:49
  • Don't have enough information to help. WinForms does not like too many controls, and you have a lot of controls. Any empty panels you are using for drawing or any labels you have should be deleted and replaced with a drawing from the container's paint event. Use `TextRenderer.DrawText(...)` in place of the label. – LarsTech Jan 30 '21 at 15:14
  • @LarsTech You were right again my friend! I created a RenderTextId method, and called it from my custom User Control's (UCs) OnPaint method. And now the text I wrote appears in all 324 UCs I add to the FlowLayoutPanel! I believe your assertion of "you have a lot of controls" is correct. I read somewhere that UCs and FLPs have graphical memory limits in WinForms, and usually people use WPF to overcome these limits. However, with today's more powerful PCs, I feel these are archaic limitations, which Microsoft have never addressed and enhanced in WinForms. Anyway, thank you so much. – Gev Jan 31 '21 at 05:36
  • @LarsTech As per your suggestion, yesterday I removed the PictureBox from my UC, since I am now drawing the ellipse, and today I removed the label for Id, since I am now drawing the text. Today I will also remove all the labels and replace them with drawn text as well. That just leaves the CheckBox as the only child control added to my UC. For some reason, the CheckBox is appearing on all 324 UC instances with no problem. I'm guessing the CheckBox does not consume as much memory as the PictureBox or Label controls. – Gev Jan 31 '21 at 05:40

0 Answers0