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.
Related
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 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);
}
}
I want to call a function like Spawn() in this case, but without losing values from it after calling it (for example, I want entityPicture to still be available after I call Spawn(), so I can use it later on MainWindow_KeyDown() function). Same with X and other variables like that.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace EpicGame
{
public partial class MainWindow : Form
{
public MainWindow()
{
InitializeComponent();
}
public void MainWindow_Load(object sender, EventArgs e)
{
int EntityCount = 0;
Background.ImageLocation = "Background.jpg";
Background.SizeMode = PictureBoxSizeMode.AutoSize;
Spawn(600, 600, EntityCount, "Player 1.png"); EntityCount++;
}
// Controls
public void MainWindow_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.D) MoveRight();
entityPicture.Location = new Point(X, Y);
}
// Movement
public void MoveRight()
{
X++;
}
// Entity spawning
public void Spawn(int X, int Y, int ID, string Path)
{
PictureBox entityPicture = new PictureBox();
Image Entity = Image.FromFile(Path);
entityPicture.Image = Entity;
entityPicture.SizeMode = PictureBoxSizeMode.AutoSize;
entityPicture.Location = new Point(X, Y);
entityPicture.BackColor = Color.Transparent;
Controls.Add(entityPicture);
entityPicture.BringToFront();
}
private void Background_Click(object sender, EventArgs e)
{
}
}
}
You can declare it public in the form class like:
public partial class MainWindow : Form
{
PictureBox entityPicture = new PictureBox();
}
I don't know the type of entityPicture so just used PictureBox as example, you use the correct type.
Your root issue is one of scope. Have a look at this video, it walks through your exact issue: https://www.youtube.com/watch?v=NemPMKTxM7w
You can technically move entityPicture to the top of the class, however given this appears to be a WinForms window, I would add the PictureBox via the designer so it's only declared once. If you declare it at the class level and reinitialize it in Spawn (i.e. = new PictureBox()) repeatedly, you're going to create a memory leak.
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);
}