I'm making a little C# project which requires me to move an image that has already been drawn into the form. Here is the drawing algorithm:
public void DrawImagePoint(PaintEventArgs e)
{
// Create image.
newImage = A_Worm_Nightmare.Properties.Resources.Worm;
// Create Point for upper-left corner of image.
Point ulCorner = new Point(50, 50);
// Draw image to screen.
e.Graphics.DrawImage(newImage, ulCorner);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
DrawImagePoint(e);
}
Question: How do you flip an already drawn object in WinForms, since implementing this method in a timer is not possible? (timer_Tick does not support PaintEventArgs) The fliping is by Cursor.Position.X. Here is the algorithm for a normal `Picturebox":
private void timer1_Tick(object sender, EventArgs e)
{
bool Ok = true;
if (Cursor.Position.X <= 135 && Ok)
{
image.RotateFlip(RotateFlipType.RotateNoneFlipY);
Ok = false;
}
else if (Cursor.Position.X >= 135 && !Ok)
{
Ok = true;
}
}
Thank you in advance
I see what you are trying to do.
You must Call
this.Refresh();
on the timer event.
this will trigger the paint event.
You definitely need a timer. Just change the location in Timer's Tick and call Invalidate, it will make your form to repaint.
private Point location = Point.Empty;
private Image newImage;
private void OnTimerTick(object sender, EventArgs e)
{
location.Offset(1,1);
//Do flipping here
newImage.RotateFlip(RotateFlipType.RotateNoneFlipY);
this.Invalidate();//Makes form to repaint
}
public void DrawImagePoint(PaintEventArgs e)
{
if(newImage == null)
{
newImage = A_Worm_Nightmare.Properties.Resources.Worm;
}
e.Graphics.DrawImage(newImage, location);
}
You can setup a timer with whatever frequency, and that should work.
also note that Resources.Image will create new Image every time you query it. So you should cache the image somewhere to avoid that overhead.
Related
I'm making simple game where I need to remove pictures after certain time without freezing everything else. I'm making explode event:
private void Explode(int x, int y)
{
PictureBox explosion = new PictureBox();
explosion.Image = Properties.Resources.explosion;
explosion.SizeMode = PictureBoxSizeMode.StretchImage;
explosion.Size = new Size(50, 50);
explosion.Tag = "explosion";
explosion.Left = x;
explosion.Top = y;
this.Controls.Add(explosion);
explosion.BringToFront();
}
I have already one timer for running the game, and i want to use if statement to remove picture when it lasts for 3 sec.
private void timer1_Tick(object sender, EventArgs e)
{
foreach (Control x in this.Controls)
{
if (x is PictureBox && x.Tag == "explosion")
{
if (EXPLOSION LASTS MORE THEN 3sec)
{
this.Controls.Remove(x);
}
}
}
}
How can I do this?
Assuming you may have multiple picture box at the same time, then instead of using a single timer for multiple picture boxes which may have different explosion timings, you can use async/await and Task.Delay like this:
private async void button1_Click(object sender, EventArgs e)
{
await AddExplodablePictureBox();
}
private async Task AddExplodablePictureBox()
{
var p = new PictureBox();
p.BackColor = Color.Red;
//Set the Image and other properties
this.Controls.Add(p);
await Task.Delay(3000);
p.Dispose();
}
Before removing the picture box, of course it needs to release picturebox resources.
However there is problem when releasing. For more info, you can read more from the link below.
c# picturebox memory releasing problem
I have a simple highlighter using a Pen object. When the size is very large, it creates odd stripes which you can fix by defining its Start & End Cap withSystem.Drawing.Drawing2D.LineCap.Round but it instantly overlaps itself and loses its transparency. A flat cap also loses its transparency when you draw over itself multiple times.
How can I create an opaque pen that does not overlap itself or create stripes with a large pen width?
private static Color baseColor = Color.Yellow;
bool Draw;
Graphics g;
Point start;
Point end;
private void Form1_Load(object sender, EventArgs e)
{
g = this.CreateGraphics();
Pen1.Color = Color.FromArgb(128, baseColor);
Pen1.Width = 50;
Pen1.StartCap = System.Drawing.Drawing2D.LineCap.Flat; //I know it's default, just for clarification's sake
Pen1.EndCap = System.Drawing.Drawing2D.LineCap.Flat; //' '''' '' ' ''''''' '''' ''' ''''''''''''' ' ''''
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
Draw = true;
start = e.Location;
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
Draw = false;
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (Draw == true)
{
end = e.Location;
g.DrawLine(Pen1, start, end);
start = end;
}
}
It behaves like this;
Note: Yes, I am aware .CreateGraphics is not a good drawing method, it has a unique purpose that is irrelevant to the question.
You have two issues with the drawing objects:
You draw each Line separately; this will create ugly artifacts where the common points overlap. Instead draw the lines all in one go with the DrawLines method! (Note the plural!)
You did not restrict the Pen.MiterLimit; this creates ugly spikes when the lines directions change sharply. Try to restrict it to around 1/2 of the Pen.Width or less . Setting LineJoin is also recommened as well a the two Caps..
Collect the points of your current line in a List<Point> currentPoints in the MouseMove and collect upon MouseUp the currentList into a List<List<Point>> allPointLists !
then you can draw both in the Paint event..
foreach (List<Point> points in allPointLists)
if (points.Count > 1) e.Graphics.DrawLines(Pens.Gold, points.ToArray());
if (currentPoints.Count > 1) e.Graphics.DrawLines(Pens.Gold, currentPoints.ToArray());
Note that it will pay immediately to do it right, i.e. draw only with a valid graphics object and always rely on the Paint event to make sure that the drawing is always updated as needed! Using control.CreateGraphics is almost always wrong and will hurt as soon as you go beyond a single non-persistent drawing operation..
Here is the full code:
List<Point> currentPoints = new List<Point>();
List<List<Point>> allPointLists = new List<List<Point>>();
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
currentPoints = new List<Point>();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
if (currentPoints.Count > 1)
{
allPointLists.Add(currentPoints.ToList());
currentPoints.Clear();
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
currentPoints.Add(e.Location);
Invalidate();
}
}
Here is the Paint event; note that I use two different Pens for the currently drawn lines and the older ones. Also note that you can use DrawCurve instead of DrawLines to get even smoother results..
Also note that I use a List<T> to be flexible wrt the number of elements and that I convert it to an array in the Draw commands..
private void Form1_Paint(object sender, PaintEventArgs e)
{
Color c1 = Color.FromArgb(66, 77, 88, 222);
using (Pen pen = new Pen(c1, 50f))
{
pen.MiterLimit = pen.MiterLimit / 12;
pen.LineJoin = LineJoin.Round;
pen.StartCap = LineCap.Round;
pen.EndCap = LineCap.Round;
foreach (List<Point> points in allPointLists)
if (points.Count > 1) e.Graphics.DrawLines(pen, points.ToArray());
}
Color c2 = Color.FromArgb(66, 33, 111, 222);
using (Pen pen = new Pen(c2, 50f))
{
pen.MiterLimit = pen.MiterLimit / 4;
pen.LineJoin = LineJoin.Round;
pen.StartCap = LineCap.Round;
pen.EndCap = LineCap.Round;
if (currentPoints.Count > 1) e.Graphics.DrawLines(pen, currentPoints.ToArray());
}
}
To prevent flicker don't forget to turn on DoubleBuffered for your Form; or use a DoubleBuffered Panel subclass or simply a PictureBox!
Final note: I left out the case of simple clicks; if they are supposed to paint you will have to catch them, probably best in the if (points.Count > 1) check and best do a FillEllipse at the right spot and with the right size..
Update
List<T> is so useful I hardly ever use arrays theses days. Here is how to make use of it to implement a Clear and an Undo button:
private void buttonClear_Click(object sender, EventArgs e)
{
allPointLists.Clear();
Invalidate();
}
private void buttonUndo_Click(object sender, EventArgs e)
{
allPointLists.Remove(allPointLists.Last());
Invalidate();
}
Note two small corrections in the MouseUp code this has required to handle the currentPoints list properly! The clearing is obvious; the ToList() call is making a copy of the data, so we don't clear the instance we have just added to te List of Lists!
I have a main PictureBox which adding to it , an other picture boxes; I pass the parent to the children and add them to the parent as follow :
public class VectorLayer : PictureBox
{
Point start, end;
Pen pen;
public VectorLayer(Control parent)
{
pen = new Pen(Color.FromArgb(255, 0, 0, 255), 8);
pen.StartCap = LineCap.ArrowAnchor;
pen.EndCap = LineCap.RoundAnchor;
parent.Controls.Add(this);
BackColor = Color.Transparent;
Location = new Point(0, 0);
}
public void OnPaint(object sender, PaintEventArgs e)
{
e.Graphics.DrawLine(pen, end, start);
}
public void OnMouseDown(object sender, MouseEventArgs e)
{
start = e.Location;
}
public void OnMouseMove(object sender, MouseEventArgs e)
{
end = e.Location;
Invalidate();
}
public void OnMouseUp(object sender, MouseEventArgs e)
{
end = e.Location;
Invalidate();
}
}
and am handling those On Events from inside the main PictureBox, now in the main PictureBox am handling on the Paint event as follow:
private void PicBox_Paint(object sender, PaintEventArgs e)
{
//current layer is now an instance of `VectorLayer` which is a child of this main picturebox
if (currentLayer != null)
{
currentLayer.OnPaint(this, e);
}
e.Graphics.Flush();
e.Graphics.Save();
}
but when I draw nothing appears , when I do Alt+Tab lose the focus from it , I see my vector , when I try to draw again and lose focus nothing happens..
why is that weird behavior and how do I fix it?
You have forgotten to hook up your events.
Add these lines to your class:
MouseDown += OnMouseDown;
MouseMove += OnMouseMove;
MouseUp += OnMouseUp;
Paint += OnPaint;
Not sure if you don't maybe want this in the MouseMove:
public void OnMouseMove(object sender, MouseEventArgs e)
{
if (e.Button == System.Windows.Forms.MouseButtons.Left)
{
end = e.Location;
Invalidate();
}
}
Aslo these lines are useless and should be removed:
e.Graphics.Flush();
e.Graphics.Save();
GraphicsState oldState = Graphics.Save would save the current state, ie the setting of the current Graphics object. Useful if you need to toggle between several states, maybe scales or clipping or rotation or translation etc.. But not here!
Graphics.Flush flushes all pending graphics operations, but there is really no reason to suspect that there are any in your application.
I want to create two picture boxes, overlapping.
The first Picturebox is used as the background, the picture of the screen.
using this method:
public void BckShow()
{
Rectangle rect = Screen.GetBounds(this);
gBackImg = Graphics.FromImage(bBackImg);
gBackImg.CopyFromScreen(0,0,0,0,
Screen.PrimaryScreen.Bounds.Size,
CopyPixelOperation.SourceCopy);
}
The second picturebox is above the first one, a transparent picture box that can be drawn using this mouse event:
public void Draw(bool draw, Point sp, Point ep)
{
if (draw)
{
gCanvas.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
pen = new Pen(new SolidBrush(ColorName), BrushSize);
if (toolPen.Checked)
{
gCanvas.DrawLine(pen, sp, ep);
}
else if (toolEreser.Checked)
{
Rectangle rect = new Rectangle(ep.X, ep.Y, BrushSize*5, BrushSize*5);
gCanvas.DrawEllipse(pen, rect);
gCanvas.FillEllipse(new SolidBrush(ColorName), rect);
}
bCanvas.MakeTransparent(Color.White);
pbxCanvas.Refresh();
dirty = true;
toolSave.Enabled = true;
}
}
private void pbxCanvas_MouseDown(object sender, MouseEventArgs e)
{
sp = e.Location;
if (e.Button == MouseButtons.Left)
{
ActivePaint = true;
}
}
private void pbxCanvas_MouseUp(object sender, MouseEventArgs e)
{
ActivePaint = false;
}
private void pbxCanvas_MouseMove(object sender, MouseEventArgs e)
{
ep = e.Location;
Draw(ActivePaint, sp, ep);
sp = ep;
}
but when i run the program, the second PictureBox does not draw anything when the mouse event was fired. how i can fix this?
I do this because I just want to save the image in the second picture box. Unlike PrintScreen but seemed to make notes on the screen and save the image apart from the screen image.
Is there another way to do this? like using controls other than picture box, or may directly use the screen as a background but still can save the image in the transparent PictureBox separately.
This is the example I want to achieve:
when drawing:
results stored images:
I hope you all will help me to fix this. sorry for poor explanation.
this the document outline window for more detail:
It's likely that your surface being overdrawn by refresh. You should be tracking what you want to draw, and then drawing it in the picture box's Paint event. That way, you get handed a Graphics object and every refresh, you're drawing.
That's assuming, of course, that you have a valid, and correct, Graphics object in the first place.
BTW: passing a form-scope variable to Draw is confusing, just use it.
Check your gCanvas initializer, if it is used from within Paint event (e.Graphics), then your changes are lost when you call the Refresh() method. Refresh() causes a new Paint event to be fired, creating a new Graphics object and therefore invalidating yours.
Create a new graphics object from your PictureBox's Image to persist your changes permanently.
private List<Point> points = new List<Point>();
private void pbxCanvas_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
ActivePaint = true;
}
}
private void pbxCanvas_MouseUp(object sender, MouseEventArgs e) {
ActivePaint = false;
points.Clear();
}
private void pbxCanvas_MouseMove(object sender, MouseEventArgs e) {
if (ActivePaint) {
points.Add(e.Location);
Refresh();
}
}
private void pbxCanvas_Paint(object sender, PaintEventArgs e) {
using (var graphics = Graphics.FromImage(pbxCanvas.Image)) {
for (int i = 0; i < points.Count - 1; i++) {
graphics.DrawLine(Pens.Black, points[i], points[i + 1]);
}
}
}
First I load an image in a picturebox. Then I measure the areas in it and create a new picture. Now I want to load the image in a panel and draw a line by mouse.
I added to my form:
private Image imag;
I also added to my project:
private void drawP_Paint(object sender, PaintEventArgs e)
{
Graphics g = drawP.CreateGraphics();
g.DrawImage(imag, new Point(0,0));
}
I set the image in a function:
imag = (Image)bm;
// or
imag = picturebox1.Image; // the made picture
drawP.Invalidate();
But nothing appears when running the project.
You should place your code in panel Paint event.
private void panel1_Paint(object sender, PaintEventArgs e)
{
Image imag = Image.FromFile(filename);
e.Graphics.DrawImage(imag, new Point(0,0));
}
This makes you sure that everytime panel is redrawn (after beeing invalidated for any reason) your image is visible.
Try changing it to this:
private void drawP_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(imag, new Point(0,0));
}
Also, from your comments, it sounds like you may not have the event wired up. Example:
public Form1()
{
InitializeComponent();
drawP.Paint += drawP_Paint;
}
To draw a line on that image:
private void button1_Click(object sender, EventArgs e) {
using (Graphics g = Graphics.FromImage(imag)) {
g.DrawLine(Pens.Red, new Point(0, 0), new Point(32, 32));
}
drawP.Invalidate();
}