netlib.narod.ru< Назад | Оглавление | Далее >

«Пишем» мышью

Вы когда-нибудь слышали о программах САПР? А о графических редакторах? Так вот, программа Scribble не относится ни к тем, ни к другим.

Scribble.cs

  //-----------------------------------------
  // Scribble.cs (C) 2001 by Charles Petzold
  //-----------------------------------------
  using System;
  using System.Drawing;
  using System.Windows.Forms;

  class Scribble: Form
  {
      bool  bTracking;
      Point ptLast;

      public static void Main()
      {
          Application.Run(new Scribble());
      }
      public Scribble()
      {
          Text = "Scribble";
          BackColor = SystemColors.Window;
          ForeColor = SystemColors.WindowText;
      }
      protected override void OnMouseDown(MouseEventArgs mea)
      {
          if (mea.Button != MouseButtons.Left)
              return;

          ptLast = new Point(mea.X, mea.Y);
          bTracking = true;
      }
      protected override void OnMouseMove(MouseEventArgs mea)
      {
          if (!bTracking)
              return;

          Point ptNew = new Point(mea.X, mea.Y);

          Graphics grfx = CreateGraphics();
          grfx.DrawLine(new Pen(ForeColor), ptLast, ptNew);
          grfx.Dispose();

          ptLast = ptNew;
      }
      protected override void OnMouseUp(MouseEventArgs mea)
      {
          bTracking = false;
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          // Ђ что делаетсЯ здесь?
      }
  }

На первый взгляд, программа работает просто замечательно. Вы наводите курсор на клиентскую область программы, нажимаете левую кнопку мыши и перемещаете ее, рисуя прямые, кривые и другие замысловатые линии. (Поскольку здесь используется простой подход с отслеживанием мыши, принцип работы этой программы не слишком сложен.) Рисование происходит во время исполнения метода OnMouseMove; программа получает от CreateGraphics объект Graphics и просто соединяет линией новую позицию мыши с предыдущей (которая сохранена в поле ptLast). Здесь я отдал дань уважения ранней рекламе Apple Macintosh:


ђис. 8.5.

Но что делает Scribble при исполнении метода OnPaint? Ой... кажется, программа забыла сохранить позиции мыши, использованные для рисования линий. Если понадобится перерисовать клиентскую область, ничего не выйдет.

Есть пара способов реализации механизма повторной прорисовки в подобной программе. Например, использовать теневой растр (shadow bitmap), который программа рисует одновременно с рисунком на экране. При исполнении метода OnPaint программа просто покажет это изображение. Это делает другая версия программы, которую вы увидите в главе 11, а в главе 15 я сохраню точки при помощи графических контуров.

Другое решение — накопить массив структур Point, после чего во время исполнения метода OnPaint просто вызвать DrawLines. Но при этом встает ряд вопросов. Число элементов, которое может хранить массив в С#, фиксировано и определяется при создании массива. Есть соблазн создать большой массив для хранения множества точек, как в этом примере:

  Point[] apt = new Point[1000000];

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

Решение — в классе ArrayList, определенном в пространстве имен System.Collections, которое включает также классы с аппетитными для программиста именами — Queue, Stack, SortedList и Hashtable. Объект ArrayList похож на одномерный массив, расширяющийся по мере необходимости. Здесь я не могу обсудить ArrayList в деталях, но дам основы, а остальное вы выясните сами.

Начать следует с создания нового объекта ArrayList:

  ArrayList arrlst = new ArrayList();

Альтернативный конструктор создает массив первоначальной емкости. По умолчанию она равна 16. Затем при помощи метода Add можно добавлять к ArrayList любые объекты. Вот инструкция, добавляющая объект Point:

  arrlst.Add(pt);

Аналогичные методы позволяют также вставлять и удалять элементы.

Один из подходов, удобных для извлечения объекта из ArrayList, — использование индексатора, что во многом напоминает извлечение данных из массива. Например, если известно, что четвертый элемент arrlst — это структура Point, ее можно получить так:

  Point pt = (Point)arrlst[3];

Здесь необходимо приведение типа, поскольку индексатор возвращает объект типа Object.

В один и тот же массив-список можно добавлять объекты различных типов. Например, сразу за структурой Point можно добавить Rectangle:

  arrlst.Add(rect);

Однако может понадобиться копировать содержимое массива-списка в обычный массив (чуть ниже я это покажу). Если попытаться скопировать объект Rectangle в массив структур Point, возникает ошибка времени выполнения.

Свойство Capacity массива-списка указывает текущее число элементов, которое он способен хранить. По мере добавления (и удаления) объектов свойство Count массива-списка указывает текущее число хранящихся в нем объектов. Значение Count всегда меньше или равно значению Capacity. Если при добавлении элемента значения Count и Capacity равны, то значение Capacity удваивается.

Версия программы Scribble, использующая класс ArrayList, не сможет обойтись одним массивом-списком для хранения всех структур Point. Наличие единственного массива-списка для хранения структур предполагает, что все точки будут соединены в одну линию. Однако пользователь может нарисовать и несколько кривых, всякий раз нажимая левую кнопку мыши и отпуская ее после рисования. Первый объект ArrayList потребуется для сохранения точек по мере их рисования. Когда кнопка мыши отпущена, набор точек следует преобразовать в массив структур Point, и каждый полученный таким образом массив структур Point надо сохранить во втором объекте ArrayList.

В программе ScribbleWithSave главным объектом ArrayList (хранящим массивы Point) является поле arrlstApts. Поле arrlstPts служит для сохранения всех точек в наборе по мере их рисования.

ScribbleWithSave.cs

  //-------------------------------------------------
  // ScribbleWithSave.cs (C) 2001 by Charles Petzold
  //-------------------------------------------------
  using System;
  using System.Collections;          // For ArrayList
  using System.Drawing;
  using System.Windows.Forms;

  class ScribbleWithSave: Form
  {
      ArrayList arrlstApts = new ArrayList();
      ArrayList arrlstPts;
      bool      bTracking;

      public static void Main()
      {
          Application.Run(new ScribbleWithSave());
      }
      public ScribbleWithSave()
      {
          Text = "Scribble with Save";
          BackColor = SystemColors.Window;
          ForeColor = SystemColors.WindowText;
      }
      protected override void OnMouseDown(MouseEventArgs mea)
      {
          if (mea.Button != MouseButtons.Left)
               return;

          arrlstPts = new ArrayList();
          arrlstPts.Add(new Point(mea.X, mea.Y));

          bTracking = true;
      }
      protected override void OnMouseMove(MouseEventArgs mea)
      {
          if (!bTracking)
              return;

          arrlstPts.Add(new Point(mea.X, mea.Y));

          Graphics grfx = CreateGraphics();
          grfx.DrawLine(new Pen(ForeColor),
                        (Point) arrlstPts[arrlstPts.Count - 2],
                        (Point) arrlstPts[arrlstPts.Count - 1]);
          grfx.Dispose();
      }
      protected override void OnMouseUp(MouseEventArgs mea)
      {
          if (!bTracking)
              return;

          Point[] apt = (Point[]) arrlstPts.ToArray(typeof(Point));
          arrlstApts.Add(apt);
          bTracking = false;
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          Graphics grfx = pea.Graphics;
          Pen      pen  = new Pen(ForeColor);

          for (int i = 0; i < arrlstApts.Count; i++)
              grfx.DrawLines(pen, (Point[]) arrlstApts[i]);
      }
  }

Каждый раз, когда пользователь нажимает левую кнопку и программа получает вызов OnMouseDown, создается новый объект ArrayList. Первый член объекта ArrayList будет описывать текущую позицию курсора мыши в тот момент:

  arrlstPts.Add{new Point(mea.X, mea.Y));

Далее при каждом вызове OnMouseMove программа добавляет дополнительные точки.

Получив вызов OnMouseUp, программа преобразует набор структур Point в массив Point при помощи метода ToArray:

  Point[] apt = (Point[]) arrlstPts.ToArray(typeof(Point));

(Здесь используется перегруженная версия метода ToArray, не требующая аргументов. При отсутствии аргумента возвращается массив с элементами типа Object, а если аргумент задан — объект типа Array.) Затем полученный массив Point добавляется к arrlstApts:

  arrlstApts.Add(apt);

Что здесь действительно красиво, так это метод OnPaint. Он просто обрабатывает в цикле все элементы arrlstApts, преобразуя каждый из них в массив Point, который затем передается методу DrawLines. (He думаю, что здесь так уж необходимо выводить линии, хранимые в arrlstPts, но соответствующий код легко добавить.)

Конечно, ScribbleWithSave не может сохранить бесконечное число точек — когда-то просто закончится свободная память. Чтобы защитить программу от подобной ситуации, надо поместить вызов метода Add объекта ArrayList в блок try. Но я не знаю, как проверить такую ситуацию, так как она возникнет лишь после того, как будет нарисовано очень много фигур.


netlib.narod.ru< Назад | Оглавление | Далее >

Сайт управляется системой uCoz