简体   繁体   中英

TabControl reordering ControlCollection when selecting a tab

Alright, I have a weird one here again.

I have a running project where I'm building a tree of Controls/Components of a given form and use that to generate the shortest unique path to any one of them. Just as a context for the actual question.

Now I've had quite a bad time dealing with TabControl, which is finished quite badly on many levels, but most of it could be dealt with WinAPI calls. Fine. Now I have a problem which I can't figure out even when looking into the Microsoft reference codebase.

TabControl for some unknown reason reorders the ControlCollection based when a tab is selected. From a tiny test app, it seems far more random than I first anticipated. It's something that's unfortunately breaking my fallback method for dealing with unnamed Controls and the stable indexing is very important to me.

using System;
using System.Windows.Forms;

namespace TabControlOrderTest
{
    using System.Threading;
    using System.Threading.Tasks;

    public partial class Form1 : Form
    {
        private TabControl tabControl1;
        private ListBox listBox1;

        public Form1()
        {
            this.tabControl1 = new TabControl();
            this.listBox1 = new ListBox();
            this.InitializeComponent();
            this.SuspendLayout();
            // 
            // tabControl1
            // 
            this.tabControl1.Location = new System.Drawing.Point(12, 12);
            this.tabControl1.Name = "tabControl1";
            this.tabControl1.SelectedIndex = 0;
            this.tabControl1.Size = new System.Drawing.Size(566, 244);
            this.tabControl1.TabIndex = 0;
            this.tabControl1.SelectedIndexChanged += new EventHandler(this.tabControl1_SelectedIndexChanged);
            // 
            // listBox1
            // 
            this.listBox1.FormattingEnabled = true;
            this.listBox1.Location = new System.Drawing.Point(658, 34);
            this.listBox1.Name = "listBox1";
            this.listBox1.Size = new System.Drawing.Size(130, 251);
            this.listBox1.TabIndex = 2;

            this.Controls.Add(this.listBox1);
            this.Controls.Add(this.tabControl1);

            this.ResumeLayout(false);


            for (var i = 0; i < 10; i++)
            {
                this.tabControl1.TabPages.Add(
                    new TabPage($"tabPage{i}")
                    {
                        Name = $"tabPage{i}"
                    });
            }
        }

        protected override void OnShown(EventArgs e)
        {
            base.OnShown(e);

            Task.Run(
                () =>
                {
                    for (var i = 9; i >= 0; i--)
                    {
                        Thread.Sleep(500);
                        this.tabControl1.SelectedIndex = i;
                    }
                });
        }

        private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
        {
            var pages = this.tabControl1.TabPages;
            var controls = this.tabControl1.Controls;
            this.Text = $"{pages.Count};{controls.Count}";
            this.listBox1.Items.Clear();
            for (int i = 0, count = this.tabControl1.TabCount; i < count; i++)
            {
                if (ReferenceEquals(pages[i], controls[i]))
                {
                    continue;

                }

                this.listBox1.Items.Add($"{pages[i].Name} != {controls[i].Name}");
            }
        }     
    }
}

Sometimes it's quite stable, sometimes it gets even more messy with every click. Could someone please give me a hint why is this happening? I would be so grateful for any clues.

EDIT: I'm asking about Controls property specifically because that's the universal place for Controls to store any underlying items. I could switch in my production code to TabPageCollection for TabControl specifically, but it could break more things than fix so that's why I'm trying to understand the behavior.

Alright, I've figured out the cause of this.

It's done by a Control.UpdateChildControlIndex(Control ctl) which gets called during handling WmWindowPosChanged

https://referencesource.microsoft.com/System.Windows.Forms/R/13dbf65f74593e7c.html

As it's obvious from the first lines, it can be optionally disabled.

It's kinda a pain before .NET 4.6 (as it requires the most ugly reflection code to do it). From 4.6+, you just call

AppContext.SetSwitch("Switch.System.Windows.Forms.AllowUpdateChildControlIndexForTabControls", false);

And profit. Hopefully.

In my case, I don't like the idea of switching some internal state of .NET FW for my Coded UI testing so I'll switch to different control collection for TabControl, as I've talk about in my question. But it's nice to know why before I do it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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