I'm building Seat reserving software using C# and I am confusing how I draw lots of seats instantly.
I'm trying three way which is..
Using Usercontrol
public void DrawUsercontrol(int x, int y)
{
int space = 4;
int SeatLimit = 165;
int RowSeatLimit = 15;
for (var i = 1; i < SeatLimit; i++)
{
UserControl1 ctrl = new UserControl1();
ctrl.Size = new System.Drawing.Size(25, 25);
ctrl.Location = new Point(x + space, y);
if (i % RowSeatLimit == 0)
{
x = 1;
y = y + 25 + space;
}
x = x + 25 + space;
ctrl.label1.Text = i.ToString();
ctrl.label1.Click += new EventHandler(label1_Click);
panel1.Controls.Add(ctrl);
}
}
Using "Panel" control
public void DrawingPanel(int x, int y)
{
Panel myPanel = new Panel();
int width = 16;
int height = 16;
myPanel.Size = new Size(width, height);
myPanel.BackColor = Color.White;
myPanel.Location = new Point(x, y);
Label mylabel = new Label();
mylabel.Text = "4";
myPanel.Controls.Add(mylabel);
myPanel.BackColor = Color.YellowGreen;
// this.Controls.Add(myPanel);
panel1.Controls.Add(myPanel);
}
Using Graphics and draw Rectangle
public void DrawingSquares(int x, int y)
{
SolidBrush myBrush = new SolidBrush(System.Drawing.Color.Red);
Graphics graphicsObj;
graphicsObj = this.panel1.CreateGraphics();
Rectangle myRectangle = new Rectangle(x, y, 30, 30);
graphicsObj.FillRectangle(myBrush, myRectangle);
graphicsObj.Dispose();
}
I refer first option but it's too slow.
And how can I decide?
Your problem is that you are adding only one control at a time. Adding a control forces a full refresh (software GDI+ rendering is quite slow) of the parent panel (best case) and perhaps the whole form (worst case).
Try creating all your controls and adding them in one line using Panel.Controls.AddRange. This will only prompt one refresh.
You should also only add these controls when the form is first shown and when the number of seats change - it is an expensive (and relatively slow) operation.
Consider creating a UserControl for each seat so that you don't have to manage the seat labels and seat borders separately - this way you can just have one list. If you add the seats in order, the index of an item in the list will map to its seat number! You probably wont get a performance increase from this but your code will be easier to work with.
Related
I create a contact manager. The user can already enter some and they are stored in a file and re-opened when the program is started. Each contact is an object of my Person class.
When launching the program (in Load()) I created a for loop until all contacts have been explored (contacts are stored when opened in a Person table)
So now I come to my problem:
I have a panel that is scrollable (I have enabled the option) and I would like every 50 pixels in height, that a new panel is created with name, first name, email and phone number of my contacts and a pictureBox.
Except, I would like to be able to do it dynamically instead of creating the same thing more than 50 times and repeating the same code 50 times
Because for the moment I have done this:
for(int i = 0; i < contacts.Count; i++) //Afficher les contacts
{
if(!panel_contact1.Visible)
{
panel_contact1.Visible = true;
label_prenom_nom1.Text = contacts[i].Prenom + " " + contacts[i].Nom;
label_email1.Text = contacts[i].mail;
label_tel1.Text = contacts[i].tel;
pictureBox1.Image = Image.FromFile(contacts[i].pathImage);
}
else if(!panel_contact2.Visible)
{
panel_contact2.Visible = true;
label_prenom_nom2.Text = contacts[i].Prenom + " " + contacts[i].Nom;
label_email2.Text = contacts[i].mail;
label_tel2.Text = contacts[i].tel;
pictureBox2.Image = Image.FromFile(contacts[i].pathImage);
}
}
It's the code only for the first two contacts and I don't want to repeat it up to 100 times.
So my question is:
How to create panels, with in each of the labels and a pictureBox, every 50px in a panel.
Thank you for reading, if you just have advice said always the same if you all have the code I'm a taker especially since I think it should be easy to do because the content of the labels are already dynamically teaching.
Thank you.
On WinForms, you can use this:
int x = 0;
int y = 0;
int delta = 10;
for ( int i = 0; i < contacts.Count; i++ )
{
// Create picture box
var picture = new PictureBox();
picture.Image = Image.FromFile(contacts[i].pathImage);
picture.Location = new Point(x, y);
picture.Size = new Size(picture.Image.Width, picture.Image.Height);
int dx = picture.Width + delta;
// Create name label
var labelName = new Label();
labelName.AutoSize = true;
labelName.Location = new Point(x + dx, y);
labelName.Font = new Font(labelName.Font, FontStyle.Bold);
labelName.Text = contacts[i].Prenom + " " + contacts[i].Nom;
// Create mail label
var labelMail = new Label();
labelMail.AutoSize = true;
labelMail.Location = new Point(x + dx, y + labelName.Height);
labelMail.Text = contacts[i].mail;
// Create phone label
var labelPhone = new Label();
labelPhone.AutoSize = true;
labelPhone.Location = new Point(x + dx, y + labelName.Height + labelMail.Height);
labelPhone.Text = contacts[i].tel;
// Add controls
panel.Controls.Add(picture);
panel.Controls.Add(labelName);
panel.Controls.Add(labelMail);
panel.Controls.Add(labelPhone);
// Iterate
int dy1 = labelName.Height + labelMail.Height + labelPhone.Height;
int dy2 = picture.Height;
y += Math.Max(dy1, dy2) + delta;
}
But you may prefer create a custom control where you put a picture box and three labels designed as you want with colors, font size, bolding, margin, borderstyle and so on, with Height at 50.
Add new user custom control with Project > Add > User control and choose a file name like PersonControl.
public partial class PersonControl : UserControl
{
public PersonControl()
{
InitializeComponent();
}
public PersonControl(Person person) : this()
{
pictureBox.Image = Image.FromFile(person.pathImage);
labelName.Text = person.Prenom + " " + person.Nom;
labelMail.Text = person.mail;
labelPhone.Text = person.tel;
}
}
int x = 0;
int y = 0;
for ( int i = 0; i < contacts.Count; i++ )
{
var control = new PersonControl(contacts[i]);
control.Location = new Point(x, y);
panel.Controls.Add(control);
y += control.Height;
}
You should take care of the file image size that must be the same for all and the same as the picture box else you need to manage that by resizing for example.
How to resize an Image C#
If you're using windows forms, create a user control with a constructor using the Person object, set the labels and picture boxes to the info of that person. In the main loop you posted, create a new instance of this and set it's position to 0, i * 50 to place it under the previous one.
Example:
for(int i = 0; i < contacts.Count; i++)
{
YourUserControl u1 = new YourUserControl(pass the person object);
Panel1.Controls.Add(u1);
u1.Location = new Point(0, i * 50);
}
This depends on the display technolgy you are using (WinForms, WPF/UWP, ASP.NET, other).
In Windows Forms you just create the elements and add them to the container. The designer wroks on it's own part of the partial class. The designer code is run with InitializeComponents() in the constructor. Anything it can do, you can do. And you can easily look at it.
In WPF/UWP stuff is a bit more complicated. The designer does not work on code, but on XAML, a dedciated markup language. You are not supposed to manually add anything to the UI from the code. WPF/UWP and XAML were designed with the MVVM pattern in mind. And dealing with lists of things is what it does best. While you can use other patterns, generally that looses 90% of it's power and runs into issues at every other corner.
For ASP.Net it would depend on wich pattern you use. While not originally designed for it, MVC has been extremely popular with WebApplication. So much so, it is almost synonimous with WebApplications and ASP.NET. However this does not look like a web Application.
I am making a program where you bassicly move from tile to tile in windows forms. So in order to do that, I wanted to use panels each panel has a tag. To detect collision. The goal is to be able to find creatures that randomly spawn somehow i don't how to do that.
So I have an image of my map. and I divided into multiple tiles. So using this code I create the pictureboxes needed for the tile map but I want to add a character that is one tile big how can I do that???
I also want to be able to move around that character tag how would i fix that.
And also give the tiles a tag so the character can't go there.
private void Form1_Load(object sender, EventArgs e)
{
int tileWidth = 30;
int tileHeight = 30;
int tileRows = 30;
int tileCols = 30;
using (Bitmap sourceBmp = new Bitmap("D:\\900x900.jpg"))
{
Size s = new Size(tileWidth, tileHeight);
Rectangle destRect = new Rectangle(Point.Empty, s);
for (int row = 0; row < tileRows; row++)
for (int col = 0; col < tileCols; col++)
{
PictureBox p = new PictureBox();
p.Size = s;
Point loc = new Point(tileWidth * col, tileHeight * row);
Rectangle srcRect = new Rectangle(loc, s);
Bitmap tile = new Bitmap(tileWidth, tileHeight);
Graphics G = Graphics.FromImage(tile);
G.DrawImage(sourceBmp, destRect, srcRect, GraphicsUnit.Pixel);
p.Image = tile;
p.Location = loc;
p.Tag = loc;
p.Name = String.Format("Col={0:00}-Row={1:00}", col, row);
// p.MouseDown += p_MouseDown;
// p.MouseUp += p_MouseUp;
// p.MouseMove += p_MouseMove;
this.Controls.Add(p);
}
}
}
I am working on an 'use-case-diagram-form' where an user can select an element and a modus
Just a simple form. It al works fine, made a class for each actor element and each use-case element. Both are added in a list after beeing created.
But somehow I just can't figure out how to select a created element and after do something with it.
classes i made:
class Actor
{
private static int _id;
private Panel _panel;
public string Name { get; private set; }
public int X { get; private set; }
public int Y { get; private set; }
public Actor(Panel panel, string name, int x, int y)
{
_id++;
_panel = panel;
Name = name;
X = x;
Y = y;
}
public void DrawActor()
{
// draw Actor
var graphics = _panel.CreateGraphics();
var pen = new Pen(Color.Black, 2);
graphics.DrawEllipse(pen, X - 10, Y - 30, 20, 20);
graphics.DrawLine(pen, X, Y - 10, X, Y + 20);
graphics.DrawLine(pen, X - 15, Y, X + 15, Y);
graphics.DrawLine(pen, X, Y + 20, X - 15, Y + 35);
graphics.DrawLine(pen, X, Y + 20, X + 15, Y + 35);
// rectangle around actor
graphics.DrawRectangle(pen, (X - 20), (Y - 30), 40, 80);
// setup font
var stringFont = new Font("Arial", 10);
// measure string
var textWith = graphics.MeasureString(Name, stringFont).Width;
// label
var label = new Label();
var actorText = (_id < 10 ? "0" : "") + _id.ToString() + "-" + Name;
label.Text = actorText;
label.Location = new Point(X - (Convert.ToInt32(textWith)/2), Y + 40);
label.AutoSize = true;
label.BorderStyle = BorderStyle.FixedSingle;
_panel.Controls.Add(label);
}
class UseCase
{
private static int _id;
private Panel _panel;
private string _name;
private int _x;
private int _y;
public UseCase(Panel panel, string name, int x, int y)
{
_id++;
_panel = panel;
_name = name;
_x = x;
_y = y;
}
public void DrawUseCase()
{
var graphics = _panel.CreateGraphics();
var pen = new Pen(Color.Black, 2);
graphics.DrawEllipse(pen, _x , _y , 120, 50);
// setup font
var stringFont = new Font("Arial", 10);
// measure string
var textWith = graphics.MeasureString(_name, stringFont).Width;
// label
var label = new Label();
var useCaseText = (_id < 10 ? "0" : "") + _id.ToString() + "-" + _name;
label.Text = useCaseText;
label.Location = new Point(_x - (Convert.ToInt32(textWith) / 2) + 60, _y + 20);
label.AutoSize = true;
label.BorderStyle = BorderStyle.FixedSingle;
_panel.Controls.Add(label);
}
}
Github repository:
https://github.com/JimVercoelen/use-case-helper
Thanks
Your code has several issues, all of which will go away once you learn how to draw properly in winforms!
There are many posts describing it but what you need to understand that you really have these two options:
Draw onto the surface of the control. This is what you do, but you do it all wrong.
Draw into a Bitmap which is displayed in the control, like the Picturbox's Image or a Panel's BackgroundImage.
Option two is best for graphics that slowly add up and won't need to be corrected all the time.
Option one is best for interactive graphics, where the user will move things around a lot or change or delete them.
You can also mix the options by caching drawn graphics in a Bitmap when they get too numerous.
Since you started with drawing onto the surface let's see how you should do it correctly:
The Golden Rule: All drawing needs to be done in the control's Paint event or be triggered from there always using only the Paint event's e.Graphics object!
Instead you have created a Graphics object by using control.CreateGraphics. This is almost always wrong.
One consequence of the above rule is that the Paint event needs to be able to draw all objects the user has created so far. So you will need to have class level lists to hold the necessary data: List<ActorClass> and List<UseCaseClass>. Then it can do maybe a
foreach(ActorClass actor in ActorList) actor.drawActor(e.Graphics)
etc.
Yes this fully repainting everything looks like a waste but it won't be a problem until you need to draw several hundreds of object.
But if you don't do it this way, nothing you draw persists.
Test it by running your present code and doing a Minimize/Maximize sequence. Poof, all drawings are gone..
Now back to your original question: How to select an e.g. Actor?
This really gets simple as you can can iterate over the ActorList in the MouseClick event (do not use the Click event, as it lacks the necessary parameters):
foreach (ActorClass actor in ActorList)
if (actor.rectangle.Contains e.Location)
{
// do stuff
break;
}
This is a simple implementation; you may want to refine it for the case of overlapping objects..
Now you could do things like maybe change the color of the rectangle or add a reference to the object in a currentActor variable.
Whenever you have made any changes to your lists of things to draw, like adding or deleting a object or moving it or changing any (visible) properties you should trigger an update via the Paint event by calling Invalidate.
Btw: You asked about a PictureBox in the title but use only a Panel in the code. Using a PictureBoxis recommended as it is doublebuffered and also combines two Bitmaps to let you use both a caching Image and a BackgroundImage with maybe a nice paper..
As far as I can see your code so far lacks the necessary classes. When you write them add a Draw routine and either a reference to the Label you add or simply use DrawString to draw the text yourself..
Update:
After looking at your project, here a the minimal changes to make the drawing work:
// private Graphics graphics; // delete!!
Never try to cache a Graphics object!
private void pl_Diagram_Paint(object sender, PaintEventArgs e)
{
pen = new Pen(Color.Black, 1);
DrawElements(e.Graphics); // pass out the 'good' object
//graphics = pl_Diagram.CreateGraphics(); // delete!
}
The same; pass the real Graphics object into the drawing routine instead!
// actor
if (rb_Actor.Checked)
{
if (e.X <= 150)
{
var actor = new Actor(name, e.X, e.Y);
_actors.Add(actor);
pl_Diagram.Invalidate(); // trigger the paint event
//DrawElements();
}
}
// use case
if (rb_Use_Cases.Checked)
{
var useCase = new UseCase(name, e.X, e.Y);
_useCases.Add(useCase);
pl_Diagram.Invalidate(); // trigger the paint event
//DrawElements();
}
Instead of calling the routine directly we trigger the Paint event, which then can pass a good Graphics object to it.
public void DrawElements(Graphics graphics)
{
foreach (var actor in _actors)
{
DrawActor(graphics, actor);
}
foreach (var useCase in _useCases)
{
DrawUseCase(graphics, useCase);
}
}
We receive the Graphics object and pass it on..
private void DrawActor(Graphics graphics, Actor actor)
and
graphics.DrawEllipse(pen, (useCase.X - 60), (useCase.Y - 30), 120, 60);
After these few changes the drawing persists.
Replacing the Panel by a Picturebox is still recommended to avoid flicker during the redraw. (Or replace by a double-buffered Panel subclass..)
I'm using simple code to generate a grid (gridSize * gridSize fields with lines dividing them in column and row, basically a TicTacToe grid).
As I'm creating the panels dynamically during Form_Load, I need to also adjust the size of the form. However, setting it to gridSize * tileSize, gridSize * tileSize is not big enough - I found by experimentation that I need to add ~15 to width and ~40 to height for gridSize = 3 and tileSize = 120. Why is this?
Code below:
private void Form1_Load(object sender, EventArgs e)
{
const int tileSize = 120;
const int gridSize = 3;
/* Here: When setting size, I need to add 15 and 40? */
this.Size = new System.Drawing.Size(tileSize * gridSize + 15, tileSize * gridSize + 40);
// initialize the "board"
tictactoeFields = new Panel[gridSize, gridSize]; // column, row
// double for loop to handle all rows and columns
for (var n = 0; n < gridSize; n++)
{
for (var m = 0; m < gridSize; m++)
{
// create new Panel control which will be one
// tic tac toe field
var newPanel = new Panel
{
Size = new Size(tileSize, tileSize),
Location = new Point(tileSize * n, tileSize * m)
};
// add to our 2d array of panels for future use
tictactoeFields[n, m] = newPanel;
newPanel.BackColor = Color.White;
if(n != 0)
{
// Draw a line in front (to the left) of this panel
Panel leftSeparator = new Panel
{
Size = new Size(1, tileSize),
Location = newPanel.Location,
BackColor = Color.Black
};
Controls.Add(leftSeparator);
}
if(m != 0)
{
// Draw a line on top (above) this panel
Panel topSeparator = new Panel
{
Size = new Size(tileSize, 1),
Location = newPanel.Location,
BackColor = Color.Black
};
Controls.Add(topSeparator);
}
}
}
foreach(Panel pan in tictactoeFields)
{
// add to Form's Controls so that they show up
Controls.Add(pan);
}
}
The Size property is just a shorthand for setting the size of the Bounds property which includes nonclient elements such as scroll bars, borders, title bars, and menus.
What you should do is to set the size of the ClientRectangle property, or use the ClientSize shorthand.
There's also a DisplayRectangle property which includes padding, but in this case use the ClientRectangle property.
this.ClientSize = new Size((tileSize * gridSize), (tileSize * gridSize));
I am creating a panel and then adding some labels/buttons to it to form a grid. The issue is that if I add more than say 25x25 items to the panel, there is a terrible performance hit. I can resize the form ok but when I scroll the panel to see all the labels the program lags, the labels/buttons tear or flicker, and sometimes it can make the program unresponsive. I have tried adding the controls to a "DoubleBufferedPanel" that I created. This seems to have no effect. What else could I do? Sorry for such a large code listing. I didn't want to waste anyone's time.
namespace GridTest
{
public partial class Form1 : Form
{
private const int COUNT = 50;
private const int SIZE = 50;
private Button[,] buttons = new Button[COUNT, COUNT];
private GridPanel pnlGrid;
public Form1()
{
InitializeComponent();
pnlGrid = new GridPanel();
pnlGrid.AutoScroll = true;
pnlGrid.Dock = DockStyle.Fill;
pnlGrid.BackColor = Color.Black;
this.Controls.Add(pnlGrid);
}
private void Form1_Load(object sender, EventArgs e)
{
int x = 0;
int y = 0;
int offset = 1;
for (int i = 0; i < COUNT; i++)
{
for (int j = 0; j < COUNT; j++)
{
buttons[i, j] = new Button();
buttons[i, j].Size = new Size(SIZE, SIZE);
buttons[i, j].Location = new Point(x, y);
buttons[i, j].BackColor = Color.White;
pnlGrid.Controls.Add(buttons[i, j]);
x = x + SIZE + offset;
}
x = 0;
y = y + SIZE + offset;
}
}
}
}
Also, the GridPanel class:
namespace GridTest
{
public class GridPanel : Panel
{
public GridPanel()
: base()
{
this.DoubleBuffered = true;
this.ResizeRedraw = false;
}
}
}
If you must add controls at run time, based on some dynamic or changing value, you might want to consider creating an image on the fly, and capturing mouse click events on its picturebox. This would be much quicker and only have one control to draw rather than hundreds. You would lose some button functionality such as the click animation and other automatic properties and events; but you could recreate most of those in the generation of the image.
This is a technique I use to offer users the ability to turn on and off individual devices among a pool of thousands, when the location in a 2-dimensional space matters. If the arrangement of the buttons is unimportant, you might be better offering a list of items in a listview or combobox, or as other answers suggest, a datagridview with button columns.
EDIT:
An example showing how to add a graphic with virtual buttons. Very basic implementation, but hopefully you will get the idea:
First, some initial variables as preferences:
int GraphicWidth = 300;
int GraphicHeight = 100;
int ButtonWidth = 60;
int ButtonHeight = 20;
Font ButtonFont = new Font("Arial", 10F);
Pen ButtonBorderColor = new Pen(Color.Black);
Brush ButtonTextColor = new SolidBrush(Color.Black);
Generating the image:
Bitmap ControlImage = new Bitmap(GraphicWidth, GraphicHeight);
using (Graphics g = Graphics.FromImage(ControlImage))
{
g.Clear(Color.White);
for (int x = 0; x < GraphicWidth; x += ButtonWidth)
for (int y = 0; y < GraphicHeight; y += ButtonHeight)
{
g.DrawRectangle(ButtonBorderColor, x, y, ButtonWidth, ButtonHeight);
string ButtonLabel = ((GraphicWidth / ButtonWidth) * (y / ButtonHeight) + x / ButtonWidth).ToString();
SizeF ButtonLabelSize = g.MeasureString(ButtonLabel, ButtonFont);
g.DrawString(ButtonLabel, ButtonFont, ButtonTextColor, x + (ButtonWidth/2) - (ButtonLabelSize.Width / 2), y + (ButtonHeight/2)-(ButtonLabelSize.Height / 2));
}
}
pictureBox1.Image = ControlImage;
And responding to the Click event of the pictureBox:
// Determine which "button" was clicked
MouseEventArgs em = (MouseEventArgs)e;
Point ClickLocation = new Point(em.X, em.Y);
int ButtonNumber = (GraphicWidth / ButtonWidth) * (ClickLocation.Y / ButtonHeight) + (ClickLocation.X / ButtonWidth);
MessageBox.Show(ButtonNumber.ToString());
I think you won't be able to prevent flickering having 625 buttons (btw, windows) on the panel. If such layout is mandatory for you, try to use the DataGridView, bind it to a fake data source containing the required number of columns and rows and create DataGridViewButtonColumns in it. This should work much more better then your current result ...