I'm using the following code as just a test for how I might use a custom progress bar in the future - it actually isn't a progress bar at all, but rather a picturebox that the code draws a rectangle on, and then fills up based on a timer.
The problem is, I'm reaching 100% before the box is filled. I've tinkered around, but not been able to locate the issue. What am I doing wrong? See code below, and imgur screenshot of the behavior on my system.
Thanks.
public partial class frmLoading : Form
{
System.Windows.Forms.Timer tLoading = new System.Windows.Forms.Timer();
Double pbLoadingUnit;
int pbLoadingWIDTH, pbLoadingHEIGHT, pbLoadingComplete;
Bitmap bmpLoading;
Graphics gLoading;
private void frmLoading_Load(object sender, EventArgs e)
{
pbLoadingWIDTH = pictureLoading.Width;
pbLoadingHEIGHT = pictureLoading.Height;
pbLoadingUnit = pbLoadingWIDTH / 100;
pbLoadingComplete = 0;
bmpLoading = new Bitmap(pbLoadingWIDTH, pbLoadingHEIGHT);
tLoading.Interval = 32;
tLoading.Tick += new EventHandler(this.tLoading_Tick);
tLoading.Start();
}
private void tLoading_Tick(object sender, EventArgs e)
{
gLoading = Graphics.FromImage(bmpLoading);
gLoading.Clear(Color.DarkSlateGray);
gLoading.FillRectangle(Brushes.DodgerBlue, new Rectangle(0, 0, (int)(pbLoadingComplete * pbLoadingUnit), pbLoadingHEIGHT));
gLoading.DrawString(pbLoadingComplete + "%", new Font("Segoe UI Semibold", pbLoadingHEIGHT / 2), Brushes.White, new PointF(pbLoadingWIDTH / 2 - pbLoadingHEIGHT, pbLoadingHEIGHT / 10));
pictureLoading.Image = bmpLoading;
pbLoadingComplete++;
if (pbLoadingComplete > 100)
{
gLoading.Dispose();
tLoading.Stop();
}
}
You should change this
pbLoadingUnit = pbLoadingWIDTH / 100;
to this
pbLoadingUnit = Convert.ToDouble(pbLoadingWIDTH) / 100;
I assume your picture width is not a multiple of a hundred.
With your old code, your pbLoadingUnit will be generate as integer since you divide integer to integer.
You may use this too :
double div = 100;
pbLoadingUnit = pbLoadingWIDTH / div;
OR
pbLoadingUnit = pbLoadingWIDTH / Convert.ToDouble(100);
The point is, you should get the double value of pbLoadingUnit.
For more information about numeric casting, you may see this link dotnetperls.com/numeric-casts
Try to put the following lines in OnResize() event of the frmLoading:
pbLoadingWIDTH = pictureLoading.Width;
pbLoadingHEIGHT = pictureLoading.Height;
pbLoadingUnit = pbLoadingWIDTH / 100;
Chances are you're getting the initial size of the pictureLoading during Load()... and not the actual width when it shows up and displayed in your form.
Also let tloading.Start() happens when you already get the appropriate size of you pictureLoading object.
you have to add this variable int pbmodal;
then calculate MOD of the unit
on form load add
pbmodal = pbLoadingWIDTH % 100;
when the completed is 100% add the modal.
Difference of division
`if (pbLoadingComplete > 100)
{
gLoading.FillRectangle(Brushes.DodgerBlue, new Rectangle(0, 0, (int)(pbLoadingComplete * pbLoadingUnit) + pbmodal, pbLoadingHEIGHT));
gLoading.DrawString(pbLoadingComplete-1 + "%", new Font("Segoe UI Semibold", pbLoadingHEIGHT / 2), Brushes.White, new PointF(pbLoadingWIDTH / 2 - pbLoadingHEIGHT, pbLoadingHEIGHT / 10));
pictureLoading.Image = bmpLoading;
}`
Related
My problem is:
System.ComponentModel.Win32Exception: 'Error creating window handle'.
I know I can solve this problem with Dispose(), but when I use it in the program, I'm displaying another error:
System.ObjectDisposedException: 'Can not access a disposed object.
Object name: 'PictureBox'. '
I use the following code:
private void SetUpPuzzle_Click(int parts)
{
Panel P = new Panel
{
Size = new Size(200, 200),
Location = new Point(394, 62),
};
Controls.Add(P);
Control board = P;
int total = parts * parts;
var PB = new PictureBox[total];
var imgarray = new Image[total];
var img = User_Image.Image;
int W = img.Width / (int.Parse(Math.Sqrt(double.Parse(parts.ToString())).ToString()));
int H = img.Height / (int.Parse(Math.Sqrt(double.Parse(parts.ToString())).ToString()));
int size = 200 / (int.Parse(Math.Sqrt(double.Parse(parts.ToString())).ToString()));
for (int x = 0; x < parts; x++)
{
for (int y = 0; y < parts; y++)
{
var index = x * parts + y;
imgarray[index] = new Bitmap(W, H);
using (Graphics graphics = Graphics.FromImage(imgarray[index]))
graphics.DrawImage(img, new Rectangle(0, 0, W, H),
new Rectangle(x * W, y * H, W, H), GraphicsUnit.Pixel);
PB[index] = new PictureBox
{
Name = "P" + index,
Size = new Size(size, size),
Location = new Point(x * size, y * size),
Image = imgarray[index],
SizeMode = PictureBoxSizeMode.StretchImage
};
PB[index].MouseEnter += Images_M_E;
PB[index].MouseLeave += Images_M_L;
PB[index].MouseClick += Form_MouseClick;
*PB[index].Dispose();
*board.Controls.Add(PB[index]);
}
}
}
When I want to create 10,000 objects
This error is displayed.
My problem is:
System.ComponentModel.Win32Exception: 'Error creating window handle'.
Indeed. You are creating way too many controls for a Winforms application.
And disposing of them doesn't really help because you can't use a disposed object any longer..
To have this kind of large puzzle (10k pieces) you need to change from using PictureBoxes (or any other Controls) to display the puzzle pieces to a different approach. This has been suggested in the original question but then you only wanted to have 100 pieces, remember?
The most common approach is this: Keep a list of images (when they are <= 256x256 pixels do put them into an ImageList!) and draw them in the board's Paint event. This will get rid of all the overhead involved with PictureBoxes.
(Aside: One may think this will not be performant with all the DrawImage calls. But all those PictureBoxes also need to draw all the pixels on all their surfaces, so that is no issue. But they also have to carry the overhead of being (under the hood) fully functional windows (see the error message!), which is why the system can only have a limited number of them; always try to keep the number of controls < 1k!)
You will have to move the placement logic to the board's Paint event and will also have to change the event model..:
Instead of having each PictureBox respond to its own events you will have to find a way to do all the work in the board's events. This will have to be diffenrent, depending on the event.
Since we don't know which event you have and what they do and which data they need for their work, it is hard to give all the necessary details, so I'll just point out a few things..:
There will not be a Enter or Leave event you can use. Instead you need to detect entering an area of a piece by testing for it in the MouseMove event. If you keep a List<Rectangle> you can use Rectangle.Contains(e.Location) for this test.
You can detect a MouseClick but then will have to find out which area was clicked. If your Enter and Leave logic from the MouseMove is working you can use its result to know where the Click went.
Similar ideas can be used for all other events; some are simple, some need a little calculation but they will all be fast and pretty easy to implement..
To optimize performance try to make the image n the right size and use Format32bppPArgb as the pixel format, because it is faster to display.
Another option is to pull the pixel data right from the original image in the Paint event with the same calculations you use now to create them. (There is a DrawImage overlay that uses two Rectangles, one to determine the target and one for the source area..) This saves GDI handles, at least if you can't use an ImageList.
Always plan for growth! For a better implementation do create a Piece class. It should hold a Rectangle and an integer index into the ImageList's Images collection. It could also have a method Switch(Piece otherPiece) which would either switch the Rectangles or the indices.
Good luck :-)
I met this exception because endless loop creating new UI control and set its properties. After looped many times, this excption was thrown when change control visible property. I found both User Object and GDI Object (From Task Manager) are quite large.
I guess your issue is similar reason that system resources are exhaust by those UI controls.
I comment PB[index].Dispose(); and it's work.
private void SetUpPuzzle(int parts)
{
// Comment ***********
//Panel P = new Panel
//{
// Size = new Size(200, 200),
// Location = new Point(394, 62),
//};
//Controls.Add(P);
//Control board = P; ***********
int total = parts * parts;
var PB = new PictureBox[total];
var imgarray = new Image[total];
var img = User_Image.Image;
int W =Convert.ToInt32(img.Width / Math.Sqrt(parts));
int H = Convert.ToInt32(img.Height / Math.Sqrt(parts));
int size = Convert.ToInt32(200 / Math.Sqrt(parts));
for (int x = 0; x < parts; x++)
{
for (int y = 0; y < parts; y++)
{
var index = x * parts + y;
imgarray[index] = new Bitmap(W, H);
using (Graphics graphics = Graphics.FromImage(imgarray[index]))
graphics.DrawImage(img, new Rectangle(0, 0, W, H),
new Rectangle(x * W, y * H, W, H), GraphicsUnit.Pixel);
PB[index] = new PictureBox
{
Name = "P" + index,
Size = new Size(size, size),
Location = new Point(x * size, y * size),
Image = imgarray[index],
SizeMode = PictureBoxSizeMode.StretchImage
};
PB[index].MouseEnter += Form1_MouseEnter;
PB[index].MouseLeave += Form1_MouseLeave;
PB[index].MouseClick += Form1_MouseClick;
//Comment
//PB[index].Dispose(); < -----------------
// Add PB in Panel in form
panel1.Controls.Add(PB[index]);
}
}
// after add all refresh panel
panel1.Refresh();
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
throw new NotImplementedException();
}
private void Form1_MouseLeave(object sender, EventArgs e)
{
throw new NotImplementedException();
}
private void Form1_MouseEnter(object sender, EventArgs e)
{
throw new NotImplementedException();
}
Then Call the SetUpPuzzle method in your button like :
private void button1_Click(object sender, EventArgs e)
{
SetUpPuzzle(10);
}
I'm trying to create an analog clock from scratch using C#, and I'm a little stuck at this point.
I have this code:
private void timer1_Tick(object sender, EventArgs e)
{
int seconde = DateTime.Now.Second;
spx = cx + (int)(length * Math.Sin(Math.PI * seconde / 30));
spy = cy - (int)(length * Math.Cos(Math.PI * seconde / 30));
a = new Point(spx, spy);
g.DrawLine(pen, m, a);
}
There are more variables in the form load but this is general how i draw the line for the second pointer on the clock. My problem is that it does exactly what i want. But after 60 seconds, I have 60 lines. How do I just display the good pointer line and/or delete the old lines.
Sorry if the answer to my problem is just easy. But I cant really find anything I understand as an answer.
You need to repaint the background of the clock every second before painting the new line, to remove the old line.
Of course, that would mean that for every paint of the seconds hand (or pointer) you will need to also paint the minutes hand and the hours hand, since both of them will also be deleted.
This is because winforms does not have the concept of painting in layers - everything gets painted on a single surface - so you have to first delete the old painting before you can paint a new one.
I would do it like this:
public partial class Form1 : Form
{
private int spx, spy, length;
private Pen pen = new Pen(new SolidBrush(Color.Red), 0.5f);
private Point a,m;
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
this.Refresh(); // force redraw
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
length = Math.Min(e.ClipRectangle.Height, e.ClipRectangle.Width) / 2;
if (length != 0) // can't draw when there's no space
{
m = new Point(e.ClipRectangle.Width / 2, e.ClipRectangle.Height / 2);
int seconde = DateTime.Now.Second;
spx = m.X + (int)(length * Math.Sin(Math.PI * seconde / 30));
spy = m.Y - (int)(length * Math.Cos(Math.PI * seconde / 30));
a = new Point(spx, spy);
e.Graphics.DrawLine(pen, m, a);
}
}
}
I was taught to draw in OnPaint of Winforms controls.
Guess there are many different approaches.
By doing it this way you don't have to clear your graphics manually. This happens when the control is invalidated.
I am trying to create a paddle using a label which must be done programmatically to a brick breaker game and my aim is so that no matter what desktop computer with different screen sizes I'm using, the paddle will always be in the bottom center of the screen but for some reason the paddle(label) is not showing. This is the code I'm using:
Screen userScreen = Screen.PrimaryScreen;
int screenWidth = userScreen.WorkingArea.Width;
int screenHeight = userScreen.WorkingArea.Height;
this.Width = screenWidth;
this.Height = screenHeight;
Label lblPaddle = new Label();
lblPaddle.BackColor = Color.White;
lblPaddle.BorderStyle = BorderStyle.FixedSingle;
//lblPaddle.Left = (this.ClientSize.Width - lblPaddle.Width) / 2;
//lblPaddle.Top = (this.ClientSize.Height - lblPaddle.Height) / 2;
//lblPaddle.Size = this.ClientSize;
int lblPaddleWidth = (int)(screenWidth * 0.15);
int lblPaddleHeight = lblPaddle.Height;
int lblPaddleXCoord = (screenWidth / 2) - (lblPaddleWidth / 2);
int lblPaddleYCoord = screenHeight - lblPaddleHeight - (int)(screenHeight * 0.1);
lblPaddle.SetBounds(lblPaddleXCoord, lblPaddleYCoord, lblPaddleWidth, lblPaddleHeight);
this.Controls.Add(lblPaddle);
The part where there are comments were different approaches I attempted in order to try and make it work.
Why is it not showing?
I have absolutely no problems with your code when called like this:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitLabel();
}
private void InitLabel()
{
Screen userScreen = Screen.PrimaryScreen;
int screenWidth = userScreen.WorkingArea.Width;
int screenHeight = userScreen.WorkingArea.Height;
this.Width = screenWidth;
this.Height = screenHeight;
// I added the following 2 lines because the form was below the task bar
this.StartPosition = FormStartPosition.Manual;
this.Location = new Point(0,0);
Label lblPaddle = new Label();
lblPaddle.Name = "lblPaddle";
lblPaddle.BackColor = Color.White;
lblPaddle.BorderStyle = BorderStyle.FixedSingle;
int lblPaddleWidth = (int)(screenWidth * 0.15);
int lblPaddleHeight = lblPaddle.Height;
int lblPaddleXCoord = (screenWidth / 2) - (lblPaddleWidth / 2);
int lblPaddleYCoord = screenHeight - lblPaddleHeight - (int)(screenHeight * 0.1);
lblPaddle.SetBounds(lblPaddleXCoord, lblPaddleYCoord, lblPaddleWidth, lblPaddleHeight);
this.Controls.Add(lblPaddle);
}
}
It ends up looking like this:
EDIT: If you're planning to do anything with it, I suggest giving it a name. I added it above.
For some odd reason this was not working. I spoke with a lecturer at my school and she also could not figure out what the problem was. Perhaps it was a bug of some sort. The only way I managed to resolve this was by creating another form and create another paddle and then I copied the code from the form I created to the original and it worked. Neither me nor the lecturer could figure out what the problem was. It was just plain weird since the code was basically the same.
I have a problem with Labels in VisulaStudio.
The version of VisualStudio I use is 2012.
The problem is, I need to show a grid and label the lines. The code I wrote seams identical to the solution of a similar problem here. It doesn't give me any compiler errors, but the labels still do not display in the pictureBox.
private void aResize()
{
Size clientSize = this.ClientSize;
int hToDraw, wToDraw;
hToDraw = clientSize.Height - 2 * marginOfTab;
wToDraw = clientSize.Width - 2 * marginOfTab;
tabControl1.Size = new Size(wToDraw, hToDraw);
piB1.Size = new Size(wToDraw, hToDraw);
piB1.Image = new Bitmap(piB1.Size.Width, piB1.Size.Height);
using (Graphics g = Graphics.FromImage(piB1.Image))
{
g.FillRectangle(new SolidBrush(Color.LightGray), 0, 0, W, H);
Pen gridPen = new Pen(Color.White, 1f);
int hDrawingStep = hToDraw / 10 -1;
int wDrawingStep = wToDraw / 10 -1;
for (int local = 1; local < 11; local++)
{
g.DrawLine(gridPen, 0, hDrawingStep*local, wToDraw, hDrawingStep*local); //horizontal axix
g.DrawLine(gridPen, wDrawingStep*local, 0, wDrawingStep*local , hToDraw); //vertical axis
Label localLabel = new Label();
localLabel.Name = "la" + local;
localLabel.Visible = true;
localLabel.Text = (local*100).ToString();
localLabel.Location = new Point((int)local*hDrawingStep, (int)10);
labelList.Add(localLabel);
}
}
}
All variables which are not declared in the code above are declared earlier. I didn't want to paste in too much. Thanks for any suggestion.
You don't set any parent for your localLabel, so how could it be rendered? Try this right before adding your localLabel to your labelList:
//...
localLabel.Parent = piB1;
labelList.Add(localLabel);
I'm using this code:
private void notifyIcon1_MouseDoubleClick(object sender, MouseEventArgs e)
{
this.Show();
this.WindowState = FormWindowState.Normal;
//this.Location = new Point(form1_location_on_x, form1_location_on_y);
//this.StartPosition = FormStartPosition.CenterScreen;
Either the line
this.Location = new Point(form1_location_on_x, form1_location_on_y);
or the line
this.StartPosition = FormStartPosition.CenterScreen;
are working when I'm on my original screen resolution 1920x1080, but once I'm changing the resolution to 1024x768, the Form is on the right bottom corner not hidden I see it all but it's not in the center.
form1_location_on_x and on_y are:
form1_location_on_x = this.Location.X;
form1_location_on_y = this.Location.Y;
The question is what should I do to make it work on any other resolution like 1024x768 or any others? I tried many changes but nothing worked so far.
Size screenSize = Screen.PrimaryScreen.WorkingArea.Size;
Location = new Point(screenSize.Width / 2 - Width / 2, screenSize.Height / 2 - Height / 2);
Make sure that you set StartPosition = FormStartPosition.Manual;
Tested and working with 1920x1080 and 1024 x 768
You could calculate the top and left position of your form using this formula:
int formWidth = yourForm.Width;
int formHeight = yourForm.Height;
int screenH = (Screen.PrimaryScreen.WorkingArea.Top +
Screen.PrimaryScreen.WorkingArea.Height) / 2;
int screenW = (Screen.PrimaryScreen.WorkingArea.Left +
Screen.PrimaryScreen.WorkingArea.Width) / 2;
int top = screenH - formWidth / 2;
int left = screenW - formHeight / 2;
yourForm.Location = new Point(top, left);
Of course, these days, you have the problem of dual monitors.
I don't know if you want your form to appear always on the primary screen or you want the form appear in the current screen (the one where the form is currently displayed). In this second case you need to find where your form is displayed
private void CenterForm(Form yuorForm)
{
foreach(var s in Screen.AllScreens)
{
if(s.WorkingArea.Contains(yourForm.Location))
{
int screenH = s.WorkingArea.Height / 2;
int screenW = s.WorkingArea.Width / 2;
int top = (screenH + s.WorkingArea.Top) - formWidth / 2;
int left = (screenW + s.WorkingArea.Left) - formHeight / 2;
yourForm.Location = new Point(top, left);
break;
}
}
}
EDIT: Thanks to #alex I will complete the answer with the information on SystemEvents class
If you want to be notified by the system when the user suddenly change the resolution of your screen you could subscribe to the event SystemEvents.DisplaySettingsChanged (using Microsoft.Win32; needed)
SystemEvents.DisplaySettingsChanged += new EventHandler(SystemEvents_DisplaySettingsChanged);
and then handle the reposition of your form in the event
// This method is called when the display settings change.
void SystemEvents_DisplaySettingsChanged(object sender, EventArgs e)
{
RecenterForm(yourForm);
}
try using one of these after the resize:
this.CenterToScreen();
or
this.CenterToParent();
You can use StartPosition property of Form objects. It determines the position of a form. Set it's value to CenterScreen if you want your form to open in the center of the screen