Panel Control Paint Event not working for User Control - c#

I created one Windows Forms User Control, I drag, dropped a panel inside it and over the panel I drew the graph in the Paint event of Panel.
private void pnlViewer_Paint(object sender, PaintEventArgs e)
{
e.Graphics.TranslateTransform(pnlViewer.AutoScrollPosition.X, pnlViewer.AutoScrollPosition.Y);
e.Graphics.FillRectangle(Brushes.Black, Screen.PrimaryScreen.Bounds);
//**draw Y Axis**
int y;
for (int i = 0; i <= 50; i++)
{
y = (i * cellHeight) + cellHeight;
e.Graphics.DrawLine(new Pen(Color.FromArgb(50, 50, 50)),
new Point(0, y), new Point(pageWidth, y));
}
//**draw X Axis**
int x;
for (int i = 0; i < 50; i++)
{
x = (i * cellWidth) + ribbonWidth;
e.Graphics.DrawLine(new Pen(Color.FromArgb(50, 50, 50)),
new Point(x, 0), new Point(x, pageHeight));
}
DrawWaveForm(e.Graphics); **// Here the actual data of graph will draw**
}
When I drag this user control onto a WinForm, it calls this paint event of User Control from windows form, but while calling to this event the graph is shown but after some time the graph becomes blank.
I tried Invalidate(true), Update(), Refresh() all such methods but they were of no use.
Actually it shows half of the graph over form and after next the same paint event is fired then it shows the full graph that I require, but actually I want on first Paint event instead of half graphs showing the full graph.

e.Graphics.DrawLine(new Pen(Color.FromArgb(50, 50, 50)),
new Point(0, y), new Point(pageWidth, y));
You are not disposing the System.Drawing object in this code. Possibly in other code as well. This can go unnoticed for a long time, the garbage collector tends to hide the problem. But if it doesn't run often enough then the operating system can get sulky about you using so many GDI handles and it won't allow your program to create any more of them. The quota is 10,000 handles, a very large number but easily consumed if you repaint often. Typical when you draw a constantly updating graph for example. What happens next varies, somewhere between an exception and noticing that your program doesn't paint correctly anymore.
Always use the using statement in painting code to avoid this failure mode:
using (var pen = new Pen(Color.FromArgb(50, 50, 50))) {
e.Graphics.DrawLine(pen, new Point(0, y), new Point(pageWidth, y));
}

Related

Drawing simultaneously on two controls in two windows without delay

In my application, there are 2 windows and both contain a PictureBox. The first (pb1) allows interaction and the image can be changed through click- and mouseMove-events. These events call pb1.Invalidate(); which works fine.
I want the second PictureBox (pb2) to redraw as well so I call pb2.Invalidate() from the paint-event of pb1. [Just for context, the second PictureBox shows nearly the same Image but on a bigger scale and some parts of the drawing will be left out in the future so I use the same Method in both paint events which decides what to draw and what not]
It works but it's "laggy" and I want it to be as smooth as the paint on the first PictureBox. I reduced the paint event just to a grid for test purposes.
Both windows are double buffered.
I tried replacing the picture boxes with SKGLControls from SkiaSharp (which should have better performance). The example code still uses the SkiaEvents so don't be confused if the problem occurs with both controls.
I tried to use .Update() or .Refresh() instead of .Invalidate() but i guess its to much to handle, the application just crashes..
Here is the method that is called by both OnPaint events
public void Update(SKPaintGLSurfaceEventArgs e, bool bigscreen)
{
SKCanvas canvas = e.Surface.Canvas;
canvas.Clear(SKColors.Beige);
//Zoom to specified area
SKMatrix matrix = SKMatrix.Identity;
if (!bigscreen)
{
matrix = matrix.PostConcat(SKMatrix.CreateScale(canvasSize / (float)zoomArea.Width, canvasSize / (float)zoomArea.Height));
}
else
{
matrix = matrix.PostConcat(SKMatrix.CreateScale(bigCanvasSize / (float)zoomArea.Width, bigCanvasSize / (float)zoomArea.Height));
}
matrix = matrix.PreConcat(SKMatrix.CreateTranslation(-zoomArea.X, -zoomArea.Y));
canvas.SetMatrix(matrix);
DrawGrid(canvas);
}
and the grid-draw method
private void DrawGrid(SKCanvas canvas)
{
using (SKPaint paint = new SKPaint() { IsAntialias = true,Color=SKColors.LightGray,StrokeWidth = 1})
{
canvas.DrawLine(0, 0, 0, gridCanvas.Height, paint); //Size gridCanvas is always the same at the moment and defines the space where the grid is drawn
canvas.DrawLine(0, 0, gridCanvas.Width, 0, paint);
for (int i = 0; i <= (gridCanvas.Width - gridoffsetX) / pxPerSquare; i++)
{
canvas.DrawLine(i * pxPerSquare + gridoffsetX, 0, i * pxPerSquare + gridoffsetX, gridCanvas.Height, paint);
}
for (int i = 0; i <= (gridCanvas.Height - gridoffsetY) / pxPerSquare; i++)
{
canvas.DrawLine(0, i * pxPerSquare + gridoffsetY, gridCanvas.Width, i * pxPerSquare + gridoffsetY, paint);
}
}
}
and finally the original Paint Event
private void Pb1_PaintSurface(object sender, SKPaintGLSurfaceEventArgs e)
{
win2.UpdateDrawing(); //Just calls .Invalidate() on pb2
painter.Update(e, false);
}
examplePicture
So my question is: Is there a way to make both controls draw at nearly the same time without delay, although I don't understand why the first PictureBox draws in real time and the second doesn't...
Thanks!
after searching for day i found this page right after posting, which helped me:
Onpaint events (invalidated) changing execution order after a period normal operation (runtime)

Draw an arrow between 2 positions in an interval of 4 seconds in c#

I want to make an arrow in c#, which goes from A position to B position in 5 seconds for example. I want to put a map image in the form and when i click on a button i want to draw an arrow from A position to B position in an interval of seconds. i have made an arrow when it is in a horizontal position, but when i try to make it oblique it draws me a triangle instead of an arrow and i don't know how to fix it.
here i made an arrow from a position 12 with a width of 300
and i try to make the same with an oblique arrow but when i put different positions it draws me a triangle not an arrow.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace WindowsFormsApp4
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(400, 273);
this.Text = "";
this.Resize += new System.EventHandler(this.Form1_Resize);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
}
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillRectangle(Brushes.White, this.ClientRectangle);
Pen p = new Pen(Color.Black, 5);
p.StartCap = LineCap.Round;
for(int i=1; i<=300;i++)
{
System.Threading.Thread.Sleep(2);
g.DrawLine(p, 12, 30, i, 30);
Cursor.Current = Cursors.Default;
}
p.EndCap = LineCap.ArrowAnchor;
g.DrawLine(p, 12, 30, 310, 30);
p.Dispose();
}
private void Form1_Resize(object sender, System.EventArgs e)
{
Invalidate();
}
}
}
The fundamental problem with your code is that you are doing the entire animation loop inside the Paint event handler. This means that the window is never clear out between each line you draw, so you get all of the copies of the line you're drawing, start to finish, laid on top of each other in the same view.
It is not clear from your question exactly what you expect to see on the screen. However, another potential problem with your code is that the moving end point of the line does not start at the start point of the line, but rather at a point with the same Y coordinate where you want the line to end. This means that the arrow end of the line traverses a horizontal line leading to the final end point, rather than gradually extending from the start point of the line.
There is also the minor point that you seem to be confused about what the DrawLine() method does. You state that the width of your line is 300, but in fact the second argument of the DrawLine() method is just another point. The width of the line is defined by the Pen you use to draw the line. The width of the box containing the line is defined by the start and end point, but in this case is not 300, but rather (at the final length of the line) the difference between your start X coordinate and end X coordinate (i.e. 288).
The fundamental problem described above can be addressed by running a loop outside of the Paint event handler, which updates values that describe the line, and then call Invalidate() so that the Paint event handler can be called to draw just the current state of the animation.
On the assumption that what you really wanted was for a line to extend out from the start point, rather than traverse a horizontal line, the example I show below implements the animation that way as well, in addition to fixing the fundamental issue. I did nothing to change the length or width of the line.
public Form1()
{
InitializeComponent();
this.DoubleBuffered = true;
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(400, 273);
this.Resize += new System.EventHandler(this.Form1_Resize);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
var task = AnimateLine();
}
private readonly Point _lineStart = new Point(12, 30);
private readonly Point _lineFinalEnd = new Point(300, 60);
private const int _animateSteps = 300;
private Point _lineCurrentEnd;
private bool _drawArrow;
private async Task AnimateLine()
{
Size size = new Size(_lineFinalEnd) - new Size(_lineStart);
for (int i = 1; i <= _animateSteps; i++)
{
await Task.Delay(2);
Size currentSize = new Size(
size.Width * i / _animateSteps, size.Height * i / _animateSteps);
_lineCurrentEnd = _lineStart + currentSize;
Invalidate();
}
_drawArrow = true;
Invalidate();
}
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
using (Pen p = new Pen(Color.Black, 5))
{
p.StartCap = LineCap.Round;
p.EndCap = _drawArrow ? LineCap.ArrowAnchor : p.EndCap;
g.DrawLine(p, _lineStart, _lineCurrentEnd);
}
}
Note that the repeated erasing and redrawing of the window would make the window flicker. This is a basic issue with any sort of animation, and the fix is to enable double-buffering for the window, hence the this.DoubleBuffered = true; statement added to the constructor.
Some other points worth mentioning:
The await Task.Delay() call is used so that the loop can yield the UI thread with each iteration of the loop, which allows the UI thread to raise the Paint event, as well as allows any other UI activity to still work during the animation. You can find lots more information about that C# feature in the How and When to use async and await article, and of course by reading the documentation.
Whether you use Thread.Sleep() or Task.Delay(), specifying a delay of 2 ms isn't very useful. The Windows thread scheduler does not schedule threads to that degree of precision. A thread that sleeps for 2 ms could be woken up as much as 50 ms later in the normal case, and even later if the CPU is under heavy load. Nor does a 2 ms delay provide a useful animation frame rate; that would be a 500 Hz refresh rate, which is easily 10x or more faster than the human brain needs in order to perceive a smooth animation.My example above does nothing to try to address this issue, but you should explore implementing the loop slightly differently, such that instead of the number of animation steps, you specify a reasonable animation interval (say, every 50 or 100 ms), make an attempt to delay that interval, but then use a Stopwatch to actually measure what the real delay was and compute the progress within the animation based on the actual time elapsed. This will allow you to have precise control over the total duration of the animation, as well as somewhat precise control over the refresh rate used for the animation.

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().

Custom control onPaint event not working

Hey people I have a problem I am writing a custom control. My control inherits from Windows.Forms.Control and I am trying to override the OnPaint method. The problem is kind of weird because it works only if I include one control in my form if I add another control then the second one does not get draw, however the OnPaint method gets called for all the controls. So what I want is that all my custom controls get draw not only one here is my code:
If you run the code you will see that only one red rectangle appears in the screen.
public partial class Form1 : Form
{
myControl one = new myControl(0, 0);
myControl two = new myControl(100, 0);
public Form1()
{
InitializeComponent();
Controls.Add(one);
Controls.Add(two);
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
public class myControl:Control
{
public myControl(int x, int y)
{
Location = new Point(x, y);
Size = new Size(100, 20);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
Pen myPen = new Pen(Color.Red);
e.Graphics.DrawRectangle(myPen, new Rectangle(Location, new Size(Size.Width - 1, Size.Height - 1)));
}
}
I'm guessing you are looking for something like this:
e.Graphics.DrawRectangle(Pens.Red, new Rectangle(0, 0,
this.ClientSize.Width - 1,
this.ClientSize.Height - 1));
Your Graphic object is for the interior of your control, so using Location isn't really effective here. The coordinate system starts at 0,0 from the upper-left corner of the client area of the control.
Also, you can just use the built-in Pens for colors, otherwise, if you are creating your own "new" pen, be sure to dispose of them.
LarsTech beat me to it, but you should understand why:
All drawing inside of a control is made to a "canvas" (properly called a Device Context in Windows) who coordinates are self-relative. The upper-left corner is always 0, 0.
The Width and Height are found in ClientSize or ClientRectangle. This is because a window (a control is a window in Windows), has two areas: Client area and non-client area. For your borderless/titlebar-less control those areas are one and the same, but for future-proofing you always want to paint in the client area (unless the rare occasion occurs where you want to paint non-client bits that the OS normally paints for you).

Categories