Элементы меню

81

Все меню конструируются из объектов MenuItem и Separator. Класс MenuItem унаследован от HeaderedltemsControl, поскольку каждый элемент меню имеет заголовок (в котором содержится предназначенный для него текст) и может умещать в себе коллекцию объектов MenuItem (с помощью которой представляется подменю). Класс Separator просто отображает горизонтальную линию для разделения элементов меню.

Ниже приведена простая комбинация объектов MenuItem, которые создают элементарную структуру меню, показанную на рисунке:

<Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="auto"></RowDefinition>
                    <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <Menu>
                    <MenuItem Header="File">
                        <MenuItem Header="New"></MenuItem>
                        <MenuItem Header="Open"></MenuItem>
                        <MenuItem Header="Save"></MenuItem>
                        <MenuItem Header="SaveAs..."></MenuItem>
                        <Separator></Separator>
                        <MenuItem Header="Exit"></MenuItem>
                    </MenuItem>
                    <MenuItem Header="Edit">
                        <MenuItem Header="Undo"></MenuItem>
                        <MenuItem Header="Redo"></MenuItem>
                        <Separator></Separator>
                        <MenuItem Header="Cut"></MenuItem>
                        <MenuItem Header="Copy"></MenuItem>
                        <MenuItem Header="Paste"></MenuItem>
                    </MenuItem>
                </Menu>
  </Grid>
Базовое меню

Как и в случае кнопок, здесь тоже можно использовать символ подчеркивания для обозначения клавиатурной комбинации <Alt+клавиша быстрого вызова>. Для кнопок это часто считается необязательным, но что касается меню, то большинство пользователей ожидает, что сокращенные клавиатурные команды в них должны присутствовать.

WPF позволяет нарушать многие из привычных правил структуризации меню. Например, внутри Menu или MenuItem допускается размещать объекты, отличные от MenuItem. Это дает возможность создавать меню, содержащие обычные элементы WPF, начиная от Checkbox и заканчивая DocumentViewer. По ряду причин размещение в меню объектов, отличных от MenuItem, практически всегда является плохой идеей, поскольку приводит к появлению нескольких необычных проблем, которые нужно отслеживать и исправлять. Например, элемент TextBox в MenuItem будет утрачивать фокус сразу же после перемещения мыши за пределы этого MenuItem.

Если же действительно необходимо создать пользовательский интерфейс, включающий нечто вроде раскрывающихся меню с элементами управления, лучше рассмотреть вариант использования другого элемента (например, Expander) и его стилизации в соответствии с имеющимися потребностями. Элементы управления Menu и MenuItems стоит применять только тогда, когда действительно требуется поведение, свойственное меню — другими словами, когда необходима группа активизируемых щелчком команд.

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

Объекты MenuItem можно также использовать и за пределами стандартных контейнеров Menu, ContextMenu и MenuItem. Они будут вести себя точно так же, как и обычные элементы меню, т.е. будут подсвечиваться голубым при наведении на них курсора мыши и позволять выполнять щелчок для инициации действий. Однако получить доступ к любым содержащимся в них подменю не получится. Опять-таки, это одна из тех возможностей Menu, которая вряд ли будет пользоваться популярностью среди разработчиков.

Для реагирования на щелчок на элементе MenuItem можно использовать событие MenuItem.Click. Можно обеспечить его обработку для отдельных элементов, а можно просто присоединить к корневому дескриптору Menu соответствующий обработчик таких событий. Другой вариант — с помощью свойств Command, CommandParameter и CommandTarget соединить MenuItem с объектом Command, как это делалось для кнопок.

Такой вариант особенно удобен, если пользовательский интерфейс включает несколько меню (например, главное меню и контекстное меню), в которых применяются одинаковые команды, или меню и панель инструментов с кнопками, аналогичными командам этого меню.

Помимо текстового содержимого (которое предоставляется через свойство Header), объекты MenuItem могут в действительности отображать еще несколько перечисленных ниже деталей:

В указании всех этих деталей нет ничего сложного. Для отображения миниатюрного значка необходимо установить свойство MenuItem.Icon. Интересно то, что свойство Icon способно принимать любой объект, что позволяет создавать для отображения даже миниатюрный векторный рисунок. Это дает возможность в полной мере воспользоваться преимуществами не зависящей от разрешения системы масштабирования WPF и отобразить больше деталей при более высоких настройках DPI системы. При желании иметь обычный значок, должен использоваться просто элемент Image с растровым изображением.

Для отображения рядом с элементом меню флажка понадобится установить свойство MenuItem.IsChecked в true. Вдобавок, если IsCheckable равно true, щелчок на данном элементе меню будет переводить его из отмеченного в неотмеченное состояние и наоборот. Однако способа организации группы элементов меню с взаимно исключающей отметкой не существует. Если необходим именно такой эффект, придется писать специальный код, снимающий отметки с других флажков при установке флажка рядом с элементом.

Текст сокращенной клавиатурной команды для элемента меню указывается с помощью свойства MenuItem.InputGestureText. Однако простое отображение этого текста активным его не сделает. Об отслеживании нажатий соответствующих клавиш разработчик должен позаботиться сам. Практически всегда это требует немалой работы, поэтому элементы меню часто используются с командами, позволяющими обеспечить поведение сокращенных клавиатурных команд и InputGestureText за один шаг.

Например, следующий элемент MenuItem связывается с командой ApplicationsCommands.Open:

<MenuItem Command="ApplicationCommands.Open"></MenuItem>

Эта команда уже имеет клавиатурную комбинацию <Ctrl+0>, которая определена в коллекции команд RoutedUICommand.InputGestures. Поэтому в качестве текста сокращенной клавиатурной команды будет автоматически отображаться Ctrl+O, а нажатие этой комбинации клавиш будет, соответственно, автоматически приводить к инициированию этой команды (естественно, при условии наличия соответствующего обработчика событий). Если необходимая клавиатурная комбинация еще не была задана, разработчик может добавить ее в коллекцию InputGestures самостоятельно.

Доступно также несколько полезных свойств, которые позволяют узнать текущее состояние MenuItem: IsChecked, IsHighlighted, IsPressed и IsSubmenuOpen. Их можно использовать для написания триггеров, применяющих различные стили в ответ на определенные действия.

Класс ContextMenu

Как и Menu, класс ContextMenu содержит коллекцию объектов MenuItem. Разница состоит лишь в том, что ContextMenu не может размещаться в окне. Вместо этого он может использоваться только для установки свойства ContextMenu другого элемента:

<TextBox>
   <TextBox.ContextMenu>
      <MenuItem ... >
      </MenuItem>
   </TextBox.ContextMenu>
</TextBox>

Свойство ContextMenu определено в классе FrameworkElement, поэтому оно поддерживается практически всеми элементами WPF. В случае, когда это свойство установлено для элемента, у которого имеется собственное контекстное меню, стандартное меню заменяется тем, что указано в этом свойстве. Если просто нужно удалить существующее меню, установите ContextMenu в null.

После присоединения объекта ContextMenu к элементу он появляется автоматически, когда пользователь щелкает на этом элементе управления правой кнопкой мыши (или нажимает комбинацию клавиш <Shift+F10>, пока на элементе находится фокус).

Если свойство IsEnabled элемента установлено в false, контекстное меню не будет появляться до тех пор, пока ему не будет явно разрешено это делать с помощью присоединенного свойства ContextMenuService.ShowOnDisabled.

Пройди тесты
Лучший чат для C# программистов