C# Winforms Tabpage Size and ClientSize wrong

2019-08-26 08:04发布

问题:

I have created a user control that contains a TabControl with 2 TabPages. The position of the child controls on both TabPages is adapted programmatically in the Layout event of the user control.
The problem is, that the 2nd TabPage has not been drawn yet when the event is called and therefore this TabPage has a wrong Size and ClientSize.

How can I work around that?
I tried a loop with var oHandle = tabpage.Handle and tabctrl.SelectedTab = each tabpage already to force the creation of the TabPages, but this hasn't helped.

EDIT #1
I have found a first "bug" which is observed in the VS Designer:
When you drag a TabControl on the Form and then resize the it in the Designer, the size of the currently visible TabPage is updated. But the sizes of all other tabpages are not; they stay the same until any additional change is done on any control (tested!).
I admit that this situation is quite uncommon, so the sizes usually are updated, but nonetheless in my opinion it's a design flaw in the TabControl.

This design flaw becomes very relevant when the TabControl is resized at runtime! Here's a minimal example for repro (without UC, just a TabControl in a Form):

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: (irrelevant parts removed)

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;

Debug prints:

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)

Based on this behaviour, the only solution that I currently have is calling the UpdateLayout() function of my UC on every call of tabControl1_SelectedIndexChanged (..).

EDIT #2
The "solution" of Edit #1 does not work because:
If the TabPages have little width, the controls on the pages are arranged vertically, causing a greater height of the respective TabPage. The overall height of the UC depends on the height of the TabControl, whose height depends on all TabPages, so the UpdateLayout() needs to have correct sizes of all TabPages, otherwise the UC height would change again later when another tab is selected, but it should be correct already at design time.

回答1:

I found a quite simple solution to get the correct size of the TabPages:

All TabPages belong to the same TabControl, thus all TabPages have the same size, no matter what the Size property of the respective TabPage says.
Additionally, there's allways one TabPage that reports the correct size, which is the currently selected TabPage.
Therefore the size of every TabPage is TabControl.SelectedTab.Size.

EDIT:
Unfortunately this does NOT work if the whole TabControl is not visible, maybe because it's placed on a TabPage of another TabControl which is currently not selected.
However, I found another solution, which I posted as an answer on this question Can I force a TabControl's TabPages to resize *before* they're selected?, which actually is more or less a duplicate question of mine.
The relevant part of that answer:

[...]
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;
}

[...]