PictureBox остается нарисованным в старой позиции при перемещении в новую позицию

Я изучаю C# по книге Head First C#. В первой лабораторной работе при создании игры Greyhound Racing я столкнулся с некоторым поведением, и я не понимаю, почему мой код отображается так, как есть. При первом нажатии кнопки «Гонка» лошади мчатся к концу дорожки, но они визуализируются таким образом, что каждая из них создает за собой след из предыдущих изображений, пока не достигнет конца дорожки, после чего предыдущие изображения окончательно исчезают. При последующих нажатиях кнопки «Гонка» происходит то же самое, но также не удается стереть PictureBox для каждой собаки с финишной черты, пока текущая гонка не будет завершена. введите здесь описание изображения

Вот короткое 19-секундное видео, демонстрирующее, что я имею в виду: Пример завершающих изображений

Почему собаки «плетутся» во время гонки и почему они не исчезают с финишной черты после повторного рендеринга на старте до завершения следующей гонки? Я думаю, что когда собак перемещают в TakeStartingPosition(), они перемещаются, а не перерисовываются. То же самое с Run(), я бы подумал, что каждая новая позиция - это ход, а не перерисовка, но, похоже, она перерисовывает изображение на каждом шаге движения и не стирает старое до самого конца гонки. Что я делаю неправильно?

Грейхаунд.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;


namespace RaceTrackSimulator
{
    class Greyhound
    {
        public int StartingPosition;
        public int RacetrackLength;
        public PictureBox MyPictureBox = null;
        public int Location = 0;
        public Random Randomizer;

        public bool Run()
        {
            // Move forward either 1, 2, 3 or 4 spaces at random
            int moveSpaces = Randomizer.Next(1, 4);


            // Update the position of my Picturebox on the form like this:
            //  MyPictureBox.Left = StartingPosition + Location;
            MyPictureBox.Left = StartingPosition + Location;

            // Return true if I won the race
            if (Location >= RacetrackLength)
            {
                return true;
            }
            else
            {
                Location += moveSpaces;
                return false;
            }
        }

        public void TakeStartingPosition()
        {
            // Reset my location to 0 and my PictureBox to starting position
            Location = 0;
            MyPictureBox.Left = StartingPosition;

        }
    }
}

Form1.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace RaceTrackSimulator
{
    public partial class Form1 : Form
    {
        Greyhound[] Dogs;

        public Form1()
        {
            InitializeComponent();
            Random MyRandomizer = new Random();

            // Initialize Dogs
            Dogs = new Greyhound[4];

            Dogs[0] = new Greyhound()
            {
                MyPictureBox = pictureBox2,
                StartingPosition = racetrackPictureBox.Left,
                RacetrackLength = racetrackPictureBox.Width - pictureBox2.Width,
                Randomizer = MyRandomizer
            };

            Dogs[1] = new Greyhound()
            {
                MyPictureBox = pictureBox3,
                StartingPosition = racetrackPictureBox.Left,
                RacetrackLength = racetrackPictureBox.Width - pictureBox3.Width,
                Randomizer = MyRandomizer
            };

            Dogs[2] = new Greyhound()
            {
                MyPictureBox = pictureBox4,
                StartingPosition = racetrackPictureBox.Left,
                RacetrackLength = racetrackPictureBox.Width - pictureBox4.Width,
                Randomizer = MyRandomizer
            };

            Dogs[3] = new Greyhound()
            {
                MyPictureBox = pictureBox5,
                StartingPosition = racetrackPictureBox.Left,
                RacetrackLength = racetrackPictureBox.Width - pictureBox5.Width,
                Randomizer = MyRandomizer
            };
        }

        private void raceButton_Click(object sender, EventArgs e)
        {
            bool winner = false;
            int winningDog = 0;

            for (int eachDog = 0; eachDog < Dogs.Length; eachDog++)
            {
                Dogs[eachDog].TakeStartingPosition();
            }

            while (!winner)
            {
                for (int i = 0; i < 4; i++)
                {
                    if (Dogs[i].Run())
                    {
                        winner = true;
                        winningDog = i+1;
                    }
                    System.Threading.Thread.Sleep(1);
                }
            }

            MessageBox.Show("Winning Dog is #" + winningDog);
        }
    }
}

Form1.Designer.cs

namespace RaceTrackSimulator
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.pictureBox2 = new System.Windows.Forms.PictureBox();
            this.racetrackPictureBox = new System.Windows.Forms.PictureBox();
            this.pictureBox3 = new System.Windows.Forms.PictureBox();
            this.pictureBox4 = new System.Windows.Forms.PictureBox();
            this.pictureBox5 = new System.Windows.Forms.PictureBox();
            this.raceButton = new System.Windows.Forms.Button();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.racetrackPictureBox)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox4)).BeginInit();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox5)).BeginInit();
            this.SuspendLayout();
            // 
            // pictureBox2
            // 
            this.pictureBox2.Image = global::RaceTrackSimulator.Properties.Resources.dog;
            this.pictureBox2.Location = new System.Drawing.Point(13, 22);
            this.pictureBox2.Name = "pictureBox2";
            this.pictureBox2.Size = new System.Drawing.Size(75, 20);
            this.pictureBox2.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pictureBox2.TabIndex = 2;
            this.pictureBox2.TabStop = false;
            // 
            // racetrackPictureBox
            // 
            this.racetrackPictureBox.Image = global::RaceTrackSimulator.Properties.Resources.racetrack;
            this.racetrackPictureBox.Location = new System.Drawing.Point(13, 12);
            this.racetrackPictureBox.Name = "racetrackPictureBox";
            this.racetrackPictureBox.Size = new System.Drawing.Size(600, 200);
            this.racetrackPictureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.racetrackPictureBox.TabIndex = 0;
            this.racetrackPictureBox.TabStop = false;
            // 
            // pictureBox3
            // 
            this.pictureBox3.Image = global::RaceTrackSimulator.Properties.Resources.dog;
            this.pictureBox3.Location = new System.Drawing.Point(13, 74);
            this.pictureBox3.Name = "pictureBox3";
            this.pictureBox3.Size = new System.Drawing.Size(75, 20);
            this.pictureBox3.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pictureBox3.TabIndex = 3;
            this.pictureBox3.TabStop = false;
            // 
            // pictureBox4
            // 
            this.pictureBox4.Image = global::RaceTrackSimulator.Properties.Resources.dog;
            this.pictureBox4.Location = new System.Drawing.Point(13, 126);
            this.pictureBox4.Name = "pictureBox4";
            this.pictureBox4.Size = new System.Drawing.Size(75, 20);
            this.pictureBox4.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pictureBox4.TabIndex = 4;
            this.pictureBox4.TabStop = false;
            // 
            // pictureBox5
            // 
            this.pictureBox5.Image = global::RaceTrackSimulator.Properties.Resources.dog;
            this.pictureBox5.Location = new System.Drawing.Point(13, 178);
            this.pictureBox5.Name = "pictureBox5";
            this.pictureBox5.Size = new System.Drawing.Size(75, 20);
            this.pictureBox5.SizeMode = System.Windows.Forms.PictureBoxSizeMode.AutoSize;
            this.pictureBox5.TabIndex = 5;
            this.pictureBox5.TabStop = false;
            // 
            // raceButton
            // 
            this.raceButton.Location = new System.Drawing.Point(538, 377);
            this.raceButton.Name = "raceButton";
            this.raceButton.Size = new System.Drawing.Size(75, 23);
            this.raceButton.TabIndex = 6;
            this.raceButton.Text = "RACE!";
            this.raceButton.UseVisualStyleBackColor = true;
            this.raceButton.Click += new System.EventHandler(this.raceButton_Click);
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(625, 412);
            this.Controls.Add(this.raceButton);
            this.Controls.Add(this.pictureBox5);
            this.Controls.Add(this.pictureBox4);
            this.Controls.Add(this.pictureBox3);
            this.Controls.Add(this.pictureBox2);
            this.Controls.Add(this.racetrackPictureBox);
            this.Name = "Form1";
            this.Text = "Form1";
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox2)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.racetrackPictureBox)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox3)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox4)).EndInit();
            ((System.ComponentModel.ISupportInitialize)(this.pictureBox5)).EndInit();
            this.ResumeLayout(false);
            this.PerformLayout();

        }

        #endregion

        private System.Windows.Forms.PictureBox racetrackPictureBox;
        private System.Windows.Forms.PictureBox pictureBox2;
        private System.Windows.Forms.PictureBox pictureBox3;
        private System.Windows.Forms.PictureBox pictureBox4;
        private System.Windows.Forms.PictureBox pictureBox5;
        private System.Windows.Forms.Button raceButton;
    }
}

person Vin Breau    schedule 22.10.2013    source источник


Ответы (1)


Вы используете замкнутый цикл в обработчике нажатия кнопки, который монополизирует основной поток пользовательского интерфейса. Когда собака движется вперед, форма должна перекрасить себя, чтобы «стереть» то место, где раньше была собака. Однако, поскольку код застрял в цикле, он не может перерисовать себя. Точно так же, когда гонка возобновляется, собаки не исчезают с финишной черты по той же причине.

Одним из возможных «быстрых решений» является вызов Application.DoEvents(); в коде, чтобы позволить форме обновить себя. Это будет выглядеть так:

    private void raceButton_Click(object sender, EventArgs e)
    {
        bool winner = false;
        int winningDog = 0;

        for (int eachDog = 0; eachDog < Dogs.Length; eachDog++)
        {
            Dogs[eachDog].TakeStartingPosition();
        }
        Application.DoEvents();

        while (!winner)
        {
            for (int i = 0; i < 4; i++)
            {
                if (Dogs[i].Run())
                {
                    winner = true;
                    winningDog = i+1;
                }
                Application.DoEvents();
                System.Threading.Thread.Sleep(1);
            }
        }

        MessageBox.Show("Winning Dog is #" + winningDog);
    }

Это, однако, просто временное решение реальной проблемы: Вы не должны монополизировать основной поток пользовательского интерфейса с помощью длинного цикла в обработчике нажатия кнопки.

Одним из возможных решений является сброс собак в обработчике нажатия кнопки, а затем запуск таймера. В событии Tick() функции Timer() вы должны вызвать метод Run() для каждой собаки и проверить победителя. Когда гонка будет выиграна, выключите таймер.

person Idle_Mind    schedule 22.10.2013
comment
Спасибо. Это был определенно недостающий кусочек знаний, который мне был нужен. Я могу запустить таймер, чтобы сдержать гонку, и отключить кнопку «Гонка», пока таймер отсчитывает гонку. - person Vin Breau; 23.10.2013
comment
Идеально. Вызов Sleep() также больше не нужен в цикле. Вы можете отрегулировать скорость собак, изменив Interval() таймера. DoEvents() также больше не нужен после того, как вы переключились на таймер, поскольку обычные сообщения (например, рисование) будут обрабатываться между событиями Tick(). - person Idle_Mind; 23.10.2013