Adding many Labels to a WinForm slow - c#

I'm playing around with WinForms and would like to add alot of Labels with a border so to create some sort of grid. Now, adding the Labels is easy enough:
for (int i = 0; i < 60; i++)
{
for (int j = 0; j < 60; j++)
{
var label = new Label();
label.BorderStyle = BorderStyle.FixedSingle;
label.SetBounds(i * 10, j * 10, 10, 10);
this.Controls.Add(label);
}
}
But this is really, really slow. I can almost see each of the squares being drawn individually. When creating an array of controls and adding them using Controls.AddRange() the same thing happens.
Now since I'm drawing 3600 controls, I can imagine it being somewhat slow, but I can't help but think there's a better way to do this.
When I time the Control.AddRange() statement, the Stopwatch tells me it's taking about 1600ms. The actual drawing seems to take a bit longer.
Is there any way to work around this and keep an application with alot of controls snappy and responsive?

Do you NEED the individual controls?
With tasks like this I usually override onpaint in a usercontrol and draw the text and boxes myself.
Then if you need user input, just position a single textbox ontop of your custom text.

Take a hint from your Form1.Designer.cs :
this.SuspendLayout();
// code to add Labels
this.ResumeLayout(false); // maybe use true

Consider creating a List<Control>, adding the controls to that in your loop. Then add the controls to the form's controls collection only one time after your loop using this.Controls.AddRange() to fill the controls collection.

You can try add labels to new Panel, an then Panel add to Form:
var NewPanel = new Panel
{
Dock = DockStyle.Fill
};
for (int i = 0; i < 60; i++)
{
for (int j = 0; j < 60; j++)
{
var label = new Label();
label.BorderStyle = BorderStyle.FixedSingle;
label.SetBounds(i * 10, j * 10, 10, 10);
NewPanel.Controls.Add(label);
}
}
this.Controls.Add(NewPanel); // (Form).Controls.Add(NewPanel);

Related

Create dynamic buttons in a grid layout - Create a magic square UI

I'm supposed to create a magic square in 2D using Windows Forms Application. It should look like this:
However, the user should be able to decide the size of the square (3x3, 5x5, 7x7, etc). I already wrote the code in a Console Application, but I don't know how to add the 2D graphics.
Somebody already asked this question (How do I put my result into a GUI?), and one of the answers was to use DataGridView, but I'm not sure if that's what I'm looking for, since I can't make it look like the picture.
Any ideas or advice?
You can use a TableLayoutPanel and add buttons to panel dynamically.
If you don't need interaction with buttons, you can add Label instead.
Create square dynamically:
public void CreateSquare(int size)
{
//Remove previously created controls and free resources
foreach (Control item in this.Controls)
{
this.Controls.Remove(item);
item.Dispose();
}
//Create TableLayoutPanel
var panel = new TableLayoutPanel();
panel.RowCount = size;
panel.ColumnCount = size;
panel.BackColor = Color.Black;
//Set the equal size for columns and rows
for (int i = 0; i < size; i++)
{
var percent = 100f / (float)size;
panel.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, percent));
panel.RowStyles.Add(new RowStyle(SizeType.Percent, percent));
}
//Add buttons, if you have your desired output in an array
//you can set the text of buttons from your array
for (var i = 0; i < size; i++)
{
for (var j = 0; j < size; j++)
{
var button = new Button();
button.BackColor = Color.Lime;
button.Font = new Font(button.Font.FontFamily, 20, FontStyle.Bold);
button.FlatStyle = FlatStyle.Flat;
//you can set the text of buttons from your array
//For example button.Text = array[i,j].ToString();
button.Text = string.Format("{0}", (i) * size + j + 1);
button.Name = string.Format("Button{0}", button.Text);
button.Dock = DockStyle.Fill;
//If you need interaction with buttons
button.Click += b_Click;
panel.Controls.Add(button, j, i);
}
}
panel.Dock = DockStyle.Fill;
this.Controls.Add(panel);
}
If you need interaction with buttons
void button_Click(object sender, EventArgs e)
{
var button = (Button)sender;
//Instead put your logic here
MessageBox.Show(string.Format("You clicked {0}", button.Text));
}
As an example, you can call
CreateSquare(3);
Screenshot:
You can create a Form and add a TableLayoutPanel with this property
tableLayoutPanel1.Dock = DockStyle.Fill;
tableLayoutPanel1.BackColor = Color.Gold;
and this is the result
When you create Row and Column, to fit correctly set the percentage in this way:
After this you can add a Button or Label in each square.

Adding objects during run time?

Background
I have a tab which is made active if there is more than one record returned from a query on my database.
For each record returned I would like a set of labels created and placed on the tab. For example if there are 8 records I would like 8 labels created.
Question
My loop only creates one label, even though my count is showing I have 8 records? Not sure why?
How do you create labels in a loop 8 times and not have them draw in the same location 8 times? I would them to appear in a horizontal list. Pretty sure the way I have coded the solution ,they will all be drawn in the same place?
Code
for (int i = 1; i <= rowCount; i++)
{
// Create objects
LinkLabel Linklabel1 = new LinkLabel();
Linklabel1.Text += ds.Tables[0].Rows[0]["code"].ToString();
Linklabel1.Location = new Point(10, 50);
Linklabel1.Height = 40;
Linklabel1.Width = 100;
tabControl1.TabPages[0].Controls.Add(Linklabel1);
}
Try something like this out:
for (int i = 0; i < rowCount; i++)
{
// Create objects
LinkLabel Linklabel1 = new LinkLabel();
Linklabel1.Text = ds.Tables[0].Rows[i]["code"].ToString();
Linklabel1.Height = 40;
Linklabel1.Width = 100;
Linklabel1.Location = new Point((i + 1) * 10 + (i * Linklabel1.Width), 50);
tabControl1.TabPages[0].Controls.Add(Linklabel1);
}
If you don't want to explicitly position them by setting the Location() property, consider putting a FlowLayoutPanel on the TabPage and added the controls to that instead. Then they will positioned automatically for you.

C# how to slide a panel

This code is just changing location, not sliding.
I am using this at the moment:
for (int i = 740; i == 740; i++)
{
panel2.Location = new Point(panel2.Location.X - i, panel2.Location.Y);
}
How can I slide the panel slowly?
Now, as I stated in my comment you really need to use float values, so you really need to draw it. However, the current implementation only makes a single iteration. The current loop could have been translated into this:
panel2.Location = new Point(panel2.Location.X - 740, panel2.Location.Y);
Consider a loop like this to slide it out:
for (int i = -(panel2.Width); i < 0; i++)
{
panel2.Location = new Point(i, panel2.Location.Y);
}
That algorithm is assuming that you've set the Location to the - of it's width (e.g. -740x) so that it's simply not visible on the screen. The reverse would hide it.
This will still be a little choppy, but it won't just hide it like your current code.
If you are just going to slide the panel try to do this
Try this code:
Panel panelArray = new Panel[];
Panel panel2 = panelArray[0];
for (int i = 0; i <= 100; i++)
{
panel2.Location = new Point(panel2.Location.X - i, panel2.Location.Y);
System.Threading.Thread.Sleep(10);
}

How to automaticly locate buttons on panel c#

I have a few buttons to add on the form. In the code I'm setting up some button properties:
class DigitButton : Button
{
private static int digitBtnTag;
public DigitButton()
: base()
{
this.Size = new Size(30, 30);
this.Tag = digitBtnTag;
this.Text = (this.Tag).ToString();
this.Margin = new Padding(2);
this.Padding = new Padding(2);
digitBtnTag++;
}
}
In the MainForm.cs I have
for (int i = 0; i < dgtBtns.Length; i++)
{
dgtBtns[i] = new DigitButton();
dgtBtns[i].Click += new EventHandler(this.digitButtonClick);
digitPanel.Controls.Add(dgtBtns[i]);
}
So when I launch a program I see all my buttons in the one place: (0;0) on digitPanel despite property Margin. So why don't all these buttons automaticly "push" each other in the different directions? And how to make it?
Have you tried using a FlowLayout Panel ?
Also, this video might help:
Windows Forms Controls Lesson 5: How to use the FlowLayout Panel
that's not the way controls works in c#. i'm guessing you programed at java a bit because the layout in jave works that whay, but in c# just do
for (int i = 0; i < dgtBtns.Length; i++)
{
dgtBtns[i] = new DigitButton();
dgtBtns[i].Location = new Point(50, 50 * i); // Multiplying by i makes the location shift in every loop
dgtBtns[i].Click += new EventHandler(this.digitButtonClick);
digitPanel.Controls.Add(dgtBtns[i]);
}
you'll have to figure out the location parameters by trying and see
You need to define Left and Top then add the button height or width each time you loop to position your buttons correctly i.e.
int bTop=0;
int bLeft=0;
for (int i = 0; i < dgtBtns.Length; i++)
{
dgtBtns[i] = new DigitButton();
dgtBtns[i].Click += new EventHandler(this.digitButtonClick);
dgtBtns[i].Top = bTop;
bTop += dgtBtns[i].Height;
digitPanel.Controls.Add(dgtBtns[i]);
}

Changing border of Control

Im having a problem with setting custom border of Control. This border can be done with DrawRectangle, DrawBorder or anything else as long as I get this behaviour
Obviously, the darkest border is where previouse border was. Other borders are around it trying to mimic fade out(or whatever). Now, the most challenging thing is I cannot override OnPaint or extend any other Control. This HAS to work on all Controls!
This is part of my extender provider whith which I set these borders when control has focus(like Google Chrome).
So far I have come up with this...
When adding controls in extender provider dictionary I hook up on Enter and Leave events of control. In there I get the parent of the control that is firing the event and on that form I draw these 3 rectangles. That way I sorted painting on non client area. The thing that remains is painting the actual border of control. I have tried and tried but to no avail.
I also hooked up on paint event of that control but ControlPaint.DrawBorder() is not working.
Okay, so this is method that is getting called on Enter and leave.
private void BojajGlow(Graphics gfx, Graphics gfxCtrl, Control parent, Control kontrola, bool novi)
{
Rectangle[] rect = new Rectangle[3];
for (int i = 0; i < 3; i++)
{
int x = kontrola.Location.X - (i + 1);
int y = kontrola.Location.Y - (i + 1);
int w = kontrola.Size.Width + 2 * (i + 1) - 1;
int h = kontrola.Size.Height + 2 * (i + 1) - 1;
rect[i] = new Rectangle(x, y, w, h);
}
if (novi)
{
Color boja = DohvatiOpcije(kontrola).Boja;
for (int i = 0; i < 3; i++)
{
if (i > 0)
boja = Posvjetli(95, ControlPaint.Light(boja));
Pen olovka = new Pen(boja);
olovka.EndCap = olovka.StartCap = LineCap.Round;
olovka.Width = 1;
GraphicsPath gfxPath = new GraphicsPath();
gfxPath.AddRectangle(rect[i]);
gfx.DrawPath(olovka, gfxPath);
}
}
else
{
for (int i = 0; i < 3; i++)
{
Pen olovka = new Pen(parent.BackColor);
olovka.EndCap = olovka.StartCap = LineCap.Round;
olovka.Width = 1;
GraphicsPath gfxPath = new GraphicsPath();
gfxPath.AddRectangle(rect[i]);
gfx.DrawPath(olovka, gfxPath);
}
}
}
From Enter event it is going to be called like this
if (((Control)sender).Parent != null)
BojajGlow(Graphics.FromHwnd(((Control)sender).Parent.Handle), Graphics.FromHwnd(((Control)sender).Handle), ((Control)sender).Parent, (Control)sender, true);
Does anyone have any valuable input on this?
In winforms you are probably going to need to create your own custom control inheriting from the TextBox control. In your control you can implement the OnPaint based on the controls state such as whether or not it has focus.
As for drawing outside of the control, dont. It will only frustrate you. Instead draw three boarders using the forms background color WITHIN your control and change them to the hightlighted color when you need them to glow.
Hope this helps.

Categories

Resources