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

Отмеченные пункты меню

Если посмотреть на меню View программы Калькулятор (особенно в научном формате), можно увидеть несколько примеров отмеченных пунктов меню. Пункты меню, представляющие выбор одной опции из двух (например, пункт меню Digit Grouping), могут быть отмечены или сброшены наподобие элемента управления CheckBox.

Другие группы пунктов меню — вроде Hex, Decimal, Octal и Binary — представляют взаимоисключающие варианты. Выбранный в настоящий момент пункт меню выделен закрашенным кружком — переключателем. (Некоторые Windows-программы используют флажки как для взаимоисключающих пунктов, так и для пунктов, могущих находиться в состоянии включено/выключено. Использовать флажок для выделения взаимоисключающих пунктов меню последнее время не принято.)

Контролировать появление флажков и переключателей позволяют свойства MenuItem:


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



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

bool Checked Чтение/запись
bool RadioCheck Чтение/запись


Присвойте значение true свойству Checked, чтобы рядом с пунктом меню выводилась отметка или переключатель. При этом, если свойство RadioCheck равно true, будет выводиться переключатель (для обозначения взаимоисключающих вариантов), а если false — будет выводиться флажок (для элементов типа включено/выключено).

Следующая программа схожа с программой RadioButtons из главы 12. Главное меню содержит единственный пункт — Format, содержащий 10 элементов: 8 переключателей для выбора цвета, девятый — горизонтальная линяя, а десятый — элемент с текстом Fill, при отметке которого эллипс закрашивается.

CheckAndRadioCheck.cs

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

  class CheckAndRadioCheck: Form
  {
      MenuItem miColor, miFill;

      public static void Main()
      {
          Application.Run(new CheckAndRadioCheck());
      }
      public CheckAndRadioCheck()
      {
          Text = "Check and Radio Check";
          ResizeRedraw = true;

          string[]     astrColor = {"Black", "Blue",    "Green",  "Cyan",
                                    "Red",   "Magenta", "Yellow", "White"};
          MenuItem[]   ami       = new MenuItem[astrColor.Length + 2];
          EventHandler ehColor   = new EventHandler(MenuFormatColorOnClick);

          for (int i = 0; i < astrColor.Length; i++)
          {
              ami[i] = new MenuItem(astrColor[i], ehColor);
              ami[i].RadioCheck = true;
          }
          miColor = ami[0];
          miColor.Checked = true;

          ami[astrColor.Length] = new MenuItem("-");

          miFill = new MenuItem("&Fill",
                                new EventHandler(MenuFormatFillOnClick));

          ami[astrColor.Length + 1] = miFill;

          MenuItem mi = new MenuItem("&Format", ami);

          Menu = new MainMenu(new MenuItem[] {mi});
      }
      void MenuFormatColorOnClick(object obj, EventArgs ea)
      {
          miColor.Checked = false;
          miColor = (MenuItem)obj;
          miColor.Checked = true;

          Invalidate();
      }
      void MenuFormatFillOnClick(object obj, EventArgs ea)
      {
          MenuItem mi = (MenuItem)obj;

          mi.Checked ^= true;

          Invalidate();
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          Graphics grfx = pea.Graphics;

          if (miFill.Checked)
          {
              Brush brush = new SolidBrush(Color.FromName(miColor.Text));
              grfx.FillEllipse(brush, 0, 0, ClientSize.Width - 1,
                                            ClientSize.Height - 1);
          }
          else
          {
              Pen pen = new Pen(Color.FromName(miColor.Text));
              grfx.DrawEllipse(pen, 0, 0, ClientSize.Width - 1,
                                          ClientSize.Height - 1);
          }
      }
  }

В конструкторе сначала определяется массив из 8 текстовых строк, соответствующих названиям цветов, а затем массив структур MenuItem достаточного размера для хранения этих 8 названий цветов и еще двух пунктов меню:

  MenuItem[] ami = new MenuItem[astrColor.Length + 2];

Пункты из группы взаимоисключающих обычно связаны с одним обработчиком события Click. Поэтому обработчик события определяется до создания пунктов меню:

  EventHandler ehColor = new EventHandler(MenuFormatColorOnClick);

В цикле for создаются 8 пунктов меню на основе 8 названий цветов и обработчика событий ehColor. Свойство RadioCheck устанавливается в true, так что когда свойство Checked равно true, вместо флажка выводится переключатель.

Переменная miColor, хранящаяся в поле, указывает, какой объект MenuIiem отмечен в настоящее время. Конструктор задает это поле равным первому объекту MenuItem в массиве и устанавливает свойство Checked в true.

  miColor = ami[0];
  miColor.Checked = true;

Рассмотрим обработчик MenuFormatColorOnClick и затем вернемся к конструктору. В начале обработчик Click снимает отметку с отмеченного в настоящий момент пункта меню:

  miColor.Checked = false;

Затем полю miColor присваивается значение первого аргумента обработчика событий, представляющего собой тот пункт меню, по которому был выполнен щелчок:

  miColor = (MenuItem)obj;

Затем обработчик события отмечает пункт меню:

  miColor.Checked = true;

и делает форму недействительной, что приводит к перерисовке эллипса. В этом блоке кода показан обычный способ отмечать и снимать отметки со взаимоисключающих пунктов меню.

Вернемся к конструктору. После создания 8 пунктов, соответствующих 8 цветам, создается девятый — горизонтальная разделительная линия, а затем десятый пункт — Fill:

  miFill = new MenuItem("&Fill",
                        new EventHandler(MenuFormatFillOnClick));
  ami[astrColor.Length + 1] = miFill;

Переменная miFill тоже хранится в поле. Метод OnPaint при рисовании эллипса обращается как к miColor, так и к miFill.

Однако методу MenuFormatFillOnClick не требуется доступ к miFill. Обработчик события получает объект MenuItem, по которому щелкнул пользователь, выполнив приведение типа для первого аргумента:

  MenuItem mi = (MenuItem)obj;

а затем переключает состояние этого пункта:

  mi.Checked ^= true;

Два предыдущих выражения можно заменить одной инструкцией:

  miFill.Checked ^= true;

но из соображений написания более «общих» обработчиков не следует обращаться к конкретному пункту меню в обработчике события. Даже если вы добавите другие пункты меню типа включено/выключено, влияющие на обработку OnPaint, все равно можно будет использовать тот же общий подход к установке/снятию отметок.

Метод OnPaint в программе CheckAndRadioCheck преобразует название цвета из пункта меню в объект Color с помощью статического метода ColorFromName во время подготовки к созданию кисти или пера:

  Color.FromName(mlColor.Text)

Ясно, что не каждый пункт меню содержит свойство Text, которое может быть преобразовано в нужный объект. Подобное использование текста пункта меню — вообще не очень хорошая идея. Во-первых, при переводе меню на другой язык, новый текст вряд ли так легко преобразуется в объект Color. Во-вторых, в названия цветов неудобно вставлять амперсанды, так как их приходится удалять перед передачей текста методу Color.FromName.

Чуть позже я продемонстрирую более общий подход к обработке взаимоисключающих пунктов меню. (Вы спросите, заключается ли он в применении классов-наследников MenuItem? А вы как думаете?)


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

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