Why does setting a MinimumSize break the table layout? - c#

I'm currently playing around with layouts and made a test project where I construct a Form which displays a Panel which contains a TableLayoutPanel with three rows:
a text box
a button
a placeholder label which is supposed to take up the remaining vertical space.
This test works properly, but if I set the Minimum Size of the Text Box to e.g. (400, 200), I can no longer see the button. Shouldn't the first row in the table layout AutoSize to its content? Note that
setting RowStyles explicitly to SizeType.AutoSize doesn't change anything.
No minimum size set:
Minimum size set:
using System;
using System.Drawing;
using System.Windows.Forms;
namespace LayoutTest
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var sampleForm = new Form();
var samplePanel = new Panel() { Dock = DockStyle.Fill };
var sampleTextBox = new TextBox() { Dock = DockStyle.Fill };
// This line breaks the layout
//sampleTextBox.MinimumSize = new Size(400, 200);
var sampleButton = new Button() { Dock = DockStyle.Fill };
var panelLayout = new TableLayoutPanel() { Dock = DockStyle.Fill };
panelLayout.Controls.Add(sampleTextBox, 0, 0);
panelLayout.Controls.Add(sampleButton, 0, 1);
// Add a placeholder label to take up the remaining space
panelLayout.Controls.Add(new Label() { Text = String.Empty, Dock = DockStyle.Fill });
samplePanel.Controls.Add(panelLayout);
sampleForm.Controls.Add(samplePanel);
Application.Run(sampleForm);
}
}
}

The button is underneath the textbox. You need to set the multiline property to true.
E.g.
sampleTextBox.Multiline = true;
The source of this behavior is either the TableLayoutPanel or the TextBox. It would be strange for the TableLayoutPanel to explicitly test if a Control is a TextBox and if Multiline property is set to true before deciding to adhere to the the MinimumSize constraint. However, in my testing, it appears that the Multiline property must be set before adding it to the TableLayoutPanel and if Multiline is unset, then the control goes back underneath the text box and never goes back even if Multiline is set to true again.
E.g.
sampleButton.Click += delegate {
Size s1 = sampleTextBox.MinimumSize; // always returns the set MinSize
sampleTextBox.Multiline = !sampleTextBox.Multiline;
Size s2 = sampleTextBox.MinimumSize; // always returns the set MinSize
panelLayout.Invalidate(true);
panelLayout.PerformLayout();
};

Related

Position of link in LinkLabel

I want a box of a fixed size to show some text and have a link in it that is clickable in the bottom right corner for editing. Clicking this edit link shows a set of fields to fill in.
I tried LinkLabel, which does the trick, but, when I change the text, the size of the box changes. I set autosize to false and longer text forces the link outside the box. Short text puts the link to far up.
I could get fancy and calculate the position of the link and insert it at the appropriate place (adding new lines if needed), but I'm wondering if there isn't an easier way to do this.
Is there a better control for doing this or another way of doing this?
EDIT:
The boxes that are filled in are concatenated and replace the text in the linklabel. The Edit link is currently appended to this and a LinkArea (of the last 4 characters) is set.
You need to build a composite layout, for instance using a Panel with Label/TextBox (Dock = Fill) and LinkLabel (Dock = Bottom, TextAlign = MiddleRight) inside, like this
using System;
using System.Drawing;
using System.Windows.Forms;
namespace Samples
{
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var text = "I want a box of a fixed size to show some text and have a link in it that is clickable in the bottom right corner for editing.";
int textSize = 50;
var form = new Form { Padding = new Padding(8) };
var panel = new Panel { Parent = form, BorderStyle = BorderStyle.FixedSingle, Padding = new Padding(4) };
var label = new Label { Dock = DockStyle.Fill, Parent = panel, AutoSize = false, Text = text, Height = textSize };
var link = new LinkLabel { Dock = DockStyle.Bottom, Parent = panel, AutoSize = false, TextAlign = ContentAlignment.MiddleRight, Text = "Edit" };
panel.Location = form.DisplayRectangle.Location;
panel.Width = form.DisplayRectangle.Width;
panel.Height = panel.Padding.Vertical + link.Height + label.Height;
Application.Run(form);
}
}
}
Result:
I found another way.
I use a text box and position a linklabel in its corner.
textbox is readonly and disabled so it acts like a label and the backcolor is set to white to over-ride the disabled/readonly colour.
The edit link now stays put

Group related controls on ToolStrip so they are always visible together

I have an application that has a tool strip with related controls set apart by ToolStripSeparaters. It looks something like this:
However, when the window size shrinks, some of the controls get moved to a little drop-down section. Unfortunately, this can split related controls, for example in the screenshot below, the "Filter by ID" label, the associated textbox for the ID, and "Clear Filter" button are no longer shown together.
If controls have to be moved to the drop-down, I'd prefer to have related controls move together. Is there a way to group related controls together on a ToolStrip? Or perhaps a better way of dealing with this sort of scenario?
I tried using the LayoutCompleted event move all of the controls to the overflow area if any of them are in the overflow.
private void toolStrip1_LayoutCompleted(object sender, EventArgs e)
{
var filterGroup = new List<ToolStripItem> { lblFilter, txtFilter, btnClearFilter };
if (filterGroup.Any(x => x.IsOnOverflow))
{
filterGroup.ForEach(x => x.Overflow = ToolStripItemOverflow.Always);
}
}
This seems to work fine, but I haven't found a good way to show them again when the window size is increased. I tried both the Resize and Layout events of the ToolStrip with the following code:
var filterGroup = new List<ToolStripItem> { lblFilter, txtFilter, btnClearFilter };
filterGroup.ForEach(x => x.Overflow = ToolStripItemOverflow.AsNeeded);
You could use a ToolStripControlHost to group a winforms TextBox and Label. E.g.
public class ToolStripLabelTextBox : ToolStripControlHost {
public Label Label { get; private set; }
public TextBox TextBox { get; private set; }
public ToolStripLabelTextBox(String labelText) : base(new FlowLayoutPanel { FlowDirection = FlowDirection.LeftToRight, WrapContents = false, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink, Padding = Padding.Empty, Margin = Padding.Empty }) {
Label = new Label { Text = labelText, AutoSize = true, Anchor = AnchorStyles.Top | AnchorStyles.Bottom, TextAlign = System.Drawing.ContentAlignment.MiddleCenter };
TextBox = new TextBox();
FlowLayoutPanel panel = (FlowLayoutPanel) Control;
panel.Controls.Add(Label);
panel.Controls.Add(TextBox);
}
}
Two other options are:
Implement a LayoutEngine that does the grouping you want.
Implement a composite ToolStripItem that displays a label and text box. You can use the ToolStripRadioButtonMenuItem as an example: https://msdn.microsoft.com/en-us/library/vstudio/ms404318%28v=vs.100%29.aspx

Why are Anchor properties not being honored within dynamically created nested components?

Let me begin by saying that I have not done a lot of Windows Forms development -- if there is an obvious mistake that I may be making, please don't hesitate to mention it.
Steps to reproduce my issue:
Create a new C# Windows Forms Project using VS 2010 or VS 2012
Using the VS Form Designer, add three FlowLayoutPanel components to the form
Set each FlowLayoutPanel to have the same height as the form and approximately 1/3 the width of the form
Position each FlowLayoutPanel so that they do not overlap each other horizontally and collectively consume approximately the entire area of the Form.
The leftmost FlowLayoutPanel is configured to have an Anchor of Top, Bottom, Left
The middle FlowLayoutPanel is configured to have an Anchor of Top, Bottom
The rightmost FlowLayoutPanel is configured to have an Anchor of Top, Bottom, Right
Add an event for Form_Shown:
private void Form1_Shown(object sender, EventArgs e)
{
Panel p = new Panel();
p.BorderStyle = BorderStyle.FixedSingle;
p.Width = 200;
p.Height = 100;
Label label1 = new Label();
label1.BorderStyle = BorderStyle.FixedSingle;
label1.Text = "Hello";
label1.Anchor = AnchorStyles.Top;
Label label2 = new Label();
label2.BorderStyle = BorderStyle.FixedSingle;
label2.Text = "World!";
label2.Anchor = AnchorStyles.Bottom;
p.Controls.Add(label1);
p.Controls.Add(label2);
middleFlow.Controls.Add(p); // add to the center most FlowLayoutPanel on Form1
}
The result seems to be that label1 is placed on top of label2, despite label2 being added second. Moreover, the anchor values seem to be ignored (as label1 is covering label2 when I intend for them to be anchored to the top and bottom of the Panel component, respectively)
If I use the Dock property instead of the Anchor property, the behavior is as desired. Why does the Anchor property not work in this situation?
Also, is there a way to anchor components to other components? I notice as I increase the size of my Form at runtime, horizontal "gaps" between panels appear. Ideally, I would like the panels to grow together, preventing any gaps/whitespace between them horizontally?
Thanks in advance for any suggestions or tips.
I'm still starting to learn c# and winforms, so the following may not be optimal but it does what you required.
Handled the labels with Dock=Top. Note that the labels are switched so that label1 is on top of label2, i.e., registering label1 last pushes down the already registered label2.
The positioning of the three panels is done without anchors and docks with an event handler for resize. Setting the size of the form after that raises a resize event. Colored to see the components.
using System;
using System.Drawing;
using System.Windows.Forms;
public class ThreePanel : Form {
FlowLayoutPanel leftFlow;
FlowLayoutPanel middleFlow;
FlowLayoutPanel rightFlow;
public ThreePanel(){
leftFlow = new FlowLayoutPanel() {
BackColor = Color.Yellow
};
middleFlow = new FlowLayoutPanel() {
BackColor = Color.LightGreen
};
rightFlow = new FlowLayoutPanel() {
BackColor = Color.LightBlue
};
this.Controls.Add(rightFlow);
this.Controls.Add(middleFlow);
this.Controls.Add(leftFlow);
this.Load += (s,e)=>Form1_Shown(s,e);
this.Resize += (s,e)=>{
int w=this.Width/3;
leftFlow.Width=middleFlow.Width
=rightFlow.Width=w;
leftFlow.Height=middleFlow.Height
=rightFlow.Height=this.Height;
leftFlow.Location=new Point(0,0);
middleFlow.Location=new Point(w,0);
rightFlow.Location=new Point(2*w,0);
};
this.Size = new Size(750,450);
}
private void Form1_Shown(object sender, EventArgs e)
{
Panel p = new Panel() {
BorderStyle = BorderStyle.FixedSingle,
Width = 200,
Height = 100,
BackColor = Color.Fuchsia,
};
Label label1 = new Label() {
BorderStyle = BorderStyle.FixedSingle,
Text = "Hello",
Dock = DockStyle.Top
};
Label label2 = new Label() {
BorderStyle = BorderStyle.FixedSingle,
Text = "World!",
Dock = DockStyle.Top
};
p.Controls.Add(label2);
p.Controls.Add(label1);
// add to the center most FlowLayoutPanel on Form1
middleFlow.Controls.Add(p);
}
public static void Main()
{
Application.Run(new ThreePanel());
}
}
I would expect exactly the behaviour that you mentioned.
The Anchor property only tells the parent container that the label should be sticked
to the parent. In your case AnchorStyles.Top means stick the label to the top and leave it there if the parent moves or resizes.
You did not specify dimensions or positions for the labels, so both overlapp.
The z-order of the controls is created implicitly from the order when added to middleFlow.Controls. You can check this using VS forms designer. Select "Bring to Front" or "Send to Back" and watch how the x.designer.cs changes.
Why it is in reverse order is one of the little .net secrets. The workaround is to change the order. Sometimes it is easier to do it manually than in the designer.

flowlayout control keeps adding control in the wrong direction in winforms

I have a flowlayout control in winforms, i have set its flow direction to TopDown but it keeps adding controls from left to right, autoscroll is also set to true.
flowLayoutPanel1.Controls.Clear();
Label labelInput = new Label();
ListBox listBoxNewInput = new ListBox();
//Initialize label's property
labelInput.Text = " #" + Convert.ToInt32(sequence);
labelInput.AutoSize = true;
//Initialize textBoxes Property
listBoxNewInput.HorizontalScrollbar = false;
listBoxNewInput.Items.Add(efforts);
//Add the newly created text box to the list of input text boxes
inputTextBoxesList.Add(listBoxNewInput);
//Add the labels and text box to the form
flowLayoutPanel1.FlowDirection = FlowDirection.TopDown;
flowLayoutPanel1.Controls.Add(labelInput);
flowLayoutPanel1.FlowDirection = FlowDirection.TopDown;
flowLayoutPanel1.Controls.Add(listBoxNewInput);
Set the WrapContents property of the flowLayoutPanel1 to false, it will not allow to move those controls on the right if they don't fit. In order to be able to scroll clipped content you can set AutoScroll property to true
Here is the code:
flowLayoutPanel1.FlowDirection = FlowDirection.TopDown;
flowLayoutPanel1.WrapContents = false;
flowLayoutPanel1.AutoScroll = true;
flowLayoutPanel1.Controls.Add(labelInput);
flowLayoutPanel1.Controls.Add(listBoxNewInput);

Word wrap for a label in Windows Forms

How can one get word wrap functionality for a Label for text which goes out of bounds?
Actually, the accepted answer is unnecessarily complicated.
If you set the label to AutoSize, it will automatically grow with whatever text you put in it. (This includes vertical growth.)
If you want to make it word wrap at a particular width, you can set the MaximumSize property.
myLabel.MaximumSize = new Size(100, 0);
myLabel.AutoSize = true;
Tested and works.
The quick answer: switch off AutoSize.
The big problem here is that the label will not change its height automatically (only width). To get this right you will need to subclass the label and include vertical resize logic.
Basically what you need to do in OnPaint is:
Measure the height of the text (Graphics.MeasureString).
If the label height is not equal to the height of the text set the height and return.
Draw the text.
You will also need to set the ResizeRedraw style flag in the constructor.
In my case (label on a panel) I set label.AutoSize = false and label.Dock = Fill.
And the label text is wrapped automatically.
There is no autowrap property but this can be done programmatically to size it dynamically. Here is one solution:
Select the properties of the label
AutoSize = True
MaximumSize = (Width, Height) where Width = max size you want the label to be and Height = how many pixels you want it to wrap
From MSDN, Automatically Wrap Text in Label:
using System;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
public class GrowLabel : Label {
private bool mGrowing;
public GrowLabel() {
this.AutoSize = false;
}
private void resizeLabel() {
if (mGrowing)
return;
try {
mGrowing = true;
Size sz = new Size(this.Width, Int32.MaxValue);
sz = TextRenderer.MeasureText(this.Text, this.Font, sz, TextFormatFlags.WordBreak);
this.Height = sz.Height;
}
finally {
mGrowing = false;
}
}
protected override void OnTextChanged(EventArgs e) {
base.OnTextChanged(e);
resizeLabel();
}
protected override void OnFontChanged(EventArgs e) {
base.OnFontChanged(e);
resizeLabel();
}
protected override void OnSizeChanged(EventArgs e) {
base.OnSizeChanged(e);
resizeLabel();
}
}
I had to find a quick solution, so I just used a TextBox with those properties:
var myLabel = new TextBox
{
Text = "xxx xxx xxx",
WordWrap = true,
AutoSize = false,
Enabled = false,
Size = new Size(60, 30),
BorderStyle = BorderStyle.None,
Multiline = true,
BackColor = container.BackColor
};
Have a better one based on #hypo 's answer
public class GrowLabel : Label {
private bool mGrowing;
public GrowLabel() {
this.AutoSize = false;
}
private void resizeLabel() {
if (mGrowing)
return;
try {
mGrowing = true;
int width = this.Parent == null ? this.Width : this.Parent.Width;
Size sz = new Size(this.Width, Int32.MaxValue);
sz = TextRenderer.MeasureText(this.Text, this.Font, sz, TextFormatFlags.WordBreak);
this.Height = sz.Height + Padding.Bottom + Padding.Top;
} finally {
mGrowing = false;
}
}
protected override void OnTextChanged(EventArgs e) {
base.OnTextChanged(e);
resizeLabel();
}
protected override void OnFontChanged(EventArgs e) {
base.OnFontChanged(e);
resizeLabel();
}
protected override void OnSizeChanged(EventArgs e) {
base.OnSizeChanged(e);
resizeLabel();
}
}
int width = this.Parent == null ? this.Width : this.Parent.Width; this allows you to use auto-grow label when docked to a parent, e.g. a panel.
this.Height = sz.Height + Padding.Bottom + Padding.Top; here we take care of padding for top and bottom.
Put the label inside a panel
Handle the ClientSizeChanged event for the panel, making the
label fill the space:
private void Panel2_ClientSizeChanged(object sender, EventArgs e)
{
label1.MaximumSize = new Size((sender as Control).ClientSize.Width - label1.Left, 10000);
}
Set Auto-Size for the label to true
Set Dock for the label to Fill
All but step 2 would typically be done in the designer window.
Not sure it will fit all use-cases but I often use a simple trick to get the wrapping behaviour:
put your Label with AutoSize=false inside a 1x1 TableLayoutPanel which will take care of the Label's size.
Set the AutoEllipsis Property to 'TRUE' and AutoSize Property to 'FALSE'.
If your panel is limiting the width of your label, you can set your label’s Anchor property to Left, Right and set AutoSize to true. This is conceptually similar to listening for the Panel’s SizeChanged event and updating the label’s MaximumSize to a new Size(((Control)sender).Size.Width, 0) as suggested by a previous answer. Every side listed in the Anchor property is, well, anchored to the containing Control’s respective inner side. So listing two opposite sides in Anchor effectively sets the control’s dimension. Anchoring to Left and Right sets the Control’s Width property and Anchoring to Top and Bottom would set its Height property.
This solution, as C#:
label.Anchor = AnchorStyles.Left | AnchorStyles.Right;
label.AutoSize = true;
If you really want to set the label width independent of the content, I find that the easiest way is this:
Set autosize true
Set maximum width to how you want it
Set minimum width identically
Now the label is of constant width, but it adapts its height automatically.
Then for dynamic text, decrease the font size. If necessary, use this snippet in the sub where the label text is set:
If Me.Size.Height - (Label12.Location.Y + Label12.Height) < 20 Then
Dim naam As String = Label12.Font.Name
Dim size As Single = Label12.Font.SizeInPoints - 1
Label12.Font = New Font(naam, size)
End If
This helped me in my Form called InpitWindow:
In Designer for Label:
AutoSize = true;
Achors = Top, Left, Right.
private void InputWindow_Shown(object sender, EventArgs e) {
lbCaption.MaximumSize = new Size(this.ClientSize.Width - btOK.Width - btOK.Margin.Left - btOK.Margin.Right -
lbCaption.Margin.Right - lbCaption.Margin.Left,
Screen.GetWorkingArea(this).Height / 2);
this.Height = this.Height + (lbCaption.Height - btOK.Height - btCancel.Height);
//Uncomment this line to prevent form height chage to values lower than initial height
//this.MinimumSize = new Size(this.MinimumSize.Width, this.Height);
}
//Use this handler if you want your label change it size according to form clientsize.
private void InputWindow_ClientSizeChanged(object sender, EventArgs e) {
lbCaption.MaximumSize = new Size(this.ClientSize.Width - btOK.Width - btOK.Margin.Left * 2 - btOK.Margin.Right * 2 -
lbCaption.Margin.Right * 2 - lbCaption.Margin.Left * 2,
Screen.GetWorkingArea(this).Height / 2);
}
Whole code of my form
If the dimensions of the button need to be kept unchanged:
myButton.Text = "word\r\nwrapped"
The simple answer for this problem is to change the DOCK property of the Label. It is "NONE" by default.
If you are entering text into the label beforehand, you can do this.
In the designer,
Right-Click on the label and click Properties.
In Properties, search for text tab.
Click in the tab and click on the arrow button next to it.
A box will popup on top of it.
You can press enter in the popup box to add lines and type as in notepad!
(PRESS ENTER WHERE YOU WANT TO WRAP THE LABEL TEXT)
I would recommend setting AutoEllipsis property of label to true and AutoSize to false. If text length exceeds label bounds, it'll add three dots (...) at the end and automatically set the complete text as a tooltip. So users can see the complete text by hovering over the label.
I have a label that autowraps and grows to whatever size in a right docked autosize panel, whose width is set by other content.
Label (in tablelayoutpanel) Autosize = True.
TableLayoutPanel (in panel) Autosize = True, AutoSizeMode = GrowAndShrink, Dock = Bottom, one Column SizeType = 100%, one Row SizeType = 100%.
Panel (right docked in form) AutoSize = True, AutoSizeMode = GrowAndShrink, Dock = Right.
Use System.Windows.Forms.LinkLabel instead of Label and set the property LinkArea as below.
myLabel.LinkArea = new LinkArea(0, 0);
Use style="overflow:Scroll" in the label as in the below HTML. This will add the scroll bar in the label within the panel.
<asp:Label
ID="txtAOI"
runat="server"
style="overflow:Scroll"
CssClass="areatext"
BackColor="White"
BorderColor="Gray"
BorderWidth="1"
Width = "900" ></asp:Label>

Categories