Question about an event perculiarity in a TabControl - c#

I have a little demonstration below of a peculiar problem.
using System;
using System.Windows.Forms;
namespace WindowsApplication1
{
public class TestForm : Form
{
private System.Windows.Forms.TabControl tabControl1;
private System.Windows.Forms.TabPage tabPage1;
private System.Windows.Forms.TabPage tabPage2;
private System.Windows.Forms.TextBox textBox1;
public TestForm()
{
//Controls
this.tabControl1 = new System.Windows.Forms.TabControl();
this.tabPage1 = new System.Windows.Forms.TabPage();
this.tabPage2 = new System.Windows.Forms.TabPage();
this.textBox1 = new System.Windows.Forms.TextBox();
// tabControl1
this.tabControl1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.tabControl1.Controls.Add(this.tabPage1);
this.tabControl1.Controls.Add(this.tabPage2);
this.tabControl1.Location = new System.Drawing.Point(12, 12);
this.tabControl1.Name = "tabControl1";
this.tabControl1.SelectedIndex = 0;
this.tabControl1.Size = new System.Drawing.Size(260, 240);
this.tabControl1.TabIndex = 0;
this.tabControl1.Selected += new System.Windows.Forms.TabControlEventHandler(this.tabControl1_Selected);
// tabPage1
this.tabPage1.Controls.Add(this.textBox1);
this.tabPage1.Location = new System.Drawing.Point(4, 22);
this.tabPage1.Name = "tabPage1";
this.tabPage1.Size = new System.Drawing.Size(252, 214);
this.tabPage1.TabIndex = 0;
this.tabPage1.Text = "tabPage1";
// tabPage2
this.tabPage2.Location = new System.Drawing.Point(4, 22);
this.tabPage2.Name = "tabPage2";
this.tabPage2.Size = new System.Drawing.Size(192, 74);
this.tabPage2.TabIndex = 1;
this.tabPage2.Text = "tabPage2";
// textBox1
this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.textBox1.Location = new System.Drawing.Point(6, 38);
this.textBox1.Name = "textBox1";
this.textBox1.Size = new System.Drawing.Size(240, 20);
this.textBox1.TabIndex = 0;
// TestForm
this.ClientSize = new System.Drawing.Size(284, 264);
this.Controls.Add(this.tabControl1);
this.Name = "Form1";
this.Text = "Form1";
}
//Tab Selected
private void tabControl1_Selected(object sender, EventArgs e)
{
this.Text = "TextBox Width: " + this.textBox1.Width.ToString();
}
}
//Main
static class Program
{
static void Main()
{
Application.Run(new TestForm());
}
}
}
If you run the above C# code you will have a small form containing a tabcontrol. Within the tabcontrol is a texbox on the first tab. If you follow these steps you will see the problem:
Select tabPage2 (textBox1's width is reported in the form title)
Resize the form
Select tabPage1 (The wrong textBox1 width is reported)
Any ideas what is going on here? The textbox is obviously bigger than what is being reported. If you click again on tabPage2 the correct size is then updated. Obviously there is an event updating the width of textBox1. Can i trigger this when tabPage1 is selected?

Firstly, thanks for the complete program - it made it much easier to work out what was going on!
While the textbox isn't visible, it isn't resized. When you select tabPage1, the Selected event fires before the controls become visible and the textbox gets laid out again.
Now, that's why it's happening - but what's your real situation? If you actually want to capture the size of controls changing, subscribe to their Resize events. If not, could you explain more about what you're trying to achieve?

I'm pretty sure that what's happening is the Selected event is raised slightly before the tab page becomes visible. The text box is not resized until the tab page becomes visible, so you end up checking the value of the text box's size before it is actually resized. When you change tabs again, the text box is already resized, so you get the correct value.
Change the last few lines of your example form to look like this and it will become apparent:
this.textBox1.SizeChanged += TextboxSizeChanged;
}
//Tab Selected
private void tabControl1_Selected(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("tab selected");
this.Text = "TextBox Width: " + this.textBox1.Width.ToString();
}
private void TextboxSizeChanged(object sender, EventArgs e)
{
System.Diagnostics.Debug.WriteLine("Textbox resized");
}

If you modify your code a little by adding an event handler to the textbox1.Resize event you will see what happens.
The tabPage1.Selected event occurs before the controls in the tab page is resized so when you check the width of the textbox you are checking it before it is resized.
Normally this wouldn't be a problem, for the resizing is done properly afterwards, but I guess that you will be using the size of the textbox for something?
You should be able to write your own TabControl that fixes this problem, but you will have to experiment to see what works here.

Not sure if I understand the problem.
But, you might use textbox's resize event to capture the width change OR form's resize.
In your example, does the select event of tabPage1 fire when you do step 3?

Related

How to get a Label with AutoSize to update its Position while anchored to the Right?

I have a simple System.Windows.Forms.Label in a System.Windows.Forms.Form.
I want to dynamically resize the label to fit text loaded runtime, while keeping it Anchored to the right and bottom of its parent form.
According to the MSDN Documentation:
It is “always true” that the Location Property remains constant (i.e., that the top left position of the Control will never change).
It is “always true” that the Anchor property is respected when AutoSize is true (i.e., that the Location Property—the top-left corner—will be modified so that the Anchored Sides maintain their initial distance from the edges of their parent controls).
From my reading of this, I would expect that the second truth overrides the first when Anchor is anything but AnchorStyles.None.
However, this doesn't seem to bear out in practice.
Consider the following:
// From ExampleForm.Designer.cs
this.label = new System.Drawing.Label();
this.label.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.label.AutoSize = true;
this.label.Location = new System.Drawing.Point(600, 400);
this.label.Size = new System.Drawing.Size(170, 20);
this.label.Text = "[Populated at Runtime]";
this.label.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
// ...
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
this.Controls.Add(this.label);
// ...
// Sometime after Form Initialization, this is called
void PopulateLabel() {
var oldRight = label.Right;
label.Text = "Hey here's some new text. It's pretty long so the control will have to resize";
// Without this next line, the Right Anchor distance is not maintained.
// label.Left -= (label.Right - oldRight);
System.Diagnostics.Debug.Assert(label.Anchor.HasFlag(AnchorStyles.Right) && label.Right == oldRight, "The label didn't stay anchored to the right");
}
Obviously I can work around this by tracking the distance manually, as above.
I just wonder if there isn't some way this is “supposed” to work that I'm doing wrong.
The one observation I have to offer is this: it works if the label is not anchored to the bottom.
Do I need to call Suspend/Resume/PerformLayout on the Label? on the Form?
Are the docs wrong?
Am I being foolishly naïve or completely misunderstanding something?
Do I need some sort of intermediary Control for this to work and the docs assume I know this?
To address some possible complications that show up in similar questions (or that I dreamt up):
rightToLeft is false,
Dock is DockStyle.None,
the label's Parent is the form itself, not an intermediary panel or other control.
the Margin seems irrelevant
Anchoring to the Top or Bottom seems irrelevant to Right not working.
System.Windows.Form.Button works as expected. I haven't tested other controls.
Try using a TableLayout, it tends to obey the Control layout properties better. E.g:
public class MyForm : Form {
Label label = new Label() { BackColor = Color.Blue, ForeColor = Color.White };
public MyForm() {
TableLayoutPanel panel = new TableLayoutPanel() { BackColor = Color.Green };
panel.ColumnCount = 1;
panel.RowCount = 1;
panel.Controls.Add(label, 0, 0);
panel.Dock = DockStyle.Bottom;
panel.AutoSize = true;
panel.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink;
panel.Controls.Add(label);
//this.label.Anchor = AnchorStyles.Right | AnchorStyles.Top;// | AnchorStyles.Bottom; // ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
//this.label.Dock = DockStyle.Fill;
this.label.Dock = DockStyle.Right;
this.label.AutoSize = true;
this.label.Margin = Padding.Empty;
//this.label.Location = new System.Drawing.Point(600, 400);
//this.label.Size = new System.Drawing.Size(170, 20);
this.label.Text = "[Populated at Runtime]";
//this.label.TextAlign = System.Drawing.ContentAlignment.MiddleRight;
// ...
//this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
//this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(800, 450);
//this.Controls.Add(this.label);
this.Controls.Add(panel);
// ...
Button btn = new Button { Text = "Change text" };
btn.Click += delegate {
PopulateLabel();
};
Controls.Add(btn);
}
// Sometime after Form Initialization, this is called
void PopulateLabel() {
var oldRight = label.Right;
label.Text = "Hey here's some new text. It's pretty long so the control will have to resize";
// Without this next line, the Right Anchor distance is not maintained.
// label.Left -= (label.Right - oldRight);
//System.Diagnostics.Debug.Assert(label.Anchor.HasFlag(AnchorStyles.Right) && label.Right == oldRight, "The label didn't stay anchored to the right");
}
}

C# Custom ComboBox - DropDown Position

I'm creating a ComboBox control using ToolStripControlHost and ToolStripDropDown that can host any kind of control in the DropDown window. For example, the DropDown window might display a listview or treeview or even another usercontrol.
I'm posting a simplified code below where dropdown host a usercontrol with a listview and a button like this:
The problem occurs when the control is positioned at the bottom of the screen in such a way that the dropdown window will extrapolate the lower boundary of the screen. When this occurs, the dropdown ends up hiding the control.
In this case, I'd like to fix the _dropDown.Show method call to show dropdown window as follows:
To repeat the problem, just run the code below and drag the window to the bottom of the screen and open the dropdown.
using System;
using System.Windows.Forms;
public class CustomComboBox : UserControl
{
ToolStripDropDown _dropDown;
public CustomComboBox()
{
var textbox = new TextBox();
textbox.Location = new System.Drawing.Point(0, 0);
textbox.Size = new System.Drawing.Size(this.Width - 22, 20);
textbox.Anchor = AnchorStyles.Left | AnchorStyles.Right | AnchorStyles.Top;
this.Controls.Add(textbox);
var button = new Button();
button.Location = new System.Drawing.Point(this.Width - 22, -1);
button.Size = new System.Drawing.Size(22, 22);
button.Text = "\u2BC6";
button.Anchor = AnchorStyles.Right | AnchorStyles.Top;
button.Click += new System.EventHandler(this.Button_Click);
this.Controls.Add(button);
var dropDownControl = new DropDownControlTest();
var controlHost = new ToolStripControlHost(dropDownControl);
_dropDown = new ToolStripDropDown();
_dropDown.AutoSize = true;
_dropDown.Items.Add(controlHost);
}
void Button_Click(object sender, EventArgs e)
{
_dropDown.Show(this, 0, this.Height);
}
}
public class DropDownControlTest : UserControl
{
public DropDownControlTest()
{
var listview = new ListView();
listview.Location = new System.Drawing.Point(3, 1);
listview.Size = new System.Drawing.Size(400,300);
listview.View = View.Details;
listview.Columns.Add("Col 1",100);
listview.Columns.Add("Col 2",100);
this.Controls.Add(listview);
var button = new Button();
button.Location = new System.Drawing.Point(3, 305);
button.Text = "More...";
this.Controls.Add(button);
}
}
public class Form1 : Form
{
private static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
public Form1 ()
{
CustomComboBox ccBox = new CustomComboBox();
ccBox.Location = new System.Drawing.Point(10, 10);
ccBox.Height = 20;
this.Text = "Test CustomComboBox";
this.Controls.Add(ccBox);
}
}
You can use the ToolStripDropDown.Show Method (Control, Point, ToolStripDropDownDirection) overload to control the drop direction. The code will need to perform bounds checking to decide whether to place the dropdown above or below the textbox.
The following is a simplistic method for doing the bounds checking and was only tested on a single screen configuration.
First, make textbox a class level variable.
private TextBox textbox;
public CustomComboBox()
{
//var textbox = new TextBox();
textbox = new TextBox();
The display logic is as follows.
void Button_Click(object sender, EventArgs e)
{
Point textBoxScreenLocation = textbox.PointToScreen(textbox.Location);
// try to position _dropDown below textbox
Point pt = textBoxScreenLocation;
pt.Offset(0, textbox.Height);
// determine if it will fit on the screen below the textbox
Size dropdownSize = _dropDown.GetPreferredSize(Size.Empty);
Rectangle dropdownBounds = new Rectangle(pt, dropdownSize);
if (dropdownBounds.Bottom <= Screen.GetWorkingArea(dropdownBounds).Bottom)
{ // show below
_dropDown.Show(pt, ToolStripDropDownDirection.BelowRight);
}
else
{ // show above
_dropDown.Show(textBoxScreenLocation, ToolStripDropDownDirection.AboveRight);
}
}
}
I can not comment that is why I am answering your question. You can use the reflection and then re-position you control. I have found a custom combobox control same as you developed. Please check this. At least, you will get some idea what you need to do.

C# - How to GET actual height of ComboBox

I know ComboBox.Height cannot be set easily. Can be changed with the Font. But I need to know it's final height. It doesn't update before the window and controls are displayed.
How can I calculate it ? When I run this the button is not below but behind the combo box:
// my forms must be disigned by code only (no designer is used)
public class Form1: Form
{
public Form1()
{
ComboBox box = new ComboBox();
box.Font = new Font("Comic Sans MS", 100, FontStyle.Regular);
Controls.Add(box);
Button button = new Button();
button.Text = "hello world";
button.SetBounds(box.Left, box.Bottom, 256, 32);
button.SetBounds(box.Left, box.Height, 256, 32); // doesn't work either
Controls.Add(button);
}
}
The problem is that the ComboBox.Bottom property will not be updated to compensate for the font size until the ComboBox has been drawn.
The solution is to dynamically add your controls in the Form.Load event instead of the constructor:
private void MainForm_Load(object sender, EventArgs e)
{
ComboBox box = new ComboBox();
box.Font = new Font("Comic Sans MS", 100, FontStyle.Regular);
Controls.Add(box);
Button button = new Button();
button.Text = "hello world";
button.SetBounds(box.Left, box.Bottom, 256, 32);
Controls.Add(button);
}

Adding UI components dynamically to my Winform

I'm trying to create components on the fly, so, I know how to make this, but, how can I access this component on the fly?
For example:
public Form1
{
Label label1 = new Label();
label1.AutoSize = true;
label1.Location = new System.Drawing.Point(e.X, e.Y);
label1.Name = string.Format("label{0}", labelsCount.ToString());
label1.Size = new System.Drawing.Size(35, 13);
label1.TabIndex = 2;
label1.Text = string.Format("Label -> {0}", labelsCount.ToString());
label1.Click += new System.EventHandler(this.label1_Click);
this.Controls.Add(label1);
label1.BringToFront();
label1.Show();
labelsCount++;
}
When I click on the label, I want to get the label's information (like position, text and name)
How I can do this? Or, what is the best way to do this?
And, to access the component based on position of the panel, inside of form, how I can do this?
Sender of event is your lablel. Simply cast sender object to Label type:
void label1_Click(object sender, EventArgs e)
{
Label label = (Label)sender;
// use
// label.Name
// label.Location
}

Winforms - how to show extra fields of an item on a form

I am trying to create a form for collecting information about an item. There are two types: default item and special item. The fields of item shows on the form by default.
public class Item
{
Id
Name
Quantity
Type //default, special
}
public class SpecialItem : Item //inherits from item
{
//extra fields here
ExpiryDate
SafeForChildren
}
How will I take the extra fields of SpecialItem to user.
I think the fields of SpecialItem should not show until the user indicates he wants to add special item by selecting the type.
I think of using a tab to show extra fields
A collapsible control - I don't if this exists
Hiding the controls and showing them when necessary
Any other idea
If the two class have a clear relation between them and this relation is well comprensible to your user, I think it's a good thing to show every field.
Put all the fields inside a groupbox and add two option buttons for the Item and SpecialItem class.
By default (at form load) the Item class will be selected and the two extra field are disabled.
If the user choose the SpecialItem class option button, enable the other two fields.
I have seen this behavior in many options dialogs when selecting an option will enable other specific options.
Try this out - it simply reflects the given type and then puts the controls onto a TableLayoutPanel adds a couple of buttons and then binds two event handlers to the click event of the buttons. It is by no means a masterpeice, but I think will get you started.
public MyForm(Type typeToDisplay)
{
InitializeComponent();
PropertyInfo[] settableProperties = typeToDisplay.GetProperties(BindingFlags.Instance | BindingFlags.Public);
TableLayoutPanel panel = new TableLayoutPanel();
panel.ColumnCount = 2;
panel.RowCount = settableProperties.Length+1;
panel.Name = "LayoutPanel";
this.Controls.Add(panel);
int rowIndex = 0;
foreach (PropertyInfo info in settableProperties)
{
Label propLabel = new Label();
propLabel.Text = info.Name;
TextBox propField = new TextBox();
panel.Controls.Add(propLabel, 0, rowIndex);
panel.Controls.Add(propField, 1, rowIndex);
rowIndex++;
}
panel.Controls.Add(new Button() { Text = "OK", Name="OK" }, 0, rowIndex);
panel.Controls.Add(new Button() { Text = "Cancel", Name="Cancel" }, 1, rowIndex);
panel.Controls["Cancel"].Click += new EventHandler(CloseForm);
panel.Controls["OK"].Click += new EventHandler(SaveChanges);
panel.Height = this.Height;
panel.Width = this.Width;
}
private void CloseForm(object sender, EventArgs e)
{
this.Close();
}
private void SaveChanges(object sender, EventArgs e)
{
MessageBox.Show("Save changes was clicked!");
this.Close();
}
Here is a full example solution to demonstrate my suggestion above. Note that it is all done in code and uses only a single column, but can work with designer-produced controls (of course) and multiple columns, too. Just be sure to set all the controls in a row (say, a label and its corresponding input control) to Visible = false to have the unused rows collapse properly.
TableLayoutPanel tlp = new TableLayoutPanel();
tlp.RowStyles.Add(new RowStyle(SizeType.Absolute, 25));
tlp.RowStyles.Add(new RowStyle(SizeType.Absolute, 25));
tlp.RowStyles.Add(new RowStyle(SizeType.Absolute, 25));
tlp.RowStyles.Add(new RowStyle(SizeType.Absolute, 25));
tlp.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tlp.RowStyles.Add(new RowStyle(SizeType.AutoSize));
tlp.RowStyles.Add(new RowStyle(SizeType.Absolute, 25));
TextBox b1 = new TextBox(); b1.Dock = DockStyle.Fill;
TextBox b2 = new TextBox(); b2.Dock = DockStyle.Fill;
TextBox b3 = new TextBox(); b3.Dock = DockStyle.Fill;
CheckBox special = new CheckBox(); special.Text = "Special?";
TextBox b4 = new TextBox(); b4.Dock = DockStyle.Fill; b4.Visible = false;
TextBox b5 = new TextBox(); b5.Dock = DockStyle.Fill; b5.Visible = false;
Button button = new Button(); button.Text = "Save";
special.CheckedChanged += new EventHandler((sender, args) => { b4.Visible = b5.Visible = special.Checked; });
tlp.Controls.Add(b1, 0, 0);
tlp.Controls.Add(b2, 0, 1);
tlp.Controls.Add(b3, 0, 2);
tlp.Controls.Add(special, 0, 3);
tlp.Controls.Add(b4, 0, 4);
tlp.Controls.Add(b5, 0, 5);
tlp.Controls.Add(button, 0, 6);
Controls.Add(tlp);
tlp.Dock = DockStyle.Fill;
tlp.BringToFront();

Categories