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

Прорисовка владельцем

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

Все, что можно сделать с меню, помимо уже рассмотренных методик, требует применения прорисовки владельцем (owner-draw). Для каждого объекта MenuItem, который вы хотите рисовать самостоятельно, надо установить равным true следующее свойство:


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



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

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


Обычно его задают только для пунктов всплывающих меню. Если присвоить OwnerDraw значение true, нужно установить обработчики двух событий:


События MenuItem (выборочно)



Событие Метод Делегат Параметры

MeasureItem OnMeasureItem MeasureItemEventHandler MeasureItemEventArgs
DrawItem OnDrawItem DrawItemEventHandler DrawItemEventArgs


При подготовке к рисованию пункта меню (обычно перед выводом всплывающего меню) Windows вызывает обработчик события MeasureItem. Событию соответствует объект типа MeasureItemEventArgs.


Свойства MeasureItemEventArgs



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

int Index Чтение
Graphics Graphics Чтение
int ItemWidth Чтение/запись
int ItemHeight Чтение/запись


Обработчик события MeasureItem получает свойства ItemWidth и ItemHeight, равные 0. Вы должны присвоить им значения ширины и высоты рисуемого пункта меню. Свойство Index позволяет определить, для какого пункта меню вычисляются размеры. При необходимости свойство Graphics получит разрешение устройства в точках на дюйм или размер текстовых элементов с помощью MeasureString.

Вскоре Windows вызывает обработчики события DrawItem, которому соответствует объект DrawItemEventArgs:


Свойства DrawItemEventArgs



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

int Index Чтение
Graphics Graphics Чтение
Rectangle Bounds Чтение
DrawItemState State Чтение
Font Font Чтение
Color BackColor Чтение
Color ForeColor Чтение


Задача программы — используя объект Graphics, нарисовать пункт меню внутри прямоугольника, определенного свойством Bounds. He следует полагать, что левый верхний угол свойства Bounds расположен в точке с координатами (0, 0)! На самом деле прямоугольник Bounds находится внутри всплывающего меню.

Ширина прямоугольника больше, чем величина, указанная во время обработки события MeasureItem, чтобы вместить отметку стандартного размера слева от пункта меню.

Перечисление DrawItemState указывает, является ли пункт меню выделенным, заблокированным или отмеченным:


Перечисление DrawItemState



Член Значение

None 0
Selected 1
Grayed 2
Disabled 4
Checked 8
Focus 16
Default 32
HotLight 64
Inactive 128
NoAccelerator 256
NoFocusRect 512
ComboBoxEdit 4096


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

Обычно свойство BackColor объекта DrawItemEventArgs равно SystemColors.Window, а свойство ForeColor — SystemColors.WindowText. Чтобы меню было совместимо с обычными меню, не используйте эти цвета! Вместо этого применяйте SystemColors.Menu и SystemColors.MenuText. Если пункт выбран, BackColor равно SystemColors.Highlight, a ForeColor — SystemColors.HighlightText. Это правильные цвета для выбранных пунктов.

Свойство Font свойства DrawItemEventArgs равно SystemInformation.MenuFont.

DrawItemEventArgs содержит также два метода, помогающие нарисовать элемент:


Методы DrawItemEventArgs



void DrawBackground();
void DrawFocusRectangle();


Метод DrawFocusRectangle не используется для пунктов меню.

Для рисования стрелок, переключателей и флажков полезным будет статический метод класса ControlPaint.


Статические методы DrawMenuGlyph класса ControlPaint



void DrawMenuGlyph(Graphics grfx, Rectangle rect,
                   MenuGlyph mg)
void DrawMenuGlyph(Graphics grfx, int x, int y, int cx, int cy,
                   MenuGlyph mg)


MenuGlyph представляет собой еще одно перечисление:


Перечисление MenuGlyph



Член Значение

Min 0
Arrow 0
Checkmark 1
Bullet 2
Max 2


Измерять размер пункта меню можно по-разному. Нормальный шрифт, применяемый для вывода текста пунктов (как я упоминал), доступен из SystemInformation.MenuFont. Другая важная величина хранится в SystemInformation.MenuCheckSize и представляет собой устанавливаемую по умолчанию ширину и высоту отметки у пункта меню. Как видите, для статического метода БееЮControlPaint.DrawMenuGlyph нужно указать высоту и ширину глифа (например, флажка). Если ваши пункты меню выше, чем обычные, и вы хотите применять флажки, возможно, вам потребуется масштабировать их. Это приведет к тому, что придется учитывать ширину масштабированного флажка при измерении размера пункта в процессе обработки события MeasureItem.

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

OwnerDrawMenu.cs

  //----------------------------------------------
  // OwnerDrawMenu.cs (C) 2001 by Charles Petzold
  //----------------------------------------------
  using System;
  using System.Drawing;
  using System.Drawing.Text;    // Для перечисления HotkeyPrefix
  using System.Windows.Forms;

  class OwnerDrawMenu: Form
  {
      const    int  iFontPointSize = 18; // Для пунктов меню
      MenuItem      miFacename;

      public static void Main()
      {
          Application.Run(new OwnerDrawMenu());
      }
      public OwnerDrawMenu()
      {
          Text = "Owner-Draw Menu";

          // Пункты верхнего уровня

          Menu = new MainMenu();
          Menu.MenuItems.Add("&Facename");

          // Массив пунктов подменю

          string[] astrText = {"&Times New Roman", "&Arial", "&Courier New"};
          MenuItem [] ami = new MenuItem[astrText.Length];

          EventHandler ehOnClick = new EventHandler(MenuFacenameOnClick);
          MeasureItemEventHandler ehOnMeasureItem = 
                    new MeasureItemEventHandler(MenuFacenameOnMeasureItem);
          DrawItemEventHandler ehOnDrawItem = 
                    new DrawItemEventHandler(MenuFacenameOnDrawItem);

          for (int i = 0; i < ami.Length; i++)
          {
              ami[i]              = new MenuItem(astrText[i]);
              ami[i].OwnerDraw    = true;
              ami[i].RadioCheck   = true;
              ami[i].Click       += ehOnClick;
              ami[i].MeasureItem += ehOnMeasureItem;
              ami[i].DrawItem    += ehOnDrawItem;
          }
          miFacename = ami[0];
          miFacename.Checked = true;

          Menu.MenuItems[0].MenuItems.AddRange(ami);
      }
      void MenuFacenameOnClick(object obj, EventArgs ea)
      {
          miFacename.Checked = false;
          miFacename = (MenuItem) obj;
          miFacename.Checked = true;

          Invalidate();
      }
      void MenuFacenameOnMeasureItem(object obj, MeasureItemEventArgs miea)
      {
          MenuItem mi = (MenuItem) obj;
          Font font = new Font(mi.Text.Substring(1), iFontPointSize);

          StringFormat strfmt = new StringFormat();
          strfmt.HotkeyPrefix = HotkeyPrefix.Show;

          SizeF sizef = miea.Graphics.MeasureString(mi.Text, font, 
                                                    1000, strfmt);

          miea.ItemWidth  = (int) Math.Ceiling(sizef.Width);
          miea.ItemHeight = (int) Math.Ceiling(sizef.Height);

          miea.ItemWidth += SystemInformation.MenuCheckSize.Width * 
                              miea.ItemHeight / 
                                   SystemInformation.MenuCheckSize.Height;

          miea.ItemWidth -= SystemInformation.MenuCheckSize.Width;
      }
      void MenuFacenameOnDrawItem(object obj, DrawItemEventArgs diea) 
      {
          MenuItem mi   = (MenuItem) obj;
          Graphics grfx = diea.Graphics;
          Brush    brush;

          // Создание Font и StringFormat.

          Font font = new Font(mi.Text.Substring(1), iFontPointSize);
          StringFormat strfmt = new StringFormat();
          strfmt.HotkeyPrefix = HotkeyPrefix.Show;

          // Вычисление прямоугольников флажка и текста

          Rectangle rectCheck = diea.Bounds;

          rectCheck.Width = SystemInformation.MenuCheckSize.Width * 
                              rectCheck.Height / 
                                   SystemInformation.MenuCheckSize.Height;

          Rectangle rectText = diea.Bounds;

          rectText.X += rectCheck.Width;

          // Выполняем все рисование

          diea.DrawBackground();

          if ((diea.State & DrawItemState.Checked) != 0)
               ControlPaint.DrawMenuGlyph(grfx, rectCheck, MenuGlyph.Bullet);
          
          if ((diea.State & DrawItemState.Selected) != 0)
               brush = SystemBrushes.HighlightText;
          else
               brush = SystemBrushes.FromSystemColor(SystemColors.MenuText);

          grfx.DrawString(mi.Text, font, brush, rectText, strfmt);
      }
      protected override void OnPaint(PaintEventArgs pea)
      {
          Graphics grfx = pea.Graphics;
          Font font = new Font(miFacename.Text.Substring(1), 12);
          StringFormat strfmt = new StringFormat();
          strfmt.Alignment = strfmt.LineAlignment = StringAlignment.Center;

          grfx.DrawString(Text, font, new SolidBrush(ForeColor), 0, 0);
      }
  }

Я присваиваю значение 18 полю iFontPointSize, просто чтобы шрифт был большим и можно было удостовериться в том, что измерение и рисование работают правильно.

Метод MenuFacenameOnMeasureItem начинает работу с получения объекта MenuItem, который нужно измерить, и создания шрифта в зависимости от свойства Text этого пункта:

  MenuItem mi = (MenuItem)obj;
  Font font = new Font(mi.Text.Substring(1), iFontPointSize);

Метод Substring свойства Text позволяет пропустить амперсанд. Затем метод создает объект StringFormat, указывающий на то, что буква после амперсанда будет подчеркнута:

  StringFormat strfmt = new StringFormat();
  strfmt.HotkeyPrefix = HotkeyPrefix.Show;

Свойство Text пункта меню измеряется на основе новых объектов Font и StringFormat:

  SizeF sizef = miea.Graphics.MeasureString(mi.Text, font, 1000, strfmt);

Структура sizef определяет размер пункта меню без отметки:

  miea.ItemWidth  = (int) Math.Ceiling(sizef.Width);
  miea.ItemHeight = (int) Math.Ceiling(sizef.Height);

Но ширину нужно увеличить на ширину отметки после того, как отметка была масштабирована соответственно высоте текста:

  miea.ItemWidth += SystemInformation.MenuCheckSize.Width *
                    mtea.ItemHeight /
                    SystemInformation.MenuCheckSize.Height;

А затем уменьшена на нормальную ширину отметки:

  miea.ItemWidth -= SystemInformation.MenuCheckSize.Width;

Метод MenuFacenameOnDrawItem точно так же создает объекты Font и StringFormat и заполняет две структуры Rectangle на основе свойства Bounds объекта DrawItemEventArgs. Первый прямоугольник соответствует положению и размеру отметки:

  Rectangle rectCheck = diea.Bounds;
  rectCheck.Width = SystemInformation.MenuCheckSize.Width *
                    rectCheck.Height /
                    SystemInformation.MenuCheckSize.Height;

Второй — размеру и положению строки текста:

  Rectangle rectText = diea.Bounds;
  rectText.X += rectCheck.Width;

Далее все просто. Метод DrawBackground рисует фон, DrawMenuGlyph рисует отметку, a DrawString — текст, цвет которого определяется тем, выделен пункт меню или нет. Вот результат:


Рис. 14.2.

Для некоторых простых приложений такая исчерпывающая обработка событий MeasureItem и DrawItem не нужна. Например, следующая программа загружает 64-пиксельное квадратное растровое изображение из ресурса и использует его как пункт меню.

HelpMenu.cs

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

  class HelpMenu: Form
  {
      Bitmap bmHelp;

      public static void Main()
      {
          Application.Run(new HelpMenu());
      }
      public HelpMenu()
      {
          Text = "Help Menu";

          bmHelp = new Bitmap(GetType(), "HelpMenu.Bighelp.bmp");

          Menu = new MainMenu();
          Menu.MenuItems.Add("&Help");

          MenuItem mi     = new MenuItem("&Help");
          mi.OwnerDraw    = true;
          mi.Click       += new EventHandler(MenuHelpOnClick);
          mi.DrawItem    += new DrawItemEventHandler(MenuHelpOnDrawItem);
          mi.MeasureItem += 
                    new MeasureItemEventHandler(MenuHelpOnMeasureItem);

          Menu.MenuItems[0].MenuItems.Add(mi);
      }
      void MenuHelpOnMeasureItem(object obj, MeasureItemEventArgs miea)
      {
          miea.ItemWidth  = bmHelp.Width;
          miea.ItemHeight = bmHelp.Height;
      }
      void MenuHelpOnDrawItem(object obj, DrawItemEventArgs diea)
      {
          Rectangle rect = diea.Bounds;
          rect.X += diea.Bounds.Width - bmHelp.Width;
          rect.Width = bmHelp.Width;

          diea.DrawBackground();
          diea.Graphics.DrawImage(bmHelp, rect);
      }
      void MenuHelpOnClick(object obj, EventArgs ea)
      {
          MessageBox.Show("Help not yet implemented.", Text);
      }
  }

Рис. 14.3.

Обработка MeasureItem и DrawItem в данной программе весьма проста. Обработчик Measurement просто устанавливает ItemWidth и ItemHeight равными ширине и высоте растрового изображения, a DrawItem рисует его, по существу выравнивая изображение по правому краю внутри прямоугольника, соответствующего свойству Bounds. Похоже, результат отражает отчаяние пользователя-новичка:


Рис. 14.4.


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

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