Compartir a través de


Cómo: Controlar el evento ContextMenuOpening

El evento ContextMenuOpening se puede controlar en una aplicación para ajustar un menú contextual existente antes de su presentación o para suprimir el menú que se mostraría en situación normal estableciendo la propiedad Handled en true en los datos de evento. El motivo habitual para establecer Handled en true en los datos de evento es reemplazar completamente el menú 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 control de tiempo entre un control ContextMenu y el servicio que es responsables de abrir y colocar los menús contextuales para los controles en general. En este tema se ilustran algunas de las técnicas de código para varios escenarios de apertura de menús contextuales y se muestra un caso donde se hace patente el problema de control de tiempo.

Hay varios escenarios de control del evento ContextMenuOpening:

  • Ajustar los elementos de menú antes de la presentación.

  • Reemplazar el menú completo antes de la presentación.

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

Ejemplo

Ajustar los elementos de menú antes de la presentación

Ajustar los elementos de menú existentes es bastante simple y, probablemente, es el escenario más común. Puede hacerlo para agregar o quitar opciones en el menú contextual en respuesta a la información de estado actual de la aplicación, o a la información de estado concreta disponible como una propiedad del objeto en que se solicita el menú contextual.

La técnica general consiste en obtener el origen del evento, que es el control concreto donde que se hizo clic con el botón secundario, y obtener la propiedad ContextMenu de él. Suele ser conveniente comprobar la colección Items para ver qué elementos de menú contextual existen ya en ella y, a continuación, agregar o quitar los elementos MenuItem nuevos en la colección.

        Private Sub AddItemToCM(ByVal sender As Object, ByVal e As ContextMenuEventArgs)
            'check if Item4 is already there, this will probably run more than once
            Dim fe As FrameworkElement = TryCast(e.Source, FrameworkElement)
            Dim cm As ContextMenu = fe.ContextMenu
            For Each mi As MenuItem In cm.Items
                If CType(mi.Header, String) = "Item4" Then
                    Return
                End If
            Next mi
            Dim mi4 As New MenuItem()
            mi4.Header = "Item4"
            fe.ContextMenu.Items.Add(mi4)
        End Sub
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);
}

Reemplazar el menú completo antes de la presentación

Un escenario alternativo es aquél en que se desea reemplazar el menú contextual completo. Por supuesto, puede utilizar una variación del código anterior para quitar todos los elementos de un menú contextual existente y agregar otros nuevos partiendo 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, a continuación, establecer la propiedad FrameworkElement.ContextMenu de un control en el nuevo ContextMenu.

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

        Private Sub HandlerForCMO(ByVal sender As Object, ByVal e As ContextMenuEventArgs)
            Dim fe As FrameworkElement = TryCast(e.Source, FrameworkElement)
            fe.ContextMenu = BuildMenu()
        End Sub
void HandlerForCMO(object sender, ContextMenuEventArgs e)
{
    FrameworkElement fe = e.Source as FrameworkElement;
    fe.ContextMenu = BuildMenu();
}
        Private Function BuildMenu() As ContextMenu
            Dim theMenu As New ContextMenu()
            Dim mia As New MenuItem()
            mia.Header = "Item1"
            Dim mib As New MenuItem()
            mib.Header = "Item2"
            Dim mic As New MenuItem()
            mic.Header = "Item3"
            theMenu.Items.Add(mia)
            theMenu.Items.Add(mib)
            theMenu.Items.Add(mic)
            Return theMenu
        End Function
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 utiliza este estilo de controlador para un evento ContextMenuOpening, potencialmente puede exponer un problema de control de tiempo si el objeto donde se establece el ContextMenu no tiene un menú contextual preexistente. Cuando un usuario hace clic con el botón secundario en un control, se provoca el evento ContextMenuOpening aunque el objeto ContextMenu existente esté vacío o sea null. Sin embargo, en este caso, sea cual fuere el nuevo ContextMenu que se establezca en el elemento de origen llega demasiado tarde para mostrarlo. Además, si el usuario hace clic con el botón secundario por segunda vez, esta vez aparece el nuevo ContextMenu, su valor no es null y el controlador se reemplaza correctamente y muestra el menú cuando el controlador se ejecuta por segunda vez. Esto sugiere dos posibles soluciones alternativas:

  1. Asegúrese de que los controladores de ContextMenuOpening se ejecuten siempre en controles que tengan como mínimo un ContextMenu marcador de posición disponible, que se reemplazará por el código del controlador. En este caso, aún así puede utilizar el controlador mostrado en el ejemplo anterior, pero normalmente suele ser conveniente asignar un ContextMenu marcador de posición 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 de ContextMenu inicial sea null, según alguna parte de la lógica preliminar. Podría comprobar si ContextMenu es null o utilizar un marcador 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, a continuación el controlador establece Handled en true en los datos de evento. Para el ContextMenuService responsable de la presentación del menú contextual, un valor true para Handled en los datos de evento representa una solicitud de cancelar la presentación correspondiente a la combinación de menú contextual y control que provocó el evento.

Ahora que ha suprimido el menú contextual que podría mostrarse, el paso siguiente es proporcionar uno nuevo y mostrarlo. Establecer el nuevo es básicamente igual que con el controlador anterior: se genera un nuevo ContextMenu y se establece en él la propiedad FrameworkElement.ContextMenu del origen del control. La operación adicional consiste en forzar ahora la presentación del menú contextual, porque se ha suprimido el primer intento. Para forzar la presentación, se establece la propiedad Popup.IsOpen en true dentro del controlador. Extreme las precauciones al hacerlo, porque al abrir el menú contextual en el controlador se provoca de nuevo el evento ContextMenuOpening. Si entra de nuevo en el controlador, se vuelve infinitamente recursivo. Por este motivo, siempre hay que comprobar si existe el valor null o utilizar un marcador al abrir un menú contextual desde el interior de un controlador de eventos de ContextMenuOpening.

Suprimir cualquier menú contextual existente y no mostrar ninguno

El último escenario, escribir un controlador que suprime un menú totalmente, es infrecuente. Si un control no está pensado para mostrar un menú contextual, es probable que existan maneras más adecuadas de asegurarse de ello que suprimir el menú sólo cuando un usuario lo solicita. Sin embargo, si desea utilizar el controlador para suprimir un menú contextual y no presentar nada, entonces el controlador debe limitarse a establecer Handled en true en los datos de evento. El ContextMenuService responsable de mostrar un menú contextual comprobará los datos del evento que se provocó en el control. Si el evento está marcado como Handled en cualquier punto de la ruta, se suprime la acción de apertura de menú contextual que ha iniciado el evento.

        Private Sub HandlerForCMO2(ByVal sender As Object, ByVal e As ContextMenuEventArgs)
            If Not FlagForCustomContextMenu Then
                e.Handled = True 'need to suppress empty menu
                Dim fe As FrameworkElement = TryCast(e.Source, FrameworkElement)
                fe.ContextMenu = BuildMenu()
                FlagForCustomContextMenu = True
                fe.ContextMenu.IsOpen = True
            End If
        End Sub
    End Class
        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

Referencia

ContextMenu

FrameworkElement.ContextMenu

Conceptos

Información general sobre elementos base

Información general sobre ContextMenu