Практическое руководство. Событие ContextMenuOpening
Обновлен: Ноябрь 2007
Событие ContextMenuOpening может быть обработано в приложении либо для корректировки существующего контекстного меню перед отображением, либо для подавления меню, которое отображалось бы в противном случае, путем установки для свойства Handled значения true в данных события. Обычно причиной задания для Handled значения true в данных события является необходимость полной замены меню новым объектом ContextMenu, который иногда требует отмены операции и запуска нового открытия. При написании обработчиков для события ContextMenuOpening, то следует иметь в виду проблемы синхронизации между элементом управления ContextMenu и службой, которая отвечает за открытие и позиционирование контекстных меню для элементов управления в целом. В этом разделе проиллюстрированы некоторые технические приемы написания кода для различных сценариев открытия контекстного меню, а также случаи, в которых возникают проблемы синхронизации.
Существует несколько сценариев для обработки события ContextMenuOpening:
Корректировка элементов меню перед отображением.
Замена всего меню перед отображением.
Полное подавление любых существующих контекстных меню и отсутствие отображения контекстного меню.
Пример
Корректировка элементов меню перед отображением
Корректировка существующих элементов меню достаточно проста и является, вероятно, наиболее распространенным сценарием. Корректировка может выполняться для добавления или удаления команд контекстного меню в соответствии с информацией о текущем состоянии приложения или конкретном состоянии, которая доступна как свойство объекта, для которого запрашивается контекстное меню.
Основным методом является получение источника события, то есть определенного элемента управления, на котором был произведен щелчок правой кнопкой мыши, и получение его свойства ContextMenu. Как правило, требуется проверить коллекцию Items для просмотра уже существующих в меню элементов контекстного меню, а затем добавить в коллекцию или удалить из нее соответствующие новые элементы MenuItem.
void AddItemToCM(object sender, ContextMenuEventArgs e)
{
//check if Item4 is already there, this will probably run more than once
FrameworkElement fe = e.Source as FrameworkElement;
ContextMenu cm = fe.ContextMenu;
foreach (MenuItem mi in cm.Items)
{
if ((String)mi.Header == "Item4") return;
}
MenuItem mi4 = new MenuItem();
mi4.Header = "Item4";
fe.ContextMenu.Items.Add(mi4);
}
Замена всего меню перед отображением
В некоторых случаях требуется заменить контекстное меню целиком. Разумеется, здесь также можно использовать вариант предыдущего кода для удаления каждого элемента существующего контекстного меню и добавления новых, начиная с нулевого элемента. Но более естественным подходом для замены всех элементов в контекстном меню является создание нового ContextMenu, заполнение его элементами и последующая установка свойства FrameworkElement.ContextMenu элемента управления для нового ContextMenu.
Ниже приведен код простого обработчика для замены ContextMenu. Код ссылается на пользовательский метод BuildMenu, который обособлен, поскольку вызывается несколькими обработчиками примера.
void HandlerForCMO(object sender, ContextMenuEventArgs e)
{
FrameworkElement fe = e.Source as FrameworkElement;
fe.ContextMenu = BuildMenu();
}
ContextMenu BuildMenu()
{
ContextMenu theMenu = new ContextMenu();
MenuItem mia = new MenuItem();
mia.Header = "Item1";
MenuItem mib = new MenuItem();
mib.Header = "Item2";
MenuItem mic = new MenuItem();
mic.Header = "Item3";
theMenu.Items.Add(mia);
theMenu.Items.Add(mib);
theMenu.Items.Add(mic);
return theMenu;
}
Однако при использовании этого стиля обработчика для ContextMenuOpening есть вероятность возникновения проблем синхронизации, если объект, в котором задано свойство ContextMenu, не имеет уже существующего контекстного меню. При щелчке элемента управления правой кнопкой мыши событие ContextMenuOpening возникает даже в том случае, если существующее ContextMenu пусто или имеет значение null. Но в этом случае любое новое ContextMenu, установленное на исходном элементе, поступает слишком поздно для отображения. Кроме того, если пользователь щелкнет правой кнопкой мыши второй раз, то появится новое ContextMenu, с отличным от null значением, и при повторном выполнении обработчика замена и отображение меню будут выполнены правильно. Это предполагает два возможных способа решения проблемы:
Убедитесь, что обработчики ContextMenuOpening всегда запускаются для элементов управления, имеющих хотя бы местозаполнитель ContextMenu, предназначенный для замены кодом обработчика. В этом случае можно по-прежнему использовать обработчик из предыдущего примера, но обычно требуется назначить местозаполнитель ContextMenu в первоначальном макете:
<StackPanel> <Rectangle Fill="Yellow" Width="200" Height="100" ContextMenuOpening="HandlerForCMO"> <Rectangle.ContextMenu> <ContextMenu> <MenuItem>Initial menu; this will be replaced ...</MenuItem> </ContextMenu> </Rectangle.ContextMenu> </Rectangle> <TextBlock>Right-click the rectangle above, context menu gets replaced</TextBlock> </StackPanel>
Предположим, что исходным значением ContextMenu может быть null по некоторой предварительной логике. Можно либо проверить ContextMenu на null, либо использовать флаг в коде для проверки, был ли обработчик запущен хоть раз. Поскольку предполагается, что ContextMenu будет отображаться, то обработчик задает для Handled значение true в данных события. Для ContextMenuService, отвечающего за отображение контекстного меню, значение true для Handled в данных события представляет собой запрос отмены отображения для сочетания контекстного меню и элемента управления, вызвавшего событие.
После подавления возможного появления контекстного меню следующим шагом является предоставление нового контекстного меню и его отображение. Настройка нового меню выполняется в общем так же, как для предыдущего обработчика: строится новый объект ContextMenu, который указывается в качестве значения свойства FrameworkElement.ContextMenu источника элемента управления. Дополнительным шагом является принудительное отображение контекстного меню, поскольку первая попытка подавляется. Для принудительного отображения присвойте свойству Popup.IsOpen значение true внутри обработчика. Будьте внимательны при этом, так как открытие контекстного меню в обработчике снова вызывает событие ContextMenuOpening. В случае повторного входа в обработчик это приведет к бесконечной рекурсии. Поэтому необходимо всегда выполнять проверку на null или использовать флаг, если контекстное меню открывается из обработчика событий ContextMenuOpening.
Полное подавление любых существующих контекстных меню и отсутствие отображения контекстного меню
Последний сценарий — написание обработчика, который полностью подавляет меню, — является редким. Если отображение контекстного меню для данного элемента управления не требуется, вероятно, существуют более подходящие способы обеспечить это, чем подавление меню при его запросе пользователем. Но если необходимо использовать обработчик для подавления контекстного меню без отображения, обработчик просто должен задавать для Handled значение true в данных события. ContextMenuService, который отвечает за отображение контекстного меню, будет проверять данные события, возникшего в элементе управления. Если событие было помечено как Handled где-либо вдоль маршрута, то действие по открытию контекстного меню, инициирующее данное событие, будет подавляться.
void HandlerForCMO2(object sender, ContextMenuEventArgs e)
{
if (!FlagForCustomContextMenu)
{
e.Handled = true; //need to suppress empty menu
FrameworkElement fe = e.Source as FrameworkElement;
fe.ContextMenu = BuildMenu();
FlagForCustomContextMenu = true;
fe.ContextMenu.IsOpen = true;
}
}
}
См. также
Основные понятия
Общие сведения о базовых элементах