I use the following code to fill up a panel.But whenever i minimize the form, the filled up portion of the rectangle gets lost.
Any ideas what I'm doing wrong? Thanks
public static void populateTable(this Panel p, int x, int y)
{
Graphics g = p.CreateGraphics();
Brush b = new SolidBrush(Color.DarkCyan);
g.FillRectangle(b, x, y,100,40);
g.Dispose();
}
You need to repaint every time the panel repaints itself (For example when you restore the window from being minimized). The correct way to do this is use the Paint event and store the objects you need to draw in some form of collection and re-draw them every call.
public Form1()
{
InitializeComponent();
rectangles = new List<Rectangle>();
panel1.Paint += panel1_Paint;
}
public void PopuplateTable(int x, int y)
{
rectangles.Add(new Rectangle(x,y, 100, 40));
//Forces a redraw to happen.
panel1.Invalidate();
}
private List<Rectangle> rectangles;
void panel1_Paint(object sender, PaintEventArgs e)
{
foreach (var rectangle in rectangles)
{
using (var b = new SolidBrush(Color.DarkCyan))
{
e.Graphics.FillRectangle(b, rectangle);
}
}
}
Now this is not exactly the same as your current code, but it points you in the right direction so you can adapt your code to do the same. You may need to create a new class based on Panel to hold your PopulateTable call instead of using a extension method, if you do end up doing that you should override OnPaint instead of using the paint event.
class MyPanel : Panel
{
private Rectangle? paintedRectangle = null;
public void PopuplateTable(int x, int y)
{
paintedRectangle = new Rectangle(x, y, 100, 40);
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
if (paintedRectangle.HasValue)
{
using (var b = new SolidBrush(Color.DarkCyan))
{
e.Graphics.FillRectangle(b, paintedRectangle.Value);
}
}
}
}
Related
I have a function which is drawing an ellipse. I want to make the previously drawn ellipse invisible by changing its color as the same color of its background when a new ellipse is drawn by changing the form size.
This is my function in my class:
class ClassClock
{
public static void drawClock(Point m, int s, Form frm, Color myColor)
{
Graphics paper = frm.CreateGraphics();
Pen myPen = new Pen(myColor);
int w = frm.ClientSize.Width;
int h = frm.ClientSize.Height;
m = new Point(w / 2, h / 2);
s = Math.Min(w, h) / 2;
paper.DrawEllipse(myPen, m.X - s, m.Y - s, s * 2, s * 2);
}
}
and this is my timer:
private void timer1_Tick(object sender, EventArgs e)
{
ClassClock.drawClock(m, s, this, this.BackColor);
ClassClock.drawClock(m, s, this, Color.Black);
}
Can someone help me find a solution to this?
You should not use CreateGraphics like this. Instead, override the OnPaint method of your form and do all your painting in that method.
Windows uses an immediate mode graphics system. That means that once your ellipse is drawn, it's gone, save for the pixels currently on the screen. If the window is minimized or another window is dragged across it, the ellipse will be gone and will have to be repainted. That is what the OnPaint method is for.
Here's a simple form that changes the color of your circle when a button is clicked. To run this code, you will need to add a button to the form and name it btnChangeColor.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//Property to hold the current color. This could be a private field also.
public Color CurrentColor { get; set; } = Color.Red;
//Used to generate a random number when the button is clicked.
private Random rnd = new Random();
//All painting of the form should be in this method.
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
//Use the graphics event provided to you in PaintEventArgs
Graphics paper = e.Graphics;
int w = this.ClientSize.Width;
int h = this.ClientSize.Height;
Point m = new Point(w / 2, h / 2);
int s = Math.Min(w, h) / 2;
//It is important to dispose of any pens you create
using (Pen myPen = new Pen(CurrentColor))
{
paper.DrawEllipse(myPen, m.X - s, m.Y - s, s * 2, s * 2);
}
}
//When the button is clicked, the `CurrentColor` property is set to a random
//color and the form is refreshed to get it to repaint itself.
private void btnChangeColor_Click(object sender, EventArgs e)
{
//Change the current color
CurrentColor = Color.FromArgb(255, rnd.Next(0, 256), rnd.Next(0, 256), rnd.Next(0, 256));
//Refresh the form so it repaints itself
this.Refresh();
}
}
Clicking a button from windows form it doesn't work.
Because, I have to use the parameters "Graphics g..." for the methods. I can't change the methods so, I have to change something when the user clicks the button.
Below is the code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
//protected override void OnPaint(PaintEventArgs e)
//{
// Graphics g = e.Graphics;
// TekenCirkel(g, 50, 50, 100, 100);
// TekenRechthoek(g, 0, 0, 100, 100);
//}
public void TekenCirkel(Graphics g, int x, int y, int w, int h) {
Pen cirkel = new Pen(Color.Blue, 2);
g.DrawEllipse(cirkel, x,y, w, h);
}
public void TekenRechthoek(Graphics g, int x, int y, int w, int h){
Pen rechthoek = new Pen(Color.Black, 2);
g.DrawRectangle(rechthoek, x, y, w, h);
}
private void Cirkel_Click(object sender, EventArgs e)
{
TekenCirkel(g, 50, 50, 100, 100);
}
}
As you can see I tested it with the onPaint method and defining what g does. When using this method the code works. But that isn't possible when trying to click the button. Because EventArgs and PaintEventArgs are different things.
E: You click a button and a cirkel/square gets painted on the form. I want to know how to call a method I created to draw the cirkel/ square.
That's not how painting works.
You must paint everything from OnPaint() only, using data structures in your class to track what you want to paint.
You then call Invalidate() to make it repaint if you want to change it.
I'm using an eye tracker to display eye movements on a form. The movements have been flickering a lot so I found out I can use BufferedGraphics which all works fine except when the eye movement starts it turns the form from the original colour to black. This is the code. Hopefully someone can help!
private void button2_Click(object sender, EventArgs e)
{
var host = new Host();
var gazeStream = host.Streams.CreateGazePointDataStream();
gazeStream.GazePoint((x, y, ts)
=> drawCircle(new PointF((float)x, (float)y)));
}
delegate void SetCallback(PointF point);
private void drawCircle(PointF point)
{
float x = point.X;
float y = point.Y;
if (this.InvokeRequired)
{
SetCallback d = new SetCallback(drawCircle);
this.Invoke(d, new object[] { point });
}
else
{
SolidBrush semiTransBrush = new SolidBrush(Color.Coral);
Pen pen = new Pen(Color.Aquamarine, 2);
BufferedGraphicsContext currentContext;
BufferedGraphics myBuffer;
// Gets a reference to the current BufferedGraphicsContext
currentContext = BufferedGraphicsManager.Current;
// Creates a BufferedGraphics instance associated with Form1, and with
// dimensions the same size as the drawing surface of Form1.
myBuffer = currentContext.Allocate(this.CreateGraphics(),this.DisplayRectangle);
myBuffer.Graphics.DrawEllipse(pen, x, y, 100, 100);
myBuffer.Graphics.FillEllipse(semiTransBrush, x, y, 100, 100);
// Renders the contents of the buffer to the specified drawing surface.
myBuffer.Render(this.CreateGraphics());
myBuffer.Dispose();
}
You can see in the image that the circle appears behind the controls which seems like the form is gone?
When you allocate the buffer, it creates a compatible rendering surface with the graphics you provided. But it will not copy it or anything so if you just paint a single circle, the remaining parts remain black.
BufferedGraphics really can help you to avoid flickering in special cases (eg. when system double buffering must be disabled for some reason), but here this is an overkill.
So the key is just enabling double buffering and do every paint in the Paint event (or OnPaint method). In your code you do immediate paint, which always flickers. Instead, you should just invalidate the form and let the system do a regular repaint session, which can use double buffering if you wish.
Into the constructor:
SetStyle(ControlStyles.OptimizedDoubleBuffer | ControlStyles.DoubleBuffer | ControlStyles.AllPaintingInWmPaint, true);
Then modify the click event:
private PointF lastGazePoint;
private void button2_Click(object sender, EventArgs e)
{
var host = new Host();
var gazeStream = host.Streams.CreateGazePointDataStream();
gazeStream.GazePoint((x, y, ts) =>
{
lastGazePoint = new PointF((float)x, (float)y);
Invalidate();
});
}
The painting itself:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
DrawCircle(e.Graphics, lastGazePoint.X, lastGazePoint.Y);
}
And finally, modify DrawCircle to use the Graphics from PaintEventArgs:
private void DrawCircle(Graphics g, float x, float y)
{
using (Brush semiTransBrush = new SolidBrush(Color.Coral))
{
using (Pen pen = new Pen(Color.Aquamarine, 2))
{
g.DrawEllipse(pen, x, y, 100, 100);
g.FillEllipse(semiTransBrush, x, y, 100, 100);
}
}
}
I'm trying to add a control to my Panel. At mouseDown on the panel the point is saved and at mouseUp the point is saved. But at panel mouseUp nothing is drawn. How to solve it?
Ellipse class:
class Ellipse : Control
{
private int x;
private int y;
private int width;
private int height;
public Ellipse(int x, int y, int width, int height)
{
setY(y);
setX(x);
setWidth(width);
setHeight(height);
}
public int getX() { return x;}
public int getY() { return y; }
public int getWidth() { return width; }
public int getHeight() { return height; }
public void setX(int newx) { x = newx; }
public void setY(int newy) { y = newy; }
public void setWidth(int newwidth) { width = newwidth; }
public void setHeight(int newheight) { height = newheight; }
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
// Call methods of the System.Drawing.Graphics object.
// Declare and instantiate a new pen.
System.Drawing.Pen myPen = new System.Drawing.Pen(Color.Aqua);
// Draw an aqua rectangle in the rectangle represented by the control.
e.Graphics.FillEllipse(Brushes.Black,x,y,width,height);
}
}
Form1 class
private void panel_MouseDown(object sender, MouseEventArgs e)
{
draw = true;
x = e.X;
y = e.Y;
}
private void panel_MouseUp(object sender, MouseEventArgs e)
{
draw = false;
xe = e.X;
ye = e.Y;
Item item;
Enum.TryParse<Item>(menuComboBoxShape.ComboBox.SelectedValue.ToString(), out item);
switch (item)
{
case Item.Pencil:
using (Graphics g = panel.CreateGraphics())
using (var pen = new Pen(System.Drawing.Color.Black)) //Create the pen used to draw the line (using statement makes sure the pen is disposed)
{
g.DrawLine(pen,new Point(x, y), new Point(xe, ye));
}
break;
case Item.Rectangle:
break;
case Item.Ellipse:
Ellipse el = new Ellipse(x,y,xe-x,ye-y);
panel.Controls.Add(el);
break;
default:
break;
}
}
You are inheriting your Ellipse class from Control, but in fact you're not using it as a control - you're not adding it in Controls collection of form, so in fact it is invisible, inactive and not receiving any events from form.
Also painting the control from outer code looks like a bad design. Control should paint itself, and you should set it bounds from outer code.
Here is snippet to drive you to the right way:
class Ellipse : Control
{
Point mDown { get; set; }
public Ellipse()
{
MouseDown += shape_MouseDown;
MouseMove += shape_MouseMove;
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
e.Graphics.FillEllipse(Brushes.Black, this.Bounds);
}
private void shape_MouseDown(object sender, MouseEventArgs e)
{
mDown = e.Location;
}
private void shape_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
Location = new Point(e.X + Left - mDown.X, e.Y + Top - mDown.Y);
}
}
}
And in the form you should create it like:
el = new Ellipse();
el.Bounds = new Rectangle(0, 0, 100, 100);
Controls.Add(el);
Update
Based on your updated code, I can see a couple of issues:
You actually don't need x, y, width, height properties of your Ellipse class and according getter/setter methods, since it's Control, and it has its own Location and Width, Height public properties.
You are drawing your ellipse incorrectly. Assuming it should fill all the area, painting should be e.Graphics.FillEllipse(Brushes.Black,0,0,Width,Height) (here I assuming using Control.Width instead of your width and so on). Otherwise you're additionally shifting your painted ellipse.
Code in panel_MouseUp concerning ellipse creation should be something like
var el = new Ellipse();
panel.Controls.Add(el);
el.Location = new Point(x, y);
el.Width = (xe - x);
el.Height = (ye - y);
Or, if it should be one single ellipse (right now you're creating new one each time) - create this one outside of mouseUp handler and inside of handler just change it's size and location.
I'm making a simple game in winform (tic-tac-toe), and I'm having some problem to paint block control.
Here is the class I made, that represent a block in the game (without game logic, is only UI).
public class UI_Block : Control
{
private Rectangle block;
private SIGNS sign;
public SIGNS Sign
{
get {return sign;}
set
{
if (sign == SIGNS.EMPTY)
sign = value;
}
}
public UI_Block( ) {
sign = SIGNS.EMPTY;
}
public void SetBlockOnBoard(int x, int y)
{
this.Location = new Point( x , y );
this.Size = new Size(Parent.Width /3, Parent.Height / 3);
block = new Rectangle(this.Location, this.Size);
}
public void DrawSign(Graphics g)
{
Pen myPen = new Pen(Color.Red);
if (sign == SIGNS.O)
{
drawO(g,new Pen(Brushes.Black));
}
if (sign == SIGNS.X)
{
drawX(g, new Pen(Brushes.Red));
}
}
protected override void OnPaint(PaintEventArgs e)
{
DrawSign(e.Graphics);
base.OnPaint(e);
}
//Draw X
private void drawX(Graphics g, Pen myPen)
{
//draw first daignol
Point daignolStart = new Point { X = this.Location.X , Y = this.Location.Y };
Point daignolEnd = new Point { X = this.Size.Width , Y = this.Size.Height };
g.DrawLine(myPen, daignolStart, daignolEnd);
//draw second daignol
daignolStart = new Point { X = Size.Width , Y = this.Location.Y };
daignolEnd = new Point { X = Location.X, Y = Size.Height };
g.DrawLine(myPen, daignolEnd, daignolStart);
}
//Draw O
private void drawO(Graphics g, Pen myPen)
{
g.DrawEllipse(myPen, block);
}
}
I added them both to the winForm class and to see how it looks like when I paint them:
public partial class Form1 : Form
{
UI.UI_Block block;
UI.UI_Block blockX;
public Form1()
{
InitializeComponent();
block = new UI.UI_Block();
blockX = new UI.UI_Block();
Controls.Add(block);
Controls.Add(blockX);
}
protected override void OnLoad(EventArgs e)
{
block. SetBlockOnBoard(0, 0);
blockX.SetBlockOnBoard(0, block.Height);
block.Sign = SIGNS.X;
blockX.Sign = SIGNS.O;
base.OnLoad(e);
}
protected override void OnPaint(PaintEventArgs e)
{
//block.DrawSign(e.Graphics);
//block.DrawSign(e.Graphics);
base.OnPaint(e);
}
}
I tried few things, like not using the onPaint event and I still get the same result.
Here what I see when I run it:
Any idea why I can't paint both of them?
You are not drawing the contents of your control in it's visible area, so it is drawing fine but you can't see it.
Every control has it's own coordinate space (client coords), which starts at 0,0 regardless of where it is positioned within the parent control. You are placing the control in it's parent correctly by setting its Location, but then you are also using the Location to offset the graphics, so they are essentially offset twice.
(If you make your control bigger you'll be able to see the X being drawn further down the screen)
To fix this, do all your drawing in the client coordinate space of your control, i.e. draw in the area (0, 0, width, height)
(P.S. You could just draw all 9 tiles in the parent control, which is a more efficient approach than creating 9 child controls. But what you are doing will work fine)