The class inherited from Control doesnt show on the form - c#

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);
}

Related

How to initialize class with argument "PaintEventArgs e" in C#

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.

Graphics DrawImage - ArgumentException: 'Parameter is not valid.'

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);
}
}

The simplest Paint program isn't working in C# Windows Forms

I'm plowing through the Dietle C# book one page at a time and I'm stuck.
On page 555, there's the most basic drawing program you can imagine. As you move your mouse around, it's supposed to draw an ellipse on the screen.
Well, mine doesn't.
I've checked everything possible. I've gone onto the Dietel website and downloaded the code and tried that. I think I'm doing something wrong outside of the text-based programming. I mean, there are settings and stuff in the properties windows.
I think I got it all right, but nothing seems to work. But obviously I don't have it all right or it would work.
The full code is a bit longer than what I have below, but even this simplified code doesn't work. It's supposed to draw ellipses any time you have the mouse on the screen. Studio Express does a nice job of catching a lot of syntax errors, but of course it can't catch logic errors. Any ideas as to what's wrong?
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication6
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
using (Graphics graphics = CreateGraphics())
{
graphics.FillEllipse(
new SolidBrush(Color.Blue), e.X, e.Y, 20, 20);
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
}
}
MouseMove Event??
public Form1()
{
InitializeComponent();
this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseMove);
}
I would do it like this:
public partial class Form1 : Form
{
private List<IDrawAble> shapes = new List<IDrawAble>();
private MyEllipse currentlyDrawing;
public Form1()
{
InitializeComponent();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
currentlyDrawing = new MyEllipse() { X1 = e.X, Y1 = e.Y, X2 = e.X, Y2 = e.Y };
Invalidate();
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
shapes.Add(currentlyDrawing);
currentlyDrawing = null;
Invalidate();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.White);
foreach (var item in shapes)
{
item.Draw(e.Graphics);
}
if (currentlyDrawing != null)
{
currentlyDrawing.Draw(e.Graphics);
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (currentlyDrawing != null)
{
currentlyDrawing.X2 = e.X;
currentlyDrawing.Y2 = e.Y;
Invalidate();
}
}
}
class MyEllipse : IDrawAble
{
public int X1 { get; set; }
public int Y1 { get; set; }
public int X2 { get; set; }
public int Y2 { get; set; }
public void Draw(Graphics g)
{
g.FillEllipse(new SolidBrush(Color.Blue), X1, Y1, X2 - X1, Y2 - Y1);
}
}
interface IDrawAble
{
void Draw(Graphics g);
}
Make sure all eventhandlers are hooked to the events of the form.
To get rid of the flickering set the DoubleBuffered Property of the Form to true.
What is it doing now?
Just to Add.
Make sure your event MouseMove is mapped with the Method Form1_MouseMove
In the Method Hieght and weight are 20 which means it is circle and not eclipse.
for to debug first replace e.X and e.Y with number in below snippet it is 0
SolidBrush redBrush = new SolidBrush(Color.Red);
// Create location and size of ellipse.
float x = 0.0F;
float y = 0.0F;
float width = 200.0F;
float height = 100.0F;
using (Graphics graphics = CreateGraphics())
{
graphics.FillEllipse(redBrush, x, y, width, height);
}

Using the OnPaint() method

I am using this library to generate QRcode into a WinForm application, but I don't really know how to take use of the OnPaint() method.
So I have this:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void OnPaint(PaintEventArgs e)
{
QrEncoder encoder = new QrEncoder(ErrorCorrectionLevel.M);
QrCode qrCode;
encoder.TryEncode("link to some website", out qrCode);
new GraphicsRenderer(new FixedCodeSize(200, QuietZoneModules.Two))
.Draw(e.Graphics, qrCode.Matrix);
base.OnPaint(e);
}
private void Form1_Load(object sender, EventArgs e)
{
this.Invalidate();
}
}
I have a simple pictureBox in the form and I just want to generate the QRcode image in there (if it is possible to generate it in a picturebox).
If you're putting your image in a picturebox and you're only producing your image once, then you don't need to worry about the paint method (you're not doing an animation etc, it's just a QR code)
Just do this in your form load (or where ever you produce your image)
mypicturebox.Image = qrCodeImage;
Update - additional code to facilitate your library
var bmp = new Bitmap(200, 200);
using (var g = Graphics.FromImage(bmp))
{
new GraphicsRenderer(
new FixedCodeSize(200, QuietZoneModules.Two)).Draw(g, qrCode.Matrix);
}
pictureBox1.Image = bmp;
This is what I eventually did:
public partial class Form1 : Form
{
public event PaintEventHandler Paint;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
pictureBox1.Paint += new System.Windows.Forms.PaintEventHandler(this.pictureBox_Paint);
this.Controls.Add(pictureBox1);
}
private void pictureBox_Paint(object sender, PaintEventArgs e)
{
QrEncoder encoder = new QrEncoder(ErrorCorrectionLevel.M);
QrCode qrCode;
encoder.TryEncode("www.abix.dk", out qrCode);
new GraphicsRenderer(
new FixedCodeSize(200, QuietZoneModules.Two)).Draw(e.Graphics, qrCode.Matrix);
}
}

Need Help In Drawing

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.

Categories

Resources