Compartilhar via


Como manipular o evento ContextMenuOpening

O evento ContextMenuOpening pode ser tratado em um aplicativo para ajustar um menu de contexto existente antes de exibir ou suprimir o menu que, de outra forma, seria exibido definindo a propriedade Handled como true nos dados do evento. O motivo típico para definir Handled para true nos dados do evento é substituir o menu inteiramente por um novo objeto ContextMenu, que às vezes requer o cancelamento da operação e o início de uma nova abertura. Se você escrever manipuladores para o evento ContextMenuOpening, deverá estar ciente dos problemas de tempo entre um controle ContextMenu e o serviço responsável por abrir e posicionar menus de contexto para controles em geral. Este tópico ilustra algumas das técnicas de código para vários cenários de abertura de menu de contexto e ilustra um caso em que o problema de tempo entra em jogo.

Há vários cenários para lidar com o evento ContextMenuOpening:

  • Ajustando os itens de menu antes da exibição.

  • Substituindo todo o menu antes da exibição.

  • Suprimindo completamente qualquer menu de contexto existente e não exibindo nenhum menu de contexto.

Exemplo

Ajustando os itens de menu antes da exibição

Ajustar os itens de menu existentes é bastante simples e provavelmente é o cenário mais comum. Você pode fazer isso para adicionar ou subtrair opções de menu de contexto em resposta às informações de estado atuais em seu aplicativo ou informações de estado específicas que estão disponíveis como uma propriedade no objeto em que o menu de contexto é solicitado.

A técnica geral é obter a origem do evento, que é o controle específico que foi acionado por clique direito, e obter a propriedade ContextMenu desse controle. Normalmente, você deseja verificar a coleção Items para ver quais itens de menu de contexto já existem no menu e, em seguida, adicionar ou remover novos itens MenuItem apropriados para ou da coleção.

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);
}

Substituindo o menu inteiro antes da exibição

Um cenário alternativo é se você quiser substituir todo o menu de contexto. É claro que você também pode usar uma variação do código anterior, para remover cada item de um menu de contexto existente e adicionar novos a partir do item zero. Mas a abordagem mais intuitiva para substituir todos os itens no menu de contexto é criar um novo ContextMenu, preenchê-lo com itens e, em seguida, definir a propriedade FrameworkElement.ContextMenu de um controle como o novo ContextMenu.

A seguir está o código do handler simples para substituir um ContextMenu. O código faz referência a um método de BuildMenu personalizado, que é separado porque é chamado por mais de um dos manipuladores de exemplo.

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;
}

No entanto, se você usar esse estilo de manipulador para ContextMenuOpening, poderá potencialmente expor um problema de tempo se o objeto em que você está definindo o ContextMenu não tiver um menu de contexto pré-existente. Quando um usuário clica com o botão direito do mouse em um controle, ContextMenuOpening é gerado mesmo se o ContextMenu existente estiver vazio ou nulo. Mas, nesse caso, qualquer novo ContextMenu que você definir no elemento de origem chega tarde demais para poder ser exibido. Além disso, se o usuário clicar com o botão direito do mouse uma segunda vez, desta vez o novo ContextMenu for exibido, o valor não será nulo e o manipulador substituirá e exibirá corretamente o menu quando o manipulador for executado uma segunda vez. Isso sugere duas soluções alternativas possíveis:

  1. Certifique-se de que os manipuladores de ContextMenuOpening sejam sempre executados em controles que tenham pelo menos um espaço reservado ContextMenu disponível, que você pretende substituir pelo código do manipulador. Nesse caso, você ainda pode usar o manipulador mostrado no exemplo anterior, mas normalmente deseja atribuir um espaço reservado ContextMenu na marcação inicial:

    <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. Suponha que o valor de ContextMenu inicial possa ser nulo, com base em alguma lógica preliminar. Você pode verificar se ContextMenu é nulo ou usar uma flag em seu código para verificar se seu manipulador de eventos foi executado pelo menos uma vez. Como você pressupõe que o ContextMenu está prestes a ser exibido, o manipulador define Handled para true nos dados do evento. Para o ContextMenuService responsável pela exibição do menu de contexto, um valor true para Handled nos dados do evento representa uma solicitação para cancelar a exibição da combinação de menu/controle de contexto que gerou o evento.

Agora que você suprimiu o menu de contexto potencialmente suspeito, a próxima etapa é fornecer um novo e exibi-lo. Definir o novo é basicamente o mesmo que o manipulador anterior: você cria um novo ContextMenu e define a propriedade FrameworkElement.ContextMenu da fonte de controle com ele. A etapa adicional é que agora você deve forçar a exibição do menu de contexto, pois você suprimiu a primeira tentativa. Para forçar a exibição, configure a propriedade Popup.IsOpen para true dentro do manipulador. Tenha cuidado ao fazer isso, pois abrir o menu de contexto no manipulador gera o evento ContextMenuOpening novamente. Se você reinserir seu handler, ele entrará em um loop infinito. É por isso que você sempre precisa verificar se há null ou usar uma flag ao abrir um menu de contexto dentro de um manipulador de eventos ContextMenuOpening.

Suprimindo qualquer menu de contexto existente e não exibindo nenhum menu de contexto

O cenário final, escrever um manipulador que suprime totalmente um menu, é incomum. Se um determinado controle não deve exibir um menu de contexto, provavelmente há maneiras mais apropriadas de garantir isso do que suprimindo o menu apenas quando um usuário o solicita. Mas se você quiser usar o manipulador para suprimir um menu de contexto e não mostrar nada, o manipulador deverá simplesmente definir Handled para true nos dados do evento. O ContextMenuService responsável por exibir um menu de contexto verificará os dados do evento que ele gerou no controle. Se o evento foi marcado Handled em qualquer lugar ao longo da rota, a ação de abertura do menu de contexto que iniciou o evento será suprimida.

    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;
        }
    }
}

Consulte também