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
Related
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
I have a simple form as an example. I've added some check boxes using designer, and set the margin and padding to 0,0,0,0. This is the form code:
public partial class Frm1 : Form
{
public Frm1()
{
InitializeComponent();
AddCheckBox();
AddCheckBox();
AddCheckBox();
}
public void AddCheckBox()
{
CheckBox cb = new CheckBox();
cb.Text = "AddedFromCode";
cb.Padding = new Padding(0,0,0,0);
cb.Margin = new Padding(0,0,0,0);
flowLayoutPanel1.Controls.Add(cb);
}
}
I would expect the check boxes that are added from designer and from code to appear the same, however, they do not.
How can I get the spacing of the AddedFromCode controls to have 0 spacing?
I don't know why it took me so long to figure this out. One of the properties that designer changes when you add a control to a FlowLayoutPanel is AutoSize = true.
So in code...
cb.AutoSize= true;
solves the problem.
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();
};
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.
I have a panel of labels, buttons and image that I wish to put into a flow layout panel.
As seen in some tutorial, I understand that it is possible to auto align new and additional buttons into a flow layout panel.
what I would like to ask is that is it possible to put a panel WITHIN a flow layout panel and call multiple instances of the same panel to appear within the flow layout panel.
My panel code would be
this.panelNotification.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle;
this.panelNotification.Controls.Add(this.button1);
this.panelNotification.Controls.Add(this.lblImage);
this.panelNotification.Controls.Add(this.lblName);
this.panelNotification.Controls.Add(this.lblLinkName);
this.panelNotification.Controls.Add(this.lblLinkLocation);
this.panelNotification.Controls.Add(this.lblLocation);
this.panelNotification.Location = new System.Drawing.Point(3, 3);
this.panelNotification.Name = "panelNotification";
this.panelNotification.Size = new System.Drawing.Size(506, 100);
this.panelNotification.TabIndex = 17;
So is it possible to include the whole panel into a flow layout panel? if yes, how do i do it. thank you.
Yes, you can put a Panel into a FlowLayoutoutPanel.
No, you can't put a control several times into a FlowLayoutoutPanel (in fact you can, but it is only displayed once).
But what you could do is writing some kind of Factory-Method that creates a new Panel with new Buttons/Labels/other Controls etc. every time you call it, and add these new instances to your FlowLayoutpanel. Something like this:
public class Form1
{
private Panel CreateNotificationPanel()
{
var p = new Panel { BackColor = Color.Red };
p.Controls.Add(new Button { Text = "Test" });
return p;
}
private void Form1_Load(System.Object sender, System.EventArgs e)
{
var flp = new FlowLayoutPanel { Dock = DockStyle.Fill };
flp.Controls.Add(CreateNotificationPanel());
flp.Controls.Add(CreateNotificationPanel());
flp.Controls.Add(CreateNotificationPanel());
this.Controls.Add(flp);
}
public Form1() { Load += Form1_Load; }
}
Another (and problably better) approach would be to create a UserControl that contains your Buttons/Labels/etc. instead of using a panel and adding all controls manually. Just create with the Designer and add new instances of the UserControl to the FlowLayoutPanel.