Why isn't my application drawing anything? - c#

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 .

Related

Method to create a workspace to draw on in C#

I'm new to c#, coming from a python / wxpython background. I'm still very inexperienced.
I'm trying to create a user workspace that allows objects that have a representative shape (box or circle) to be placed. I'd like the user to be able to pan and zoom within the workspace. Eventually I want to have the items have attributes and be able to be connected together with lines.
I created a simple c# app that allows me to paint squares over a tiled .png grid background, but I can't figure out to set up a real workspace to pan or zoom.
At this stage some high level suggestions would be great.
Here is my code just FYI (be kind)
public partial class MainForm : Form
{
bool global_draw_redbox = false;
public MainForm()
{
InitializeComponent();
}
private void redbox(Point mousepos)
{
System.Drawing.Graphics graphicsObj;
graphicsObj = CreateGraphics();
Pen myPen = new Pen(System.Drawing.Color.Red, 3);
Rectangle myRectangle = new Rectangle(mousepos.X - 125, mousepos.Y - 100, 250, 200);
graphicsObj.DrawRectangle(myPen, myRectangle);
}
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
var relativePoint = this.PointToClient(Cursor.Position);
if (global_draw_redbox)
{
//Draw the object based on the mouse position
redbox(relativePoint);
//Reset the flag so we draw just one
global_draw_redbox = false;
}
else
{
MessageBox.Show("No object selected");
}
}
private void Form1_MouseDoubleClick(object sender, MouseEventArgs e)
{
MessageBox.Show("Test Mouse Double Click");
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
var relativePoint = this.PointToClient(Cursor.Position);
XY_Mouse_Position.Text = relativePoint.ToString();
}
private void boxToolStripMenuItem_Click(object sender, EventArgs e)
{
global_draw_redbox = true;
}
}

Use Fillpath with mouse input as flood fill between drawn paths?

(New to this and playing around with a basic paint application) Ive found detailed instructions to code a flood-fill but as i am new it is very hard to understand every bits of it, and instead of copying, i would like to try to make my own simple(small scale) flood-fill.
Would it be possible to use fillpath as a flood-fill? i would draw paths and use my mouse to determine my x,y, on screen and have the graphicspath find out if it has borders(points from the drawn paths) and if so, fill these paths with a color?
this is what ive come up with but obviously it doesnt work, so how would i go about to make this working?
namespace WindowsFormsApplication3
{
public partial class Form1 : Form
{
Graphics g;
readonly Pen pen = new Pen(Color.Navy, 2);
Point oldCoords;
GraphicsPath graphicsPaths = new GraphicsPath();
bool spaceFound = false;
public Form1()
{
InitializeComponent();
g = panel1.CreateGraphics();
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
Point mousePt = new Point(e.X, e.Y);
if (e.Button == MouseButtons.Right &&
graphicsPaths.IsVisible(mousePt))
{
spaceFound = true;
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (oldCoords.IsEmpty)
graphicsPaths.StartFigure();
else
{
graphicsPaths.AddLine(oldCoords, new Point(e.X, e.Y));
g.DrawPath(pen, graphicsPaths);
}
oldCoords = new Point(e.X, e.Y);
}
else
oldCoords = Point.Empty;
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
g.DrawPath(pen, graphicsPaths);
if(spaceFound == true)
{
g.FillPath(Brushes.AliceBlue, graphicsPaths);
}
}
}
}
Yes, this is quite possible; of course you would want to store the path in a List<GraphicsPath> in the MouseUp event to allow for more filled shapes..
You need to correct a few issues in your code:
Set the path.FillMode to Winding
Never cache a Graphics object
Never use control.CreateGraphics()
Don't cache Pens or Brushes
Only draw in the Paint event, unless you do not want the drawing to persist
The last point might actually apply here: Maybe you don't want the currently drawing outline to stay visible? In that, and only that case you can stick with drawing it in the MouseMove with a Graphics object created there on the fly.
Here is a corrected version:
Point oldCoords;
GraphicsPath graphicsPaths = new GraphicsPath() { FillMode = FillMode.Winding };
bool spaceFound = false;
private void drawPanel1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right && graphicsPaths.IsVisible(e.Location))
{
spaceFound = true;
drawPanel1.Invalidate();
}
}
private void drawPanel1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
if (oldCoords.IsEmpty) graphicsPaths.StartFigure();
else
{
graphicsPaths.AddLine(oldCoords, new Point(e.X, e.Y));
drawPanel1.Invalidate();
}
oldCoords = new Point(e.X, e.Y);
}
else oldCoords = Point.Empty;
}
private void drawPanel1_Paint(object sender, PaintEventArgs e)
{
using (Pen pen = new Pen(Color.Black, 2f))
e.Graphics.DrawPath(pen, graphicsPaths);
if (spaceFound == true)
{
e.Graphics.FillPath(Brushes.AliceBlue, graphicsPaths);
}
}
Note that it will fill your path but not in the way of a true floodfill, i.e. it will always fill the whole path, not just the innermost segment you have clicked in. For a true floodfill much more involved code is needed that actually goes over all neighbouring pixels starting at the click location..
Examples of a true floodfill are here and here

2D Array of RectangleShapes

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.

How do I draw a rectangle based on the movement of the mouse?

I found sample code here for drawing on a form:
http://msdn.microsoft.com/en-us/library/aa287522(v=vs.71).aspx
As a followup to this requirement (discovering which controls are beneath a rectangle described by
the user dragging the mouse):
There seems to be a mismatch between the location of my controls and the location of my MouseDown and -Up events
...I want to provide the user instant/constant feedback about just what they are about to select
(when/if they release the mouse button). I want to not just draw a line following the mouse's
movement, but draw the rectangle that is being described by their mousewrangling efforts.
I'm thinking the MouseMove event, coupled with code from the two links above, could do the trick, but is that fired too often/would that have a malevolent impact on performance? If so, what would be a preferable event to hook, or would a timer be the way to go here?
UPDATE
This code, adapted from John's example below (the only difference is the StackOverflow-inducing calls to base.* are commented out, and I changed the color from red to black (no reference to Stendahl intended)), works except that previously drawn rectangles display again after releasing the mouse. IOW, the first rectangle draws perfectly - it disappears with the mouse up click (as intended). However, when I describe a second rectangle by depressing the left mouse key and dragging down and to the right, the first rectangle displays again! And this continues to happen - every previously drawn rectangle is remembered and brought back to the fore when a new rectangle is being drawn.
public partial class Form1 : Form
{
private Point? _start;
private Rectangle _previousBounds;
public Form1()
{
InitializeComponent();
}
private void Form1_MouseDown(object sender, MouseEventArgs e)
{
_start = e.Location;
//base.OnMouseDown(e);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
if (_start.HasValue)
DrawFrame(e.Location);
//base.OnMouseMove(e);
}
private void Form1_MouseUp(object sender, MouseEventArgs e)
{
ReverseFrame();
_start = null;
//base.OnMouseUp(e);
}
private void ReverseFrame()
{
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Black, FrameStyle.Dashed);
}
private void DrawFrame(Point end)
{
ReverseFrame();
var size = new Size(end.X - _start.Value.X, end.Y - _start.Value.Y);
_previousBounds = new Rectangle(_start.Value, size);
_previousBounds = this.RectangleToScreen(_previousBounds);
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Black, FrameStyle.Dashed);
}
}
ControlPaint.DrawReversibleFrame() will do what you want. Performance is not generally a problem - just keep it small and clean.
--
EDIT: Added a code sample. StackOverflowException indicates something is wrong - but without seeing yours, can't answer directly.
private Point? _start;
private Rectangle _previousBounds;
protected override void OnMouseDown(MouseEventArgs e)
{
_start = e.Location;
base.OnMouseDown(e);
}
protected override void OnMouseMove(MouseEventArgs e)
{
if( _start.HasValue ) {
ReverseFrame();
DrawFrame(e.Location);
}
base.OnMouseMove(e);
}
protected override void OnMouseUp(MouseEventArgs e)
{
ReverseFrame();
_start = null;
base.OnMouseUp(e);
}
private void ReverseFrame()
{
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Red, FrameStyle.Dashed);
}
private void DrawFrame(Point end)
{
ReverseFrame();
var size = new Size(end.X - _start.Value.X, end.Y - _start.Value.Y);
_previousBounds = new Rectangle(_start.Value, size);
_previousBounds = this.RectangleToScreen(_previousBounds);
ControlPaint.DrawReversibleFrame(_previousBounds, Color.Red, FrameStyle.Dashed);
}

C# paint program flickering

I'm trying to make a simple paint program in C#, but it keeps flickering when I'm drawing, like I need some kind of double buffering, but I don't know how to do it.
I am drawing on a Panel and I'm using a Bitmap to store the graphics.
Here's my code:
public partial class Form1 : Form
{
private Bitmap drawing;
private bool leftDown = false;
private int prevX;
private int prevY;
private int currentX;
private int currentY;
public Form1()
{
InitializeComponent();
drawing = new Bitmap(panel1.Width, panel1.Height);
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
leftDown = true;
prevX = e.X;
prevY = e.Y;
}
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (leftDown)
{
Graphics g = Graphics.FromImage(drawing);
currentX = e.X;
currentY = e.Y;
g.DrawLine(new Pen(Color.Black), new Point(currentX, currentY), new Point(prevX, prevY));
panel1.Invalidate();
prevX = currentX;
prevY = currentY;
g.Dispose();
}
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
leftDown = false;
}
private void panel1_MouseLeave(object sender, EventArgs e)
{
leftDown = false;
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.DrawImageUnscaled(drawing, 0, 0);
}
}
You not only should turn DoubleBuffered to true, but also use PictureBox instead of Panel and draw on it. This should solve your issue :).
You need to subclass Panel to do this, because you need to override certain things. A Panel like this should work:
class DoubleBufferedPanel : Panel {
public DoubleBufferedPanel() : base() {
this.SetStyle(ControlStyles.AllPaintingInWmPaint |
ControlStyles.DoubleBuffered |
ControlStyles.Opaque |
ControlStyles.OptimizedDoubleBuffer, true);
}
public override void OnPaint(PaintEventArgs e) {
// Do your painting *here* instead, and don't call the base method.
}
// Override OnMouseMove, etc. here as well.
}
However, you don't need the functionality Panel adds to Control at all, that is for it to be functioning as a container. So, in fact, you should be inheriting from Control unless you need subcontrols.
Another improvement might be to only Invalidate with the Rectangle that changed. This will repaint one area and reduce drawing time. You can also pass a srcRect to Graphics.DrawImage, that srcRect being calculated from e.ClipRectangle, for even better performance if subclassing Panel doesn't work for you.
When painting pixel maps in the Paint event, the flickering is often caused by Windows forms because it first draws the background and then the pixel map. So the flicker is the background that becomes visible for a fraction of a second.
You can set the Opaque style in the panel's ControlStyle property. That will turn of the background drawing because Windows Forms now assumes that your code will completely draw the contents of the panel.
Normally, simply adding this.DoubleBuffered = true; in your code should do the trick.
If it does not, add this code to your form :
protected override CreateParams CreateParams
{
get
{
CreateParams cp = base.CreateParams;
cp.ExStyle |= 0x02000000;
return cp;
}
}
You can also change it using Reflection, but there is no advantage, neither readability or speed.

Categories

Resources