Customer progress bar not filling properly - c#

I created a simple progress bar for my C# Forms Application. It loads and functions properly with the sole exception that it doesn't fill completely by the time it gets to 100% It needs to go to 115% to complete. I have no idea why its doing this. Any insight, you could be my hero. Here is the code:
private void CustomProgressBar()
{
picBoxPB.Visible = true;
width = picBoxPB.Width;
height = picBoxPB.Height;
Unit = width / 100;
complete = 0;
bmp = new Bitmap(width, height);
t.Interval = 30;
t.Tick += new EventHandler(this.t_Tick);
t.Start();
}
private void t_Tick(object sender, EventArgs e)
{
graph = Graphics.FromImage(bmp);
graph.Clear(Color.DarkGoldenrod);
graph.FillRectangle(Brushes.ForestGreen, new Rectangle(0,0,(int)(complete * Unit),height));
graph.DrawString(complete + "%", new Font("Papyrus", height / 3), Brushes.Black, new PointF(width / 2 - height, height / 10));
picBoxPB.Image = bmp;
complete++;
if (complete>100)
{
graph.Dispose();
t.Stop();
}
}

Without any more information all I can see is that you are incrementing the complete variable after you draw the graph.
A wild guess is you move the complete to the top of the method such as
private void t_Tick(object sender, EventArgs e)
{
complete++;
graph = Graphics.FromImage(bmp);
graph.Clear(Color.DarkGoldenrod);
graph.FillRectangle(Brushes.ForestGreen, new Rectangle(0,0,(int)(complete * Unit),height));
graph.DrawString(complete + "%", new Font("Papyrus", height / 3), Brushes.Black, new PointF(width / 2 - height, height / 10));
picBoxPB.Image = bmp;
if (complete>100)
{
graph.Dispose();
t.Stop();
}
}
This assumption is made bearing no understanding of the Unit field (which appears to be a percentage of the Width field (not shown).

You initialize Unit to be width / 100 that's ok, but you do it in CustomProgressBar() and not where it is used.
If your control is used elsewhere, it will probably be resized. So the width will change. But you did not update Unit accordingly (nether your Bitmap object).
Yo have two option:
Listen for size change and update Unit when the event occurs.
Move your Unit variable in your t_tick function and compute it each time.
The second option cost a little more in time but the code readability is way better.
If you did not use the Unit variable elsewhere, choose the second option.
Also, if your goal is to draw a custom control, you should learn on how to overload Control.OnPaint, using a bitmap is not the solution here.

Related

How to call Invalidate not for the whole panel from another event/class

I have a paint event which looks like this:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Rectangle rec = new Rectangle(2, 2, 820, 620);
Pen pi = new Pen(Color.Black, 2);
e.Graphics.DrawRectangle(pi, rec);
Rectangle rec2 = new Rectangle(Convert.ToInt32((410 + 2500 * GlobaleVariablen.IstWerte[0])), Convert.ToInt32(310 + 1875 * GlobaleVariablen.IstWerte[1]), 2, 2);
e.Graphics.DrawRectangle(pi,rec2);
}
I have a datastream from a serialport, and everytime I receive data I want to invalidate rec2 but not the whole form. I was able to invalidate the whole form with within my Datareceived Event with:
panel1.Invalidate();
However I do not know how I can make it happen to only invalidate my rec2, because if you invalidate the whole form all the time with a datastream it blinks like crazy and it really does not look very good.
Invalidate() has an overload version with Rectangle you want to invalidate:
panel1.Invalidate(GetRect2());
Where GetRect2() (please pick a better name) is something like:
static Rectangle GetRect2() {
int x Convert.ToInt32((410 + 2500 * GlobaleVariablen.IstWerte[0]));
int y = Convert.ToInt32(310 + 1875 * GlobaleVariablen.IstWerte[1]);
return new Rectangle(x, y, 2, 2);
}
In your paint event handler you have first to check if invalidated region intersects each object you want to write (example is simple because you're working with rectangles and you do not have slow expansive filling).
What will hurt performance more in your code is fact you're creating a new Pen for each paint operation. It's something you have to absolutely avoid: expansive native resources must be reused. Final code may be something similar to:
private Pen _pen = new Pen(Color.Black, 2);
private void panel1_Paint(object sender, PaintEventArgs e)
{
var rec = new Rectangle(2, 2, 820, 620);
if (e.ClipRectangle.IntersectsWith(rec))
e.Graphics.DrawRectangle(_pen, rec);
var rec2 = GetRect2();
if (e.ClipRectangle.IntersectsWith(rec2))
e.Graphics.DrawRectangle(pi, rec2);
}
Now your code is slightly more optimized but it may still blink. To avoid that you have to enable double buffering for your panel. Derive your own class from Panel and add in its constructor:
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
It may also be a good chance to refactor your code and move some paint logic in a separate class (but not panel itself). Please refer to MSDN for other flags you may need to use (such as AllPaintingInWmPaint).
Final note: you hard-coded coordinates, it's not a good practice unless you have a fixed size panel (with or without scrolling) because it won't scale well with future changes and it may be broken in many circumstances (as soon as your code will become slightly more complex than a fictional example).

PictureBox Refresh causes layers above to flicker

I am trying to read data from ports and show that on a dial. the background is a PictureBox containing the image of a circular scale giving readings, and a Lineshape has been used to represent the dial. The PictureBox has been 'sent to back' to allow visibility of the Lineshape. I'm enclosing representative codes:
if i do this:
private void timer1_Tick(object sender, EventArgs e)
{
ang += 2.0 * Math.PI / 180.0;
lineShape1.X1 = Convert.ToInt32(lineShape1.X2 - r * Math.Sin(ang));
lineShape1.Y1 = Convert.ToInt32(lineShape1.Y2 - r * Math.Cos(ang));
}
then it leaves a trace on the PictureBox.
But if i use the Refresh function of the PictureBox, then the dial appears to flicker. i have tried with timer intervals of 10, 15, 20, 50 and 100, but the problem persists.
private void timer1_Tick(object sender, EventArgs e)
{
ang += 2.0 * Math.PI / 180.0;
lineShape1.X1 = Convert.ToInt32(lineShape1.X2 - r * Math.Sin(ang));
lineShape1.Y1 = Convert.ToInt32(lineShape1.Y2 - r * Math.Cos(ang));
pictureBox1.Refresh();
}
For the sake of the actual problem, it is not possible to increase the interval. What can i do to Show a smooth variation of the dial?
As #Hans Passant suggested, you should not be using a LineShape object. This is a example of code that implement his suggestion (quick, untested):
private void timer1_Tick(object sender, EventArgs e)
{
pictureBox1.Invalidate();
}
void pictureBox1_Paint(object sender, PaintEventArgs e)
{
ang += 2.0 * Math.PI / 180.0;
var X1 = Convert.ToInt32(X2 - r * Math.Sin(ang));
var Y1 = Convert.ToInt32(Y2 - r * Math.Cos(ang));
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
e.Graphics.DrawLine(
new Pen(Color.Yellow, 1f),
new Point(X1, Y1),
new Point(X2, Y2 ));
}
Also, try to set this.DoubleBuffered = true; to avoid the flickering problem.
GDI+ is known to have this effect, in general. This is why WinForms has generally been replaced with WPF and XAML. I think, IIRC, PictureBox is especially prone to this problem. What you need to do is perform all of your "drawing" on the image in the PictureBox outsde of the PictureBox. Once all of your drawing is complete, update the PictureBox with the new image.
However, if you're drawing new images really quickly and subsequently updating the PictureBox, you're still likely to get flicker. PictureBox was just not designed for this use case. It's designed, more or less, to display a static image (or one that may change occassionally, but not at a high frequency).

C# transparent image above 2 images

I have make a playbar for my new MP3 player but I have a problem as shown in attachement
and this is my simple code :
public void setpercent(int percent)
{
pic1.Width = pic2.Width / 100 * percent;
pic3.Left = pic1.Width - ( pic3.Width /2);
}
I thought to use Graphics but I can't move it after.
and the method of ".Parent" doesn't work in this case.
In the Load() event of your Form, modify the Region() property of pic3 so that it becomes circular instead of rectangular. This can be done by construction a GraphicsPath and adding an Ellipse to it. If this doesn't line up properly with your image, then manually determine the correct bounding box for the ellipse and use that instead:
private void Form1_Load(object sender, EventArgs e)
{
System.Drawing.Drawing2D.GraphicsPath GP = new System.Drawing.Drawing2D.GraphicsPath();
GP.AddEllipse(pic3.ClientRectangle);
pic3.Region = new Region(GP);
}

Screen not redrawing until end of mousesup event handler

Ok, I'm having an issue that I'm not even sure exactly what is happening. Basically: I have a mouseup event for clicking on a button. That event will remove 1 button and physically move all the buttons on the screen to fill the gap. So if you have 2 rows of 2 buttons
Button1 Button2
Button3 Button4
and you click Button1, it moves the last 3 so you now have
Button2 Button3
Button4
Ok, so, at the end of this event handler it takes a screenshot and saves it (replacing the previous image of buttons 1-4 with the new image of buttons 2-4).
The event handler looks like this
public void Handle_Button_MouseUp(object sender, MouseEventArgs e)
{
//Get rid of the button that was clicked
...some unnecessary to the question code here...
//Rearrange the remaining buttons to fill the gap
Rearrange_Buttons_In_Tray(element);
//Save the screenshot
imageBox.Image = SavePanel1Screenshot();
}
The screenshot code is
public Image SavePanel1Screenshot()
{
int BorderWidth = (this.Width - this.ClientSize.Width) / 2;
int TitlebarHeight = this.Height - this.ClientSize.Height - BorderWidth;
Rectangle rect = new Rectangle(0, 0, panel1.Width, panel1.Height);
Bitmap bmp = new Bitmap(panel1.Width, panel1.Height, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(bmp);
g.CopyFromScreen(this.Left + panel1.Left + BorderWidth, this.Top + panel1.Top + TitlebarHeight, 0, 0, bmp.Size, CopyPixelOperation.SourceCopy);
Image screenShot;
screenShot = (Image)bmp;
return screenShot;
}
.
public void Rearrange_Buttons_In_Tray(int element)
{
for (int i = element; i < buttonList.Count; i++)
{
Place_Button_In_Tray(buttonList[i].buttonSaved, i, true);
buttonList[i].element = i;
}
}
Cleaned out some unnecessary to the question parts to avoid clutter. Sorry for the messed up indentation. NOTE: The buttons are not IN the panel, just on top of it. I just use the panel for measurement and aesthetic purposes
private void Place_Button_In_Tray(Button button, int element, bool isReArrange)
{
button.Width = bigButtonWidth;
button.Height = bigButtonHeight;
Image buttonImage = button.Image;
int numButtonsHoriz = panel1.Width / button.Width;
int numButtonsVerti = panel1.Height / button.Height;
int extraSpaceHoriz = (panel1.Width % button.Width) / (numButtonsHoriz);
int extraSpaceVerti = (panel1.Height % button.Height) / numButtonsHoriz;
int buttonCount;
if (!isReArrange)
buttonCount = buttonList.Count - 1;
else
buttonCount = element;
if ((buttonCount) < numButtonsHoriz)
{
button.Location = new Point(panel1MinX + (button.Width * (buttonCount)) + extraSpaceHoriz, (panel1MinY + extraSpaceVerti));
}
else
{
int newLine = (buttonCount) % numButtonsHoriz;
button.Location = new Point(panel1MinX + (button.Width * (newLine)) + extraSpaceHoriz, ((panel1MinY + extraSpaceVerti) + (button.Height * ((buttonCount) / 2) - ((buttonCount) % 2))));
}
And now my problem: The image is of a blank screen. It's not that it isn't taking the picture- it's taking the picture before buttons 2-4 are visible on the screen (I can visibly see this happen as I step through the program. At the time the screenshot is taken, the screen is completely blank. Only after it takes do the buttons reappear on the screen)! For some reason, they are not rendering until AFTER the event handler is finished. Once the final piece of code in the event handler is done (the screenshot saving), the buttons all reappear in their respective spots, despite the fact that the visibility of the buttons is not modified at all during this entire process (thus they remain visible the entire time).
I'm a little confused as to what exactly is happening and, more importantly, how to go about getting that screenshot after the event handler takes place. >_> Does anyone have any suggestions on what might be going on and, more importantly, how to get that screenshot?
EDIT: My description is somewhat difficult to understand. I do apologize. It's hard to articulate exactly what I'm trying to do and what is happening. Here's a more compact and to the point version:
Basically, I'm only trying to hide 1 button out of 4. The other 3 on the screen are moved to new locations. For some reason, when they get moved, they vanish from the screen. They don't reappear until after the mouseup function completes, despite me never having modified whether they are visible or not. I only change their location. I want the screenshot to contain those 3 buttons, but for some reason they aren't popping back into existence until after the entire mouseup function ends. >_> So my screenshot is of an empty screen, devoid of buttons
It's a bit confusing what you are describing. I clicked a button to hide it, run your screen shot code, and the image did not show the button.
Anyway, to "delay" the screen shot to after the event is called, you can try using BeginInvoke:
this.BeginInvoke(new Action(() => { imageBox.Image = SavePanel1Screenshot(); }));
I think you need to call a Refresh after moving your controls:
if ((buttonCount) < numButtonsHoriz) {
button.Location = new Point(panel1MinX + (button.Width * (buttonCount)) + extraSpaceHoriz, (panel1MinY + extraSpaceVerti));
} else {
int newLine = (buttonCount) % numButtonsHoriz;
button.Location = new Point(panel1MinX + (button.Width * (newLine)) + extraSpaceHoriz, ((panel1MinY + extraSpaceVerti) + (button.Height * ((buttonCount) / 2) - ((buttonCount) % 2))));
}
panel1.Refresh();
You're doing your work in the UI thread. See How to force Buttons, TextBoxes to repaint on form after a MessageBox closes in C#. I'd suggest you move the screenshot to a background thread if you can.
You may also need to wait until the buttons have rendered, either using the blunt expedient of sleeping for 100ms or so, or by investigating the Paint event and using some sort of flag to indicate that both required events have occurred and the screenshot can be taken.
Alternatively, you could force it to redraw: How do I call paint event?
as long as you only need the pixels of your own form ...
private void button1_Click(object sender, EventArgs e)
{
//Button magic
button1.Visible = false;
button2.Location = button1.Location;
Bitmap bmp = new Bitmap(this.Width, this.Height);
this.DrawToBitmap(bmp, new Rectangle(0, 0, this.Width, this.Height));
pictureBox1.Image = bmp;
//or do whatever you want with the bitmap
}

Windows Form, display indicator over a dynamically generated bitmap

I have a usercontrol that displays a gradient of colors which, once created will be constant.
The usercontrol doesn't contain any controls, not sure if i need to add a picturebox or dynamically add one.
Over that image, I'd like to display a line that will display what the current result is. I have no problem creating the gradient image on the map, however I'd like to somehow cache it so everytime I update the indicator (call CurrentValue from parent form), it will put the indicator line above the gradient image. This is updating about 30 times a second, thus, as of how the code below is working, it's repainting the gradient everytime, which is flickering.
Here's a code sample:
namespace Maps.UserControls
{
public partial class UserControlLegend : UserControl
{
private double m_CurrentValue;
public double CurrentValue
{
get
{
return m_CurrentValue;
}
set
{
m_CurrentValue = value;
RefreshValue();
}
}
public UserControlLegend()
{
InitializeComponent();
}
private void UserControlLegend_Paint(object sender, PaintEventArgs e)
{
if (b == null)
{
g = e.Graphics;
b = new System.Drawing.Bitmap(menuWidth, menuHeight, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
// Code here that draws Menu
// Cache bitmap here?
g.Dispose();
}
}
private void RefreshValue()
{
this.Refresh();
g = this.CreateGraphics();
g.DrawImage(b, 0, 0);
//Code to Calcuate current Indicator Location
int x3 = 0;
// Draws current indicator correctly
g.DrawLine(new Pen(new SolidBrush(Color.Black)), this.Width / 2 - 15, x3, this.Width / 2 - 5, x3);
g.Dispose();
}
}
}
Explained above in comments, used a bitmap, and just set the x,y of the control.
First, I'd suggest you set your Control's property DoubleBuffered to True, so that flickering goes away. However, if you don't draw on the Control itself, that will be not useful at all. Drawing on a PictureBox is better, however, becuase it is automatically DoubleBuffered.
Second, you are painting into a new Bitmap every time, which is very bad in terms of memory, since the Bitmap is a few megabytes in size. I'd suggest you have a single Bitmap initialized in the constructor, and a single Graphics, created from that Bitmap in the constructor, too. Every time the paint accurs, just clear the old Graphics g and then draw onto it again and again. g Graphics and b Bitmap should be Disposed one time only, when the entire Control is Disposed.
This may inhance your code.

Categories

Resources