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

Курсор мыши

Курсор мыши — это небольшая растровая картинка, показывающая позицию мыши на экране. Как известно, в зависимости от положения на экране его внешний вид может меняться. Часто это стрелка, но если навести ее на границу формы, она превратится в двунаправленную стрелку. В поле для ввода текста курсор становится похож на букву I.

Курсор мыши — это объект типа Cursor, определенный в пространстве имен System.Windows.Forms. Вообще легче всего получить курсор при помощи класса Cursors (обратите внимание на множественное число). Класс Cursors также определяется в пространстве имен System.Windows.Forms и состоит исключительно из 28 статических неизменяемых свойств, возвращающих предопределенные объекты типа Cursor.


Статические неизменяемые свойства Cursors



AppStarting PanNorth
Arrow PanNW
Cross PanSE
Default PanSouth
Hand PanSW
Help PanWest
HSplit SizeAll
IBeam SizeNESW
No SizeNS
NoMove2D SizeNWSE
NoMoveHoriz SizeWE
NoMoveVert UpArrow
PanEast VSplit
PanNE WaitCursor


Даже если получать объекты Cursor только из класса Cursors, три свойства класса Cursor могут быть полезны:


Статические свойства Cursor



Тип Свойство Доступ

Cursor Current Чтение/запись
Point Position Чтение/запись
Rectangle Clip Чтение/запись


Как вы знаете, у класса Control есть статическое свойство MousePosition, но оно неизменяемо. Оно не годится для установки позиции курсора мыши. Свойство Cursor.Position изменяемое, но приложения редко устанавливают позицию курсора самостоятельно (см. программу Beziers из главы 13, устанавливающую позицию курсора с помощью свойства Cursor.Position). Свойство Cursor.Clip ограничивает область движения курсора мыши заданным прямоугольником. Это свойство можно установить только при захвате мыши. Значения свойств Position и Clip исчисляются в координатах экрана, поэтому после получения их значения, вероятно придется использовать метод PointToClient, а перед их установкой — метод PointToScreen.

Текущий курсор мыши также можно устанавливать с помощью свойства Cursor.Current, однако это работает не всегда. Но прежде позвольте мне показать пару примеров, где это свойство работает.

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

  Cursor.Current = Cursors.WaitCursor;

После этого, закончив длительную обработку, программа может восстановить прежний вид курсора, вызвав:

  Cursor.Current = Cursors.Arrow;

Однако если пользователь работает в Windows без мыши, «песочные часы» будут невидимы. Чтобы выводить курсор независимо от наличия мыши, программа может использовать один из следующих статических методов класса Cursor:


Статические методы Cursor



void Show()
void Hide()


Можно рассматривать курсор как объект, с которым ассоциирована переменная счетчика видимости (show-count variable). Если мышь установлена, этой переменной изначально присваивается значение 1, в противном случае она равна 0. Метод Cursor.Show увеличивает значение переменной видимости, a Cursor.Hide — уменьшает. Курсор мыши видим, если значение этой переменной больше 0, и скрыт в противном случае.

Это значит, что приложение должно поддерживать баланс вызовов Cursor.Show и Cursor.Hide. Если число вызовов Show больше, чем Hide, то программа рискует оставить курсор на экране в отсутствие мыши, а если число вызовов Hide превышает число вызовов Show, то курсор станет невидим, даже если мышь установлена. К счастью, подобные ошибки влияют на курсор, только когда он находится над окном сбойного приложения.

В этой главе есть одна программа, которая может тратить значительное время на исполнение метода OnPaint, — это MouseConnect. Следующая программа создает подкласс класса MouseConnect и выводит «песочные часы» при обработке метода OnPaint.

В этом конкретном случае вызывать Show и Hide не обязательно, поскольку, если мышь не установлена, пользователь изначально не сможет инициировать длительную обработку вызова OnPaint!

MouseConnectWaitCursor.cs

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

  class MouseConnectWaitCursor: MouseConnect
  {
      public new static void Main()
      {
          Application.Run(new MouseConnectWaitCursor());
      }
      public MouseConnectWaitCursor()
      {
          Text = "Mouse Connect with Wait Cursor";
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          Cursor.Current = Cursors.WaitCursor;
          Cursor.Show();

          base.OnPaint(pea);

          Cursor.Hide();
          Cursor.Current = Cursors.Arrow;
      }
  }

Следующая программа при помощи вызова Cursor.Current во время исполнения метода OnMouseMove показывает, как выглядят все 28 предопределенных курсоров.

MouseCursors.cs

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

  class MouseCursors: Form
  {
      Cursor[] acursor = 
      { 
          Cursors.AppStarting, Cursors.Arrow,       Cursors.Cross,
          Cursors.Default,     Cursors.Hand,        Cursors.Help,
          Cursors.HSplit,      Cursors.IBeam,       Cursors.No,
          Cursors.NoMove2D,    Cursors.NoMoveHoriz, Cursors.NoMoveVert,
          Cursors.PanEast,     Cursors.PanNE,       Cursors.PanNorth,
          Cursors.PanNW,       Cursors.PanSE,       Cursors.PanSouth,
          Cursors.PanSW,       Cursors.PanWest,     Cursors.SizeAll,
          Cursors.SizeNESW,    Cursors.SizeNS,      Cursors.SizeNWSE,
          Cursors.SizeWE,      Cursors.UpArrow,     Cursors.VSplit,
          Cursors.WaitCursor
      };
      string[] astrCursor = 
      { 
          "AppStarting",       "Arrow",             "Cross",
          "Default",           "Hand",              "Help",
          "HSplit",            "IBeam",             "No",
          "NoMove2D",          "NoMoveHoriz",       "NoMoveVert",
          "PanEast",           "PanNE",             "PanNorth",
          "PanNW",             "PanSE",             "PanSouth",
          "PanSW",             "PanWest",           "SizeAll",
          "SizeNESW",          "SizeNS",            "SizeNWSE",
          "SizeWE",            "UpArrow",           "VSplit",
          "WaitCursor"
      };

      public static void Main()
      {
          Application.Run(new MouseCursors());
      }
      public MouseCursors()
      {
          Text = "Mouse Cursors";
          BackColor = SystemColors.Window;
          ForeColor = SystemColors.WindowText;
          ResizeRedraw = true;
      }
      protected override void OnMouseMove(MouseEventArgs mea)
      {
          int x = Math.Max(0, Math.Min(3, mea.X / (ClientSize.Width  / 4)));
          int y = Math.Max(0, Math.Min(6, mea.Y / (ClientSize.Height / 7)));

          Cursor.Current = acursor[4 * y + x];
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          Graphics     grfx   = pea.Graphics;
          Brush        brush  = new SolidBrush(ForeColor);
          Pen          pen    = new Pen(ForeColor);
          StringFormat strfmt = new StringFormat();

          strfmt.LineAlignment = strfmt.Alignment = StringAlignment.Center;

          for (int y = 0; y < 7; y++)
          for (int x = 0; x < 4; x++)
          {
              Rectangle rect = Rectangle.FromLTRB(
                                        x      * ClientSize.Width  / 4,
                                        y      * ClientSize.Height / 7,
                                       (x + 1) * ClientSize.Width  / 4,
                                       (y + 1) * ClientSize.Height / 7);

              grfx.DrawRectangle(pen, rect);
              grfx.DrawString(astrCursor[4 * y + x], 
                              Font, brush, rect, strfmt);
          }
      }
  }

Программа выводит таблицу с возможными значениями свойства Cursors. Просто наведите курсор мыши на какую-нибудь ячейку, чтобы увидеть, как выглядит соответствующий курсор. Вот снимок экрана с курсором Cursors.Help:


Рис. 8.3.

Выше сказано, что свойство Cursor.Current работает не всегда. Оно точно работает в двух приведенных выше программах, но они иллюстрируют лишь два из возможных способов использования Cursor.Current. Запомните важное правило: если форма (или любой объект-потомок Control) не устанавливает свойство Cursor.Current при событии MouseMove, то во время этого события курсор мыши будет установлен как обычный курсор для данной формы (элемента управления). Программа MouseCursors работает, поскольку устанавливает свойство Cursor.Current при вызове OnMouseMove.

А как насчет MouseConnectWaitCursor? Хотя эта программа не устанавливает Cursor.Current во время OnMouseMove, она устанавливает Cursor.Current при исполнении метода OnPaint и сбрасывает значение этого свойства до завершения OnPaint, причем в это время она не получает вызовы OnMouseMove. Исполнение методов в этой программе никогда не прерывается для исполнения другого метода в том же потоке.

Однако, установив свойство Cursor.Current при исполнении конструктора, OnMouseDown или какого-нибудь другого события, нельзя ожидать, что по окончании обработки установленный вид курсора сохранится. Как только программа получит вызов OnMouseMove, произойдет сброс курсора.

Но есть способ установить курсор раз и навсегда. Для этого надо назначить курсор элементу управления (форме), используя свойство Cursor, определенное в классе Control:


Свойства Control (выборочно)



Тип Свойство Доступ

Cursor Cursor Чтение/запись


Например, в конструкторе формы можно вызвать:

  Cursor = Cursors.Hand;

и каждый раз, проходя над клиентской областью этой формы, курсор будет принимать вид ладони. Выше я говорил, что, если не устанавливать свойство Cursor.Current при исполнении метода OnMouseMove, курсор будет установлен как обычный курсор для этой формы или элемента управления (реально он будет установлен в соответствии со значением свойства Cursor). Вероятно, прежде чем программа получает вызов OnMouseMove при движении мыши, где-то за кулисами происходит процесс, эквивалентный исполнению инструкции:

  Cursor.Current = Cursor;

Слева от знака равенства стоит класс Cursor, а справа — свойство Cursor формы. Свойство Current класса Cursor — статическое, а одноименное свойство класса Control — нет.

Имеет смысл назначать курсоры элементам управления, поскольку у разных элементов управления иногда должны быть разные курсоры. Самые очевидные примеры — элементы управления TextBox и RichTextBox, с которыми связан курсор Cursors.IBeam.

Поэкспериментируем с этой методикой, создав программу, сходную по функциональности с MouseCursors. Но вместо 28 ячеек мы создадим 28 элементов управления и назначим всем им разные курсоры мыши.

Раньше (в главе 4), занимаясь созданием элемента управления Panel в программе SysInfoPanel, я отмечал, что это довольно пассивный элемент, выполняющий сравнительно немного действий. Но панель неплохо подошла для решения наших задач, дав поверхность для рисования. Элемент управления Label тоже нельзя назвать очень активным. Его единственное назначение — вывод небольшого фрагмента текста. Вот программа, создающая матрицу из 28 элементов Label каждому из которых назначен свой курсор.

MouseCursorsProperty.cs

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

  class MouseCursorsProperty: Form
  {
      Label[] acntl = new Label[28];

      public static void Main()
      {
          Application.Run(new MouseCursorsProperty());
      }
      public MouseCursorsProperty()
      {
          Cursor[] acursor = 
          { 
              Cursors.AppStarting, Cursors.Arrow,       Cursors.Cross,
              Cursors.Default,     Cursors.Hand,        Cursors.Help,
              Cursors.HSplit,      Cursors.IBeam,       Cursors.No,
              Cursors.NoMove2D,    Cursors.NoMoveHoriz, Cursors.NoMoveVert,
              Cursors.PanEast,     Cursors.PanNE,       Cursors.PanNorth,
              Cursors.PanNW,       Cursors.PanSE,       Cursors.PanSouth,
              Cursors.PanSW,       Cursors.PanWest,     Cursors.SizeAll,
              Cursors.SizeNESW,    Cursors.SizeNS,      Cursors.SizeNWSE,
              Cursors.SizeWE,      Cursors.UpArrow,     Cursors.VSplit,
              Cursors.WaitCursor
          };
          string[] astrCursor = 
          { 
              "AppStarting",       "Arrow",             "Cross",
              "Default",           "Hand",              "Help",
              "HSplit",            "IBeam",             "No",
              "NoMove2D",          "NoMoveHoriz",       "NoMoveVert",
              "PanEast",           "PanNE",             "PanNorth",
              "PanNW",             "PanSE",             "PanSouth",
              "PanSW",             "PanWest",           "SizeAll",
              "SizeNESW",          "SizeNS",            "SizeNWSE",
              "SizeWE",            "UpArrow",           "VSplit",
              "WaitCursor"
          };

          Text = "Mouse Cursors Using Cursor Property";

          for (int i = 0; i < 28; i++)
          {
              acntl[i] = new Label();
              acntl[i].Parent = this;
              acntl[i].Text = astrCursor[i];
              acntl[i].Cursor = acursor[i]; 
              acntl[i].BorderStyle = BorderStyle.FixedSingle;
          }
          OnResize(EventArgs.Empty);
      }
      protected override void OnResize(EventArgs ea)
      {
          for (int i = 0; i < acntl.Length; i++)
          {
              acntl[i].Bounds = Rectangle.FromLTRB(
                                       (i % 4    ) * ClientSize.Width  / 4, 
                                       (i / 4    ) * ClientSize.Height / 7,
                                       (i % 4 + 1) * ClientSize.Width  / 4, 
                                       (i / 4 + 1) * ClientSize.Height / 7);
          }
      }
  }

Во время исполнения конструктора формы программа создает 28 объектов Label и сохраняет их в массиве acntl, который представляет собой поле класса. Конструктор устанавливает четыре свойства у каждого объекта Label. Первое из них указывает родительский объект элемента управления, которым является форма. Это означает, что данный элемент управления появится в клиентской области формы:

  acntl[i].Parent = this;

В свойство Text каждого элемента управления заносится имя одного из 28 предопределенных курсоров:

  acntl[i].Text = astrCursor[i];

Программа также заносит в свойство Cursor объекта Label соответствующий объект Cursor:

  acntl[i].Cursor = acursor[i];

В завершение свойству BorderStyle присваивается значение, определяющее одиночную линию:

  acntl[i].BorderStyle = BorderStyle.FixedSingle;

Однако конструктор не задает ни позиции, ни размера элементов управления. Эти параметры устанавливаются при исполнении метода OnResize формы. Размер всех элементов управления становится равным 1/4 ширины и 1/7 высоты клиентской области формы. (Ну, не совсем так. Если задать одинаковую ширину и высоту для всех элементов управления, возникнут проблемы с округлением, что приведет к появлению промежутков между ними. Расчет значения свойства Bounds для элементов управления при помощи метода Rectangle.FromLTRB позволяет избавиться от этой проблемы.)

В работе эта программа выглядит так же, как предыдущая версия MouseCursors, но при проходе над каждым элементом управления курсор мыши изменяется автоматически, не требуя обработки события MouseMove.


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

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