Animating Children Of a Control after drawing some graphics on it - c#

I have a User Control which contains a panel by name of panel1. there is a instruction in the panel1_paint which draws some lines on the panel,I am going to execute paint event in a windows form by line below :
NOTE : DrawLines has defined in UserControl1.CS
public void DrawLines()
{
...
...
panel1.Invalidate();
}
I don't know why this won't run , maybe it draws line and then panel makes a refresh and this make the panel to be cleared.
So I changed my code to this :
public void DrawLines()
{
...
...
panel1_Paint(this, new PaintEventArgs(panel1.CreateGraphics(),
panel1.ClientRectangle));
}
This works nice.it draws some lines in the panel, now the main part is :
there are some objects that they will create at run time on the panel as children . I want to move them over the panel (for example suppose there are fire particles) . so when I move (animate) them by timer , the lines which drawn by the e.Graphics of the Paint Event will be cleared and removed if any of those objects(particles) on the panel move over them.
So How can I avoid this ?! Is there any problem with my Invalidation ?
How can I have both, lines and moving particles on the panel ?
panel has done the graphics , but why by moving objects on it, the graphics will removed?

Here's how to use the supplied Graphics in the Paint() event:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics G = e.Graphics;
// ... use "G" to draw with ...
G.DrawLine(Pens.Black, 0, 0, panel1.Width, panel1.Height);
}
Then you can simply use:
panel1.Invalidate();

Related

How to repaint certain part of panel / or use two panels on top of each other with the top one having transparent background?

I have a C# WinForms project where I have to paint some things on my panel. I load in a grid from a file that translates to squares that are painted on the panel. Then I load in a file with dots that are then painted on top of the squares.
I then have a function that moves the dots around. But the repaint function is called every tick which causes the whole grid to flicker continously because it is painted so quick after each other.
How do I make it so that only the dots are repainted?
The repaint and paint functions are as follows:
private void Repaint(object sender, EventArgs e)
{
GridPanel.Invalidate();
}
private void GridPanel_Paint(object sender, PaintEventArgs e)
{
if ((GridPanel.Width != grid.Width || GridPanel.Height != grid.Height))
{
grid.Height = GridPanel.Height;
grid.Width = GridPanel.Width;
grid.setDimensions();
}
Graphics g = e.Graphics;
g.FillRectangle(new SolidBrush(Color.White), new Rectangle(0, 0, grid.Width, grid.Height));
foreach(Square square in grid.Squares)
{
if (square.Letter != '_')
{
SolidBrush color = new SolidBrush(square.Color);
g.FillRectangle(color, new Rectangle(square.X * grid.squareWidth, square.Y * grid.squareHeight, grid.squareWidth, grid.squareHeight));
}
}
foreach(Artist artist in grid.Artists)
{
SolidBrush color = new SolidBrush(artist.Color);
g.FillRectangle(color, new Rectangle(Convert.ToInt32(artist.X * grid.squareWidth), Convert.ToInt32(artist.Y * grid.squareHeight), grid.artistWidth, grid.artistHeight));
}
}
I also tried to use a second panel for the dots so I only have to repaint that one. But I cant get a transparent background working on the second panel, so the first panel is not visible this way.
Somebody knows a good solution for this problem?

Existing Graphics into Bitmap

I'm writing a plugin for a trading software (C#, winforms, .NET 3.5) and I'd like to draw a crosshair cursor over a panel (let's say ChartPanel) which contains data that might be expensive to paint. What I've done so far is:
I added a CursorControl to the panel
this CursorControl is positioned over the main drawing panel so that it covers it's entire area
it's Enabled = false so that all input events are passed to the parent
ChartPanel
it's Paint method is implemented so that it draws lines from top to bottom and from left to right at current mouse position
When MouseMove event is fired, I have two possibilities:
A) Call ChartPanel.Invalidate(), but as I said, the underlying data may be expensive to paint and this would cause everything to redraw everytime I move a mouse, which is wrong (but it is the only way I can make this work now)
B) Call CursorControl.Invalidate() and before the cursor is drawn I would take a snapshot of currently drawn data and keep it as a background for the cursor that would be just restored everytime the cursor needs to be repainted ... the problem with this is ... I don't know how to do that.
2.B. Would mean to:
Turn existing Graphics object into Bitmap (it (the Graphics) is given to me through Paint method and I have to paint at it, so I just can't create a new Graphics object ... maybe I get it wrong, but that's the way I understand it)
before the crosshair is painted, restore the Graphics contents from the Bitmap and repaint the crosshair
I can't control the process of painting the expensive data. I can just access my CursorControl and it's methods that are called through the API.
So is there any way to store existing Graphics contents into Bitmap and restore it later? Or is there any better way to solve this problem?
RESOLVED: So after many hours of trial and error I came up with a working solution. There are many issues with the software I use that can't be discussed generally, but the main principles are clear:
existing Graphics with already painted stuff can't be converted to Bitmap directly, instead I had to use panel.DrawToBitmap method first mentioned in #Gusman's answer. I knew about it, I wanted to avoid it, but in the end I had to accept, because it seems to be the only way
also I wanted to avoid double drawing of every frame, so the first crosshair paint is always drawn directly to the ChartPanel. After the mouse moves without changing the chart image I take a snapshow through DrawToBitmap and proceed as described in chosen answer.
The control has to be Opaque (not enabled Transparent background) so that refreshing it doesn't call Paint on it's parent controls (which would cause the whole chart to repaint)
I still experience occasional flicker every few seconds or so, but I guess I can figure that out somehow. Although I picked Gusman's answer, I would like to thank everyone involved, as I used many other tricks mentioned in other answers, like the Panel.BackgroundImage, use of Plot() method instead of Paint() to lock the image, etc.
This can be done in several ways, always storing the graphics as a Bitmap. The most direct and efficient way is to let the Panel do all the work for you.
Here is the idea: Most winforms Controls have a two-layered display.
In the case of a Panel the two layers are its BackgroundImage and its Control surface.
The same is true for many other controls, like Label, CheckBox, RadioButton or Button.
(One interesting exception is PictureBox, which in addition has an (Foreground) Image. )
So we can move the expensive stuff into the BackgroundImage and draw the crosshair on the surcafe.
In our case, the Panel, all nice extras are in place and you could pick all values for the BackgroundImageLayout property, including Tile, Stretch, Center or Zoom. We choose None.
Now we add one flag to your project:
bool panelLocked = false;
and a function to set it as needed:
void lockPanel( bool lockIt)
{
if (lockIt)
{
Bitmap bmp = new Bitmap(panel1.ClientSize.Width, panel1.ClientSize.Width);
panel1.DrawToBitmap(bmp, panel1.ClientRectangle);
panel1.BackgroundImage = bmp;
}
else
{
if (panel1.BackgroundImage != null)
panel1.BackgroundImage.Dispose();
panel1.BackgroundImage = null;
}
panelLocked = lockIt;
}
Here you can see the magic at work: Before we actually lock the Panel from doing the expensive stuff, we tell it to create a snapshot of its graphics and put it into the BackgroundImage..
Now we need to use the flag to control the Paint event:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Size size = panel1.ClientSize;
if (panelLocked)
{
// draw a full size cross-hair cursor over the whole Panel
// change this to suit your own needs!
e.Graphics.DrawLine(Pens.Red, 0, mouseCursor.Y, size.Width - 1, mouseCursor.Y);
e.Graphics.DrawLine(Pens.Red, mouseCursor.X, 0, mouseCursor.X, size.Height);
}
// expensive drawing, you insert your own stuff here..
else
{
List<Pen> pens = new List<Pen>();
for (int i = 0; i < 111; i++)
pens.Add(new Pen(Color.FromArgb(R.Next(111),
R.Next(111), R.Next(111), R.Next(111)), R.Next(5) / 2f));
for (int i = 0; i < 11111; i++)
e.Graphics.DrawEllipse(pens[R.Next(pens.Count)], R.Next(211),
R.Next(211), 1 + R.Next(11), 1 + R.Next(11));
}
}
Finally we script the MouseMove of the Panel:
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
mouseCursor = e.Location;
if (panelLocked) panel1.Invalidate();
}
using a second class level variable:
Point mouseCursor = Point.Empty;
You call lockPanel(true) or lockPanel(false) as needed..
If you implement this directly you will notice some flicker. This goes away if you use a double-buffered Panel:
class DrawPanel : Panel
{
public DrawPanel() { this.DoubleBuffered = true; }
}
This moves the crosshair over the Panels in a perfectly smooth way. You may want to turn on & off the Mouse cursor upon MouseLeave and MouseEnter..
Why don't you clone all the graphics in the ChartPanel over your CursorControl?
All the code here must be placed inside your CursorControl.
First, create a property which will hold a reference to the chart and hook to it's paint event, something like this:
ChartPanel panel;
public ChartPanel Panel
{
get{ return panel; }
set{
if(panel != null)
panel.Paint -= CloneAspect;
panel = value;
panel.Paint += CloneAspect;
}
}
Now define the CloneAspect function which will render the control's appearance to a bitmap whenever a Paint opperation has been done in the Chart panel:
Bitmap aspect;
void CloneAspect(object sender, PaintEventArgs e)
{
if(aspect == null || aspect.Width != panel.Width || aspect.Height != panel.Height)
{
if(aspect != null)
aspect.Dispose();
aspect = new Bitmap(panel.Width, panel.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
}
panel.DrawToBitmap(aspect, new Rectangle(0,0, panel.Width, panel.Height);
}
Then in the OnPaint overriden method do this:
public override void OnPaint(PaintEventArgs e)
{
e.Graphics.DrawImage(aspect);
//Now draw the cursor
(...)
}
And finally wherever you create the chart and the customcursor you do:
CursorControl.Panel = ChartPanel;
And voila, you can redraw as many times you need without recalculating the chart's content.
Cheers.

C# Double Buffered not painting

I simulate map navigation and draw generated part of the map on a panel. Since image is flickering I have to use double buffering.
Here's my Panel code:
public class MapPanel : System.Windows.Forms.Panel
{
public MapPanel()
{
DoubleBuffered = true;
ResizeRedraw = true;
}
}
And I have the following method:
public void panelMap_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
g.DrawImage(mapController.GetCurrentMap(), 0, 0, panelMap.Width, panelMap.Height);
}
}
I'm not calling this method. I have the following code in .Designer.cs:
this.panelMap.Paint += new PaintEventHandler(this.panelMap_Paint);
And call Invalidate() in MouseMove. I'm sure that this event occurs, I've checked it. Everything seems to be correct.
And then the image is not drawing. I mean, the panel is empty and seems to be transparent or colored in default control color. However, if I turn double buffering off, the image is properly drawn, but, obviously, it's flickering. Could you help me?
Remove the using statement. You are disposing of the Graphics object before it is used to draw to the screen.
As mentioned, you should also remove the Invalidate() call from the paint method.
public void panelMap_Paint(object sender, PaintEventArgs e)
{
var g = e.Graphics;
g.DrawImage(mapController.GetCurrentMap(), 0, 0, panelMap.Width, panelMap.Height);
}
You should comment out the following code
//panelMap.Invalidate();
According to MSDN
Invalidates the entire surface of the control and causes the control to be
redrawn.

how to clear panel in a simple paint application

I am working on a simple windows forms paint application. I am having problem in clearing the panel. The code i am using to draw is
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics g = Graphics.FromImage(tempDraw);
Pen myPen = new Pen(foreColor, lineWidth);
g.DrawLine(myPen, x1, y1, x2, y2);
myPen.Width = 100;
myPen.Dispose();
e.Graphics.DrawImageUnscaled(tempDraw, 0, 0);
g.Dispose();
}
How to clear the panel?
Are drawing in the paint handler of the Panel instance? If not then calling Invalidate on the panel would do.
But you will probably be persisting the drawing items and so to clear them you would need to delete what has been drawn and then call Invalidate. You could also fill the Panel with a particular color using FillRect but that would be a dirty workaround and not fit your final design.
You should also check out CodeProject.com for examples like this one to give you an idea on what needs to be handled when creating a drawing app like this.
EDIT:
Per the edited answer, you cannot clear the panel with the existing logic. You are painting inside Paint handler of the form which will happen any time it needs to be redrawn. This means that you should change your approach. You need some sort of condition inside the Paint handler which decides whether or not it will paint anything at all. This is where the persistence of drawing objects comes in. If you want to create a drawing program then you will have to handle the mouse Down, Up and Move events over the panel objects and store the data in a points array. (As an example of one type of drawing.) Then in your Paint handler if the Points[] is not empty you draw the points. Otherwise you draw nothing... which ends up in an empty container. Then if you need to clear the drawing you delete the contents of the Points array and call Invalidate on the Panel. That will clear the persisted data and repaint to nothing.
You can use
Panel1.Invalidate();
But there is a problem with this, after you call this function it clears all the graphics from the panel, but it also recalls the function i.e.
private void panel1_Paint(object sender, PaintEventArgs e)
{
//This function is recalled after Panel1.Invalidate();
}
So solution is to make your paint code in some other function
private void MyDrawing()
{
Graphics g = Graphics.FromImage(tempDraw);
// if above line doesn't work you can use the following commented line
//Graphics g = Graphics.Panel1.CreateGraphics();
Pen myPen = new Pen(foreColor, lineWidth);
g.DrawLine(myPen, x1, y1, x2, y2);
myPen.Width = 100;
myPen.Dispose();
Panel1.Graphics.DrawImageUnscaled(tempDraw, 0, 0);
g.Dispose();
}
You'll have to draw over the panel again with whatever base colour you're using eg. white\grey with the Graphics.FillRectangle method:
// Create solid brush.
SolidBrush whiteBrush = new SolidBrush(Color.White);
// Create location and size of rectangle.
// Fill rectangle to screen.
e.Graphics.FillRectangle(whiteBrush, panel.Location.X, panel.Location.Y, panel.Width, panel.Height);
this.Invalidate();

PictureBox resize & paint problem

I want to show some graphics in a Winform app, it will be a stock's chart drawing tool. I think (but I am not sure...) I have to use a PictureBox, and using the System.Drawing.Graphics class' drawing primitives to draw the chart. I have started coding it, now it works more-or-less, but I have a problem with the resizing feature, as follows: when I resize the entire form, I see that the program shows the graphics then inmediately clear it. When I stop the mouse movement (without to release the mouse button) the graphics disappears!?!?
I made a small test environment to demo the bug:
Using VS2005, creating a new C# Windows Forms app, adding only a PictureBox to the form.
Setting the PictureBox's anchor to left, top, right and bottom. Add two event handler, the Resize to the PictureBox, and the Paint to the Form.
namespace PictureBox_Resize {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
Ellipse_Area = this.pictureBox1.Size;
}
private Pen penBlack = new Pen(Color.Black, 1.0f);
private Size Ellipse_Area;
private void Form1_Paint(object sender, PaintEventArgs e) {
Graphics g = this.pictureBox1.CreateGraphics();
g.DrawEllipse(penBlack, 0, 0, Ellipse_Area.Width, Ellipse_Area.Height);
}
private void pictureBox1_Resize(object sender, EventArgs e) {
Control control = (Control)sender;
Ellipse_Area = control.Size;
this.pictureBox1.Invalidate();
}
}
}
This small app shows the problem. It only draws an ellipse, but of course my drawing code is much more complicated one...
Any idea why the ellipse disappears when I resize the Form????
Why are you using a PictureBox? I would create a UserControl for your chart and draw the ellipse in its Paint method, just using its current size. In its constructor, set it up for double buffering and all painting in the paint method.
this.SetStyle(ControlStyles.DoubleBuffer |
ControlStyles.UserPaint |
ControlStyles.AllPaintingInWmPaint,
true);
As far as I remember from my C++ days - where I did loads of such image-stuff - you need to call the repaint method - or override it to fit it for your behaviour.

Categories

Resources