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 — текст, цвет которого определяется тем, выделен пункт меню или нет. Вот результат:
Для некоторых простых приложений такая исчерпывающая обработка событий 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); } }
Обработка MeasureItem и DrawItem в данной программе весьма проста. Обработчик Measurement просто устанавливает ItemWidth и ItemHeight равными ширине и высоте растрового изображения, a DrawItem рисует его, по существу выравнивая изображение по правому краю внутри прямоугольника, соответствующего свойству Bounds. Похоже, результат отражает отчаяние пользователя-новичка:
netlib.narod.ru | < Назад | Оглавление | Далее > |