-1

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!

Christian T
  • 128
  • 1
  • 12
  • 2
    Your while-loop is probably blocking the UI from refreshing properly, despite the DoEvents call. DoEvents is usually a code smell that you are coding something wrong. Use a timer or another thread. – LarsTech Jan 30 '18 at 20:11

1 Answers1

0

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

Trevor
  • 1,251
  • 1
  • 9
  • 11
  • 2
    I've always been fine with just calling `Invalidate`. [MSDN says](https://msdn.microsoft.com/en-us/library/system.windows.forms.control.refresh(v=vs.110).aspx) the `Refresh` method also invalidates on its own, so you should go with either one or the other. Both would be redundant. – Bradley Uffner Jan 30 '18 at 19:09
  • @BradleyUffner Fair point, I stand corrected. I'll update this into the answer when I get a moment. – Trevor Jan 31 '18 at 09:04
  • Unfortunately this didn't solve the problem. Surely the OnPaint() must be called for it to draw part of the rectangle? – Christian T Jan 31 '18 at 09:40
  • @ChristianT I have updated my answer to reflecting testing I did against you code with the fix, which works in my testing. I have also added more description on why and when you need to call `Invalidate()`. – Trevor Feb 01 '18 at 09:53
  • @Trevor thankyou so much for your answer and your patience. I realised the first suggestion you made didn't work for me because i called invalidate just outside of the loop (details!). Your answer was very thorough and really helped me to understand what was going on. – Christian T Feb 01 '18 at 11:28