C#: Winforms Progress Bar - c#

I have a custom progress bar on my main form (UI thread). I create a background thread to read input data from a CSV file (on a menu click), and I update the value of the progress bar using anonymous delegate. Everything works great the first time only.
If I repeat the read data process (from menu click), the data reads in as it should (I dump it to a console as it is being read), but the progress bar does not show up at all. Am I missing something here?
Essentially the progress bar works only once and then does not show up if I repeat the read operation.
Any advice would be greatly appreciated. Thank you.
I am using the code essentially from William Daniel. Here it is:
class CustomProgressBar : ProgressBar
{
public CustomProgressBar()
{
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnPaintBackground(PaintEventArgs pevent)
{
// None... Helps control the flicker.
}
protected override void OnPaint(PaintEventArgs e)
{
const int inset = 2;
using (Image offscreenImage = new Bitmap(this.Width, this.Height))
{
using (Graphics offscreen = Graphics.FromImage(offscreenImage))
{
offscreen.Clear(Color.DarkBlue);
Rectangle rect = new Rectangle(0, 0, this.Width, this.Height);
if (ProgressBarRenderer.IsSupported)
ProgressBarRenderer.DrawHorizontalBar(offscreen, rect);
rect.Inflate(new Size(-inset, -inset)); // Deflate inner rectangle
rect.Width = (int)(rect.Width * ((double)this.Value / this.Maximum));
if (rect.Width == 0) rect.Width = 1;
LinearGradientBrush brush = new LinearGradientBrush(new Point(0, 0),
new Point(0, Height - inset * 2), BackColor, ForeColor);
offscreen.FillRectangle(brush, inset, inset, rect.Width, rect.Height);
e.Graphics.DrawImage(offscreenImage, 0, 0);
offscreenImage.Dispose();
}
}
}
}
The usage code is:
// ... update progress bar value
this.pBar.Invoke((MethodInvoker)delegate
{
this.pBar.Value = (int) ( ((double) nRows) * 100.0 / ((double) fileLines) );
});
I also use this.pBar.Show() to show it and I also hide it using this.pBar.Hide().

First of all, make sure that other thread always updates the progress bar through Invoke method. I'm not sure if that's what you meant by "I update the value of the progress bar using anonymous delegate"?
Also, did you remember to reset the progress bar's position before the second run?

Related

Flickering Image in C#

I have some code which renders an image to a form and using a trackbar to rotate that image. The code was taken from a you tube tutorial and works. However, when the image rotates it flickers. The tutorial poster says that using threads will remove the flickering, but I have no clue as to how to go about doing that. Any help would be very much appreciated. The code is set out below:
public partial class Form1 : Form
{
Image img;
int angle;
public Form1()
{
InitializeComponent();
}
Thread th;
Graphics graphicToDraw;
Graphics graphicToScreen;
Bitmap bmp;
private void Form1_Load(object sender, EventArgs e)
{
img = Image.FromFile(#"c:\images\clock face with second hand 2.png");
}
private void Form1_Paint(object sender, PaintEventArgs paint)
{
bmp = new Bitmap(img.Width / 2, img.Height / 2);
graphicToDraw = Graphics.FromImage(bmp);
graphicToDraw.TranslateTransform(bmp.Width / 2, bmp.Height / 2);
graphicToDraw.RotateTransform(angle);
graphicToDraw.TranslateTransform(-bmp.Width / 2, -bmp.Height / 2);
graphicToDraw.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphicToDraw.DrawImage(img, 0, 0);
paint.Graphics.TranslateTransform(this.Width / 2, this.Height / 2);
paint.Graphics.DrawImage(bmp, -bmp.Width / 2, -bmp.Height / 2);
Debug.WriteLine("image width = " + bmp.Width);
Debug.WriteLine("image height = " + bmp.Height);
}
private void Form1_Resize(object sender, EventArgs e)
{
Invalidate();
}
private void trackBar1_ValueChanged(object sender, EventArgs e)
{
angle = trackBar1.Value;
Invalidate();
}
}
Using threads is probably the wrong approach here. See How to: Reduce Graphics Flicker with Double Buffering for Forms and Controls. I.e. set DoubleBuffered to true for your form or control.
DoubleBuffered = true;
You can not use any background thread to draw to the UI. Only the UI thread is allowed to access any UI classes, and the paint event will always be called on the UI thread. You could use a background thread to do the intermediate drawing to the buffer. But as far as I can see this is completely unnecessary, you should just set whatever transform you need on the graphics object and draw directly to it, there should be no need for the intermediate buffer. And if you do use a intermediate buffer, please ensure everything is correctly disposed.
The main case where such a background buffer could be useful is if you have lots of complicated drawing to do, and you only need to update the buffer infrequently. Then it may make sense to do the drawing to this buffer on a background thread.

C# Form Draws Slowly

I made an application on Visual Studio 2012 and im trying to speed up the draw times of the forms.
I have a main form and inside of it i have a container in which depending on the selection of a tool strip, the new form will show inside of it. It works like a charm, but the issue is, it takes a lot of time to draw, no matter how good the computer is (tried on different computers), and the issue seems to be the background.
I have set a background image for the main form, for the container inside that form, and for all the forms in my project, so when they show up, the background image isnt chopped and it continues the image. But, if instead of using a background for picture and i leave the back in white, for all, the main form, container, and forms, it works like a charm.
I've read around the internet about setting the double buffer inside the form and stuff to true, but it didnt do anything, it takes the same ammount of time.
Any advice? Thanks in advance!
You can squeeze a little more speed out of it by drawing the background manually. This helps because it allows you to disable the underlying background color, which just wastes time because it gets overwritten with the image anyway.
// Reference to manually-loaded background image
Image _bmp;
// In your constructor, set these styles to ensure that the background
// is not going to be automatically erased and filled with a color
public Form1() {
InitializeComponent();
SetStyle(
ControlStyles.Opaque |
ControlStyles.OptimizedDoubleBuffer |
ControlStyles.AllPaintingInWmPaint, true);
// Load background image
_bmp = Image.FromFile("c:\\path\\to\\background.bmp");
}
// Override OnPaint to draw the background
protected override void OnPaint(PaintEventArgs e) {
var g = e.Graphics;
var srcRect = new Rectangle(0, 0, _bmp.Width, _bmp.Height);
int startY = Math.Max(0, (e.ClipRectangle.Top / _bmp.Height) * _bmp.Height);
int startX = Math.Max(0, (e.ClipRectangle.Left / _bmp.Width) * _bmp.Width);
for (int y = startY; y < e.ClipRectangle.Bottom; y+= _bmp.Height)
for (int x = startX; x < e.ClipRectangle.Right; x += _bmp.Width)
{
var destRect = new Rectangle(x, y, _bmp.Width, _bmp.Height);
g.DrawImage(_bmp, destRect, srcRect, GraphicsUnit.Pixel);
}
base.OnPaint(e);
}

C# Drawing - best solution

Today I am trying to solve problem with a blinking panel, when I draw onto it.
Lots of threads I read, like these:
how to stop flickering C# winforms,
Double buffering with Panel,
How can I draw on Panel so it does not blink?
So I tried to draw onto PictureBox, MyPanel with doubleBuffered, but the best solution I found, when I read, that I can't use g.Clear() every time, after that, even on non-doubleBuffered panel, blinking disappeared.
I even read, that I should free Graphics after draw is done. So I use everywhere using(Graphics g = panel.CreateGraphics()).
So my question, is it a great idea to create graphics for bitmap only when I draw something to it? Because before I created Bitmap, and Graphics (only for this bitmap, not for all components), so I had Graphics available for this bitmap every time
Here is my code:
public void newSizeDrawing()
{
Size size = collector.getLetterSize(selectedName);
Size drawingSize = new Size(size.Width * (pixelSizeArray[pixelSize] + 1),size.Height * (pixelSizeArray[pixelSize] + 1));
bitmapDraw = new Bitmap(drawingSize.Width, drawingSize.Height);
int width = (this.MinimumSize.Width - panelDraw.MinimumSize.Width) + drawingSize.Width + 10;
int height = (this.MinimumSize.Height - panelDraw.MinimumSize.Height) + drawingSize.Height + 10;
this.Size = new Size(
(width > this.MinimumSize.Width) ? width : this.MinimumSize.Width,
(height > this.MinimumSize.Height) ? height : this.MinimumSize.Height);
zeroDrawPosition = new Point((panelDraw.Size.Width - bitmapDraw.Width) / 2 - 1, (panelDraw.Size.Height - bitmapDraw.Height) / 2 - 1);
using (Graphics g = panelDraw.CreateGraphics())
{
g.Clear(panelDraw.BackColor);
}
redrawDrawingLetter();
}
public void redrawDrawingLetter()
{
bool[][] grid = collector.getArray(selectedName);
using (Graphics graphicDraw = Graphics.FromImage(bitmapDraw))
{
graphicDraw.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
graphicDraw.Clear(panelDraw.BackColor);
int pxSize = pixelSizeArray[pixelSize];
for (int y = 0; y < grid.Length; y++)
{
for (int x = 0; x < grid[y].Length; x++)
{
graphicDraw.FillRectangle((grid[y][x] ? Brushes.Black : Brushes.White), x * (pxSize + 1), y * (pxSize + 1), pxSize, pxSize);
}
}
}
redrawDrawingPanel();
}
private void redrawDrawingPanel()
{
using (Graphics g = panelDraw.CreateGraphics())
{
if (bitmapDraw != null)
g.DrawImage(bitmapDraw, zeroDrawPosition);
}
}
private void panelDraw_Paint(object sender, PaintEventArgs e)
{
redrawDrawingPanel();
}
Nobody can explain to me how to draw in C# the best way. So maybe my code isn't good, but that is reason why I asking how to do it correctly.
newSizeDrawing is called by myself only, when user click on + or - button. I have bool double-dimension array if pixel is on or off. This is program for drawing letters for microchips and LED display (often 8px height of letter).
I wrote a method that checks if the mouse moved from one "pixel" to another, so I don't redraw it after every call mouseMove event, because "pixel" can be from 10x10 px to 30x30 px.
private void panelDraw_Paint(object sender, PaintEventArgs e)
{
redrawDrawingPanel();
}
This is fundamentally wrong. The Paint event passes e.Graphics to let you draw whatever you want to paint. When you turn on double-buffering, e.Graphics refers to a bitmap, it is initialized with the BackColor. You then proceed to drawing using another Graphics object you got from CreateGraphics(). That one draws directly to the screen.
The flicker effect you see if very pronounced. For a split second you see what the other Graphics context draws. Then your panelDraw_Paint() method returns and Winforms draws the double-buffered bitmap. There's nothing on it so it immediately erases what you drew.
Modify the redrawDrawingPanel() method and give it an argument of type Graphics. Pass e.Graphics in the call. And only use that Graphics object, remove all calls to CreateGraphics().

OnPaint in winform - .NET Compact Framework 3.5

I am currently working on a curved progress bar in a smart device project. I override the OnPaint function.
In Override OnPaint() function, I draw the curved progress bar in dark gray and draw a part of the progress bar in yellow to reflect the changing value(such as 30%) of the progress bar. However, When the value is changing continuously, I can see that part of the progress bar's colour changes to yellow. However, the curved progress bar itself is also redrawn. Does anyone know a way of how to avoid redrawing the orginal curved progress bar while the value is changing? So that when the value is changing, it only redraw a part of the progress bar with yellow colour, not the orginal progress bar with gray. Here is the code I use in OnPaint function.
protected override void OnPaint(PaintEventArgs e)
{
gx = e.Graphics;
// Draw the original curved progress bar
int intPosition1 = m_NumberOfSpoke;
for (int intCounter1 = 0; intCounter1 < m_NumberOfSpoke; intCounter1++)
{
intPosition1 = intPosition1 % m_NumberOfSpoke;
DrawLine(e.Graphics,
GetCoordinate(m_CenterPoint, m_InnerCircleRadius, m_Angles[intPosition1]),
GetCoordinate(m_CenterPoint, m_OuterCircleRadius, m_Angles[intPosition1]),
Color.DarkGray, m_SpokeThickness);
intPosition1++;
}
// Draw a part of the progress bar to reflect the changing current value(such as 30%)
int intPosition = CurrentValue;
for (int intCounter1 = 0; intCounter1 < CurrentValue; intCounter1++)
{
intPosition = intPosition % CurrentValue;
DrawLine(gx,
GetCoordinate(m_CenterPoint, m_InnerCircleRadius, m_Angles[intPosition]),
GetCoordinate(m_CenterPoint, m_OuterCircleRadius, m_Angles[intPosition]),
Color.Yellow, m_SpokeThickness);
intPosition++;
}
base.OnPaint(e);
}
I have tried to use Override OnBackgroundPaint to draw the orginal curved progress bar as a background to avoid redraw it again in OnPaint, but it doesn't work. I cannot see any things when the form is loaded. Any ideas?
Thanks for any help in advance.
Regards
That's a load of DrawLine calls to the actual screen, and it's very likely going to cause flickering. You should double buffer by creating a back buffer, do all you drawing to it, then blit that to the screen with a single call to DrawBitmap along these lines:
protected override void OnPaint(PaintEventArgs e)
{
using(var buffer = new Bitmap(this.Width, this.Height))
using(var gx = Graphics.FromImage(buffer))
{
// for loops to draw to gx
....
e.Graphics.DrawBitmap(buffer, ...);
}
}
I'd also be highly inclined to not do exactly what's above, but cache that buffer to prevent the garbage generation of a Bitmap on every call.
Bitmap m_buffer;
Gramphic m_gx;
protected override void OnPaint(PaintEventArgs e)
{
if(m_buffer == null)
{
m_buffer = new Bitmap(this.Width, this.Height))
m_gx = Graphics.FromImage(buffer))
}
// clear the backbuffer with a FillRect
// for loops to draw to m_gx
....
e.Graphics.DrawBitmap(m_buffer, ...);
}
I'd probably even take this a step further, if the grey part of the control is always the same and do a "triple buffer", keeping a cached version of the image with the grey drawn, then in OnPaint, blit that to the graphics, draw the yellow, then blit to the screen.
Bitmap m_buffer;
Bitmap m_backimage;
Gramphic m_gx;
protected override void OnPaint(PaintEventArgs e)
{
if(m_buffer == null)
{
m_backimage = new Bitmap(this.Width, this.Height);
var g = Graphics.FromImage(m_backImage);
// for loop to draw the grey stuff to g
....
m_buffer = new Bitmap(this.Width, this.Height))
m_gx = Graphics.FromImage(buffer))
}
m_gx.DrawImage(m_backImage);
// for loop to draw *just the yellow* to m_gx
....
e.Graphics.DrawBitmap(m_buffer, ...);
}
You're going to probably have to override OnResize and some other stuff too, plus extend Dispose to clean up these member-level GDI objects, but perf will be way better and it's not going to flicker.

Drop shadow in Winforms Controls?

is there a way to add a drop shadow to controls?
are there any controls out there with this feature?
You have to overwrite the CreateParamsproperty like this:
private const int CS_DROPSHADOW = 0x00020000;
protected override CreateParams CreateParams
{
get
{
// add the drop shadow flag for automatically drawing
// a drop shadow around the form
CreateParams cp = base.CreateParams;
cp.ClassStyle |= CS_DROPSHADOW;
return cp;
}
}
This question has been around for 6 years and needs an answer. I hope that anyone who needs to do this can extrapolate an answer for any control set from my solution. I had a panel and wanted to draw a drop shadow underneath every child control - in this instance one or more panels (but the solution should hold good for other control types with some minor code changes).
As the drop shadow for a control has to be drawn on the surface of that control's container we start by adding a function to the container's Paint() event.
Container.Paint += dropShadow;
dropShadow() looks like this:
private void dropShadow(object sender, PaintEventArgs e)
{
Panel panel = (Panel)sender;
Color[] shadow = new Color[3];
shadow[0] = Color.FromArgb(181, 181, 181);
shadow[1] = Color.FromArgb(195, 195, 195);
shadow[2] = Color.FromArgb(211, 211, 211);
Pen pen = new Pen(shadow[0]);
using (pen)
{
foreach (Panel p in panel.Controls.OfType<Panel>())
{
Point pt = p.Location;
pt.Y += p.Height;
for (var sp = 0; sp < 3; sp++)
{
pen.Color = shadow[sp];
e.Graphics.DrawLine(pen, pt.X, pt.Y, pt.X + p.Width - 1, pt.Y);
pt.Y++;
}
}
}
}
Clearly you can pick a different control type from the container's collection and you can vary the colour and depth of the shadow with some minor tweaks.
The top answer does in fact generate a shadow, but I personally wasn't satisfied with it for a few reasons:
It only works for rectangles (granted, WinForms controls are all rectangles, but we might want to use this in other cases)
More importantly: It's not smooth. It doesn't look as natural as other shadows in other programs look.
Finally, it's slightly annoying to configure.
So, because of all these things, I ended up writing my own for my project and I thought I'd share it here:
public partial class Form1 : Form
{
List<Control> shadowControls = new List<Control>();
Bitmap shadowBmp = null;
public Form1()
{
InitializeComponent();
shadowControls.Add(panel1);
this.Refresh();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (shadowBmp == null || shadowBmp.Size != this.Size)
{
shadowBmp?.Dispose();
shadowBmp = new Bitmap(this.Width, this.Height, PixelFormat.Format32bppArgb);
}
foreach (Control control in shadowControls)
{
using (GraphicsPath gp = new GraphicsPath())
{
gp.AddRectangle(new Rectangle(control.Location.X, control.Location.Y, control.Size.Width, control.Size.Height));
DrawShadowSmooth(gp, 100, 60, shadowBmp);
}
e.Graphics.DrawImage(shadowBmp, new Point(0, 0));
}
}
private static void DrawShadowSmooth(GraphicsPath gp, int intensity, int radius, Bitmap dest)
{
using (Graphics g = Graphics.FromImage(dest))
{
g.Clear(Color.Transparent);
g.CompositingMode = CompositingMode.SourceCopy;
double alpha = 0;
double astep = 0;
double astepstep = (double)intensity / radius / (radius / 2D);
for (int thickness = radius; thickness > 0; thickness--)
{
using (Pen p = new Pen(Color.FromArgb((int)alpha, 0, 0, 0), thickness))
{
p.LineJoin = LineJoin.Round;
g.DrawPath(p, gp);
}
alpha += astep;
astep += astepstep;
}
}
}
}
In this implementation, all Controls added to the shadowControls will be painted with a smooth shadow. You should be able to implement this for non-rectangular shapes because the main function to generate the shadows takes a GraphicsPath. Please note that it's important you draw the shadow to another bitmap before drawing it to the form because the main function requires a compositing mode of SourceCopy to work, which means if you don't draw it to another surface first anything behind the shadow will be completely replaced and the transparency aspect is useless. I'm on a roll of answering 10-year-old questions, but hopefully, this helps someone!
There is in WPF if you can stretch to using that instead, I don't believe there is an alternative in Windows Forms due to the limited capabilities of GDI+.
Here's a controversial opinion, you do it without code.
Set your main panel Border Style to Fixed Single.
Create 3 panels below it, each 1 pixel larger in every direction.
Each of the 3 panels is of a lighter shade of gray.
Not perfect but cheap and easy.
panel with pseudo-shadow

Categories