I have a question about a program I am writing for practice, which in its current state allows the user to move a label around the Form using the arrow keys.
I want to start adding some graphical rectangles to my program, and am currently practicing by trying to draw a simple rectangle once a timer hits 100.
Here's the weird part, the rectangle only draws once the label has passed over part of it, and will only draw the part the label passes over. Picture of this happening: label passing over rectangle. Ideally I would like to understand why this is happening, but will also be very happy if anyone can offer a solution!
I will post my whole code as I'm not even sure which part the problem could be coming from. Very sorry if its untidy, hopefully someone will have an idea just from the image:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
int direction;
int X = 200;
int Y = 200;
Rectangle myRock;
public System.Windows.Forms.Timer aTimer = new System.Windows.Forms.Timer();
public Form1()
{
InitializeComponent();
this.Load += new System.EventHandler(this.Form1_Load);
this.KeyDown += new System.Windows.Forms.KeyEventHandler(this.Form1_KeyDown);
this.Click += new System.EventHandler(this.Form1_Click);
}
void worker()
{
for (int i = 0; i < 100000; i++) {
if (label2.InvokeRequired)
{
label2.Invoke((MethodInvoker)delegate
{
label2.Text = i.ToString(); // this is a timer acting as a score keeper
});
Thread.Sleep(100);
}
}
}
public void DrawRectangleRectangle(PaintEventArgs e, Rectangle rect)
{
// Create pen.
Pen blackPen = new Pen(Color.White, 3);
SolidBrush whiteBrush = new SolidBrush(Color.White);
// Create rectangle.
// Draw rectangle to screen.
e.Graphics.DrawRectangle(blackPen, rect);
e.Graphics.FillRectangle(whiteBrush, rect);
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void Form1_Click(object sender, EventArgs e)
{
Thread newThread = new Thread(worker);
direction = 2;
newThread.Start();
}
private void SetDirection(KeyEventArgs e)
{
if (e.KeyCode == Keys.Down) direction = 4;
else if (e.KeyCode == Keys.Up) direction = 2;
else if (e.KeyCode == Keys.Right) direction = 3;
else if (e.KeyCode == Keys.Left) direction = 1;
}
private void ApplyMovement()
{
Size size = new Size(100,100);
Point position = new Point(100, 100);
while (direction != 0)
{
Application.DoEvents();
if (direction == 1) X--;
else if (direction == 2) Y--;
else if (direction == 3) X++;
else if (direction == 4) Y++;
if (label2.Text == "100") myRock = new Rectangle(position, size);
Thread.Sleep(10);
label1.Location = new Point(X, Y);
}
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
SetDirection(e);
ApplyMovement();
}
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
int label2Variable = Convert.ToInt32(label2.Text);
if (label2Variable > 100)
{
DrawRectangleRectangle(e, myRock);
}
}
private void label1_Click(object sender, EventArgs e)
{
}
}
}
Thanks in advance!
What is happening is that the OnPaint()
function is not being called, so you aren't repainting all of the form. When Windows believes that something on your form has changed, it will issue a Repaint command to your application, asking it to redraw all or a section of the form's area, which appears in your code as the Form.OnPaint()
method being fired.
What you need to do here is tell Windows that your form has changed. The easiest way to do this is to call:
this.Invalidate();
once something has changed, so possibly at the end of the loop in ApplyMovement()
.
(You can also call this.Refresh()
, which will Invalidate
the whole form, and then cause a synchronous repaint of it. Thanks @Bradley)
That way, you're telling Windows that the form is "invalid" and needs to be completely repainted, whereupon Windows will (indirectly) call the OnPaint()
method.
Edit
Having rehydrated your app, I have applied the following change to the ApplyMovement()
method:
if (label2.Text == "100") myRock = new Rectangle(position, size);
... changed to ...
if (label2.Text == "100")
{
myRock = new Rectangle(position, size);
this.Invalidate();
}
Now, when the value of the incrementing label field hits "100", the myRock
rectangle appears immediately in my tests.
It's important to understand that your code never calls OnPaint()
directly. You use the Invalidate()
method to tell Windows itself that a part (or all) of your form has changed and needs to be updated on-screen. Windows will then "call" the OnPaint()
method.
If you're using the OnPaint()
method like this to draw information from your program to the screen, such as the myRock
rectangle, or some custom painted text, then any time that information changes, you need to call Invalidate()
to trigger an OnPaint()
.
It's the way all the common controls, such as textboxes and buttons, update themselves to the screen. They call Invalidate()
internally any time you change one of their properties.
A quick way to think about it is that anytime you want OnPaint()
to be called, you call Invalidate()
.
Hope this helps