Лабораторная работа №4. Меню и menubutton.
Меню предоставляет пользователю пункты, которые внешне выглядят как кнопки. Пункт меню не является полнофункциональным компонентом Тк. При формировании интерфейса создается компонент меню, а затем в него включаются пункты различного назначения. Типы пунктов меню перечислены ниже.
- Пункты вызова команд; их можно сравнить с обычными кнопками.
- Пункты с независимой фиксацией; действуют подобно компонентам checkbutton.
- Пункты с зависимой фиксацией; их можно сравнить с компонентами radiobutton.
- Разделители, предназначенные для визуального разделения пунктов меню на группы.
- Каскадные пункты; используются для вызова подменю.
- Пункты разъединения. При активизации такого пункта связь между меню и соответствующей кнопкой разрывается и меню становится новым окном верхнего уровня.
Компонент menubutton представляет собой кнопку специального типа, после щелчка на которой передается, или отображается, меню. Если вы щелкнете мышью на компоненте menubutton, меню отобразится и будет присутствовать на экране до выбора пункта меню или до тех пор, пока вы не щелкнете за пределами меню. Если, поместив курсор мыши на компонент menubutton, вы нажмете кнопку и будете удерживать ее, то при отпускании кнопки меню е будет отображаться на экране. Если вы отпустите кнопку тогда, когда курсор мыши указывает на меню, будет выбран тот пункт, на который указывает курсоp.
С компонентом menubutton можно также связывать команды. Команда выполняется перед отображением меню, а это значит, что вы можете динамически формировать содержимое меню с ее помощью.
В листинге 30.6 приведен код, посредством которого создаются пункты меню различных типов.
menubutton .mb -text Sampler -menu .mb.menu
pack .mb -padx 10 -pady 10
set m [menu .mb.menu -tearoff 1]
$m add command -label Hello! -command {puts «Hello, World!»}
$m add check -label Boolean -variable foo -command {puts «foo = $foo»}
$m add separator
$m add cascade -label Fruit -menu $m.sub1
set m2 [menu $m.sub1 -tearoff 0]
$m2 add radio -label apple -variable fruit -value apple
$m2 add radio -label orange -variable fruit -value orange
$m2 add radio -label kiwi -variable fruit -value kiwi
В данном примере создаются компонент menubutton и два меню. Главное меню .mb.menu является дочерним по отношению к компоненту menubutton .mb. Это соотношение позволяет корректно отображать меню при выборе menubutton. Аналогично, каскадное подменю .mb.menu.subl является дочерним по отношению к главному меню. Первый пункт — пункт разъединения — меню отображается штриховой линией. При его выборе создается копия меню, которая отображается на экране как новое окно верхнего уровня. Эта возможность полезна в том случае, если пользователю приходится часто выполнять действия с меню. Параметр —tearoff 0 используется для создания подменю без пункта разъединения.
Пункты вызова команд, а также пункты с независимой и зависимой фиксацией аналогичны соответствующим типам кнопок. Конфигурационные параметры для этих пунктов подобны параметрам для кнопок. Основное отличие состоит в том, что в пунктах меню текстовые строки задаются с помощью опции —label, а в кнопках — посредством опции —text. Опции для пунктов меню описаны в табл. 30.6.
Каскадные пункты предназначены для организации связи с другими меню. В правой части каскадного пункта отображается небольшая стрелка. При выборе такого пункта отображается подменю. По необходимости вы можете организовать несколько уровней каскадных меню. При этом число уровней ограничивается только соображениями удобства работы пользователя.
Строка меню.
Строку меню можно сформировать вручную, включив с помощью диспетчера компоновки в состав фрейма несколько компонентов menubutton. По молчанию для menubutton связывание определено таким образом, что при перетаскивании курсора мыши по компонентам отображаются соответствующие меню.
Тк позволяет создавать горизонтальное меню, связанное с окном верхнего уровня. В системах Windows и Unix строка меню отображается в верхней части самого окна. В Macintosh при активизации окна строка меню заменяет главное меню в верхней части экрана. Все пункты в строке меню должны быть каскадными, т.е. при активизации такого пункта должно отображать-•л подменю. Пример создания строки меню показан в листинге 30.7. В нем определены переменные для хранения имен компонентов меню.
set $m [menu .menubar.m$m]
При выполнении кода создаются переменные File, Edit и Help, в которых находятся имена компонентов меню. Далее рассматривается модификация данного примера со скрытыми именами компонентов меню.
Листинг 30.7. Строка меню в Тк
menu .menubar
#Связь с главным окном
. config —menu .menubar
#Создание каскадных меню
foreach m {File Edit Help} {
set $m [menu .menubar.m$m]
.menubar add cascade -label $m -menu .menubar.m$m
$File add command -label Quit -command exit
# Добавление пунктов меню …
Системные меню.
Строка меню, реализованная в Тк 8.0, позволяет добавлять пункты в системное меню Windows, в меню Macintosh, а также в меню Help для всех платформ. Добавление меню осуществляется путем распознавания специальных имен. Например, если строка меню имеет имя .menubar, то специальными именами являются .menubar.system, .menubar.apple и .menubar.help. Меню Help на всех платформах выравнивается по правому краю. Меню Apple обычно связывается с пунктом About… меню приложения. Пункты, добавленные в меню Apple, размещаются в его верхней части. Меню System отображается в строке заголовка окна Windows и содержит, в частности, пункты Close и Minimize.
Контекстные меню.
Контекстные меню не связываются с компонентом menubutton. Вместо этого они отображаются в ответ на нажатие клавиши или при возникновении другого события в приложении. Контекстное меню вызывается с помощью команды tk_popup.
tk_popup меню х у ?пункт?
Последний параметр определяет пункт, который выбирается при отображении меню. Этот параметр не является обязательным. По умолчанию его значение принимается равным 1, т.е. выбирается пункт, следующий за пунктом разъединения. Меню выводится внутри родительского окна, в позиции, определяемой координатами х и у.
Меню опций.
Меню опций содержит пункты с зависимой фиксацией; надпись на компоненте menubutton отображает выбранный пункт. Для создания компонента menubutton и меню, заполненного пунктами с зависимой фиксацией, используется команда tk_optionMenu.
tk_optionMenu путь имя_переменной первое_значение
?значение значение . . .?
При вызове команды в качестве первого параметра задается путь к создаваемому компоненту menubutton. Второй параметр содержит имя переменной. Третий параметр задает исходное значение переменной, а остальные — другие допустимые значения. Помимо текущего выбранного пункта, компонент menubutton отображает символ небольшого размера, указывающий на то, что данный компонент представляет меню опций.
Расположение пунктов меню в несколько столбцов.
В Тк 8.0 была реализована поддержка опции —columnbreak, помещающей пункт меню в начало нового столбца. Такая возможность полезна в том случае, когда пунктами меню являются изображения и они должны быть размещены в виде палитры. Для того чтобы задать в качестве пункта меню изображение, надо использовать опцию —image. Опция —hidemargin позволяет использовать изображения для пунктов с зависимой и независимой фиксацией. В этом случае вокруг изображения, представляющего выбранный пункт, отображается рамка.
События меню.
Выбор пунктов меню с помощью клавиатуры.
По умолчанию связывание для меню позволяет выбирать его пункты с помощью клавиатуры. Процедура выбора начинается с нажатия комбинации клавиш <Alt+x>, где х — буква, идентифицирующая компонент menubutton или каскадный пункт в строке меню. Эта буква определяется с помощью атрибута underline. Значением данного атрибута является номер позиции в названии пункта (отсчет ведется начиная с нуля). Например, чтобы меню File выбиралось с помощью символа F, компонент menubutton должен быть создан следующим образом:
menubutton .menubar.file -text File -underline 0 -menu .menubar.file.m
Если пункт File содержится в строке меню и представляет собой каскадный пункт, то надо использовать следующее выражение:
menu .mbar
. configure -menu .mbar
.mbar add cascade -label File -underline 0 -menu .mbar.file
Если в главном окне пользователь нажмет комбинацию клавиш <Alt+F>, данное меню отобразится на экране. Регистр символа, идентифицирующего меню, не имеет значения.
После того как меню отобразится на экране, вы можете использовать для выбора пункта клавиши со стрелками. Стрелки, направленные вверх и вниз, осуществляют перемещение внутри меню, а стрелки, направленные влево и вправо, — переход между соседними меню. Связывание, определенное по умолчанию, предполагает, что вы создаете меню слева направо.
Если для какого-либо из пунктов меню с помощью опции -underline был определен идентифицирующий символ, то после нажатия соответствующей клавиши этот пункт активизируется. Например, для того, чтобы после нажатия клавиши <Х> вызывался пункт Export, он должен быть создан следующим образом:
.menubar.file.m add command -label Export -underline 1 \ -command File_Export
После нажатия клавиши пробела или <Return> активизируется текущий пункт меню. Клавиша <Esc> отменяет выбор и удаляет меню с экрана.
Виртуальные события меню.
Начиная с Тк 8.0 после выбора пунктов меню генерируется виртуальное событие «MenuSelect». Команда, связанная с событием, вызывается после выбора нового пункта и имеет доступ к этому пункту. Получать информацию об изменении состояния меню проще всего, определив связывание для данного виртуального события. Пример такого подхода показан в листинге 30.8. Подобное оповещение бывает необходимо для организации контекстной подсказки.
Листинг 30.8. Использование виртуального события <<MenuSelect>>
proс MenuChanged {w} {
puts «Menu $w selection: [$w entrycget active -label]» }
bind .mbar.file «MenuSelect» {MenuChanged %W}
Выполнение действий с меню.
Над пунктами меню можно выполнить ряд операций. С операцией add вы уже знакомы. Операция entry configure выполняется подобно операции configure, определенной для компонентов. Она получает такие же пары атрибут-значение, которые используются при добавлении пункта меню. Операция delete удаляет пункты меню в определенном диапазоне. Остальные операции используются библиотечными сценариями, которые реализуют,
стандартные связывания для меню.
При обращении к пункту меню используется его индекс. Индекс может быть числовым (индекс первого пункта равен нулю) или символьным. В табл. 30.3 приведены сведения о формате индекса. Чаще всего в качестве индекса используется шаблон, по которому определяется метка нужного пункта меню. Сравнение с шаблоном происходит по тем же правилам, что и при выполнении команды string match. Применение шаблона избавляет от необходимости вычислять числовое значение индекса.
табл. 30.3
| индекс | Числовой индекс. Отсчет индексов ведется с нуля |
| active | Активизированный пункт. Активизация может быть осуществлена как путем помещения на пункт курсора мыши, так и посредством клавиатуры |
| end | Последний пункт меню |
| last | Синоним end |
| none | Отсутствие пунктов меню |
| @координата_у | Пункт, определяемый посредством координаты у. В связываниях используется выражение @%у |
| pattern | Строка-шаблон для проверки текста, соответствующего пункту меню |
В табл. 30.4 описаны операции с меню. Компонент меню обозначается как $w.
Таблица 30.4. Операции с меню
| Sw activate индекс | Подсвечивание указанного пункта меню |
| Sw add тип ?’опция
значение? … |
Добавляет новый пункт меню указанного типа, задавая при этом значения атрибутов |
| Sw cget опция | Возвращает значение конфигурационной опции |
| Sw clone | Создает копию меню. Данная операция используется для создания строк меню, а также меню, не связанного с вышестоящими компонентами |
| configure ?опция? «значение? … | Возвращает конфигурационную информацию для меню |
| $w delete индекс_1 ‘нндекс_2? | Удаляет пункты меню от первого до второго индекса |
| $w entrycget индекс опция | Возвращает значение опции для указанного пункта меню |
| $w entryconfigure индекс ?опция? ?значение? … | Запрашивает конфигурационную информацию для указанного пункта меню или создает новые установки |
| $w index индекс | Возвращает числовое значение индекса |
| $w insert тип индекс ?опция значение? … | Действует подобно операции add, но включает новый пункт после пункта с указанным индексом |
| $w invoke индекс | Вызывает команду, связанную с указанным пунктом меню |
| $w post x у | Отображает меню в позиции с указанными координатами |
| $w postcascade индекс | Отображает каскадное меню, соответствующее указанному пункту |
| $w type индекс | Возвращает тип указанного пункта меню |
| $w impost | Удаляет меню |
| $w yposition индекс | Возвращает значение координаты у, соответствующей верхней части пункта |
Атрибуты меню.
Компонент меню содержит несколько глобальных атрибутов, кроме того, для каждого пункта меню определяются атрибуты, которые описывают его внешний вид и поведение. В табл. 30.5 представлены атрибуты, которые относятся к меню в целом, однако они могут быть переопределены для каждого пункта. В таблице используются имена ресурсов X Window; составляющие их слова начинаются с прописной буквы. В Tcl-командах для определения атрибутов применяются опции. Имя опции совпадает с именем соответствующего атрибута, за исключением того, что опция начинается с символа — и в ней используются только буквы нижнего регистра.
Таблица 30.5. Имена ресурсов для атрибутов меню
| activeBackground | Цвет фона, отображаемый тогда, когда курсор мыши находится на пункте меню |
| act iveBorderWidth | Толщина обрамления вокруг активизированного пункта |
| act iveForeground | Цвет, которым текст отображается в тот момент, когда курсор мыши находится на пункте меню |
| background | Цвет фона, отображаемый тогда, когда пункт меню находится в нормальном состоянии |
| borderWidth | Толщина обрамления, отображаемого вокруг меню (за исключением тех систем, в которых используются платформенно-ориентированные меню, например Windows) |
| cursor | Курсор, отображаемый тогда, когда он находится на пункте меню |
| disabledForeground | Цвет переднего плана (текста), отображаемый в случае, если пункт меню недоступен |
| font | Шрифт для отображения текста, используемый по умолчанию |
| foreground | Цвет переднего плана. Допустимо сокращение до f g |
| postCommand | Tcl-команда, которая вызывается перед отображением меню |
| relief | Тип рельефного отображения для меню (за исключением тех систем, в которых используются платформенно-ориентированные меню, например Windows) |
| selectColor | Цвет селектора для пунктов меню с зависимой и независимой фиксацией |
| takeFocus | Управляет передачей фокуса ввода в результате действий с клавиатурой |
| tearOff | Принимает значение true, если меню содержит пункт -разъединения |
| tearOffCommand | Команда, которая выполняется при разъединении меню. При вызове команды добавляются два параметра: исходное меню и новое разъединенное меню |
| title | Заголовок окна, создаваемого при разъединении меню. Если значение представляет собой пустую строку (такое значение принимается по умолчанию), в качестве заголовка используется текст, отображаемый на menubutton, либо текст каскадного пункта, от которого меню было отсоединено (Тк 8.0) |
| type | Значение normal, menubar или tear of f. Доступно только для чтения (Тк 8.0) |
В табл. 30.6 описаны атрибуты пунктов меню. Они представлены в формате опций, используемых в Tcl-командах (т.е. начинаются со знака — и содержат только символы нижнего регистра). Атрибуты для пунктов меню непосредственно не поддерживаются базой данных ресурсов. Однако в листинге 31.6 будет показан способ использования базы данных ресурсов дляработы с пунктами меню. .
Таблица 30.6. Атрибуты пунктов меню
| -activebackground | Цвет фона, отображаемый тогда, когда курсор мыши находится на пункте меню | ||
| -activeforeground | Цвет переднего плана, т.е. цвет, которым текст отображается в тот момент, когда курсор мыши находится на пункте меню | ||
| -accelerator | Текст, представляющий собой напоминание о связывании с клавишей | ||
| -background | Цвет фона, отображаемый тогда, когда пункт меню находится в нормальном состоянии | ||
| -bitmap | Битовая карта, отображаемая вместо текста | ||
| -columnbreak | Указывает на то, что пункт меню должен стать первым в новом столбце (Тк 8.0) | ||
| -command | Tcl-команда, которая вызывается при активизации пункта меню | ||
| -compound | Расположение изображения или битовой карты относительно текста. Значения: bottom, center, left, right, top или none (принимается по умолчанию) (Tk 8.4) | ||
| -font | Шрифт, используемый по умолчанию для отображения текста | ||
| -foreground | Цвет переднего плана. Допустимо сокращение до f g | ||
| -hidemargin | Отказ от границ, зарезервированных для индикатора (Тк 8.0) | ||
| -image | Изображение, отображаемое вместо текста или битовой карты | ||
| -indicatoron | Логическое значение, управляющее отображением индик.-.-тора. Используется для пунктов с зависимой или независимой фиксацией | ||
| -label | Текст пункта меню | ||
| -menu | Меню, отображаемое при вызове каскадного пункта | ||
| -offvalue | Значение переменной при условии, что соответствуют^ ей пункт с независимой фиксацией не установлен | ||
| -onvalue | Значение Tcl-переменной при условии, что соответствую» щий ей пункт с независимой фиксацией установлен | ||
| -selectcolor | Цвет селектора. Используется для пунктов с зависимА и независимой фиксацией # | ||
| -selectimage | Изображение, соответствующее выбранному пункту. Используется для пунктов с зависимой и независимой фиксацией | ||
| -state | Состояние пункта: normal, active или disabled | ||
| -underline | Индекс символа, который должен отображаться с подчеркиванием | ||
| -value | Значение Tcl-переменной в случае, если выбран пункт с зависимой фиксацией | ||
| -variable | Tcl-переменная, связанная с пунктом с зависимой или независимой фиксацией | ||
Пакет для работы с меню по именам.
Если ваше приложение поддерживает расширяемое меню или меню, определяемое пользователем, то для уточнения всех его деталей потребуется много времени и усилий. Мы создадим в качестве примера простой пакет, который позволит пользователям обращаться к меню и его пунктам по именам. Кроме того, данный пакет будет поддерживать комбинации клавиш для быстрого доступа к меню.
Процедура Menu_Setup инициализирует пакет. Она создает фрейм, содержащий набор кнопок, и инициализирует некоторые переменные состояния, в частности счетчик для генерации путей к компонентам. Информация о состоянии пакета содержится в массиве с именем menu.
Процедура Menu создает компонент menubutton и меню. Она также записывает информацию о соответствии между текстом на компоненте menubutton и меню, созданном для него. Данное отображение используется остальными частями пакета так, что клиент, работающий с пакетом, может вместо пути к меню (например, .top.menubar.file.menu) обращаться к нему по имени (например, File).
Листинг 30.9. Простой пакет для обращения к меню по имени
proc Menu_Setup { menubar } {
global menu
frame $menubar
pack $menubar -side top -fill x
set menu(menubar) $menubar
set menu(uid) 0 }
proc Menu { label } { global menu
if [info exists menu(menu,$label)] { error «Menu $label already defined»
}
# Создание menubutton и меню
set name $menu(menubar).mb$menu(uid)
set menuName $name.menu
incr menu(uid)
set mb [menubutton $name -text $label -menu $menuName]
pack $mb -side left
menu $menuName -tearoff 1
# Запоминание имени
set menu(menu,$label) $menuName }
В листинге 30.10 содержатся новые определения тех же процедур. Отличие от предыдущего примера состоит в том, что здесь используется механизм для работы со строкой меню, реализованный в Tk 8.O. Остальные процедуры пакета остаются такими же, как и при работе с предыдущей версией строк» меню.
Листинг 30.10. Использование средств работы с меню, определенных в Tk 8.П
proc Menu_Setup { menubar } {
global menu menu $menubar
# Связывание меню с главным окном
set top [winfo parent $menubar]
$top config -menu $menubar
set menu(menubar) $menubar
set menu(uid) 0 > proc Menu { label } {
global menu
if [info exists menu(menu, $label)] {
error «Menu $label already defined»
}
# Создание каскадного меню
set menuName $menu(menubar).mb$menu(uid)
incr menu(uid)
menu $menuName -tearoff 1
$meim(menubar) add cascade -label $label -menu $menuName
# Запоминание имени
set menu(menu, $label) $menuName}
После настройки меню массив menu используется для отображения имени меню (например, File) в имя компонента Тк (например, .menubar.mb3). Несмотря на то что для этого достаточно нескольких строк Tcl-кода, действия по отображению имен оформлены в виде процедуры MenuGet. Если имя меню неизвестно, в процедуре MenuGet выполняется команда return —code error, которая слегка видоизменяет сообщение об ошибке. Если пользователь некорректно укажет имя меню, переменная ошибки будет перехвачена и процедура сформирует более информативное сообщение. Процедура MenuGet предназначена для использования внутри пакета, поэтому в ее имени отсутствует подчеркивание.
Листинг 30.11. Процедура MenuGet отображает имя в меню
proc MenuGet {menuName} {
global menu
if [catch {set menu(menu,$menuName)} m] {
return -code error «No such menu: $menuName» }
return $m
}
Процедуры Menu_Command, Menu_Check, Menu_Radio и Menu_Separator играют роль простых оболочек, в которые помещаются базовые команды меню. Они используют MenuGet для отображения текстовой метки в имя компонента Тк.
Листинг 30.12. Добавление новых пунктов меню
proc Menu_Command { menuName label command } {
set m [MenuGet $menuName]
$m add command -label $label -command $command }
proc Menu_Check { menuName label var { command {} } } {
set m [MenuGet $menuName]
$m add check -label $label -command $command -variable $var
proc Menu_Radio { menuName label var {val {} {command {}} }
{ set m [MenuGet $menuName]
if {[string length $val] == 0> {
set val $label
$m add radio -label $label -command $command -value $val -variable $var }
proc Menu_Separator { menuName } {
[MenuGet $menuName] add separator}
Для генерации каскадного меню также необходимо сохранять отображение между меткой каскадного пункта и имени пути Тк к подменю. Данный пакет накладывает ограничение, согласно которому для различных меню, в том числе подменю, не может использоваться одна и та же текстовая метка.
Листинг 30.13. Оболочка для каскадных пунктов
proc Menu_Cascade { menuName label } {
global menu
set m [MenuGet $menuName]
if [info exists menu(menu, $label)] {
error «Menu $label already defined» }
set sub $m.sub$menu(uid) incr menu(uid) menu $sub -tearoff 0
$m add cascade -label $label -menu $sub
set menu(menu,$label) $sub
В листинге 30.14 приведен пример создания меню с использованием пакета, рассмотренного в данном разделе.
Листинг 30.14. Использование пакета для обращения к меню по имени
Menu_Setup .menubar
Menu Sampler
Menu_Command Sampler Hello! {puts «Hello, World!»}
Menu_Check Sampler Boolean foo {puts «foo = $foo»}
Menu_Separator Sampler
Menu_Cascade Sampler Fruit
Menu_Radio Fruit apple fruit
Menu_Radio Fruit orange fruit
Menu_Radio Fruit kiwi fruit
Комбинации клавиш для доступа к меню.
Как было сказано ранее, пакет, позволяющий обращаться к меню по именам, поддерживает также комбинации клавиш, предназначенные для обращения к пунктам меню. Во многих приложениях рядом с некоторыми пунктами меню приведены сведения о комбинациях клавиш. Они напоминают пользователям о том, что с комбинациями клавиш связаны те же команды, что и с соответствующими пунктами меню. Однако при этом нет никакой гарантии, что символы указаны верно или что при модификации меню связывание не будет изменено. В листинге 30.15 приведен код процедуры, отслеживающей подобные ситуации.
Листинг 30.15. Поддержка корректных данных о комбинациях клавиш
proc Menu_Bind { what sequence accText menuName label } {
variable menu
set m [MenuGet $menuName]
if {[catch {$m index $label> index]} {
error «$label not in menu $menuName» }
bind $what $sequence [list Menulnvoke $m $index]
$m entryconfigure $index -accelerator $accText
proc Menulnvoke {m index} {
set state [$m entrycget $index -state]
if {[string equal $state normal]} {
$m invoke $index}
Процедура Menu_Bind использует операцию index для поиска пункта меню с данной текстовой меткой. Она устанавливает для комбинаций клавиш такое связывание, которое вызывает соответствующую операцию меню и отображает корректные сведения о комбинациях клавиш, используя для этого операцию entryconfigure. Данный подход позволяет автоматически поддерживать соответствие между пунктами меню и комбинациями клавиш.
Процедура Menulnvoke используется для связывания. Команда загружается с помощью операции entrycget, после чего с ней устанавливается непосредственная связь. Однако при этом не учитывается состояние пункта меню, доступ к которому может быть временно запрещен. Операция invoke предназначена для обработки специальных ситуаций, например, для обновления переменных, соответствующих пунктам меню с зависимой фиксацией.
Для того чтобы проверить, как работает процедура Menu_Bind, надо создать пустой фрейм и связать с ним комбинацию клавиш и одну из команд меню. Необходимые для этого команды приведены ниже.
frame .body —width 100 —height 50
pack .body ; focus .body
Menu_Bind .body <Control-q> Ctrl-Q Sampler Hello!
