If I put PictureBox initialization into form's constructor or form.Load / form.Shown handler nothing is drawn on PictureBox. If initialization is being made just before drawing, graphics appears.
Why this code draws an array on PictureBox:
public partial class Form1 : Form
{
Bitmap drawArea;
public Form1()
{
InitializeComponent();
}
private void drawArray(int[] arr, PictureBox box)
{
//=========== Attention to this code ================
drawArea = new Bitmap(pictureBox1.Size.Width, pictureBox1.Size.Height);
pictureBox1.Image = drawArea;
//===================================================
using (Graphics g = Graphics.FromImage(drawArea))
{
Pen mypen = new Pen(Brushes.Black);
g.Clear(Color.White);
for (int i = 0; i < arr.Length; i++)
g.DrawLine(mypen, i*2, drawArea.Height,
i*2, drawArea.Height - arr[i]);
}
}
private void button1_Click(object sender, EventArgs e)
{
int[] ar1 = randomArray(20, 1, 20);
drawArray(ar1, pictureBox1);
}
}
but this code doesn't?
public partial class Form1 : Form
{
Bitmap drawArea;
public Form1()
{
InitializeComponent();
//=========== Attention to this code ================
//Breakpoint here: pictureBox1.Size.Width==409, pictureBox1.Size.Height==205
drawArea = new Bitmap(pictureBox1.Size.Width, pictureBox1.Size.Height);
pictureBox1.Image = drawArea;
//===================================================
}
private void drawArray(int[] arr, PictureBox box)
{
using (Graphics g = Graphics.FromImage(drawArea))
{
Pen mypen = new Pen(Brushes.Black);
g.Clear(Color.White);
for (int i = 0; i < arr.Length; i++)
g.DrawLine(mypen, i*2, drawArea.Height,
i*2, drawArea.Height - arr[i]);
}
}
private void button1_Click(object sender, EventArgs e)
{
int[] ar1 = randomArray(20, 1, 20);
drawArray(ar1, pictureBox1);
}
}
It does not even work if I make a second button and put the initialization code in its handler (and of course click second button before clicking the first).
No exception is thrown.
Not sure...just Invalidate() the PictureBox in drawArray() so it refreshes itself:
private void drawArray(int[] arr, PictureBox box)
{
using (Graphics g = Graphics.FromImage(drawArea))
{
Pen mypen = new Pen(Brushes.Black);
g.Clear(Color.White);
for (int i = 0; i < arr.Length; i++)
g.DrawLine(mypen, i * 2, drawArea.Height,
i * 2, drawArea.Height - arr[i]);
}
box.Invalidate();
}
*Why were you passing the PictureBox anyways, if you weren't using it?
Related
My problem is:
When I try to save the graphics object to a bitmap image it does not save correctly, instead the image is black in color and there is nothing else in the file.
I've seen other answers, but I think it's different when you draw multiple times in the graphics object.
So, here's my attempt, please let me know where my issue is.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
namespace PenFlip
{
public partial class Match : Form
{
Graphics g;
private int x = -1;
private int y = -1;
private bool moving;
private Pen pen;
private Bitmap testBmp;
public Match()
{
InitializeComponent();
g = panel1.CreateGraphics();
g.SmoothingMode = SmoothingMode.AntiAlias;
pen = new Pen(Color.Black, 5);
pen.StartCap = pen.EndCap = LineCap.Round;
}
private void pictureBox1_Click(object sender, EventArgs e)
{
PictureBox pictureBox = (PictureBox) sender;
pen.Color = pictureBox.BackColor;
}
private void panel1_MouseDown(object sender, MouseEventArgs e)
{
moving = true;
x = e.X;
y = e.Y;
}
private void panel1_MouseMove(object sender, MouseEventArgs e)
{
if (moving && x != -1 && y != -1)
{
g.DrawLine(pen, new Point(x, y), e.Location);
x = e.X;
y = e.Y;
}
}
private void panel1_MouseUp(object sender, MouseEventArgs e)
{
moving = false;
x = -1;
y = -1;
g.Save();
}
private void button1_Click(object sender, EventArgs e)
{
// prints out the black image
testBmp = new Bitmap(400, 200, g);
testBmp.Save(#"test.bmp", ImageFormat.Bmp);
}
}
}
Uh, usage of Bitmap is tricky and has a lot of pitfalls (that's why it's use is deprecated, btw). Try to create the Graphics object from an in-memory bitmap, instead of grabbing it from a control. The bitmap should be the primary object, not the Graphics instance. You don't know what the control does to your bitmap.
So in the constructor, do something like:
public Match()
{
InitializeComponent();
bitmap = new Bitmap(panel1.Width, panel1.Height, PixelFormat.Format32bppArgb);
pen = new Pen(Color.Black, 5);
pen.StartCap = pen.EndCap = LineCap.Round;
}
and in each function, create a graphics object from the bitmap:
using (var graphics = Graphics.FromImage(image))
{
graphics.Draw(...);
}
You need to update the panel manually now, e.g. by attaching to it's OnPaint event.
I'm trying to make sorting visualization algorithm in c# but I got a problem with the canvas refreshing.
I tried to refresh the canvas every time I redraw but its not looks good. I'm sure there is another way to do it and I hope someone can help me.
In this picture you can see the black rectangles that I want to delete from the canvas
This is my code :
private void GenerateArrayButton_Click(object sender, EventArgs e)
{
MyCanvas.Refresh();
Random random = new Random();
int xPosition = 0 , yPosition = MyCanvas.Height/2;
const int k_RectangleWight = 2;
for(int i = 0; i < k_SizeOfArray; i++)
{
int rectangleHeight = random.Next(MyCanvas.Height / 2);
m_UnsortedArray[i] = new Rectangle(xPosition,yPosition, k_RectangleWight, rectangleHeight);
xPosition += 5;
}
draw(m_UnsortedArray, Pens.Black);
}
private void draw(Rectangle[] i_ArrayToDraw, Pen i_PenColor)
{
var graphics = MyCanvas.CreateGraphics();
graphics.DrawRectangles(i_PenColor, i_ArrayToDraw);
graphics.Dispose();
}
private void SortingButton_Click(object sender, EventArgs e)
{
bubbleSort();
draw(m_UnsortedArray, Pens.Green);
}
private void bubbleSort()
{
for(int i = 0; i < m_UnsortedArray.Length; i++)
{
for(int j = 0; j < m_UnsortedArray.Length - 1; j++)
{
if(m_UnsortedArray[j].Height > m_UnsortedArray[j + 1].Height)
{
swap(ref m_UnsortedArray[j], ref m_UnsortedArray[j+1]);
}
}
draw(m_UnsortedArray,Pens.Black);
}
}
private void swap(ref Rectangle i_Rectangle1, ref Rectangle i_Rectangle2)
{
// Swap the position of the rectangle
var location = i_Rectangle1.Location;
i_Rectangle1.Location = i_Rectangle2.Location;
i_Rectangle2.Location = location;
// Swap the position of the current rectangle in the array
var copyRect = i_Rectangle1;
i_Rectangle1 = i_Rectangle2;
i_Rectangle2 = copyRect;
}
}
The drawing canvas in question MyCanvas whether its a PictureBox or a Panel or the Form itself, provides specific events for the painting routines, particularly the Paint event in this context. The event has a PaintEventArgs which provides a free Graphics object to do your drawings. Meaning, you don't need to create extra Graphics objects like in your draw method. Now let's draw those rectangles.
Class level fields:
using System.Threading.Tasks;
//...
public partial class Form1 : Form
{
private const int k_RectangleWight = 2;
private const int k_SizeOfArray = 100; //assign the right value.
private Rectangle[] m_UnsortedArray;
Random rand = new Random();
private Pen MyPen;
Handle the Paint event of the MyCanvas control.
public Form1()
{
InitializeComponent();
//You can add normal event handler instead if you prefer so.
MyCanvas.Paint += (s, e) =>
{
if (MyPen != null)
e.Graphics.DrawRectangles(MyPen, m_UnsortedArray);
};
}
In the GenerateArrayButton_Click event, create the rectangles, assign the drawing pen, and call the Invalidate() method of the drawing canvas.
private void GenerateArrayButton_Click(object sender, EventArgs e)
{
m_UnsortedArray = new Rectangle[k_SizeOfArray];
var xPosition = 0;
var yPosition = MyCanvas.Height / 2;
for(var i = 0; i < k_SizeOfArray; i++)
{
var rectangleHeight = rand.Next(MyCanvas.Height / 2);
m_UnsortedArray[i] = new Rectangle(
xPosition,
yPosition,
k_RectangleWight,
rectangleHeight);
xPosition += 5;
}
MyPen = Pens.Black;
MyCanvas.Invalidate();
}
At this point, you will get something drawn like this:
Now the second part. Your methods for swapping the rectangles:
private async void bubbleSort()
{
for (int i = 0; i < m_UnsortedArray.Length; i++)
{
for (int j = 0; j < m_UnsortedArray.Length - 1; j++)
if (m_UnsortedArray[j].Height > m_UnsortedArray[j + 1].Height)
swap(ref m_UnsortedArray[j], ref m_UnsortedArray[j + 1]);
await Task.Delay(30);
MyCanvas.Invalidate();
}
}
private void swap(ref Rectangle i_Rectangle1, ref Rectangle i_Rectangle2)
{
var location = i_Rectangle1.Location;
i_Rectangle1.Location = i_Rectangle2.Location;
i_Rectangle2.Location = location;
var copyRect = i_Rectangle1;
i_Rectangle1 = i_Rectangle2;
i_Rectangle2 = copyRect;
}
In the click event of the SortingButton, you just need to:
private void SortingButton_Click(object sender, EventArgs e)
{
MyPen = Pens.Green;
bubbleSort();
}
}
... and you will get:
So this should be very simple but I have looked at some similar questions and can't find an answer.
I have a Form1 class and a Resistor class. Inside the Form1 class I have a Panel(I changed the name to Canvas), inside the Canvas_Paint method I am calling the method Draw from the Resistor class but is not drawing anything.
Form1 Class:
public partial class Form1 : Form
{
static float lineWidth = 2.0F;
static float backgroundLineWidth = 2.0F;
static Pen pen = new Pen(Color.Yellow, lineWidth);
static Pen backgroundPen = new Pen(Color.LightGray, backgroundLineWidth);
private bool drawBackground = true;
private List<Resistor> resistors = new List<Resistor>();
public Form1()
{
InitializeComponent();
}
private void Canvas_Paint(object sender, PaintEventArgs e)
{
if (drawBackground)
{
Console.WriteLine("Drawing background...");
Draw_Background(e.Graphics, backgroundPen);
}
if (resistors != null)
{
foreach (Resistor r in resistors)
{
//This does not work.
r.Draw(e.Graphics);
}
}
//The line below draws the line fine.
e.Graphics.DrawLine(pen, 0, 0, 100, 100);
}
private void Draw_Background(Graphics g, Pen pen)
{
for (int i = 0; i < Canvas.Width; i += 10)
{
g.DrawLine(pen, new Point(i, 0), new Point(i, Canvas.Height));
}
for (int j = 0; j < Canvas.Height; j += 10)
{
g.DrawLine(pen, new Point(0, j), new Point(Canvas.Width, j));
}
drawBackground = false;
}
private void AddResistor_Click(object sender, EventArgs e)
{
resistors.Add(new Resistor());
Console.WriteLine("Added a Resistor...");
}
}
Resistor Class:
public class Resistor
{
static private Point startingPoint;
static Pen defaultPen;
private Point[] points;
public Resistor()
{
startingPoint.X = 100;
startingPoint.Y = 100;
defaultPen = new Pen(Color.Yellow, 2.0F);
points = new Point[] {
new Point( 10, 10),
new Point( 10, 100),
new Point(200, 50),
new Point(250, 300)
};
}
public void Draw(Graphics g)
{
//Is this drawing somewhere else?
g.DrawLines(defaultPen, points);
}
}
I have looked at this question which suggests to pass the e.Graphics object in this case to the Draw method in the Resistor class but is not working.
I am new to C# so I would really appreciate any help.
EDIT :
I put the project on github if you want to download and try it out.
EDIT :
So the problem was that after clicking the button the panel Paint method was not being called. The solution was to add Canvas.Invalidate inside the AddResistor_Click method
Run your code in the debugger and put a breakpoint in your event handler and you'll be able to check that your code is actually trying to draw something. If not, then is your event handler ever called? is there anything in your list of resistors? If it is drawing but you don't see anything, then you're not using the correct Graphics context or you're not drawing things in the visible part of your control, or you're drawing over things with subsequent drawing code.
The problem was that when the button was clicked the panel's paint method was not getting called because I tough that the paint method was always getting called. The solution was to add Canvas.Invalidate inside the AddResistor_Click method.
private void AddResistor_Click(object sender, EventArgs e)
{
resistors.Add(new Resistor());
Console.WriteLine("Added a Resistor...");
Canvas.Invalidate();
}
How can I transfer the values of my graphics to a bitmap so I can save it as jpg or bmp file.
here's my code:
private void pictureBox1_Paint_1(object sender, PaintEventArgs e)
{
using(var p = new Pen(Color.Blue, 4)){
for (int i = 0; i < _listPS.Count; i++)
{
e.Graphics.DrawLine(_pen, _listPS[i], _listPE[i]);
}
}
}
suppose that _listPS and _listPE have values.
ah! Solved it LOL! :)
Here's my solution:
private Bitmap _mybitmap;
private void pictureBox1_Paint_1(object sender, PaintEventArgs e)
{
_mybitmap = new Bitmap(pictureBox1.Width, pictureBox1.Heigth);
Graphics _tempg = Graphics.FromImage(_mybitmap);
using(var p = new Pen(Color.Blue, 4){
for (int i = 0; i < _listPS.Count; i++)
{
e.Graphics.DrawLine(_pen, _listPS[i], _listPE[i]);
_tempg.DrawLine(_pen, _listPS[i], _listPE[i]);
}
_tempg.Dispose();
}
}
Try this one
Bitmap _image = new Bitmap(100, 100);
Graphics _g = Graphics.FromImage(_image);
//Graphics _g = pictureBox1.CreateGraphics();
Pen _pen = new Pen(Color.Red, 3);
Point myPoint1 = new Point(10, 20);
Point myPoint2 = new Point(30, 40);
for (int i = 0; i < _listPS.Count; i++)
{
_g.DrawLine(_pen, _listPS[i], _listPE[i]);
}
_image.Save(#"D:\test.bmp");
_image.Dispose();
_g.Dispose();
I have written the following simple program, which draws lines on the screen every 100 milliseconds (triggered by timer1). I noticed that the drawing flickers a bit (that is, the window is not always completely blue, but some gray shines through). So my idea was to use double-buffering. But when I did that, it made things even worse. Now the screen was almost always gray, and only occasionally did the blue color come through (demonstrated by timer2, switching the DoubleBuffered property every 2000 milliseconds).
What could be an explanation for this?
using System;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1 {
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
}
private void Form1_Paint(object sender, PaintEventArgs e) {
Graphics g = CreateGraphics();
Pen pen = new Pen(Color.Blue, 1.0f);
Random rnd = new Random();
for (int i = 0; i < Height; i++)
g.DrawLine(pen, 0, i, Width, i);
}
// every 100 ms
private void timer1_Tick(object sender, EventArgs e) {
Invalidate();
}
// every 2000 ms
private void timer2_Tick(object sender, EventArgs e) {
DoubleBuffered = !DoubleBuffered;
this.Text = DoubleBuffered ? "yes" : "no";
}
}
}
I would just draw all of your items to your own buffer, then copy it all in at once. I've used this for graphics in many applications, and it has always worked very well for me:
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
Invalidate();// every 100 ms
}
private void Form1_Load(object sender, EventArgs e)
{
DoubleBuffered = true;
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Bitmap buffer = new Bitmap(Width, Height);
Graphics g = Graphics.FromImage(buffer);
Pen pen = new Pen(Color.Blue, 1.0f);
//Random rnd = new Random();
for (int i = 0; i < Height; i++)
g.DrawLine(pen, 0, i, Width, i);
BackgroundImage = buffer;
}
EDIT: After further investigation, it looks like your problem is what you're setting your Graphics object to:
Graphics g = CreateGraphics();
needs to be:
Graphics g = e.Graphics();
So your problem can be solved by either creating a manual buffer like I did above, or simply changing you Graphics object. I've tested both and they both work.
No need to use multiple buffers or Bitmap objects or anything.
Why don't you use the Graphics object provided by the Paint event? Like this:
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Pen pen = new Pen(Color.Blue, 1.0f);
Random rnd = new Random();
for (int i = 0; i < Height; i++)
g.DrawLine(pen, 0, i, Width, i);
}
Try setting the double buffered property to true just once in the constructor while you're testing.
You need to make use of the back buffer. Try this:
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 DoubleBufferTest
{
public partial class Form1 : Form
{
private BufferedGraphicsContext context;
private BufferedGraphics grafx;
public Form1()
{
InitializeComponent();
this.Resize += new EventHandler(this.OnResize);
DoubleBuffered = true;
// Retrieves the BufferedGraphicsContext for the
// current application domain.
context = BufferedGraphicsManager.Current;
UpdateBuffer();
}
private void timer1_Tick(object sender, EventArgs e)
{
this.Refresh();
}
private void OnResize(object sender, EventArgs e)
{
UpdateBuffer();
this.Refresh();
}
private void UpdateBuffer()
{
// Sets the maximum size for the primary graphics buffer
// of the buffered graphics context for the application
// domain. Any allocation requests for a buffer larger
// than this will create a temporary buffered graphics
// context to host the graphics buffer.
context.MaximumBuffer = new Size(this.Width + 1, this.Height + 1);
// Allocates a graphics buffer the size of this form
// using the pixel format of the Graphics created by
// the Form.CreateGraphics() method, which returns a
// Graphics object that matches the pixel format of the form.
grafx = context.Allocate(this.CreateGraphics(),
new Rectangle(0, 0, this.Width, this.Height));
// Draw the first frame to the buffer.
DrawToBuffer(grafx.Graphics);
}
protected override void OnPaint(PaintEventArgs e)
{
grafx.Render(e.Graphics);
}
private void DrawToBuffer(Graphics g)
{
//Graphics g = grafx.Graphics;
Pen pen = new Pen(Color.Blue, 1.0f);
//Random rnd = new Random();
for (int i = 0; i < Height; i++)
g.DrawLine(pen, 0, i, Width, i);
}
}
}
It's a slightly hacked around version of a double buffering example on MSDN.