Поделиться через


Как обрабатывать событие ContextMenuOpening

Событие 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, и обработчик правильно заменит и отобразит меню при запуске обработчика во второй раз. Это предлагает два возможных обходных решения:

  1. Убедитесь, что обработчики 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>
    
  2. Предположим, что начальное значение ContextMenu может иметь значение NULL на основе некоторой предварительной логики. Можно или проверить, является ли ContextMenu равным NULL, или использовать флаг в коде, чтобы проверить, запускался ли ваш обработчик хотя бы один раз. Так как предполагается, что ContextMenu будет отображаться, обработчик затем задает Handledtrue в данных события. Чтобы 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;
        }
    }
}

См. также