The problem
I'm dynamically adding Buttons to the WinForm. As I do so, I'm repositioning existing Buttons to prevent overlap. The AutoSize property is being used to automatically set Width.
For longer text (that pushes Buttons beyond their default Width), the below code doesn't work.
For example:
b.Width is 75 before AutoSize is set
b.Width is 75 after AutoSize is set
When shifting other Buttons, it shifts them by b.Width + buffer = 83
However after addButton()completes, the AutoSize kicks in and sets the width to 150, overlapping the next Button which is only 83 pixels away instead of 158.
AutoSize appears to change the size of the control too late for it to be of use. How can I make it happen immediately?
Attempt 1 - Code
public void addButton(string text)
{
const int buffer = 8;
//Construct new button
Button b = new Button();
b.Text = text;
b.AutoSize = true;
b.Location = new Point(0, 0);
//Shift over all other buttons to prevent overlap
//b.Width is incorrect below, because b.AutoSize hasn't taken effect
for (int i = 0; i < Controls.Count; i++)
if (Controls[i] is Button)
Controls[i].Location = new Point(Controls[i].Location.X + b.Width + buffer, Controls[i].Location.Y);
Controls.add(b);
}
Attempt 2
Searched Google and StackOverflow for the following:
c# autosize immediately
c# autosize fast
c# autosize not working
Attempt 3
Asking here.
Last Resort
If nothing else works, a timer could be set to reposition Buttons on each tick. However this is very sloppy design, and doesn't aid in learning the intricacies of AutoSize. I'd like to avoid this workaround if possible.
The AutoSize and AutoSizeMode mode are applied only when the control is parented to the another control or form.
So invoke first
Controls.Add(b);
Now the b.Size will the adjusted accordingly and can be used in the calculations.
Alternatively, instead of Size property you can use the GetPreferredSize method to get the correct size without actually applying AutoSize and use it inside the calculations:
var bSize = b.GetPreferredSize(Size.Empty);
//Shift over all other buttons to prevent overlap
//b.Width is incorrect below, because b.AutoSize hasn't taken effect
for (int i = 0; i < Controls.Count; i++)
if (Controls[i] is Button)
Controls[i].Location = new Point(Controls[i].Location.X + bSize.Width + buffer, Controls[i].Location.Y);
The FlowLayoutPanel control does this work for you.
Place one on your form and try adding buttons in the following manner:
Button b = new Button();
b.AutoSize = true;
b.Text = text;
flowLayoutPanel1.SuspendLayout();
flowLayoutPanel1.Controls.Add(b);
flowLayoutPanel1.Controls.SetChildIndex(b, 0);
flowLayoutPanel1.ResumeLayout();
You can subscribe to the Resize event of the last button added. This will allow you to accurately change the locations of all of the buttons because now all of the buttons have been AutoSized.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var button1 = NewButton(0);
button1.Location = new Point(10, 10);
var button2 = NewButton(1);
button2.Location = new Point(button1.Right, 10);
var button3 = NewButton(2);
button3.Location = new Point(button2.Right, 10);
button3.Resize += (s, e) =>
{
button2.Location = new Point(button1.Right, 10);
button3.Location = new Point(button2.Right, 10);
};
Controls.Add(button1);
Controls.Add(button2);
Controls.Add(button3);
}
private Button NewButton(int index)
{
return new Button()
{
Text = "ButtonButtonButton" + index.ToString(),
AutoSize = true
};
}
}
Related
I've been using a UserControl to display a list of controls. I initially had AutoScroll enabled, and then opted to not using it. I chose against using it as it stood out and simply didn't 'look' good with the controls theme I've been using.
I took a shot at a framework called MetroFramework, and I've opted to use the MetroScrollBar scrollbar control for a vertical scrollbar.
I've fully disabled AutoScroll, and I then decided to implement the Scrollbar. I simply did this by:
scbMain.Scroll += (sender, e) => { VerticalScroll.Value = scbMain.Value; };
(where scbMain is the Scrollbar I'm discussing)
This works, but not as expected. As soon as I scroll, I get a crazy flickering effect from the default scrollbar, as shown here. A longer list has the same effect, but more pronounced.
I've attempted to hide the existing scrollbars:
VerticalScroll.Visible = false;
HorizontalScroll.Visible = false;
VerticalScroll.Enabled = false;
HorizontalScroll.Enabled = false;
This has had no effect on fixing my issue.
It should be noted: My scrollbar is docked to the right and there're no other container controls within the UserControl.
Ok. Problem solved. The issue was in this line of code:
scbMain.Scroll += (sender, e) => { ----> /*(Here*/ VerticalScroll.Value = scbMain.Value; <---- };
You are actully setting the scroll value of your user control, basically you tell the system to invoke the autoscroll property to set the value!
The correct way is to NOT autoscroll the user control but to scroll a container inside eg a panel. So add a panel to your user control. You are going to scroll the panel and all the controls inside it (in this example i will add the button).
this.btnExample.Location = new System.Drawing.Point(62, 0);
this.btnExample.Name = "btnExample";
this.btnExample.Size = new System.Drawing.Size(75, 390);
this.btnExample.TabIndex = 1;
this.btnExample.Text = "Out of Bounds";
this.btnExample.UseVisualStyleBackColor = true;
this.panel1.Controls.Add(this.btnExample);
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(270, 391); //the width must fit inside your user control and the height was arbitrary
this.panel1.TabIndex = 2;
this.Controls.Add(this.panel1);
this.Controls.Add(this.scbMain);
this.Name = "CtrlScroll";
this.Size = new System.Drawing.Size(474, 300);
The scrolling:
public CtrlScroll() {
InitializeComponent();
scbMain.Scroll += ( sender, e ) => {
//Normally the if statement whouldn't be needed but the metro srollbar
//has a weird behaviour when the scroll value becomes max
if( scbMain.Value > panel1.Height - this.Height ) {
panel1.Top = -( panel1.Height - this.Height );
}
else {
panel1.Top = -scbMain.Value;
};
};
int maxVertical = panel1.Height;
// SmallChange is typically 1%.
int smallChangeVertical = Math.Max( (int)( maxVertical / 100 ), 1 );
// LargeChange is one page.
int largeChangeVertical = this.Height;
scbMain.Minimum = 0;
scbMain.Maximum = maxVertical;
scbMain.SmallChange = smallChangeVertical;
scbMain.LargeChange = largeChangeVertical;
}
I am writing my Windows Forms app. And I have some problem with TableLayoutContainer element. Or rows are superpose (without needed scroll bar) or there is a big interval between first and second row.
I need a container with dynamic changing sizes according to Form size, with auto vertical scroll (if the container's size to big). Please help me to correct my code or container's properties.
Label LabelG = new Label[len];
NumericUpDown NumberControlBars = new NumericUpDown[len];
for (int i = 0; i < len; i++)
{
TablePanelContainer.RowCount++;
TablePanelContainer.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25F));
LabelG[i] = new System.Windows.Forms.Label();
LabelG[i].Name = "Label" + i.ToString();
LabelG[i].Size = new System.Drawing.Size(40, 23);
LabelG[i].Text = Groups[i].ToString();
LabelG[i].Dock = DockStyle.Right;
LabelG[i].Anchor = (AnchorStyles.Right | AnchorStyles.Top);
LabelG[i].TextAlign = ContentAlignment.MiddleRight;
TablePanelContainer.Controls.Add(LabelG[i], 0, i);
NumberControlBars[i] = new System.Windows.Forms.NumericUpDown();
NumberControlBars[i].Name = "Label" + i.ToString();
NumberControlBars[i].MaximumSize = new System.Drawing.Size(40,23);
NumberControlBars[i].Text = "0";
NumberControlBars[i].Dock = DockStyle.Left;
NumberControlBars[i].Anchor = (AnchorStyles.Left | AnchorStyles.Top);
TablePanelContainer.Controls.Add(NumberControlBars[i], 1, i);
}
Properties
Bug
I have recreated your functionality in a small app. These are the settings for your TablePanelLayout control
Anchor: Top, Bottom, Left, Right
AutoScroll: True
AutoSize: False
Specially the AutoSize setting to false is important. If you don't do that the control will resize itself to the height needed to accommodate all rows. Because the container has enough space in that case to show everything it will not show the scrollbars. It doesn't care that its size doesn't fit on the form.
This is what the designer should look like:
To overcome the quirks with the first row I adapted the Style of that first row. It looks like the designer plays some tricks here. Your code will look like this.
tableLayoutPanel1.SuspendLayout();
// adapt styling of first row
if (tableLayoutPanel1.RowStyles.Count > 0)
{
tableLayoutPanel1.RowStyles[0].SizeType = SizeType.Absolute;
tableLayoutPanel1.RowStyles[0].Height = 25F;
}
for(int i=0; i<100; i++)
{
var lbl = new Label();
lbl.Text = i.ToString();
tableLayoutPanel1.Controls.Add(lbl, 0, i);
var num = new NumericUpDown();
tableLayoutPanel1.Controls.Add(num,1 ,i);
tableLayoutPanel1.RowCount++;
}
tableLayoutPanel1.ResumeLayout();
When run this is the result:
I'm trying dynamically add panels within a panel dependent on the count of people in a list using the following code when the form loads:
private void Form1_Load(object sender, EventArgs e)
{
const int xConst = 2;
var people = new List<string>
{
"Person1",
"Person2",
"Person3",
"Person4",
};
var y = 2;
for (var x = 0; x < people.Count; x++)
{
var newpan = new MyPanel
{
BorderStyle = BorderStyle.None,
Height = 25,
Width = panel1.Width - 5,
Location = new Point(xConst, y)
};
var newlbl = new Label
{
BorderStyle = BorderStyle.None,
AutoSize = false,
Text = people[x],
Font = new Font("Segoe UI", 9.5F, FontStyle.Bold, GraphicsUnit.Point, ((byte)(0))),
Size = new Size(75,20),
Location = new Point(newpan.Location.X + 2, newpan.Location.Y + 2),
};
var newbtn = new Button
{
FlatStyle = FlatStyle.Flat,
FlatAppearance = { BorderSize = 0 },
UseVisualStyleBackColor = true,
Text = #"+",
Size = new Size(15,21),
Location = new Point(newpan.Width - 20,newpan.Location.Y - 1),
Font = new Font("Segoe UI", 9.0F, FontStyle.Regular, GraphicsUnit.Point, ((byte)(0)))
};
newpan.Controls.Add(newlbl);
newpan.Controls.Add(newbtn);
panel1.Controls.Add(newpan);
y += 27;
}
}
The problem is that if I specify the Location property in both the button and the label, only the first iteration of the labels and buttons show up for the Person1 iteration. But, if I leave the Location property out, they all show up. The problem with that is that I have a custom panel that overrides some stuff allowing me to put a customer border and color around the panels, and if I don't specify a location, the labels and buttons aren't positioned correctly on the panel, so it covers my border and looks sloppy.
Can someone help me figure out why this is happening? I've stepped through the program completely and watched all the values I can think of increment accordingly in the watch window. All the panels show up correctly, so I don't understand why their respective labels and buttons don't show up when I specify the location.
Looking at your code I notice that you are setting your newpan height to 25, and its position is offset by 27 with each iteration. You also are using
'Location = new Point(newpan.Location.X + 2, newpan.Location.Y + 2)
to set the location of your button and label within your newpan panel. newpan.Location is referenced in the coordinates of your panel1, your button and label's location is referenced in the coordinates of your newpan panel therefore after the first iteration of your For statement your label and buttons y location value is 29 which is greater than the height of your newpan panel making it not able to be seen, the next iteration after that will be y will be 56 and so forth. Each content control, in this case your panels will have its own coordinate system, the easiest fix would be to do something like this:
'Location = new Point( 2, 2) //for your label
'Location = new Point(newpan.Width - 20, - 1) //for your button
The other alternative is to do like jmcilhinney suggests and make an UserControl with your button and label already in position, you would then create individual instances of it and assign it to your panel1.
I have lots of buttons on flowlayoutpanel, and then there's text labels to break the flow. Last button before label and label itself has SetFlowBreak. All works kind of fine, but what I don't understand, is why there is so much space under the text label? If form is resized so narrow that there's only one column of buttons, then the unwanted space disappears. Can someone explain how that space can be removed?
Code:
public Form1()
{
InitializeComponent();
for (int i = 1; i <= 100; i++)
{
Button button = new Button();
button.Text = i.ToString();
button.Width = 150;
button.Height = 50;
button.Margin = new Padding(5);
flowLayoutPanel1.Controls.Add(button);
if (i % 10 == 0)
{
flowLayoutPanel1.SetFlowBreak(button, true);
Label label = new Label();
label.Text = "Some random text";
label.AutoSize = true;
label.Margin = new Padding(5, 5, 0, 0);
label.BackColor = ColorTranslator.FromHtml("#ccc");
flowLayoutPanel1.Controls.Add(label);
flowLayoutPanel1.SetFlowBreak(label, true);
}
}
}
And couple of images to show what I mean:
Image1: Strange space under the Label
Image2: No space under the Label when the form is resized (this is how I'd like this to work)
Thank you Hans! I thinks this is a real answer, as it solved my problem: (quote from comments)
It is a bug, same one as this one. The extra space is the height of the next label. The workaround is exactly the same, just add a dummy control with a Width of 0 after the label. – Hans Passant
So first I removed flowbreak after the real label:
flowLayoutPanel1.SetFlowBreak(label, true);
And then replaced it with the following code, and the mysterious space disappeared!
Label dummyLabel = new Label();
dummyLabel.Width = 0;
dummyLabel.Height = 0;
dummyLabel.Margin = new Padding(0, 0, 0, 0);
flowLayoutPanel1.Controls.Add(dummyLabel);
flowLayoutPanel1.SetFlowBreak(dummyLabel, true);
I try to create simple app with 2 columns using SpliterContainer and control panel with buttons. And I would like that on every screen it will look good. That's why I decided to use relative position of elements.
I read documentation and different forums, but I get something strange. Second column of splitter doesn't appear at all.
Please, can you help me find the reason of that problem?
private void Form1_Load(object sender, EventArgs e)
{
WindowState = FormWindowState.Maximized;
int screenWidth = Screen.PrimaryScreen.Bounds.Width;
int screenHeight = Screen.PrimaryScreen.Bounds.Height;
//set form size
this.Size = new Size(screenWidth, screenHeight);
//set button panel size
const double percentOfHeightPanel = 0.05;
int heightOfPanelButton = Convert.ToInt32(screenHeight * percentOfHeightPanel);
this.panel_button.Size = new System.Drawing.Size(screenWidth, heightOfPanelButton);
this.panel_button.Location = new Point(0, 0);
//set splitContainer size
int widthOfContainer = Convert.ToInt32(0.5 * screenWidth);
int heightOfContainers = Convert.ToInt32(screenHeight * (0.95));
splitContainer1.Panel1.MinimumSize = new Size(widthOfContainer, heightOfContainers);
splitContainer1.Panel2.MinimumSize = new Size(widthOfContainer, heightOfContainers);
splitContainer1.Location = new Point(0, heightOfPanelButton);
//this.splitContainer1.Panel2MinSize = screenWidth - widthOfContainer;
//set textBox size
this.textBox1.Multiline = true;
this.textBox1.Location = new Point(0, heightOfPanelButton);
this.textBox1.MinimumSize = new System.Drawing.Size(widthOfContainer, heightOfContainers);
this.textBox2.Multiline = true;
this.textBox2.Location = new Point(widthOfContainer, heightOfPanelButton);
this.textBox1.MinimumSize = new System.Drawing.Size(widthOfContainer, heightOfContainers);
}
If you want two have two splitter panels of the same size set
splitContainer1.SplitterDistance =
(splitContainer1.Width - splitContainer1.SplitterWidth) / 2;
Then set
splitContainer1.IsSplitterFixed = true;
You can set these two properties manually at design time. The user will then not be able to resize the panels and the panels will automatically resize to be of same size.
Consider using a TableLayoutPanel instead.
If further, the two sides should look the same, place your controls on a UserControl and place two instances of them into the two panels with a docked property set to Fill.