Can't put a horizontal scroll on my TableLayoutPanel - c#

I have a form in which there is a TableLayoutPanel in which there are several RadioButtons.
The form has a fixed size and the controls are dynamically generated.
Sometimes, the text of the RadioButtons is too long to be entirely displayed and I'd like to enable a horizontal scroll to the TableLayout in this case. But no matter how I tried, I can't figure out how to do it. Simply setting the TableLayoutPanel Autoscroll property to true did nothing.
I've tried to nest the TableLayout inside a Panel, but it didn't work. I've tried to put each RadioButton inside a Panel, then put the Panel inside the TableLayout. This made the horizontal scroll appear but of course the RadioButtons weren't in the same container any more so they were independant from each other. I've messed with several properties of the controls but to no avail.
I really have no idea what to do.
Here is how the controls are generated (this is the original code, prior to my modifications) :
public static Panel ChoicePanel(string title, string description, string internalName, bool radio, string oldValue, List<string> choices, int num, bool required)
{
var nbrow = (string.IsNullOrWhiteSpace(description) ? 1 : 2);
var nbcol = 1; var currentRow = realFirstRow;
if (radio) nbrow += choices.Count();
else nbrow += 1;
var panel = GeneratePanel(title, nbcol, nbrow, num, required);
foreach (var ch in choices)
{
var rb = new RadioButton()
{
Name = "rb" + internalName + currentRow,
Text = ch,
Checked = !string.IsNullOrEmpty(oldValue) && ch == oldValue,
Dock = DockStyle.Fill,
AutoCheck = true,
};
panel.Controls.Add(rb, firstCol, currentRow);
currentRow++;
}
AddDescription(description, currentRow, panel);
return panel;
}
private static TableLayoutPanel GeneratePanel(string title, int numcol, int numlines, int num, bool required)
{
var panel = new TableLayoutPanel()
{
AutoSize = true,
AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowOnly,
Dock = DockStyle.Fill,
ColumnCount = numcol + 1,
RowCount = numlines,
Name = "panel" + num,
};
for (var i = 0; i < numlines; i++)
{
panel.RowStyles.Add(new RowStyle(SizeType.AutoSize));
}
((Panel)panel).Margin = new Padding(5, 10, 10, 5);
var lTitle = GenerateTitle(title, required);
panel.Controls.Add(lTitle, 1, 0);
panel.SetColumnSpan(lTitle, numcol);
var labColor = new Label()
{
Dock = DockStyle.Fill,
BackColor = notErrorColor,
Name = "lbColor",
};
panel.Controls.Add(labColor, 0, 0);
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Absolute, 10));
panel.SetRowSpan(labColor, numlines);
return panel;
}
Please note that it was someone else who wrote this code, but I will try to answer questions as best as I can.
Thanks for your help.

Related

Dynamically created controls names not increasing its value through loops C#

I'm working on a personal project where I'm building a notes generator, and I have this TextBox dynamically created everytimes a button is clicked, it works fine just like how I expected but things gets weird when I tried to name each of these TextBox to different name by using a loop Name = "Note" + i where i is the loop variable.
So what I was expecting to happens is that each of the TextBox names to be something like Note1 Note2 Note3 ... but instead when I retrieved each of the TextBox name to a MessageBox in the same loop which is used to generates the TextBox, the MessageBox throws this: Note 1 Note 1 Note 1 ... instead when I clicked the button thrice.
int curr = 0;
private void guna2Button1_Click(object sender, EventArgs e) {
int top = 25;
int h_p = 170;
curr++;
for(int i=1; i<curr+1; i++) {
// Notes
var new_note = new Guna2TextBox() {
Text = "Title\n",
Name = "Note" + i,
Multiline = true,
AcceptsTab = true,
AcceptsReturn = true,
WordWrap = false,
ScrollBars = ScrollBars.Vertical,
Width = 220,
Height = 110,
BorderRadius = 8,
Font = new Font("Bahnschrift", 13),
ForeColor = Color.White,
FillColor = ColorTranslator.FromHtml("#1E1E1E"),
BorderColor = ColorTranslator.FromHtml("#2C2C2C"),
Location = new Point(450,top)
};
MessageBox.Show(i.ToString());
top += h_p;
flowLayoutPanel1.Controls.Add(new_note);
curr = 0;
}
}
The problem is caused by the fact that inside the loop you set always curr back to zero. And the loop is not needed at all because you want to add a textbox at each click. So you just need to look at the Count property of the FlowLayoutPanel and use that value to prepare the name.
Another problem to solve is how to position the next control inside the panel but again you could calculate easily with a the Count property
private void guna2Button1_Click(object sender, EventArgs e)
{
int nextTop = 25 + (flowLayoutPanel.Controls.Count * 170);
var new_note = new Guna2TextBox() {
Text = "Title\n",
Name = "Note" + flowLayoutPanel.Controls.Count + 1,
.....
Location = new Point(450,nextTop)
};
flowLayoutPanel1.Controls.Add(new_note);
}
I don't think you need to use the For loop in this case. You can just use the variable curr for each textboxes. So instead of the For loop, you can use this :
int top = 25;
int h_p = 170;
curr++;
var new_note = new Guna2TextBox() {
Text = "Title\n",
Name = "Note" + curr,
Multiline = true,
AcceptsTab = true,
AcceptsReturn = true,
WordWrap = false,
ScrollBars = ScrollBars.Vertical,
Width = 220,
Height = 110,
BorderRadius = 8,
Font = new Font("Bahnschrift", 13),
ForeColor = Color.White,
FillColor = ColorTranslator.FromHtml("#1E1E1E"),
BorderColor = ColorTranslator.FromHtml("#2C2C2C"),
Location = new Point(450,top)
top += h_p;
flowLayoutPanel1.Controls.Add(new_note);
};

Resize TableLayoutPanel rows only as cells are dynamically populated

I have a TableLayoutPanel, ug_degrees, with 3 columns and 1 row. Each cell gets dynamically populated with another TableLayoutPanel, degreePanel, containing 1 label and 1 textbox.
I need to get my layout to look something like this:
Right now my layout looks like this:
I'm at a loss for why I have those giant gaps between the cells, and why the row will not expand to fill its contents (a label & a textbox). I have tried to set the whole TableLayoutPanel's autosize property to true, but the columns get resized, even if I set only the rows' sizetype to autosize as well.
The properties behind the table shown are default. All non-default properties are customized in C# below.
// Dynamically load undergraduate degrees
int row = 0;
for (int i = 0; i < degrees.undergraduate.Count; i++) {
// Create and populate panel for each degree
TableLayoutPanel degreePanel = new TableLayoutPanel();
degreePanel.ColumnCount = 1;
degreePanel.RowCount = 2;
degreePanel.AutoSize = true;
foreach (RowStyle style in degreePanel.RowStyles) {
style.SizeType = SizeType.AutoSize;
}
degreePanel.BorderStyle = BorderStyle.FixedSingle;
//degreePanel.Margin = new Padding(0);
Label degTitle = new Label();
degTitle.Text = degrees.undergraduate[i].title;
degTitle.Dock = DockStyle.Fill;
TextBox degDesc = new TextBox();
degDesc.ReadOnly = true;
degDesc.Multiline = true;
degDesc.Dock = DockStyle.Fill;
degDesc.Text = degrees.undergraduate[i].description;
SizeF size = degDesc.CreateGraphics()
.MeasureString(degDesc.Text,
degDesc.Font,
degDesc.Width,
new StringFormat(0));
degDesc.Height = (int)size.Height;
degreePanel.Controls.Add(degTitle, 0, 0);
degreePanel.Controls.Add(degDesc, 0, 1);
ug_degrees.Controls.Add(degreePanel, i, row);
// Resize rows and columns (only after adding controls)
foreach (RowStyle style in ug_degrees.RowStyles) {
style.SizeType = SizeType.AutoSize;
}
// Jump to next row if current row is full
if ((i+1) % 3 == 0) {
row++;
}
Add degreePanel.Dock = DockStyle.Fill

Programmatically Added Textboxes not Appearing

I'm using WinForms in VS15 with C#.
I'm dynamically adding TextBoxs and Labels to my Form based upon a user selected value in a ComboBox (essentially this looks up a value in a data collection which tells my UI what controls it needs).
When I attempt to generate the controls, the Labels appear and layout just fine, however, the TextBoxs are remarkable in there absence.
I've tried fidelling with the MaximumSize and MinimumSize properties to see if they could be messing with something but it doesn't seem to be making any difference.
The code I use for doing this is below (I know the use of the List<Pair<Label,TextBox>> is pretty unecessary but I find it helps readability):
private void GenerateControls(string formType)
{
string[] formParameters = engine.GetFormParameters(formType);
if (formParameters == null) return;
SplitterPanel panel = splitContainer.Panel1;
panel.Controls.Clear();
List<Pair<Label, TextBox>> controlPairs = new List<Pair<Label, TextBox>>();
int tabIndex = 0;
Point labelPoint = panel.Location + new Size(20, 20);
Size initialOffset = new Size(0, 30);
Size horizontalOffset = new Size(40, 0);
Size tBoxSize = new Size(40,20);
foreach (string parameter in formParameters)
{
Label label = new Label
{
Text = parameter,
Tag = "Parameter Label",
Name = $"lbl{parameter}",
Location = (labelPoint += initialOffset)
};
TextBox textBox = new TextBox
{
AcceptsTab = true,
TabIndex = tabIndex++,
Text = "",
Tag = parameter,
Name = $"txt{parameter}",
MaximumSize = tBoxSize,
MinimumSize = tBoxSize,
Size = tBoxSize,
Location = labelPoint + horizontalOffset
};
controlPairs.Add(new Pair<Label, TextBox>(label, textBox));
}
foreach (Pair<Label, TextBox> pair in controlPairs)
{
panel.Controls.Add(pair.First);
panel.Controls.Add(pair.Second);
}
}
I don't believe that my use of Point + Size is the issue as the Point class overrides the + operator like so:
Unfortunately for me the issue appears to be simply that the dX was not a big enough value to prevent the text boxes from being hidden under the labels, I forgot that labels don't have transparent backgrounds.
While I was at it: I've removed the redundant List<Pair<<>>; added support for dynamically adjusting TextBox location based on Label size; and split it out into two separate loops, so my code now looks as below and works just fine:
private void GenerateControls(string formType)
{
string[] formParameters = engine.GetFormParameters(formType);
if (formParameters == null) return;
SplitterPanel panel = splitContainer.Panel1;
panel.Controls.Clear();
int tabIndex = 0;
Point labelPoint = panel.Location + new Size(20, 20);
Size verticalOffset = new Size(0, 30);
Size tBoxSize = new Size(200,20);
int maxLabelLength = 0;
foreach (string parameter in formParameters)
{
Label label = new Label
{
Text = parameter,
Tag = "Parameter Label",
Name = $"lbl{parameter}",
Location = (labelPoint += verticalOffset),
AutoSize = true
};
panel.Controls.Add(label);
if (label.Size.Width > maxLabelLength)
{
maxLabelLength = label.Size.Width;
}
}
Size horizontalOffset = new Size(maxLabelLength + 30, 0);
labelPoint = panel.Location + new Size(20, 20) + horizontalOffset;
foreach (string parameter in formParameters)
{
TextBox textBox = new TextBox
{
AcceptsTab = true,
TabIndex = tabIndex++,
Text = "",
Tag = parameter,
Name = $"txt{parameter}",
MaximumSize = tBoxSize,
MinimumSize = tBoxSize,
Size = tBoxSize,
Location = labelPoint += verticalOffset
};
panel.Controls.Add(textBox);
}
}
Thanks everyone who helped!

Size adjustments for Panel control in windows forms

I have a Panel control on my winform which will display multiple panels inside that. For each inner panel I am setting its height. But some has less content to display some has more.
Panel hrvPanel = new Panel();
ArrayList hrvColl = pnlColl ; //Panel collection list gets from a Method
if(hrvColl.Count == 0)
return;
int splits = 0;
for(int p= hrvColl.Count-1;p>=0;p--)
{
Panel hrv = hrvColl[p] as Panel;
hrv.Height = 150;
hrvPanel.Controls.Add(hrv);
//Adding splliter
if(splits < hrvColl.Count - 1)
{
Splitter splitGrid = new Splitter();
splitGrid.Dock = DockStyle.Top;
hrvPanel.Controls.Add(splitGrid);
splits++;
}
}
hrvPanel.Dock = DockStyle.Top;
How to adjust the height of each inner panel based on its content size? I tried setting hrv.AutoSize to true,then I can see only the last panel And hrv.Dock = Top but the result is same.
If the outer Panel has Autosize = true you will be able to see all inner Panels. Promise.
If you don't, you have got some settings wrong. Make sure no unwanted settings of Dock and Anchor are used in the inner Panels.
It is also very simple to write code to find out the maximum of Top + Height over all inner Panels:
int max = 0;
foreach (Control ctl in panelOuter.Controls)
if (ctl.Top + ctl.Height > max) max = ctl.Top + ctl.Height;
panelOuter.Height = max + 3; // add the default margin!
This may be useful if you only want to set the Height and leave the Width as it is..other than that: The AutoSize property will do its job!
This is where WPF overcomes Winform, you probably can't do this automatically in Winforms. But you may have a work around like this-
Create an extended panel class that should know its preferred height
class ExPanel : Panel
{
public int PreferredHeight
{
get;
private set;
}
public ExPanel(int preferredHeight)
: base()
{
PreferredHeight = preferredHeight;
}
}
and then you can use this class as-
ExPanel hrvPanel = new ExPanel(150);
System.Collections.ArrayList hrvColl = pnlColl; //Panel collection list gets from a Method
if (hrvColl.Count == 0)
return;
int splits = 0;
for (int p = hrvColl.Count - 1; p >= 0; p--)
{
ExPanel hrv = hrvColl[p] as ExPanel;
hrv.Height = hrv.PreferredHeight;
hrvPanel.Controls.Add(hrv);
//Adding splliter
if (splits < hrvColl.Count - 1)
{
Splitter splitGrid = new Splitter();
splitGrid.Dock = DockStyle.Top;
hrvPanel.Controls.Add(splitGrid);
splits++;
}
}
hrvPanel.Dock = DockStyle.Top;
it's just an workaround to achieve your target, if you don't want to manage the height for your every panel.

Why does my dynamically created GroupBox place its RadioButtons too far right on subsequent usages?

I am adding various dynamically created controls to a panel, based on what the user selects. If a Groupbox, with associated RadioButtons, is the first control, it looks fine:
...but if it's anything other than that, the associated radio buttons seem right-aligned instead of left-aligned, as seen above, and the groupbox is too wide, to boot.
Here is the pertinent code (RepaintMockupPanel() is called when the user opts to see what his mockup looks like at any time, and getGroupBox() is the method it calls that should be where the problem lies, but I can't see it.
private void RepaintMockupPanel(Control padre)
{
const string BTN = "BUTTON";
const string CKBX = "CHECKBOX";
const string EDTTXT = "EDITTEXT";
const string RADGRP = "RADIOGROUP";
const string SPNR = "SPINNER";
const string TXTVU = "TEXTVIEW";
const int LEFT_STARTING_POINT = 4;
const int STANDARD_PADDING = 4;
int currentLeft = LEFT_STARTING_POINT;
string currentSel;
string currentSettings;
ComboBox cmbx;
Label lbl;
try
{
TabPage tp = padre as TabPage;
string panelName = tp.Name.Replace("tabPage", "panel");
Panel p = tp.Controls[panelName] as Panel;
p.Controls.Clear();
for (int i = 0; i < p.Controls.Count; i++)
{
p.Controls[i].Dispose();
}
//cmbxRow0Element0 and lblRow0Element0 to cmbxRow11Element5 and lblRow11Element5
int rowNum = getRowNum(panelName);
for (int i = 0; i < WIDGETS_PER_TABPAGE; i++)
{
cmbx = tp.Controls[string.Format("cmbxRow{0}Element{1}", rowNum, i)] as ComboBox;
lbl = tp.Controls[string.Format("lblRow{0}Element{1}", rowNum, i)] as Label;
if (cmbx.SelectedIndex < 0) continue;
currentSel = cmbx.SelectedItem.ToString().ToUpper();
currentSettings = lbl.Text;
// Possible vals (Android on left, Windows equivalents on the right:
//Button ""
//CheckBox ""
//EditText TextBox
//RadioGroup GroupBox (w. RadioButtons nested within)
//Spinner ComboBox
//TextView Label
if ((currentSel.Length > 0) && (currentSettings.Length > 0))
{
if (currentSel.Equals(BTN))
{
Button btn = getButton(currentSettings, currentLeft);
p.Controls.Add(btn);
currentLeft += btn.Width + STANDARD_PADDING;
}
else if (currentSel.Equals(CKBX))
{
CheckBox ckbx = getCheckBox(currentSettings, currentLeft);
p.Controls.Add(ckbx);
currentLeft += ckbx.Width + STANDARD_PADDING;
}
else if (currentSel.Equals(EDTTXT))
{
TextBox txtbx = getTextBox(currentSettings, currentLeft);
p.Controls.Add(txtbx);
currentLeft += txtbx.Width + STANDARD_PADDING;
}
else if (currentSel.Equals(RADGRP))
{
GroupBox grpbx = getGroupBox(currentSettings, currentLeft);
p.Controls.Add(grpbx);
currentLeft += grpbx.Width + STANDARD_PADDING;
}
else if (currentSel.Equals(SPNR))
{
ComboBox cmbxDyn = getComboBox(currentSettings, currentLeft);
p.Controls.Add(cmbxDyn);
currentLeft += cmbxDyn.Width + STANDARD_PADDING;
}
else if (currentSel.Equals(TXTVU))
{
Label lblDyn = getLabel(currentSettings, currentLeft);
p.Controls.Add(lblDyn);
currentLeft += lblDyn.Width + STANDARD_PADDING;
}
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private GroupBox getGroupBox(string currentSettings, int curLeftVal)
{
// "apple~orange~peach~True (must look for "enclose group in a black box" as the last val (ignore for the quick-and-dirty mockup, though))
// Adapted from Pierre's answer at http://stackoverflow.com/questions/23944419/why-is-only-the-first-radiobutton-being-added-to-the-groupbox
IList<string> grpbxVals = new List<string>(currentSettings.Split('~'));
GroupBox gb = new GroupBox { Height = 60, Location = new Point(curLeftVal, 0) };
gb.AutoSize = true;
int radButtonYVal = 0;
for (int i = 0; i < grpbxVals.Count() - 1; i++)
{
//gb.Controls.Add(new RadioButton { Text = grpbxVals[i], Location = new Point(curLeftVal, radButtonPosition) });
gb.Controls.Add(new RadioButton { Text = grpbxVals[i], Location = new Point(gb.Location.X+2, radButtonYVal) });
radButtonYVal += new RadioButton().Height;
}
return gb;
}
The getGroupBox() method is INDEED where the issue lies.
As a Container, GroupBox has its own canvas upon which its child controls are drawn, so when you create a control with an X value of 5, it means it's 5 from the left of the GroupBox, NOT from the left of the form. It's absolute value on the form would be it's own X value (say in this case 5) plus the X value of the GroupBox (which we'll assume has a Left value of 25) for an absolute positon of being 30 from the Left.
This is why your example shows the radio buttons pushed over so far: if you examine the distance between the left edge of the RadioButtons in relation to the left edge of their containing GroupBox, it should be about the same distance as the left edge of the GroupBox from the left edge of ITS container.
Why not use a TableLayoutPanel or FlowLayoutPanel to automatically position the controls, you can insert with fill dock the GroupBox.
Then you just need to add the controls to ... LayoutPanel and positioned automatically.
You have several options to control the rows and / or columns of the TableLayoutPanel
And as other controls to control flow into the FlowLayoutPanel
Here a example using layout panel, place a Button docked Top, and a empty TabControl docked Fill, and try this code
private void button1_Click(object sender, EventArgs e)
{
for (int t = 0; t < 4;t++ )
tabControl1.TabPages.Add(CreateTabPage(t));
}
private TabPage CreateTabPage(int t)
{
TabPage result = new TabPage()
{
Text=string.Format("TabPage {0}",t)
};
FlowLayoutPanel flp = new FlowLayoutPanel()
{
Dock = DockStyle.Fill,
AutoScroll = true,
};
for (int i = 0; i < 10; i++)
{
flp.Controls.Add(CreateGroupBox(i));
}
result.Controls.Add(flp);
return result;
}
private Control CreateGroupBox(int i)
{
GroupBox result = new GroupBox()
{
Text = string.Format("GroupBox {0}", i),
Width = 150,
Height = 100
};
FlowLayoutPanel flp = new FlowLayoutPanel()
{
Dock = DockStyle.Fill,
WrapContents = false,
AutoScroll = true,
FlowDirection=FlowDirection.TopDown
};
CreateRadios(flp, i);
result.Controls.Add(flp);
return result;
}
private void CreateRadios(FlowLayoutPanel flp, int i)
{
for (int c = 0; c < 10; c++) {
flp.Controls.Add(new RadioButton()
{
Text = string.Format("RadioButton {0} in {1}", c, i)
});
}
}
Tricycle Omnivore was right; this works:
int radButtonYVal = 4;
int leftVal = 4;
for (int i = 0; i < grpbxVals.Count() - 1; i++)
{
gb.Controls.Add(new RadioButton { Text = grpbxVals[i], AutoSize = true, Location = new Point(leftVal, radButtonYVal) });
radButtonYVal += new RadioButton().Height -4; // the "-4" is a kludge to scrunch the radiobuttons together a bit
}

Categories