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)
Related
I found out how to draw Rectangles and some code to find when two rectangles overlap but I can't connect these procedures.
I have the two rectangles that I wanted but then a cannot determine whether these intersect, to then add this information to a ListBox.
Here is my code:
public partial class Form1 : Form
{
Graphics g;
Pen p;
Point cursor;
int k = 0;
Point[] tocke = new Point[2];
public Form1()
{
InitializeComponent();
g = this.CreateGraphics();
p = new Pen(Color.Black, 3);
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
cursor = this.PointToClient(Cursor.Position);
statusMisa.Text = "X: " + cursor.X + " Y: " + cursor.Y;
}
private void Form1_Click(object sender, EventArgs e)
{
bool Preklapanje(int l1x, int l1y, int l2x, int l2y, int r1x, int r1y, int r2x, int r2y)
{
if (l1x >= r2x || l2x >= r1x)
{
return false;
}
if (l1y <= r2y || l2y <= r1y)
{
return false;
}
return true;
}
List<int> pozX = new List<int>();
List<int> pozY = new List<int>();
if (checkCrtanje.Checked == true)
{
Rectangle rect = new Rectangle(cursor.X - 50, cursor.Y - 50, 100, 100);
if (k < 2)
{
g.DrawRectangle(p, rect);
tocke[k++] = new Point(cursor.X, cursor.Y);
listBox1.Items.Add("X: " + cursor.X + " Y: " + cursor.Y);
pozX.Add(cursor.X);
pozY.Add(cursor.Y);
}
else
{
MessageBox.Show("Možeš nacrtati samo dva kvadrata!");
}
}
if (k == 3)
{
if (Preklapanje(pozX[0] - 50, pozY[0] - 50, pozX[0] + 50, pozY[0] + 50, pozX[1] - 50, pozY[1] - 50, pozX[1] + 50, pozY[1] + 50))
{
listBox1.Items.Add("Preklapaju se.");
}
else
{
listBox1.Items.Add("Ne preklapaju se.");
}
}
}
}
► As noted, you shouldn't use CreateGraphics() to draw on a Control's surface: this object becomes invalid (belongs to the past) as soon as the Control where the drawing is performed is invalidated (is repainted).
All Controls that have a drawable surface, raise a Paint event and have an OnPaint method that we can override to perform custom painting (the OnPaint method is responsible to raise the Paint event, so we need to handle just one).
The argument of both the event and the method, represents a PaintEventArgs object, which provides a fresh Graphics object that we can use to paint.
We can call the Invalidate() method to repaint a Control when needed. This method causes the generation of a new PaintEventArgs object (thus, a new Graphics object). After, the OnPaint method is called, which - in turn - raises the Paint event.
► To determine whether a Rectangle intersects another (or more than one), we can use the Rectangle.IntersetWith() method (it returns true or false) and the Rectangle.Interset() method → this is used to generate a Rectangle that represents the intersection of two other rectangles.
See also:
→ Rectangle.Contains([Rectangle])
→ Rectangle.Union([Rectangle a], [Rectangle b]).
Here, I'm using a few collections to store the shapes currently drawn and their intersections (just rectangles, but you can build more complex shapes using GraphicsPath objects):
A List<Rectangle> (rects) which stores the Rectangles already created.
A List<Rectangle> (intersections), to store the intersections which belong to the past (intersections already drawn).
A List<Rectangle> (currentIntersects), used to temporarily store the intersection generated when a new Rectangle shaped is being drawn, so we can use different colors (as soon as we release the Mouse Button, this collection is fixed and added to the intersections collection).
A Rectangle structure (currentRect) which represents the Rectangle that is currently being drawn on the surface (when the Mouse Button is released, this object is added to the rects collection).
A Point structure (startPosition), used to store the initial position of the Rectangle currently drawn. It's reset when the OnMouseDown method is called (when a new Rectangle shape is generated).
► To use this code, create a new Form and paste the code you find here in its Code file. No need to subscribe to any event: since we're drawing on a Form, I'm overriding its methods (OnPaint, OnMouseDown, OnMouseUp, OnMouseMove), no event is used.
You can do the same with a Custom Control or a UserControl.
To add these collection, or just the intersections collection, to e.g., a ListBox, to handle the collections visually, see here (the currentIntersects and intersections collections already contain the information):
How to call a method that uses PaintEventArgs and coordinates variables
NOTE:
► Here, in the OnPaint method override, I'm not calling base.OnPaint(), so the event is not generated. This speeds up the process a bit, but keep in mind that subscribing to the Form's Paint event is useless.
► You need to activate double-buffering: (set DoubleBuffered = true), otherwise you'll notice a lot of flickering (this is quite normal).
This is how it works:
public partial class FormDrawings : Form
{
private List<Rectangle> rects = new List<Rectangle>();
private List<Rectangle> intersections = new List<Rectangle>();
private List<Rectangle> currentIntersects = new List<Rectangle>();
private Rectangle currentRect = Rectangle.Empty;
private Point startPosition = Point.Empty;
private float penSize = 2.0f;
protected override void OnMouseDown(MouseEventArgs e)
{
base.OnMouseDown(e);
if (e.Button == MouseButtons.Left) {
startPosition = e.Location;
currentRect = new Rectangle(startPosition, Size.Empty);
}
}
protected override void OnMouseMove(MouseEventArgs e)
{
base.OnMouseMove(e);
if (e.Button == MouseButtons.Left) {
if (e.Y < startPosition.Y) { currentRect.Location = new Point(currentRect.X, e.Y); }
if (e.X < startPosition.X) { currentRect.Location = new Point(e.X, currentRect.Y); }
currentRect.Size = new Size(Math.Abs(startPosition.X - e.X), Math.Abs(startPosition.Y - e.Y));
currentIntersects.Clear();
foreach (var rect in rects) {
if (currentRect.IntersectsWith(rect)) {
currentIntersects.Add(Rectangle.Intersect(currentRect, rect));
}
}
this.Invalidate();
}
}
protected override void OnMouseUp(MouseEventArgs e)
{
base.OnMouseUp(e);
if (currentRect.Size != Size.Empty) rects.Add(currentRect);
if (currentIntersects.Count > 0) {
intersections.AddRange(currentIntersects);
}
}
protected override void OnPaint(PaintEventArgs e)
{
using (var borderPen = new Pen(Color.LightGreen, penSize))
using (var iBrush = new SolidBrush(Color.FromArgb(128, Color.Orange)))
using (var crBrush = new SolidBrush(Color.FromArgb(128, Color.DeepSkyBlue))) {
intersections.ForEach(r => e.Graphics.FillRectangle(iBrush, r));
currentIntersects.ForEach(r => e.Graphics.FillRectangle(crBrush, r));
e.Graphics.DrawRectangle(borderPen, currentRect);
rects.ForEach(r => e.Graphics.DrawRectangle(borderPen, r));
}
}
}
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();
}
}
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 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);
}
}
}
}