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);
};
Related
I have a TableLayoutPanel that contains a Label and a ComboBox:
var lblDesignGroup = new Label
{
Name = "lblDesignGroup",
Text = "DesignGroup",
Margin = new Padding(0, 50, 0, 0)
};
tlpCallLog.Controls.Add(lblDesignGroup, 0, 0);
var cboDesignGroup = new ComboBox
{
Name = "cboDesignGroup",
DataSource = designGroups,
DisplayMember = "DesignGroupName",
ValueMember = "DesignGroupId",
Margin = new Padding(0, 50, 0, 0),
DropDownStyle = ComboBoxStyle.DropDownList
};
Once I create them, I setup the TableLayoutPanel as:
tlpCallLog.ColumnStyles.Clear();
for (int i = 0; i < tlpCallLog.ColumnCount; i++)
{
tlpCallLog.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize));
}
tlpCallLog.RowStyles.Clear();
for (int i = 0; i < tlpCallLog.RowCount; i++)
{
tlpCallLog.RowStyles.Add(new RowStyle(SizeType.AutoSize));
}
tlpCallLog.CellBorderStyle = TableLayoutPanelCellBorderStyle.Single;
But for some reason, I have blank space between the Label and the DropDown.
As you can see, the DropDown width adjusts correctly, it does not have any free space, but the Label has it.
I want to remove that space. How can I achieve that?
The Label Control auto-sizes in a default layout (when child of a Form, as a standard scenario, since the Form performs its layout when initialized). When created like this, the AutoSize property is set to true because that's the default value, but the Layout is never performed.
When you explicitly set AutoSize = true, then the layout is performed.
Set both the TableLayoutPanel and the Label to AutoSize:
tlpCallLog.AutoSize = true;
tlpCallLog.RowStyles.Clear();
//[...]
var lblDesignGroup = new Label {
AutoSize = true,
Name = "lblDesignGroup",
Text = "DesignGroup",
Margin = new Padding(0, 53, 0, 0)
};
tlpCallLog.Controls.Add(lblDesignGroup, 0, 0);
var cboDesignGroup = new ComboBox {
//[...]
}
I suggest to set the Label's Margin = new Padding(0, 53, 0, 0): 3 pixels down, to align with the ComboBox text. It's a valid measure with (almost) any Font size.
Another suggestion is to set the [Form].AutoScaleMode = AutoScaleMode.Dpi.
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!
I am working with Winforms and I have a question about making these more dynamic. For instance I can create a winform that has a group of text boxes that displays data however how would I make this dynamic? In terms of the number of text boxes the user can see depends on what data is found?
I know I can do something along the lines of the below:
TextBox tb = new TextBox();
In my scenario I have an application that reads through a bunch of documents and if a $ is found then a prompt appears asking the user to input a proper value, however, if the document has a lot of values that need updating then this is a lot of dialog boxes. So a good way to resolve this is to have the dialog box appear at the end (after the file has been read) with all the values that need updating and the user can update all these at once.
The problem that I see is that the number of values that need to be displayed can be anything from 1 on wards, which means the loop would need to account for this.
My current code is as below;
foreach (FileInfo fi in rgFiles)
{
current++;
fileProcessBar.Value = current / count * 60 + 40;
string[] alllines = File.ReadAllLines(fi.FullName);
for (int i = 0; i < alllines.Length; i++)
{
if (alllines[i].Contains("$"))
{
// prompt
int dollarIndex = alllines[i].IndexOf("--");
Regex regex = new Regex(#"(--.{1,100})");
var chars = regex.Match(alllines[i]).ToString();
string PromptText = chars.Replace("-", "");
string promptValue = CreateInput.ShowDialog(PromptText, fi.FullName);
if (promptValue.Equals(""))
{
}
else
{
alllines[i] = alllines[i].Replace("$", promptValue);
File.WriteAllLines(fi.FullName, alllines.ToArray());
}
}
}
prompt method:
public static string ShowDialog(string text, string caption)
{
Form prompt = new Form()
{
Width = 600,
Height = 150,
FormBorderStyle = FormBorderStyle.FixedDialog,
Text = caption,
StartPosition = FormStartPosition.CenterScreen
};
Label textLabel = new Label() { Left = 50, Top = 15, Width = 500, Text = text };
TextBox textBox = new TextBox() { Left = 50, Top = 52, Width = 500 };
Button confirmation = new Button() { Text = "Add", Left = 450, Width = 100, Top = 72, DialogResult = DialogResult.OK };
confirmation.Click += (sender, e) => { prompt.Close(); };
prompt.Controls.Add(textBox);
prompt.Controls.Add(confirmation);
prompt.Controls.Add(textLabel);
prompt.AcceptButton = confirmation;
prompt.MaximizeBox = false;
return prompt.ShowDialog() == DialogResult.OK ? textBox.Text : "";
}
My question is how can a winform be more dynamic as in terms of size and what is displayed? how can I create a new form without specifying size and position? but still not being a jumbled mess?
Make a new Form with a certain size. Then add a FlowLayoutPanel to the form which has almost the same width as the form and almost the same height. Leave enough space for the button that you need:
In the panel properties set the fields AutoSize to true and AutoSizeMode to GrowAndShrink:
Don't forget to specify the FlowDirection:
this.panel.FlowDirection = FlowDirection.TopDown;
Now you need just a method which adds your prompting controls to the controls of the FlowLayoutPanel (which will order them in an automatic fashion) :
public void AddToCanvas(string text)
{
this.flowLayoutPanel1.Controls.Add(new Label() {Text = text});
this.flowLayoutPanel1.Controls.Add(new TextBox());
Resize();
}
And a resize method to adjust the form to the amount of current controls inside it:
public void Resize()
{
Size s = new Size();
s.Height = this.flowLayoutPanel1.Height + this.button_Accept.Height +
(this.flowLayoutPanel1.Controls.Count * 10) + y_offset;
s.Width = this.flowLayoutPanel1.Width + 10;
this.MaximumSize = s;
this.Size = s;
}
With this input:
random text
$ Name
$ Address
random text
$ Age
random text
$ Planet
$ Continent
random text
$ StarSystem
I get the following Result:
EDIT:
Create a variable after you have read the contents of one file (before you loop through the lines):
DynamicPrompt dp = new DynamicPrompt("YourCaption");
for (int i = 0; i < alllines.Length; i++)
{
if (alllines[i].Contains("$"))
{
Start the loop and if you get to the important line call
dp.AddToCanvas(PromptText);
I'm currently designing a WinForms music player and I'm having some trouble figuring out how to set what a button does when the button is created via
Controls.Add(new Button(){})
The code I'm currently using is a while loop inside of which is the code to create a button name + x where x is a value that increments at the end of the loop. What i would like to do is set it so that on click the button will execute the function to play music which is named say "mpPlay". What would be the best way to go about this?
Here is the current code I'm using:
while (trackNoReader.Read())
{
flpTrackNo.Controls.Add(new Button()
{
Name = "btnTrackNo" + x,
Text = trackNoReader[0] as string,
BackColor = Color.Transparent,
FlatStyle = FlatStyle.Flat,
AutoSize = false,
Dock = DockStyle.Top,
Width = flpArtist.Width,
ForeColor = ColorTranslator.FromHtml("#444444"),
Font = new Font("Trebuchet MS", 9),
Enabled = true,
TextAlign = ContentAlignment.MiddleLeft,
FlatAppearance =
{
BorderSize = 0
},
});
x++;
}
If you wish to see the source code you can download the git repository here. Many Thanks - Ross
[EDIT]
Here's the code for mpPlay()
public void mpPlay(int x, string Column)
{
MySqlCommand selectPath = new MySqlCommand("SELECT" + Column + "FROM Music WHERE TrackNo=" + x);
selectPath.Connection = DB.connect;
MySqlDataReader readerPath = selectPath.ExecuteReader();
while (readerPath.Read())
{
path = readerPath[0] as string;
path = path.Replace("\"", "\\");
}
mpPlayer.Open(path);
mpPlayer.Play();
}
You could have a simple helper method like this:
public Button AttachMethodToButton(Button b, Action buttonMethod)
{
b.Click += (s, e) => buttonMethod();
return b;
}
which you would call like this:
flpTrackNo.Controls.Add(AttachMethodToButton(new Button()
{
// ... button properties like you already have
},
mpPlay)); // I'm assuming 'mpPlay' is a void method
If you want to call mpPlay with arguments, you'd call it like this:
flpTrackNo.Controls.Add(AttachMethodToButton(new Button()
{
// ... button properties like you already have
},
() => mpPlay(intParam, stringParam))); // replace intParam and stringParam with whatever arguments you want here
while (trackNoReader.Read())
{
flpTrackNo.Controls.Add(new Button()
{
Name = "btnTrackNo" + x,
Text = trackNoReader[0] as string,
BackColor = Color.Transparent,
FlatStyle = FlatStyle.Flat,
AutoSize = false,
Dock = DockStyle.Top,
Width = flpArtist.Width,
ForeColor = ColorTranslator.FromHtml("#444444"),
Font = new Font("Trebuchet MS", 9),
Enabled = true,
TextAlign = ContentAlignment.MiddleLeft,
FlatAppearance =
{
BorderSize = 0
},
});
// Adds Click event handler to last added button
flpTrackNo.Controls[flpTrackNo.Controls.Count - 1].Click += (sender, args) =>
{
// Call the function to play a sound
// Additionally the sender gives you the specific button as object
};
x++;
}
public int dialog()
{
Form prompt = new Form(); // creates form
//dimensions
prompt.Width = 300;
prompt.Height = 125;
prompt.Text = "Adding Rows"; // title
Label amountLabel = new Label() { Left = 50, Top = 0, Text = "Enter a number from 1-50" }; // label for prompt
amountLabel.Font = new Font("Microsoft Sans Serif", 9.75F);
TextBox value = new TextBox() { Left = 50, Top = 25, Width = prompt.Width / 2 }; // text box for prompt
Button confirmation = new Button() { Text = "Ok", Left = prompt.Width / 2 - 50, Width = 50, Top = 50 }; // ok button
confirmation.Click += (sender, e) => { prompt.Close(); }; // if clicked it will close
prompt.AcceptButton = confirmation; // enter
prompt.KeyPreview = true;
prompt.KeyDown += (sender, e) =>
{
if (e.KeyCode == Keys.Escape) prompt.DialogResult = DialogResult.Cancel; // user presses ESC key to close
};
// adding the controls
prompt.Controls.Add(value);
prompt.Controls.Add(confirmation);
prompt.Controls.Add(amountLabel);
prompt.ShowDialog();
// returning and checking if int block
int num;
Int32.TryParse(value.Text, out num);
return num;
}
This is the full code. The short version of the code is:
Label amountLabel = new Label() { Left = 50, Top = 0, Text = "Enter a number from 1-50" };
prompt.Controls.Add(amountLabel);
The problem is that it will only display up to "Enter a number". It won't display the full text for some reason. I tried shorter "E" and it worked. I even tried "Enter a number from" but it still didn't fully display.
You could enable AutoSize, which is set to true by default if you create it via the designer:
Label amountLabel
= new Label { AutoSize = true, Left = 50, Top = 0, Text = "Enter a number from 1-50" };
Set the width of the label:
Label amountLabel = new Label() { Left = 75, Top = 0, Width = 1000, Text = "Enter a number from 1-50" };
Don't make the width that wide, though. I just wanted to make sure the text fits without testing different values.
I was surprised that it didn't adjust the width automatically.
Set the Label's AutoSize property to true.
Label amountLabel = new Label() { AutoSize=true, Left = 50, Top = 0, Text = "Enter a number from 1-50" };