Procedura: gestire l'evento ContextMenuOpening
L'evento ContextMenuOpening può essere gestito in un'applicazione per modificare un menu di scelta rapida esistente prima di visualizzare o eliminare il menu che sarebbe altrimenti visualizzato impostando la proprietà Handled su true nei dati dell'evento. In genere, l'impostazione di Handled su true nei dati dell'evento consente di sostituire completamente il menu con un nuovo oggetto ContextMenu, rendendo talvolta necessario l'annullamento dell'operazione e l'avvio di una nuova apertura. Se si scrivono gestori per l'evento ContextMenuOpening, è necessario tenere in considerazione eventuali problemi di temporizzazione tra un controllo ContextMenu e il servizio responsabile dell'apertura e del posizionamento dei menu di scelta rapida per i controlli in genere. In questo argomento vengono descritte alcune delle tecniche del codice per diversi scenari di apertura dei menu di scelta rapida e viene illustrato un caso in cui si verifica il problema della temporizzazione.
Esistono molti scenari per la gestione dell'evento ContextMenuOpening:
Modifica delle voci di menu prima della visualizzazione.
Sostituzione di tutto il menu prima della visualizzazione.
Eliminazione completa di qualsiasi menu di scelta rapida esistente e conseguente assenza di visualizzazione di menu di questo tipo.
Esempio
Modifica delle voci di menu prima della visualizzazione
La modifica delle voci di menu esistenti è abbastanza semplice e si tratta probabilmente dello scenario più comune. Tale operazione in genere viene eseguita per aggiungere o sottrarre opzioni del menu di scelta rapida in risposta alle informazioni sullo stato corrente dell'applicazione o a informazioni particolari sullo stato, disponibili come proprietà per l'oggetto in cui viene richiesto il menu di scelta rapida.
La tecnica generale consiste nell'ottenere l'origine dell'evento, vale a dire il controllo specifico selezionato con un clic del pulsante destro del mouse, acquisendone la proprietà ContextMenu. È possibile verificare l'insieme Items per vedere quali sono le voci del menu di scelta rapida già presenti nel menu e successivamente aggiungere o rimuovere nuove voci appropriate MenuItem dall'insieme.
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);
}
Sostituzione di tutto il menu prima della visualizzazione
La sostituzione di tutto il menu di scelta rapida rappresenta uno scenario alternativo. Naturalmente, per rimuovere tutte le voci di un menu di scelta rapida esistente e aggiungerne di nuove iniziando dall'elemento zero è anche possibile utilizzare una variazione del codice precedente. Tuttavia, l'approccio più intuitivo per sostituire tutte le voci del menu di scelta rapida consiste nel creare un nuovo oggetto ContextMenu, popolarlo con delle voci, quindi impostare la proprietà FrameworkElement.ContextMenu di un controllo come un nuovo oggetto ContextMenu.
Di seguito viene riportato il semplice codice di gestione per la sostituzione di un oggetto ContextMenu. Il codice fa riferimento a un metodo BuildMenu personalizzato, separato poiché viene chiamato da più di uno dei gestori di esempio.
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;
}
Tuttavia, se si utilizza questo stile di gestore per ContextMenuOpening, è possibile che si verifichi un problema di temporizzazione, se l'oggetto per cui si sta impostando ContextMenu non dispone di un menu di scelta rapida preesistente. Quando un utente fa clic con il pulsante destro del mouse su un controllo, viene generato un oggetto ContextMenuOpening anche se quello esistente ContextMenu è vuoto o null. Tuttavia, in questo caso, qualsiasi nuovo oggetto ContextMenu impostato per l'elemento di origine non può più essere visualizzato per motivi di tempo. Inoltre, se l'utente fa nuovamente clic con il pulsante destro del mouse, questa volta viene visualizzato il nuovo oggetto ContextMenu, il valore risulta diverso da null e il gestore sostituirà e visualizzerà correttamente il menu alla successiva esecuzione. Ne conseguono due possibili soluzioni alternative:
Assicurarsi che i gestori ContextMenuOpening vengano sempre eseguiti in controlli che dispongono di almeno un oggetto ContextMenu segnaposto che si desidera sostituire con il codice di gestione. In questo caso, è ancora possibile utilizzare il gestore riportato nell'esempio precedente, assegnando tuttavia un oggetto ContextMenu segnaposto nel markup iniziale:
<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>
Si presupponga che il valore ContextMenu iniziale sia null, in base a una logica preliminare. È possibile controllare se l'oggetto ContextMenu è null o utilizzare un flag all'interno del codice per verificare se il gestore è stato eseguito almeno una volta. Dal momento che si presuppone che l'oggetto ContextMenu stia per essere visualizzato, il gestore utilizzato imposta Handled su true nei dati dell'evento. Per l'oggetto ContextMenuService responsabile della visualizzazione del menu di scelta rapida, un valore true per Handled nei dati dell'evento rappresenta una richiesta di annullamento della visualizzazione della combinazione menu di scelta rapida/controllo che ha generato l'evento.
Dopo aver eliminato il menu di scelta rapida potenzialmente sospetto, il passaggio successivo consiste nella creazione di un nuovo menu e nella relativa visualizzazione. L'impostazione di un nuovo menu è fondamentalmente uguale al gestore precedente: viene compilato un nuovo oggetto ContextMenu, che sarà utilizzato per impostare la proprietà FrameworkElement.ContextMenu dell'origine del controllo. Il passaggio aggiuntivo consiste nel forzare a questo punto la visualizzazione del menu di scelta rapida, in quanto il primo tentativo è stato evitato. Per forzare la visualizzazione, impostare la proprietà Popup.IsOpen su true all'interno del gestore. Prestare attenzione all'esecuzione di questa operazione, poiché l'apertura del menu di scelta rapida nel gestore genera nuovamente l'evento ContextMenuOpening. Se si accede nuovamente al gestore, l'evento diventa ricorsivo all'infinito. Pertanto, è necessario sempre verificare la presenza di un valore null o utilizzare un flag quando si apre un menu di scelta rapida dall'interno di un gestore eventi ContextMenuOpening.
Eliminazione di tutti i menu di scelta rapida esistenti e conseguente assenza di visualizzazione di menu di questo tipo
Lo scenario finale, vale a dire la scrittura di un gestore che elimina completamente un menu è insolito. Se un controllo specifico non è destinato alla visualizzazione di un menu di scelta rapida, esistono altre modalità più appropriate per assicurare che questa situazione non si verifichi, rispetto all'eliminazione del menu nel momento in cui un utente lo richiede. Se tuttavia si desidera utilizzare il gestore per eliminare un menu di scelta rapida senza visualizzare nulla, il gestore deve semplicemente impostare Handled su true nei dati dell'evento. L'oggetto ContextMenuService responsabile della visualizzazione di un menu di scelta rapida verificherà i dati evento dell'evento generato sul controllo. Se l'evento è stato contrassegnato come Handled in un punto della route, l'azione di apertura del menu di scelta rapida che ha iniziato l'evento viene eliminata.
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;
}
}
}