2015-04-20 5 views
0

Я пытаюсь сделать простой проект на C#, но, поскольку я в нем как бы новый, у меня много проблем. Что я хочу сделать, это имитировать движение мухи внутри круга. Он должен работать с щелчками мыши - сначала нажмите, вы выбрали центр круга. Второй щелчок, вы выбрали радиус круга и нарисуете его. На третьем клике (внутри круга) вы должны нарисовать муху (давайте сделаем ее маленькой, заполненной Ellipse), и она должна немедленно начать движение.Перемещение по выделенному кругу в C#

Я пытался сделать эту работу более недели, но без почти никакого результата.

Я использую панель в качестве холста.

Вот то, что я до сих пор:

private int start_x = 0; 
    private int start_y = 0; 
    private int end_x = 0; 
    private int end_y = 0; 
    private int fly_x = 0; 
    private int fly_y = 0; 
    private int clicks = 0; 
    private int distance = 0; 


    public Form1() 
    { 
     InitializeComponent(); 
    } 

    private void panel1_MouseDown(object sender, MouseEventArgs e) 
    { 
     clicks++; 
     if (clicks == 1) 
     { 
      start_x = e.X; 
      start_y = e.Y; 
     } 
     else if (clicks == 2) 
     { 
      end_x = e.X; 
      end_y = e.Y; 
     } 
     else if (clicks == 3) 
     { 
      fly_x = e.X; 
      fly_y = e.Y; 
     } 
     else if (clicks == 4) 
     { 

     } 
     this.panel1.Refresh(); 

    } 

    private void panel1_Paint(object sender, PaintEventArgs e) 
    { 
     Random r = new Random(); 
     Thread t = new Thread(new ThreadStart(Fly)); 

     if (clicks == 1) 
     { 
      e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4); 
     } 
     else if (clicks == 2) 
     { 
      distance = Distance(start_x, start_y, end_x, end_y); 
      e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4); 
      e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);    
     } 
     else if (clicks == 3) 
     { 
      t.Start(); 
     } 
     else if (clicks == 4) 
     { 
      t.Abort(); 
      clicks = 0; 
     } 
    } 

    private int Distance(int a_x, int a_y, int b_x, int b_y) 
    { 
     int a, b; 
     a = a_x - b_x; 
     b = a_y - b_y; 

     return (Convert.ToInt32(Math.Sqrt(a * a + b * b))); 
    } 

    private void Fly() 
    { 
     Random r = new Random(); 
     Graphics e = CreateGraphics(); 
     distance = Distance(start_x, start_y, end_x, end_y); 

     while (clicks < 4) 
     {    
      e.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4); 
      e.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2); 
      e.FillEllipse(Brushes.Red, fly_x - 3, fly_y - 3, 6, 6); 
      fly_x = fly_x - 5 + r.Next(1, 11); 
      fly_y = fly_y - 5 + r.Next(1, 11); 
      Invalidate(); 
      if (Distance(start_x, start_y, fly_x, fly_y) > distance - 1) 
      { 
       if (fly_x < start_x) 
       { 
        fly_x = fly_x + 5; 
       } 
       else 
       { 
        fly_x = fly_x - 5; 
       } 
       if (fly_y < start_y) 
       { 
        fly_y = fly_y + 5; 
       } 
       else 
       { 
        fly_y = fly_y - 5; 
       } 
      } 
     } 
    } 

Но, очевидно, это не работает. Я могу нарисовать круг, но как только я снова нажму на панель, кружок исчезнет. Не могли бы вы указать мне в правильном направлении? Я имею в виду, что я делаю неправильно? Я попытался сделать это без использования потоков, и я дошел до того, что рисовал даже муху, но в этот момент все это застыло. EDIT: я удалил нить и поставить таймер:

private void panel1_Paint(object sender, PaintEventArgs e) 
    { 
     Random r = new Random(); 
     this.DoubleBuffered = true; 

     if (clicks == 1) 
     { 
      e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4); 
     } 
     else if (clicks == 2) 
     { 
      distance = Distance(start_x, start_y, end_x, end_y); 
      e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4); 
      e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2);    
     } 
     else if (clicks == 3) 
     { 
      e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4); 
      e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2); 
      e.Graphics.FillEllipse(Brushes.Red, fly_x - 3, fly_y - 3, 6, 6); 
     } 
     else if (clicks == 4) 
     { 
      clicks = 0; 
     } 
    } 

     private void timer1_Tick(object sender, EventArgs e) 
    { 
     Random r = new Random(); 
     fly_x = fly_x - 5 + r.Next(1, 11); 
     fly_y = fly_y - 5 + r.Next(1, 11); 
     if (Distance(start_x, start_y, fly_x, fly_y) > distance - 1) 
     { 
      if (fly_x < start_x) 
      { 
       fly_x = fly_x + 5; 
      } 
      else 
      { 
       fly_x = fly_x - 5; 
      } 
      if (fly_y < start_y) 
      { 
       fly_y = fly_y + 5; 
      } 
      else 
      { 
       fly_y = fly_y - 5; 
      } 
     } 
     panel1.Invalidate(); 
    } 

Сейчас он работает, но до сих пор, изображение мерцает много, хотя я использовал двойную буферизацию. У меня есть решение от моего коллеги, и почему-то его не мерцает. Есть ли другой способ исправить это, чем двойной буфер?

+0

Вам не нужна нить, в которой вам нужен таймер. – TaW

+0

Вы можете использовать таймер, или вы можете просто вызвать panel.Invalidate() в конце вашего метода Paint, чтобы он постоянно перерисовывался. Я также рекомендую вам использовать настраиваемый элемент управления вместо панели. Это позволит вам включить стиль OptimizedDoubleBuffer в вашем конструкторе. Без него вы увидите некоторое уродливое мерцание, поскольку Windows продолжает стирать ваши рисунки с цветом фона перед каждым вызовом Paint. – RogerN

+0

Я попытался использовать таймер, но ничего не сделал. Однако более вероятно, что я использовал его неправильно. Есть ли что-нибудь, что я должен знать при использовании таймера? – martin

ответ

1

Избежать мерцания в Winforms не сложно, но для этого требуется постоянство. Winforms не имеет никакого механизма компоновки — - это тонкий слой поверх неуправляемого API user32.dll Windows, и каждый элемент управления в окне сам является окном, которое рисуется отдельно —, и поэтому каждый элемент управления, который может потенциально мерцать быть двойным буфером.

Вот версия кода, который делает не фликера:

public partial class Form1 : Form 
{ 
    private readonly Random r = new Random(); 

    private int start_x = 0; 
    private int start_y = 0; 
    private int end_x = 0; 
    private int end_y = 0; 
    private int fly_x = 0; 
    private int fly_y = 0; 
    private int clicks = 0; 
    private int distance = 0; 

    public Form1() 
    { 
     InitializeComponent(); 
     DoubleBuffered = true; 
     label1.Text = "clicks: 0"; 
    } 

    protected override void OnMouseDown(MouseEventArgs e) 
    { 
     base.OnMouseDown(e); 

     clicks++; 
     if (clicks == 1) 
     { 
      start_x = e.X; 
      start_y = e.Y; 
     } 
     else if (clicks == 2) 
     { 
      end_x = e.X; 
      end_y = e.Y; 
     } 
     else if (clicks == 3) 
     { 
      fly_x = e.X; 
      fly_y = e.Y; 
      timer1.Start(); 
     } 
     else if (clicks == 4) 
     { 
      timer1.Stop(); 
      clicks = 0; 
     } 
     label1.Text = "clicks: " + clicks; 
     Invalidate(); 
    } 

    protected override void OnPaint(PaintEventArgs e) 
    { 
     base.OnPaint(e); 

     if (clicks == 1) 
     { 
      e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4); 
     } 
     else if (clicks == 2) 
     { 
      distance = Distance(start_x, start_y, end_x, end_y); 
      e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4); 
      e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2); 
     } 
     else if (clicks == 3) 
     { 
      e.Graphics.DrawEllipse(Pens.Black, start_x - 2, start_y - 2, 4, 4); 
      e.Graphics.DrawEllipse(Pens.Black, start_x - distance, start_y - distance, distance * 2, distance * 2); 
      e.Graphics.FillEllipse(Brushes.Red, fly_x - 3, fly_y - 3, 6, 6); 
     } 
    } 

    private int Distance(int a_x, int a_y, int b_x, int b_y) 
    { 
     int a, b; 
     a = a_x - b_x; 
     b = a_y - b_y; 

     return (Convert.ToInt32(Math.Sqrt(a * a + b * b))); 
    } 

    private void timer1_Tick(object sender, EventArgs e) 
    { 
     fly_x = fly_x + r.Next(-4, 5); 
     fly_y = fly_y + r.Next(-4, 5); 
     if (Distance(start_x, start_y, fly_x, fly_y) > distance - 1) 
     { 
      if (fly_x < start_x) 
      { 
       fly_x = fly_x + 5; 
      } 
      else 
      { 
       fly_x = fly_x - 5; 
      } 
      if (fly_y < start_y) 
      { 
       fly_y = fly_y + 5; 
      } 
      else 
      { 
       fly_y = fly_y - 5; 
      } 
     } 

     Invalidate(); 
    } 
} 

Обратите внимание, что я не рисовал в Panel объект здесь, но вместо этого используется только сам Form1 объект. Вы можете выполнить один и тот же базовый результат, используя тот же базовый метод, что и для Panel или другого настраиваемого элемента управления, но это будет означать создание собственного подкласса Panel (или обычного пользовательского элемента управления, наследующего Control) и помещения всего вышеуказанного кода в это а не в форме. То есть вы по-прежнему будете использовать override по методам OnMouseDown() и OnPaint() вместо привязки обработчиков событий; вы все равно можете использовать конструктор для перетаскивания объекта Timer в свой собственный класс.

Ключ в том, что элемент управления, на котором на самом деле выполняется чертеж, должен иметь свойство DoubleBuffered, установленное на true (по умолчанию это false). Поскольку свойство не равно public, правильный способ сделать это - подклассифицировать тип и установить его в конструкторе (вы можете использовать отражение на простом объекте Panel, но это уродливо и неочевидно). В приведенном выше примере класс Form1 уже является подклассом, над которым я управляю, поэтому его проще установить, чем создать для него новый настраиваемый элемент управления.

Пар других точек о коде:

  • Вы можете рассмотреть вопрос об использовании MouseClick события вместо MouseDown для взаимодействия мыши. Это ведет себя таким образом, что, скорее всего, будет более ожидаемым пользователем; два основных отличия: событие происходит при наведении мышью, а не на мыши, и это совсем не происходит, если пользователь отталкивается от элемента управления, прежде чем отпускать кнопку мыши (т.е. предлагает пользователю способ изменить их ум и избежать щелчка).
  • Я заметил, что ваша случайная прогулка показала мне что-то вроде ошибки. Вы генерировали значения между -4 и +5, что, конечно же, заставляет «летать» прокладывать путь в нижний правый угол круга. Я изменил ваш код рандомизации так, чтобы он генерировал значения от -4 до 4 вместо того, чтобы обеспечить равномерное распределение во всех направлениях. Я исправил это в приведенном выше примере кода, просто указав правильные значения в методе Next() overload и удалив ненужный - 5 из расчета
  • Самое главное, что ваш код рандомизации имел серьезную, но общую ошибку: вы создавал новый объект Random каждый раз, когда вы хотели получить новое значение. Это дает неслучайные результаты из-за того, что по умолчанию генератор случайных чисел засевается системными часами. В худшем случае код выбирает значения так быстро, что каждый новый объект Random инициализируется идентично и поэтому возвращает то же значение; даже в лучшем случае, когда код задерживается между случайными значениями, возвращаемые значения по-прежнему коррелируют с часами, а не являются истинным случайным распределением. Я исправил это в приведенном выше примере кода, переместив объект Random в личное поле с инициализатором, чтобы создать хорошую случайную последовательность значений (технически, «псевдослучайно»).
+0

FYI: PictureBox - это простой способ получить контроль с DoubleBuffering по умолчанию.Я бы также использовал перегрузку Invalidate (Rectangle), когда перемещается только крошечная ошибка ;-) – TaW

+0

@TaW: это, но IMHO - это много лишнего «веса» (возможно, не время исполнения, но, по крайней мере, концептуально) для переноса только для установки флага. Вы правы, что недействительность только измененной области более эффективна; однако, по моему опыту, людям трудно изучить модель рендеринга, управляемую событиями, чтобы правильно определить прямоугольник, чтобы сделать недействительным. ИМХО, это лучше, чем «промежуточная» тема, хотя и предоставленная здесь черта для рисования настолько проста, что, вероятно, не будет отвлекать многое от основной точки. –

+0

Спасибо @Peter Duniho. Это был удивительно подробный ответ, и, хотя я в основном новичок, я все еще понимал большую часть этого. – martin

Смежные вопросы