Заполнение TabControl приводит к неправильному порядку скрытых элементов управления на TabPage

#c# #winforms #tabcontrol

#c# #winforms #tabcontrol

Вопрос:

Кажется, что элементы управления Handles , которые еще не созданы, перемещаются в нижнюю часть Parent Control при TabControl Padding изменении свойства ‘s. Просматривая .NET исходный код, Padding делает вызов RecreateHandle(); , который, по-видимому, имеет к нему какое-то отношение.

Ниже приведен код, который иллюстрирует проблему. У каждого checkbox есть соответствующий label чуть ниже. Некоторые метки видны в начале, некоторые скрыты. person0 При установке флажка person0 метка отображается, но неправильно внизу. Вместо этого метка person0 должна отображаться прямо под флажком person0, в том порядке, в котором она была добавлена FlowLayoutPanel . Смотрите скриншот.

В коде предусмотрена bool doWorkaround опция, но должен быть лучший способ. Принудительное использование всего, Handles что нужно создать CreateControl() , также кажется неправильным.

пример

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

namespace WindowsFormsApplication3 {

public class TabControl3 : TabControl {

    private bool doWorkaround = false; // set to true to prevent the bug

    protected override void OnFontChanged(EventArgs e) {
        base.OnFontChanged(e);
        int h = (int) this.Font.Height;
        SetPadding(new Point(h, h)); // calling this causes the Controls to re-order incorrectly
    }

    ///<summary>Setting the padding causes the TabControl to recreate all the handles, which causes the hidden handleless controls to rearrange.</summary>
    public virtual void SetPadding(Point pt) {
        // Workaround solution: remove all controls from tab pages and then add them back after.
        int n = TabPages.Count;
        Control[][] arr = null;
        if (doWorkaround) {
            arr = new Control[n][];
            for (int i = 0; i < n; i  ) {
                TabPage tp = TabPages[i];
                arr[i] = tp.Controls.Cast<Control>().ToArray();
                tp.Controls.Clear();
            }
        }

        this.Padding = pt; // in the .NET source code, setting Padding calls RecreateHandle()

        if (doWorkaround) {
            for (int i = 0; i < n; i  )
                TabPages[i].Controls.AddRange(arr[i]);
        }
    }
}

public class CheckBox2 : CheckBox {
    public CheckBox2(String text, bool isChecked = false) : base() {
        this.Text = text;
        this.AutoSize = true;
        this.Checked = isChecked;
    }
}

public class Label2 : Label {
    public Label2(String text) : base() {
        this.Text = text;
        this.AutoSize = true;
    }
}

public class MyForm : Form {

    TabControl tc = new TabControl3 { Dock = DockStyle.Fill };

    public MyForm() {
        this.Size = new System.Drawing.Size(600, 800);
        this.StartPosition = FormStartPosition.CenterScreen;
        TabPage tp1 = new TabPage("Page1");

        FlowLayoutPanel p = new FlowLayoutPanel { FlowDirection = System.Windows.Forms.FlowDirection.TopDown, Dock = DockStyle.Fill };

        p.Controls.Add(new CheckBox2("Person0"));
        p.Controls.Add(new Label2("Person0") { Visible = false });

        p.Controls.Add(new CheckBox2("Person1", true));
        p.Controls.Add(new Label2("Person1"));

        p.Controls.Add(new CheckBox2("Person2", true));
        p.Controls.Add(new Label2("Person2"));

        p.Controls.Add(new CheckBox2("Person3"));
        p.Controls.Add(new Label2("Person3") { Visible = false });

        p.Controls.Add(new CheckBox2("Person4"));
        p.Controls.Add(new Label2("Person4") { Visible = false });

        for (int i = 0; i < p.Controls.Count; i  = 2) {
            CheckBox cb = (CheckBox) p.Controls[i];
            Label lb = (Label) p.Controls[i 1];
            cb.CheckedChanged  = delegate {
                bool b = cb.Checked;
                lb.Visible = b;
            };
        }

        tp1.Controls.Add(p);
        tc.TabPages.Add(tp1);

        Controls.Add(tc);
    }

    protected override void OnLoad(EventArgs e) {
        base.OnLoad(e);
        this.Font = SystemFonts.MenuFont; // to trigger the bug
    }
}

static class Program {
    [STAThread]
    static void Main() {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MyForm());
    }
}
}
  

Я протестировал использование .NET 3.5, 4, 4.52 и увидел одинаковое поведение во всех трех.

Комментарии:

1. Переопределите OnControlAdded ваш TabControl и добавьте base.OnControlAdded(e); if (e.Control is TabPage) { e.Control.Controls.OfType<Control>().ToList().ForEach(c => c.CreateControl()); } . Также добавьте, в OnFontChanged (вы также можете использовать OnLayout ), после base() , if (!this.IsHandleCreated) return; . Удалить все SetPadding() , кроме this.Padding = pt;

2. В OnFontChanged, вероятно, лучше, если у вас есть: var p = new Point(Font.Height, Font.Height); if (!Padding.Equals(p)) SetPadding(p);

3. Кстати, вы могли бы использовать TableLayoutPanel вместо FlowLayoutPanel 🙂 (или один UserControl вместо двух элементов управления)