Cómo: Controlar el evento ContextMenuOpening

El evento ContextMenuOpening se puede controlar en una aplicación para ajustar un menú contextual existente antes de mostrarlo o para suprimir el menú que de otro modo se mostraría estableciendo la propiedad Handled a true en los datos del evento. La razón habitual para establecer Handled a true en los datos del evento es reemplazar el menú por completo con un nuevo objeto ContextMenu, lo que a veces requiere cancelar la operación e iniciar una nueva apertura. Si escribe controladores para el evento ContextMenuOpening, debe tener en cuenta los problemas de sincronización entre un control ContextMenu y el servicio que se encarga de abrir y posicionar los menús contextuales de los controles en general. En este tema se muestran algunas de las técnicas de código para varios escenarios de apertura de menús contextuales y se muestra un caso en el que entra en juego el problema de sincronización.

Hay varios escenarios para controlar el evento ContextMenuOpening:

  • Ajuste de los elementos de menú antes de mostrarlos.

  • Sustitución de todo el menú antes de mostrarlo.

  • Suprimir completamente cualquier menú contextual existente y no mostrar ningún menú contextual.

Ejemplo

Ajuste de los elementos de menú antes de mostrarlos

Ajustar los elementos de menú existentes es bastante sencillo y probablemente sea el escenario más común. Puede hacerlo para agregar o restar opciones de menú contextual en respuesta a la información de estado actual de la aplicación o información de estado determinada que está disponible como propiedad en el objeto en el que se solicita el menú contextual.

La técnica general es obtener el origen del evento, que es el control específico en el que se hizo clic con el botón derecho y obtener la propiedad ContextMenu de él. Normalmente, desea comprobar la colección Items para ver qué elementos de menú contextual ya existen en el menú y, a continuación, agregar o quitar los elementos nuevos MenuItem adecuados a o desde la colección.

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

Sustitución de todo el menú antes de mostrarlo

Un escenario alternativo es si desea reemplazar todo el menú contextual. Por supuesto, también podría usar una variación del código anterior, para quitar todos los elementos de un menú contextual existente y agregar otros nuevos a partir del elemento cero. Pero el enfoque más intuitivo para reemplazar todos los elementos del menú contextual es crear un nuevo ContextMenu, rellenarlo con elementos, y luego establecer la propiedad FrameworkElement.ContextMenu de un control para que sea el nuevo ContextMenu.

A continuación se muestra el código de controlador simple para reemplazar un ContextMenu. El código hace referencia a un método personalizado BuildMenu, que se separa porque lo llaman más de uno de los controladores de ejemplo.

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

Sin embargo, si usa este estilo de controlador para ContextMenuOpening, puede exponer un problema de sincronización si el objeto en el que se establece ContextMenu no tiene un menú contextual preexistente. Cuando un usuario hace clic con el botón derecho en un control, ContextMenuOpening se genera incluso si el objeto existente ContextMenu está vacío o es NULL. Pero en este caso, cualquier novedad ContextMenu que establezca en el elemento de origen llega demasiado tarde para mostrarse. Además, si el usuario hace clic con el botón derecho una segunda vez, esta vez el nuevo ContextMenu aparece, el valor no es NULL y el controlador reemplazará y mostrará correctamente el menú cuando el controlador se ejecute una segunda vez. Esto sugiere dos posibles soluciones alternativas:

  1. Asegúrese de que los controladores ContextMenuOpening siempre se ejecuten en controles que tengan al menos un marcador de posición ContextMenu disponible, que pretende reemplazar por el código del controlador. En este caso, todavía puede usar el controlador que se muestra en el ejemplo anterior, pero normalmente quiere asignar un marcador de posición ContextMenu en el marcado 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. Supongamos que el valor inicial ContextMenu podría ser NULL, en función de alguna lógica preliminar. Puede comprobar si ContextMenu es NULL o usar una marca en el código para comprobar si el controlador se ha ejecutado al menos una vez. Dado que se supone que ContextMenu está a punto de mostrarse, su controlador entonces establece Handled a true en los datos del evento. Para ContextMenuService que es responsable de la visualización del menú contextual, un valor true para Handled en los datos del evento representa una solicitud de cancelación de la visualización para la combinación menú contextual/control que ha provocado el evento.

Ahora que ha suprimido el menú contextual potencialmente sospechoso, el siguiente paso es proporcionar uno nuevo y, a continuación, mostrarlo. Establecer el nuevo es básicamente lo mismo que el controlador anterior: se construye un nuevo ContextMenu y se establece la propiedad FrameworkElement.ContextMenu de la fuente de control con él. El paso adicional es que ahora debe forzar la presentación del menú contextual, ya que ha suprimido el primer intento. Para forzar la visualización, establezca la propiedad Popup.IsOpen a true en el controlador. Tenga cuidado al hacerlo, porque al abrir el menú contextual en el controlador se vuelve a lanzar el evento ContextMenuOpening. Si vuelve a escribir el controlador, se convierte en infinitamente recursivo. Por eso siempre hay que comprobar null o utilizar una marca si se abre un menú contextual desde un controlador de eventos ContextMenuOpening.

Suprimir completamente cualquier menú contextual existente y no mostrar ningún menú contextual

El escenario final, escribir un controlador que suprime un menú totalmente, es poco común. Si se supone que un control determinado no debe mostrar un menú contextual, probablemente haya formas más apropiadas de asegurarlo que suprimiendo el menú justo cuando un usuario lo solicite. Pero si desea usar el controlador para suprimir un menú contextual y no mostrar nada, entonces el controlador debería simplemente establecer Handled en true en los datos del evento. El ContextMenuService que se encarga de mostrar un menú contextual comprobará los datos del evento que generó en el control. Si el evento fue marcado como Handled en cualquier parte de la ruta, entonces se suprime la acción de apertura del menú contextual que inició el evento.

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

Vea también