C# Winforms Tabpage Size 和 ClientSize 错误

C# Winforms Tabpage Size and ClientSize wrong

我创建了一个用户控件,其中包含一个带有 2 个 TabPage 的 TabControl。在用户控件的 Layout 事件中以编程方式调整两个 T​​abPage 上子控件的位置。
问题是,调用事件时,第二个 TabPage 尚未绘制,因此此 TabPage 的 Size 和 ClientSize 错误。

我该如何解决这个问题?
我已经尝试使用 var oHandle = tabpage.Handletabctrl.SelectedTab = each tabpage 的循环来强制创建 TabPage,但这没有帮助。

编辑#1
我发现第一个 "bug" 在 VS Designer 中观察到:
当您在表单上拖动 TabControl 然后在设计器中调整它的大小时,当前可见的 TabPage 的大小会更新。但是所有其他标签页的大小都不是;它们保持不变,直到对任何控件进行任何其他更改(已测试!)。
我承认这种情况很少见,所以尺寸通常会更新,但我认为这是 TabControl 中的设计缺陷。

当 TabControl 在运行时调整大小时,这个设计缺陷变得非常重要!这是 repro 的最小示例(没有 UC,只是表单中的 TabControl):

Form1.cs:

public Form1 ()
{
  InitializeComponent ();

  Debug.Print ("ctor before resize");
  Debug.Print ("TC: " + tabControl1.Size);
  Debug.Print ("T1: " + tabPage1.Size);
  Debug.Print ("T2: " + tabPage2.Size);

  tabControl1.Size = tabControl1.Size + new Size (10, 10);
  Debug.Print ("ctor after resize");
  Debug.Print ("TC: " + tabControl1.Size);
  Debug.Print ("T1: " + tabPage1.Size);
  Debug.Print ("T2: " + tabPage2.Size);
}

private void Form1_Load (object sender, EventArgs e)
{
  ... same as ctor, prints adapted ("load before/after resize)
}

private void Form1_Layout (object sender, LayoutEventArgs e)
{
  Debug.Print ("Layout");
}

private void button1_Click (object sender, EventArgs e)
{
  ... same as ctor, prints adapted ("button before/after resize)
}

Form1.Designer.cs:(删除无关部分)

private void InitializeComponent ()
{
  this.tabControl1 = new System.Windows.Forms.TabControl ();
  this.tabPage1 = new System.Windows.Forms.TabPage ();
  this.tabPage2 = new System.Windows.Forms.TabPage ();
  this.button1 = new System.Windows.Forms.Button ();
  this.tabControl1.SuspendLayout ();
  this.SuspendLayout ();
  //
  this.tabControl1.Controls.Add (this.tabPage1);
  this.tabControl1.Controls.Add (this.tabPage2);
  this.tabControl1.Size = new System.Drawing.Size (300, 120);
  //
  this.tabPage1.Size = new System.Drawing.Size (292, 91);
  //
  this.tabPage2.Size = new System.Drawing.Size (292, 91);
  //
  this.button1.Click += new System.EventHandler (this.button1_Click);
  //
  this.AutoScaleDimensions = new System.Drawing.SizeF (96F, 96F);
  this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;
  this.ClientSize = new System.Drawing.Size (384, 262);
  this.Controls.Add (this.tabControl1);
  this.Controls.Add (this.button1);
  this.Load += new System.EventHandler (this.Form1_Load);
  this.tabControl1.ResumeLayout (false);
  this.ResumeLayout (false);
}
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tabPage1;
private System.Windows.Forms.TabPage tabPage2;
private System.Windows.Forms.Button button1;

调试打印:

ctor before resize
TC: {Width=300, Height=120}
T1: {Width=292, Height=91}
T2: {Width=292, Height=91}
Layout
Layout
ctor after resize
TC: {Width=310, Height=130}
T1: {Width=292, Height=91}    (wrong)
T2: {Width=292, Height=91}    (wrong)

Load before resize
TC: {Width=310, Height=130}
T1: {Width=302, Height=101}    (now correct because updated after ctor)
T2: {Width=302, Height=101}    (now correct because updated after ctor)
Layout
Layout
Load after resize
TC: {Width=320, Height=140}
T1: {Width=312, Height=111}    (correct because visible)
T2: {Width=302, Height=101}    (wrong again)
Layout

(TabPage1 selected, TabPage2 is not updated)
button before resize
TC: {Width=320, Height=140}
T1: {Width=312, Height=111}
T2: {Width=302, Height=101}    (still wrong: TabPage2 HAS NOT BEEN UPDATED WHILE THE UI-THREAD WAS IDLE)
Layout
Layout
button after resize
TC: {Width=330, Height=150}
T1: {Width=322, Height=121}
T2: {Width=302, Height=101}    (even more wrong)

(TabPage1 selected, TabPage2 is not updated)
button before resize
TC: {Width=330, Height=150}
T1: {Width=322, Height=121}
T2: {Width=302, Height=101}    (still wrong)
Layout
Layout
button after resize
TC: {Width=340, Height=160}
T1: {Width=332, Height=131}
T2: {Width=302, Height=101}    (again more wrong)

(TabPage2 selected, now TabPage1 is not updated)
button before resize
TC: {Width=340, Height=160}
T1: {Width=332, Height=131}
T2: {Width=332, Height=131}    (now correct because visible)
Layout
Layout
button after resize
TC: {Width=350, Height=170}
T1: {Width=332, Height=131}    (now wrong)
T2: {Width=342, Height=141}    (still correct because visible)

基于这种行为,我目前唯一的解决方案是在每次调用 tabControl1_SelectedIndexChanged (..).

时调用我的 UC 的 UpdateLayout() 函数

编辑 #2
编辑 #1 的 "solution" 不起作用,因为:
如果 TabPage 的宽度很小,则页面上的控件垂直排列,导致相应 TabPage 的高度较大。 UC的整体高度取决于TabControl的高度,TabControl的高度取决于所有TabPages,所以UpdateLayout()需要所有TabPages的尺寸正确,否则稍后选择另一个标签时UC高度会再次变化, 但它在设计时应该已经正确了。

我找到了一个非常简单的解决方案来获得正确的 TabPages 大小:

所有 TabPages 都属于同一个 TabControl,因此所有 TabPages 具有相同的大小,无论相应 TabPage 的 Size 属性 是什么。
此外,总有一个报告正确大小的 TabPage,即当前选定的 TabPage。
因此每个 TabPage 的大小是 TabControl.SelectedTab.Size.

编辑:
不幸的是,如果整个 TabControl 不可见,这将不起作用,可能是因为它被放置在当前未被选中的另一个 TabControlTabPage 上。
但是,我找到了另一个解决方案,我将其发布为 answer on this question Can I force a TabControl's TabPages to resize *before* they're selected?,这实际上或多或少是我的重复问题。
该答案的相关部分:

[...]
You have to add just 1 line to your code:

var oSize = i_oTabControl.DisplayRectangle.Size;

and avoid that it's probably optimized away by the compiler:

[MethodImpl (MethodImplOptions.NoOptimization | MethodImplOptions.NoInlining)]
public static void TabControlForceUpdateOfTabpageSize (this TabControl i_oTabControl)
{
  if (i_oTabControl == null)
    return;
  var oSize = i_oTabControl.DisplayRectangle.Size;
}

[...]