I have timer which adds new image on the panel every second. First i create my global variable Graphics g, create timer in the construcor and start timer there. In my Panel method i create Graphics object (g = e.Graphics) and then in my timer method i use that g object to draw new image. Can't find what's the problem, here's the core code (program stops when on the first call - g.DrawImage()):
public partial class MyClass: Form
{
private Timer addImage;
private Image img;
private Graphics g;
private Point pos;
public MyClass()
{
InitializeComponent();
img = Image.FromFile("C:/image.png");
pos = new Point(100, 100);
addImage = new Timer()
{
Enabled = true,
Interval = 3000,
};
addImage.Tick += new EventHandler(AddImage);
addImage.Start();
}
private void MyPanel_Paint(object sender, PaintEventArgs e)
{
g = e.Graphics;
}
private void AddImage(Object myObject, EventArgs myEventArgs)
{
g.DrawImage(img, pos); // ArgumentException: 'Parameter is not valid.'
MyPanel.Invalidate();
}
}
You have to draw your image in the OnPaint override because the Graphics object will be disposed. To redraw the form you can call Refresh. Also look that your that your path to the image is correct.
public partial class MyClass : Form
{
private readonly Image _image;
private readonly Point _position;
private bool _isImageVisible;
public MyClass()
{
InitializeComponent();
_image = Image.FromFile(#"C:\img.png");
_position = new Point(100, 100);
var addImageCountdown = new Timer
{
Enabled = true,
Interval = 3000,
};
addImageCountdown.Tick += new EventHandler(AddImage);
addImageCountdown.Start();
}
private void AddImage(Object myObject, EventArgs myEventArgs)
{
_isImageVisible = true;
Refresh();
}
protected override void OnPaint(PaintEventArgs e)
{
if(_isImageVisible)
{
e.Graphics.DrawImage(_image, _position);
}
base.OnPaint(e);
}
}
Related
I have my Gallow class which is responsible for rendering the gallow using e.Graphics, I need to use the object elsewhere and therefore it must be initialized at the beginning, but there I don't have access to "PaintEventArgs e". So how can I create an object with the argument "PaintEventArgs e"?
internal class Gallow: Hangman
{
public Graphics g;
public Gallow(PaintEventArgs e)
{
this.g = e.Graphics;
}
}
//...
Gallow gallow = new Gallow(e);
The PaintEventArgs e should not be cached in a field. Always use the e which is passed. It's not 'yours'. It belongs to an object which is some higher in the callstack. This owner instance is might recreate the Graphics instance on size changes etc.
Instead of painting directly on the control, you'd better create a bitmap, which you can blit to the Graphics of the e parameter. Create the bitmap ones and recreate the bitmap on resizing the control (of applicable). It's what John Wu advices.
I assume that you are using winforms.
Example:
public partial class Form1 : Form
{
// your gallow class instance.
private readonly Gallow _gallow = new();
// default constructor
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
// check if the backpage and the bitmap have the same dimensions.
_gallow.Draw(e.Graphics);
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
// when the size is changed, we need a repaint.
Invalidate();
}
}
_This will draw lines directly onto the form, so you see all lines draw separately. This will slow it down.
And via a bitmap:
public partial class Form1 : Form
{
// the field for the bitmap.
private Bitmap _bitmap;
// your gallow class instance.
private readonly Gallow _gallow = new();
// default constructor
public Form1()
{
InitializeComponent();
}
// on the Form_Load, (then the size is know) create the initial bitmap.
private void Form1_Load(object sender, EventArgs e)
{
_bitmap = new Bitmap(Width, Height);
}
// on the paint even, check if the bitmap needs to be resized.
private void Form1_Paint(object sender, PaintEventArgs e)
{
// check if the backpage and the bitmap have the same dimensions.
if (_bitmap.Width != Width || _bitmap.Height != Height)
{
// dispose the old one.
_bitmap?.Dispose();
// recreate the bitmap with a new size.
_bitmap = new Bitmap(Width, Height);
}
// draw you gallow graphics on the Graphics object.
using (Graphics g = Graphics.FromImage(_bitmap))
_gallow.Draw(g);
// copy the bitmap to the form.
e.Graphics.DrawImage(_bitmap, 0, 0, _bitmap.Width, _bitmap.Height);
}
private void Form1_SizeChanged(object sender, EventArgs e)
{
// when the size is changed, we need a repaint.
Invalidate();
}
}
_This will draw lines first on a bitmap and ones it is ready, it will copy the bitmap to the form.
And here is the gallow class: (for testing)
internal class Gallow
{
private readonly Random _rnd = new();
public void Draw(Graphics graphics)
{
var width = (int)graphics.VisibleClipBounds.Width;
var height = (int)graphics.VisibleClipBounds.Height;
// some random lines....(for testing)
for (int i = 0; i < 500; i++)
{
int x1 = _rnd.Next(width);
int y1 = _rnd.Next(height);
int x2 = _rnd.Next(width);
int y2 = _rnd.Next(height);
graphics.DrawLine(Pens.Red, x1, y1, x2, y2);
}
}
}
Note that the gallow class does not cache the graphics instance. Also the gallow class isn't changed, because both bitmap and the form uses the Graphics.
I haven't been learning C# for very long and was wondering if it is possible to create a mouse event from another class?
The intention is that when a mousedown happens, something is drawn on a panel. I would like to do all this from a different class is this possible?
This is what I've already tried:
class Circle: Form1
{
Graphics g;
Pen pen = new Pen(Color.Black);
// Making a event ?
public event EventHandler<MouseEventArgs> MouseDown;
protected void OnClick(MouseEventArgs e)
{
EventHandler<MouseEventArgs> handler = MouseDown;
if (handler != null)
{
handler(this, e);
}
}
public Circle()
{
g = this.panel1.CreateGraphics();
}
public void Mouse_Down(object sender, MouseEventArgs e)
{
// Draw something
g.DrawLine(pen, 0, 0, 50, 50);
}
}
}
So instead of calling the panel1_MouseDown event in this class I want to call it from the circle class:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
// So instead of calling the panel1_MouseDown event in this class I want to call it from the circle class
}
}
}
Yes, you can "do all this from a different class"
Referring to the the example below:
In Form1 constructor create initializes the instance of the Circle class and passes to its constructor an instance of Form1.
Make sure that the control you want to draw on is public (panel1 in the example).
Add MouseDown and Paint events to panel1.
Always use the Paint event to draw graphics - it's the best practice.
More info on Control.Paint Event please: read here
Form1:
public partial class Form1 : Form
{
Circle circle;
public Form1()
{
InitializeComponent();
// Start the measuring time for reauthentication
circle = new Circle(this);
}
}
Circle Class:
class Circle
{
private Form1 Instance;
public Circle(Form1 instance)
{
this.Instance = instance;
// Make sure the access modifier of panel1 is Public and add mousedown event
Instance.panel1.MouseDown += panel1_MouseDown;
// Add a paint event - this is the best practice to draw graphics
Instance.panel1.Paint += panel1_Paint;
}
private int X;
private int Y;
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
this.X = e.X;
this.Y = e.Y;
// Redraw the panel when mouse is down
Instance.panel1.Refresh();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
if (this.X > 0 && this.Y > 0)
{
Graphics g = e.Graphics;
Pen p = new Pen(Color.Red);
g.DrawEllipse(p, X, Y, 50, 50);
}
}
}
Output:
I want to paint a graphics object from a method (paint) I created in a separate class (Paintball). I want it to paint in a picturebox only when I left-click with my mouse and I want the point where I shoot to be stored in a List. When I try the code below, it doesn't shoot. Below is the class Paintball.
{
private List<Point> myClick;
public Paintball()
{
myClick = new List<Point>();
}
public void add(Point location)
{
myClick.Add(location);
}
public void paint(Graphics g, Point point)
{
g.FillEllipse(Brushes.Blue, point.X, point.Y, 20, 20);
}
}
}
This is form1 below.
namespace AmazingPaintball
{
public partial class Form1 : Form
{
Random positionX = new Random();
Random positionY = new Random();
Target einstein;
int count;
List<Point> ballList = new List<Point>();
Paintball gun;
public Form1()
{
InitializeComponent();
Point point = new Point(positionX.Next(0, 638), positionY.Next(0, 404));
einstein = new Target(point);
ptrEinstein.Location = point;
gun = new Paintball();
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
ptrEinstein.Location = einstein.Move(e.KeyData);
pictureBox1.Update();
pictureBox1.Refresh();
}
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
count++;
gun.add(e.Location);
pictureBox1.Refresh();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
foreach (var Paintball in ballList)
{
gun.paint(e.Graphics, this.PointToClient(Cursor.Position));
pictureBox1.Refresh();
}
}
private void Form1_Load(object sender, EventArgs e)
{
timer1.Start();
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
pictureBox1.Refresh();
}
}
}
Please let me know if you know what has to be edited/created. Thank You
Your original code has many mistakes. Let's try to simplify what you are doing and tackle simply storing a list of points and drawing them to the picturebox.
public partial class Form1 : Form
{
List<Point> ballList = new List<Point>();
public Form1()
{
InitializeComponent();
}
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
ballList.Add(e.Location);
pictureBox1.Refresh();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
foreach (Point pBall in ballList)
{
e.Graphics.FillEllipse(Brushes.Blue, pBall.X, pBall.Y, 20, 20);
}
}
}
Here we have a list, we add the click points to it in the click handler and paint them in the paint handler. Once you get comfortable with this, perhaps move to the next task in your program and ask a new question if you get stuck with the next feature.
Ok, I've got a bit of time, so let's look at your paintball class. I've renamed it Paintballs since it contains many of them and this name is more appropriate. If you want to keep the list of points private that's ok. You are trying to implement a Paint method in the class, but it takes a Point as argument and does not operate on any of the class's instance state - this probably isn't what you want. Consider now :
public class Paintballs
{
private List<Point> myClick;
public Paintballs()
{
myClick = new List<Point>();
}
public void Add(Point location)
{
myClick.Add(location);
}
public void Paint(Graphics g)
{
foreach (Point p in myClick)
{
g.FillEllipse(Brushes.Blue, p.X, p.Y, 20, 20);
}
}
}
Here we have a public Paint method that will draw all of the paintballs in the class to any graphics instance you pass to it. Now your form code would look like :
public partial class Form1 : Form
{
Paintballs pBalls = new Paintballs();
public Form1()
{
InitializeComponent();
}
private void pictureBox1_MouseClick(object sender, MouseEventArgs e)
{
pBalls.Add(e.Location);
pictureBox1.Refresh();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
pBalls.Paint(e.Graphics);
}
}
So we've simplified the form code by pushing the painting method into the paintballs class itself. This makes the class responsible for knowing what the paintballs look like, how many there are, where they are, and how to draw them to a Graphics object. This is step 1 in encapsulating responsibility.
You're drawing from a list of points stored in that ballList variable. However, you've never added any points to that list.
Make the myClick list in Paintball public and, in the pictureBox1_Paint method, iterate through that list instead of ballList.
i have a class written in c# inherited from Control like below.
class MyImage:Control
{
private Bitmap bitmap;
public MyImage(int width, int height)
{
this.Width = width;
this.Height = height;
bitmap = new Bitmap(width,height);
Graphics gr = Graphics.FromImage(bitmap);
gr.FillRectangle(Brushes.BlueViolet,0,0,width,height);
this.CreateGraphics().DrawImage(bitmap,0,0);
}
}
And from my main form i create an object of this class. and add thid object to the form, like below.
private void button1_Click(object sender, EventArgs e)
{
MyImage m = new MyImage(100,100);
m.Left = 100;
m.Top = 100;
this.Controls.Add(m);
}
but it doesnt appear on the form. What is the problem.
Thanks.
You should not draw anything in a class constructor. You should override OnPaint method and draw all of your custom graphics here.
You can write someting like this:
public partial class MyImage : Control
{
public MyImage()
{
InitializeComponent();
bitmap = new Lazy<Bitmap>(InitializeBitmap);
}
private Lazy<Bitmap> bitmap;
private Bitmap InitializeBitmap()
{
var myImage = new Bitmap(Width, Height);
using(var gr = Graphics.FromImage(myImage))
{
gr.FillRectangle(Brushes.BlueViolet, 0, 0, Width, Height);
}
return myImage;
}
protected override void OnPaint(PaintEventArgs pe)
{
base.OnPaint(pe);
pe.Graphics.DrawImage(bitmap.Value, 0, 0);
}
}
The recepient code:
private void button1_Click(object sender, EventArgs e)
{
var m = new MyImage(100,100)
{
Width = 100,
Height = 100,
Left = 100,
Top = 100
}
Controls.Add(m);
}
Following is my code. i am trying to draw line, filled rectangle, etc.....
Problem is that, lets suppose i draw a line but when i try to draw an other line first drawn line disappears. so i want help that i'll be able to draw multiple shapes on a form and first draw lines don't disappears.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace finalPaint
{
public partial class Form1 : Form
{
List<Point> points = new List<Point>();
Rectangle rect;
Point first;
Point last;
string op;
public Form1()
{
InitializeComponent();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
points.Add(e.Location);
rect = new Rectangle(rect.Left, rect.Top, e.X - rect.Left, e.Y - rect.Top);
last = e.Location;
this.Invalidate();
this.Update();
}
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
switch (op)
{
case "toolStripButton1":
{
if (points.Count > 2)
{
e.Graphics.DrawLines(Pens.Black, points.ToArray());
}
}
break;
case "toolStripButton2":
{
using (Pen pen = new Pen(Color.Red, 2))
{
e.Graphics.DrawRectangle(pen, rect);
}
}
break;
case "toolStripButton3":
{
Pen pen = new Pen(Color.Red, 2);
e.Graphics.DrawLine(pen, first, last);
this.Update();
}
break;
case "toolStripButton4":
{
using (SolidBrush pen = new SolidBrush(Color.Red))
{
e.Graphics.FillRectangle(pen, rect);
}
}
break;
case "toolStripButton5":
{
using (SolidBrush pen = new SolidBrush(Color.Red))
{
e.Graphics.FillEllipse(pen, rect);
}
}
break;
case "toolStripButton6":
{
using (Pen pen = new Pen(Color.Red,2))
{
e.Graphics.DrawEllipse(pen, rect);
}
}
break;
default:
break;
}
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
rect = new Rectangle(e.X, e.Y, 0, 0);
first = e.Location;
this.Invalidate();
}
private void toolStrip1_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
}
private void selectedButton(object sender, EventArgs e)
{
foreach (ToolStripButton btn in toolStrip1.Items)
{
btn.Checked = false;
}
ToolStripButton btnClicked = sender as ToolStripButton;
btnClicked.Checked = true;
op = btnClicked.Name;
}
}
}
On each Paint event, you need to paint all of the objects you want on the screen. You are not just painting on top of what is already there. You are repainting the entire scene.
The reason for this is that your control may be obscured from view at some point, and Windows will repaint it when it is revealed again.
If you want to keep a memory of all the the objects, you need to store them in your code. Since each object is different (lines, rectangles, ellipses) you will want to store them in a manner that lets you differentiate. You could create classes like this:
public class DrawingShape
{
public string Name { get; set; }
public DrawingShapeType Type { get; set; }
// other shared properties
public virtual void Draw(Graphics g)
{
}
}
public class DrawingRectangle : DrawingShape
{
public DrawingRectangle()
{
Name = "Rectangle";
Type = DrawingShapeType.Rectangle;
}
public override void Draw(Graphics g)
{
// draw this shape
}
}
public enum DrawingShapeType
{
Rectangle,
Ellipse,
Line,
}
Then you can just store all your objects in a List. The order of the items in the list is your z-order, so you add items to the list and you enumerate through the list in your Paint event and draw each one differently depending on the type.
From here you can store pen and brush information in the class and other info. Your Paint event can tell each class to paint itself and it doesn't need to know which type they are.
You need a possibility to store all your shapes, in order to draw them all in the Paint event, since the background of the form is repainted before each call to Paint. Let us define a base class from which we will derive all the shapes
abstract class Shape
{
public abstract void Paint(PaintEventArgs e);
public abstract void UpdateShape(Point newLocation);
}
Here we declare the abstract methods Paint and UpdateShape that we will have to override in the derived classes. UpdateShape will be called in MouseMove.
Let us start with the freeform line
class FreeformLine : Shape
{
private List<Point> _points = new List<Point>();
public FreeformLine(Point startLocation)
{
_points.Add(startLocation);
}
public override void Paint(PaintEventArgs e)
{
if (_points.Count >= 2) {
e.Graphics.DrawLines(Pens.Black, _points.ToArray());
}
}
public override void UpdateShape(Point newLocation)
{
const int minDist = 3;
// Add new point only if it has a minimal distance from the last one.
// This creates a smoother line.
Point last = _points[_points.Count - 1];
if (Math.Abs(newLocation.X - last.X) >= minDist ||
Math.Abs(newLocation.Y - last.Y) >= minDist)
{
_points.Add(newLocation);
}
}
}
Here we need a list of points. In the constructor, we pass the first point. The Paint method just executes the paint logic that you had already defined and the UpdateShape method adds new points to our points list.
The straight line works in a very similar way, but defines only the first and the last point.
class StraightLine : Shape
{
private Point _first;
private Point _last;
public StraightLine(Point startLocation)
{
_first = startLocation;
}
public override void Paint(PaintEventArgs e)
{
if (!_last.IsEmpty) {
Pen pen2 = new Pen(Color.Red, 2);
e.Graphics.DrawLine(pen2, _first, _last);
}
}
public override void UpdateShape(Point newLocation)
{
_last = newLocation;
}
}
We define only one rectangle class and add a variable in order to remember if the shape is filled or not.
class RectangleShape : Shape
{
protected bool _filled;
protected Rectangle _rect;
protected Point _start;
public RectangleShape(Point startLocation, bool filled)
{
_start = startLocation;
_rect = new Rectangle(startLocation.X, startLocation.Y, 0, 0);
_filled = filled;
}
public override void Paint(PaintEventArgs e)
{
if (_filled) {
using (SolidBrush brush = new SolidBrush(Color.Red)) {
e.Graphics.FillRectangle(brush, _rect);
}
} else {
using (Pen pen = new Pen(Color.Red, 2)) {
e.Graphics.DrawRectangle(pen, _rect);
}
}
}
public override void UpdateShape(Point newLocation)
{
int x = Math.Min(_start.X, newLocation.X);
int y = Math.Min(_start.Y, newLocation.Y);
int width = Math.Abs(newLocation.X - _start.X);
int height = Math.Abs(newLocation.Y - _start.Y);
_rect = new Rectangle(x, y, width, height);
}
}
Finally, we declare the ellipse class. Since this one uses a rectangle as well, we just derive it from our rectangle class.
class Ellipse : RectangleShape
{
public Ellipse(Point startLocation, bool filled)
: base(startLocation, filled)
{
}
public override void Paint(PaintEventArgs e)
{
if (_filled) {
using (SolidBrush brush = new SolidBrush(Color.Red)) {
e.Graphics.FillEllipse(brush, _rect);
}
} else {
using (Pen pen = new Pen(Color.Red, 2)) {
e.Graphics.DrawEllipse(pen, _rect);
}
}
}
}
Here we only override the Paint method. All the rectangle update logic remains the same.
Now to the form. Here we declare the global variables
List<Shape> _shapes = new List<Shape>();
Shape _lastShape;
string op;
In the mouse down event we create a new shape and add it to the list like this
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
switch (op) {
case "toolStripButton1":
_lastShape = new FreeformLine(e.Location);
break;
case "toolStripButton2":
_lastShape = new RectangleShape(e.Location, false);
break;
case "toolStripButton3":
_lastShape = new StraightLine(e.Location);
break;
case "toolStripButton4":
_lastShape = new RectangleShape(e.Location, true);
break;
case "toolStripButton5":
_lastShape = new Ellipse(e.Location, true);
break;
case "toolStripButton6":
_lastShape = new Ellipse(e.Location, false);
break;
default:
break;
}
_shapes.Add(_lastShape);
Refresh();
}
In the MouseMove we update the last shape like this
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && _lastShape != null) {
_lastShape.UpdateShape(e.Location);
this.Refresh();
}
}
The Paint method is now much simpler
private void Form1_Paint(object sender, PaintEventArgs e)
{
foreach (Shape shape in _shapes) {
shape.Paint(e);
}
}
Note that we do all the shape specific things in the shape classes, instead of doing them in the form. The only place in the form where have to care about the different shapes, is where we create the different shapes. This is a typical object-oriented approach. It is easier to maintain and to extend. You could add new shapes, with only a minimum changes in the form itself.