I am trying to write a drawing program for use with a tablet. For this I need fall-off and transparency for use with pressures. So I am using the bitmap system in C# for image construction.
I cannot seem to get my drawing code at the moment to display anything. It is being rendered to a picture box. I know there is some stuff being input to the bitmap as it shows up when I do a bitmap save.
I have had a look around an pretty much all C# drawing questions refer to using the line drawing or ellipse drawing stuff as opposed to bitmaps
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 paint1
{
public partial class Form2 : Form
{
public Bitmap m_bitmap;
public bool m_penDown;
public int m_lastX;
public int m_lastY;
public int m_currentX;
public int m_currentY;
public Form2()
{
InitializeComponent();
// Create the bitmap area
m_bitmap = new Bitmap(this.Width, this.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
m_penDown = false;
Graphics m_graphics = Graphics.FromImage(m_bitmap);
m_lastX = System.Windows.Forms.Cursor.Position.X;
m_lastY = System.Windows.Forms.Cursor.Position.Y;
m_currentX = System.Windows.Forms.Cursor.Position.X;
m_currentY = System.Windows.Forms.Cursor.Position.Y;
}
private void Form2_Load(object sender, EventArgs e)
{
}
private void Form2_Paint(object sender, PaintEventArgs e)
{
Graphics objGraphics;
//You can't modify e.Graphics directly.
objGraphics = e.Graphics;
// Draw the contents of the bitmap on the form.
objGraphics.DrawImage(m_bitmap, 0, 0,
m_bitmap.Width,
m_bitmap.Height);
objGraphics.Dispose();
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
m_penDown = true;
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
m_penDown = false;
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
m_lastX = m_currentX;
m_lastY = m_currentY;
m_currentX = System.Windows.Forms.Cursor.Position.X;
m_currentY = System.Windows.Forms.Cursor.Position.Y;
if(m_penDown)
m_bitmap.SetPixel(m_currentX, m_currentY, Color.Gray);
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
Form2_Paint(sender, e);
this.pictureBox1.Image = m_bitmap;
}
private void Form2_KeyUp(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Space)
{
m_bitmap.Save(#"C:\Users\rpettefar\Documents\My Dropbox\Programming\paint1\preview.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
}
}
}
}
I am a bit new to c# so I am very open to any other issues or things that may come to your attention too.
You will have to assign your bitmap to the picture box.
myPictureBox.Image = m_bitmap;
You can do that after you changed the bitmap or assign it once and then invalidate your PictureBox.
myPictureBox.Invalidate();
This tells your form to refresh the picture on the screen. There is no need to override OnPaint. Draw to the bitmap using the Graphics object you created in the constructor of the form (if you want to make more complicated things than just drawing single pixels). The PictureBox will do the rest.
It looks like there's at least two ways you're trying to get the image on screen; can't say immediately what's wrong, but I would say definitely get rid of that objGraphics.Dispose(); line - you didn't create the Graphics (you were passed it), so you shouldn't Dispose it.
I cleaned up your code a bit. You probably shouldn't use a picturebox for this.
Here is a form with just a panel:
public partial class Form1 : Form
{
public Bitmap m_bitmap;
public Point m_lastPoint = Point.Empty;
public Form1()
{
InitializeComponent();
m_bitmap = new Bitmap(panel1.ClientSize.Width, panel1.ClientSize.Height, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(m_bitmap))
g.Clear(SystemColors.Window);
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(m_bitmap, new Point(0, 0));
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
m_lastPoint = e.Location;
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
using (Graphics g = Graphics.FromImage(m_bitmap))
g.DrawLine(Pens.Black, m_lastPoint, e.Location);
m_lastPoint = e.Location;
panel1.Invalidate();
}
}
}
The other posters have largely answered the question, but in my experience, I'd add that you'll likely get some flicker with this method. If you do, one thing you can do to help with this is sub-class your rendering target and enable double buffering. For a picture box, it would look something like this:
public class DoubleBufferedPictureBox : PictureBox
{
/// <summary>
/// Creates an instance of the DoubleBufferedPictureBox.
/// </summary>
public DoubleBufferedPictureBox() : base()
{
this.SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer, true);
}
}
Related
This little program opens a windows form and draws 70 red rectangles, where the user clicks on the form.
Every time the user clicks, the rectangles disappear, and new ones are drawn on the new click-Point.
I want to make the rectangles to stay when the user clicks and draws a new set of rectangles.
How do i do that?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace tegnRektangel
{
public partial class Form1 : Form
{
int x;
int y;
bool mouseClicked = false;
Graphics g = null;
public Form1()
{
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
}
private void Form1_Resize(object sender, EventArgs e)
{
Invalidate();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
if (mouseClicked)
{
g = panel1.CreateGraphics();
paintRectangel();
}
}
private void paintRectangel()
{
for (int i = 1; i <= 70; i++)
{
g.DrawRectangle(Pens.Red, x - 50-i*5, y - 40-i*5, 100, 80);
}
g.Dispose();
}//end paint
private void panel1_MouseClick(object sender, MouseEventArgs e)
{
mouseClicked = true;
Point clickPoint = new Point(e.X,e.Y);
x = clickPoint.X;
y = clickPoint.Y;
panel1.Invalidate();
}
}
}
From MSDN:
The Graphics object that you retrieve through the CreateGraphics
method should not normally be retained after the current Windows
message has been processed, because anything painted with that object
will be erased with the next WM_PAINT message.
You can work around it like this:
In the click event, add the (x, y) coordinate to a list of coordinates.
In the paint event, iterate all these (x, y) coordinates and paint each rectangle.
Here is some code to demonstrate creating rectangles for each click, storing them, and painting all stored rectangles.
public partial class Form1 : Form
{
private List<Rectangle> Rectangles { get; set; }
public Form1()
{
InitializeComponent();
Rectangles = new List<Rectangle>();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
if (Rectangles.Count > 0)
e.Graphics.DrawRectangles(Pens.Red, Rectangles.ToArray());
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
for (int i = 1; i <= 70; i++)
{
Rectangles.Add(new Rectangle(e.X - 50 - i * 5, e.Y - 40 - i * 5, 100, 80));
}
Invalidate();
}
}
I'm very new(read 3 weeks exp) to C#(programming in general),started with html/css and javascript and now on my way with C#.
I'm trying to make my own simple 'Paint' application in windows form. But i've encountered an issue and just cant wrap my head around it, doesnt matter how much i read or follow other mans code, i'm stuck. The following code works fine but when resizing the application window the drawing dissappears.
As a solution ive read that declaring the Graphics method within the panel1_Paint event this should be resolved And here is my issue. See last code sample, ive come up with this(yes like i said, im new to this)and its not drawing anything.
ive simply tried to recreate the first example under the panel1_Paint event but i guess something went wrong during the mouseMove event and i cant figure out what it is.
Could someone explain to me what i am missing here, that would be very appreciated. thanks in advance.
[Old code]
namespace Painter
{
public partial class Form1 : Form
{
Graphics graphics;
Pen pen = new Pen(Color.Black, 1);
Point startingPoint = new Point(0, 0);
Point endPoint = new Point(0, 0);
bool mousePaint = false;
public Form1()
{
InitializeComponent();
this.DoubleBuffered = true;
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
startingPoint = e.Location;
if (e.Button == MouseButtons.Left)
{
mousePaint = true;
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if(mousePaint == true)
{
endPoint = e.Location;
graphics = panel1.CreateGraphics();
graphics.DrawLine(pen, startingPoint, endPoint);
}
startingPoint = endPoint;
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
mousePaint = false;
}
}
}
[New Code]
namespace Painter
{
public partial class Form1 : Form
{
Pen pen = new Pen(Color.Black, 1);
Point startingPoint = new Point(0, 0);
Point endPoint = new Point(0, 0);
bool mousePaint = false;
public Form1()
{
InitializeComponent();
this.DoubleBuffered = true;
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics graphics = panel1.CreateGraphics();
if (mousePaint == true)
{
graphics.DrawLine(pen, startingPoint, endPoint);
}
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
startingPoint = e.Location;
if (e.Button == MouseButtons.Left)
{
mousePaint = true;
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if(mousePaint == true)
{
endPoint = e.Location;
}
startingPoint = endPoint;
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
mousePaint = false;
}
}
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
Graphics graphics = panel1.CreateGraphics();
This is nonsense! Always and only use the e.Graphics object from the Paint param!!
Also: To trigger the Paint event do a panel1.Invalidate(); whenever your drawing data have changed!
Also: Make sure you understand just what your mousePaint flag is supposed to control: the mouse painting (i.e. adding new shapes to draw) or the regular painting (i. all shape previously drawn)!? Note that all drawing, current and previous needs to be done from the Paint event, whenever necessary i.e. over and over again!
To be able to do so: Collect all the shpes' data in a List<T>..
To Doublebuffer a Panel you need to subclass it. Your code turns on DoubleBuffering for the Form, which fine but won't help the Panel..
Instead simply use a PictureBox, which is control meant for drawing on!
A DoubleBuffered Panel subclass is as simple as this:
class DrawPanel : Panel
{
public DrawPanel()
{
DoubleBuffered = true;
}
}
Update: Instead you can also use a Label (with Autosize=false); it also has the DoubleBuffered property turned on out of the box and supports drawing better than Panels do.
The following code works fine but when resizing the application window the drawing dissappears.
This happens because resizing the application window invalidates portion of your panel which causes the portion to be redrawn.
Reason why your second approach is not working (the one labelled as [NEW CODE]) is because the Paint event is called only when relevant component is redrawn. You could partially solve this by forcing redraw of the panel in your MouseDown/MouseMove event handlers but you would still lose your previously painted stuff.
Possible solution is to create instance of Bitmap and paint there. Then just set this Bitmap as BackgroundImage of the panel. You can find more information on that here. Of course you would need to think about stuff like resizing and what should happen to the bitmap if application window gets shrunk or enlarged.
Here is some code that I quickly put together to get you started:
namespace WinForms_PaintTest
{
public partial class Form1 : Form
{
private Pen pen;
private Bitmap bitmap;
public Form1()
{
InitializeComponent();
this.pen = new Pen(Color.Black, 1);
this.bitmap = new Bitmap(this.panel1.Width, this.panel1.Height);
this.panel1.BackgroundImage = this.bitmap;
}
private void panel1_MouseMove(Object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
using (Graphics g = Graphics.FromImage(this.bitmap))
{
g.DrawRectangle(this.pen, e.Location.X, e.Location.Y, 1, 1);
}
this.panel1.Refresh();
}
}
private void Form1_FormClosed(Object sender, FormClosedEventArgs e)
{
this.pen.Dispose();
this.bitmap.Dispose();
}
}
}
Also regarding this:
this.DoubleBuffered = true;
I believe your intention was to prevent the flickering when relevant control is being redrawn? If that is case you need to set this property against the panel and not against the form itself. It is little bit tricky though because DoubleBuffered property of the panel is protected so you will need to either inherit from the panel or resort to reflection. You can find more information here .
I am developing a very rudimentary drawing program: A 2D grid comprised of multiple RectangleShapes, around 20x30 pixels each, which when clicked change color based on user RGB input, which works just fine:
Color SelectedColor = new Color();
private void Pixel_1_1_Click(object sender, EventArgs e) // on Rectangle click
{
Pixel_1_1.FillColor = SelectedColor; // change to currently desired color.
}
Since the number of squares is rising dramatically, I'm looking for a way to arrange the "pixel" rectangles into a 2D array. (I really don't want to have to make a Pixel_Click method for every single Rectangle on the screen!) Hoping eventually to be able to call something like:
private void Pixel_[x]_[y]_Click(object sender, EventArgs e)
{
Pixel_[x]_[y].FillColor = SelectedColor;
}
My friends suggest the use of an anonymous delegate, but I don't understand how to fully use one to solve my problem.
What would be the best way to generate a 2D array of rectangles in a C# Windows Form? And once generated, how can I access them with a single method for variant values of x and y?
While you are probably correct in thinking of each rectangle as an object, it probably isn't correct to think of each rectangle as a windows control, especially since you have so many of them.
So try creating your own rectangle object:
public class MyRect {
public Color FillColor { get; set; }
public Rectangle Rectangle { get; set; }
public MyRect(Rectangle r, Color c) {
this.Rectangle = r;
this.FillColor = c;
}
}
Now you just need to keep a list of your objects and paint on a single Panel control (or PictureBox) all of your rectangles:
private List<MyRect> myRectangles = new List<MyRect>();
public Form1() {
InitializeComponent();
myRectangles.Add(new MyRect(new Rectangle(10, 10, 64, 16), Color.Blue));
myRectangles.Add(new MyRect(new Rectangle(20, 48, 16, 64), Color.Red));
}
private void panel1_Paint(object sender, PaintEventArgs e) {
foreach (MyRect mr in myRectangles) {
using (SolidBrush sb = new SolidBrush(mr.FillColor)) {
e.Graphics.FillRectangle(sb, mr.Rectangle);
}
}
}
To handle the "click" event of the rectangles, you just handle the MouseDown or MouseClick event of your container control and determine yourself which rectangle is being clicked on:
void panel1_MouseDown(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {
foreach (MyRect mr in myRectangles) {
if (mr.Rectangle.Contains(e.Location)) {
ChangeColor(mr, Color.Green);
}
}
panel1.Invalidate();
}
}
private void ChangeColor(MyRect mr, Color newColor) {
mr.FillColor = newColor;
}
If you want to maintain the rectangles as components on screen then you can assign all of them the same click event, the click event will have a little dropdown to pick an existing event. To know which recantangle was clicked use the sender parameter ((Pixel)sender).FillColor = SelectedColor;
For ease I would recommend using something like a panel and drawing rectangles on it, That means you only have a single click event to deal with. So now your question becomes "How do I draw a grid of rectangles on a panel" and "How do I know which rectangle was clicked"
So for the first part you could use this not the very efficient way.
Create a class which stores the information about your pixels
class MyPixel
{
public Color PixelColour;
public Rectangle Bounds;
}
Keep a list of them in memory
List<MyPixels> MyGrid = new List<MyPixels>();
then in the onpaint event of the panel Draw the pixels on the panel
foreach(MyPixel Pixel in MyGrid)
{
using(Brush B = new SolidBrush(Pixel.PixelColor))
{
e.Graphics.DrawRectangle(B, Pixel.Bounds);
}
}
Now in the click event you'll need to know which pixel was clicked
foreach(MyPixel Pixel in MyGrid)
{
if (Pixel.Bounds.Contains(e.Location))
{
PixelClicked(Pixel);
}
}
I believe you're going about this the wrong way. What you want to do is to draw directly into a bitmap. Here is some code that uses a PictureBox to allow the user to draw into it.
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Pen _pen;
private bool _mouseDown;
private int _startX;
private int _startY;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
pictureBox1.Image = new Bitmap(pictureBox1.Width, pictureBox1.Height);
_pen = new Pen(Color.Black);
}
private void pictureBox1_MouseDown(object sender, MouseEventArgs e)
{
_mouseDown = true;
_startX = e.X;
_startY = e.Y;
}
private void pictureBox1_MouseUp(object sender, MouseEventArgs e)
{
_mouseDown = false;
}
private void pictureBox1_MouseMove(object sender, MouseEventArgs e)
{
if (_mouseDown)
{
using (var graphics = Graphics.FromImage(pictureBox1.Image))
{
graphics.DrawLine(_pen, _startX, _startY, e.X, e.Y);
_startX = e.X;
_startY = e.Y;
}
pictureBox1.Invalidate();
}
}
}
}
This is the normal method to write a painting app and is quite performant as well. You can also easily save, write new tools or manipulate images better in this way.
I have a panel named dPanel. I set the the backgroundImage of that panel with an image named dImage.Now I want to draw points on the panel, in other words I want to color the panel by using mouse.I want to be able to save the drawing and the image together later. My codes do this but the picture lights up during drawing and its very slow.Here is my code:
private void drawP_MouseDown(object sender, MouseEventArgs e)
{
if (!drawbool)
{
dStartPoint = e.Location;
drawbool = true;
}
drawP.Invalidate();
}
private void drawP_MouseMove(object sender, MouseEventArgs e)
{
if (drawbool)
{
dStartPoint = e.Location;
drawP.Invalidate();
}
}
private void drawP_MouseUp(object sender, MouseEventArgs e)
{
if (drawbool)
{
drawbool = false;
}
}
private void drawP_Paint_1(object sender, PaintEventArgs e)
{
if (drawbool)
{
int dStartX = dStartPoint.X;
int dStartY = dStartPoint.Y;
e.Graphics.DrawEllipse(dP, dStartX, dStartY, 2, 2);
Bitmap dPPB = new Bitmap(drawP.Width, drawP.Height);
drawP.DrawToBitmap(dPPB, new Rectangle(0, 0, drawP.Width, drawP.Height));
drawP.BackgroundImage = (Image)dPPB;
}
}
Whani's the solution? Thanks in advance.
Check out this great example about drawing on panel: Painting on a Panel.
Later you can save your drawing just by invoking panel's method DrawToBitmap.
Bitmap hh = (Bitmap)System.Drawing.Bitmap.FromFile("example.png");
Graphics.FromImage(hh);
IntPtr ptr = hh.GetHicon();
Cursor c = new Cursor(ptr);
this.Cursor = c;
I use this code to create a custom image cursor. I want to retrieve the coordinates of this custom image cursor when on a Click event. So that these coordinates can be used to draw the image of this cursor in a picture box when clicked on the image loaded in the picture box. I'm doing this in C#.
I tried another approach
public partial class Form1 : Form
{
private Bitmap _bmp = new Bitmap(250, 250);
public Form1()
{
InitializeComponent();
panel1.MouseDown += new MouseEventHandler(panel1_MouseDown);
panel1.Paint += new PaintEventHandler(panel1_Paint);
using (Graphics g = Graphics.FromImage(_bmp))
g.Clear(SystemColors.Window);
}
private void pictureBox1_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
Point mouseDownLocation = new Point(e.X, e.Y);
label1.Text = mouseDownLocation.X.ToString();
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImage(_bmp, new Point(0, 0));
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
using (Graphics g = Graphics.FromImage(_bmp))
{
g.DrawString("Mouse Clicked Here!", panel1.Font, Brushes.Black, e.Location);
}
panel1.Invalidate();
}
private void button1_Click(object sender, EventArgs e)
{
panel1.Image.Save(#"C:\test.jpg", ImageFormat.Jpeg);
}
But when i try so save the image i get an Exception: Object reference not set to an instance of an object.
Please note that panel1 in the code above refers to a picture box
To get the coordinates of the mouse on a PictureBox you should not handle the OnClick event but the OnMouseDown, for example in this way:
private void pb_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
Point mouseDownLocation = new Point(e.X, e.Y);
}
now you have the mouseDownLocation which contains the coordinates you were looking for.
i know the way to get the coordinate of mouse you can code it like
Cursor.Position.X and Cursor.Position.Y to get the Coordinate under the mouse