Il meccanismo di funzionamento degli eventi in WPF è cambiato non poco, questo ovviamente a causa delle potenzialità di composizione che WPF mette a disposizione.
In WPF al concetto di evento è stato associato l'aspetto Routing, ovvero quando un determinato elemento genera un evento, questo segue un percorso ben preciso fino a quando un determinato gestore informa il sistema che il propagare dell'evento non è più necessario.
La strategia di routing si divide in Bubbling e Tunneling

  • Bubbling
    Quando un elemento genera un evento questo viene propagato al proprio parent fino al raggiungimento del root element, ogni elemento può decidere di gestire l'evento ed eventualmente interromperne la propagazione.
  • Tunneling
    Un evento prima di raggiungere l'elemento che lo ha generato passa attraverso i vari children i quali possono gestirlo ed eventualmente interromperne la propagazione eliminando a tutti gli effetti l'evento stesso.




Spesso lo stesso evento viene reso disponibile sia in modalità Bubbling che Tunneling, per convenzione l'evento Tunneling ha il nome prefissato da Preview, ad esempio il Button ha l'evento MouseDown (bubbling) e PreviewMouseDown (Tunneling).

Nell'immagine qui a fianco nel caso un evento MouseDown venga generato dall'elemento Leaf#2, l'ordine di generazione degli eventi sarà:

PreviewMouseDown (tunnel) on root element.
PreviewMouseDown (tunnel) on intermediate element #1.
PreviewMouseDown (tunnel) on leaf element #2.
MouseDown (bubble) on leaf element #2.
MouseDown (bubble) on intermediate element #1.
MouseDown (bubble) on root element.





 



Per gestire un evento in WPF è sufficiente definire il nome del gestore in XAML:

<Button Name="Button1" Click="OnClick">Item 1</Button>

e nel relativo CodeBehind aggiungere il gestore:

void OnClick (object sender, RoutedEventArgs e)
{...};

Lo scattare dell'evento Click andrà quindi ad eseguire il codice contenuto nel metodo OnClick.
In alcuni casi può essere utile condividere un gestore con eventi provenienti da controlli diversi, grazie al bubbling anzichè mappare ogni evento verso lo stesso gestore in XAML, è possibile inserire il gestore nel parent che dei controlli di cui vogliamo gestire gli eventi.

<StackPanel Name="sp" ButtonBase.Click="OnClick" Label.PreviewMouseDown="OnSPPreviewMouseDn" Label.MouseDown="OnSPMouseDn">
 <StackPanel.Resources>
   <Style TargetType="{x:Type RadioButton}">
     <EventSetter Event="RadioButton.Checked" Handler="OnRadioChecked"/>
   </Style>
 </StackPanel.Resources>
 <Button Name="Button1">Item 1</Button>
 <Button Name="Button2" Click="NoPropagateClick">Item 2</Button>
 <RadioButton Name="rb1">Rb1</RadioButton>
 <Label Background="Yellow" MouseDown="OnMouseDn" PreviewMouseDown="OnLblPreviewMouseDn" Name="lbl1">Click</Label>
</StackPanel>

Nello snippet sopra riportato, tutti gli eventi click dei controlli presenti nello StackPanel verranno gestiti dal metodo OnClick, incluso il RadioButton in quando anch'esso come Button eredita dalla classe ButtonBase che definisce l'evento Click.
Interessante è anche il fatto che eventuali gestori evento possono anche essere dichiarati anche all'interno di uno stile mediante EventSetter.

Il metodo che gestisce l'evento Click è così descritto:

void OnClick (object sender, RoutedEventArgs e)
{
   if (e.Source == Button1) ExecButton1();
   if (e.Source == Button2) ExecButton2();
}

dove per discriminare chi ha generato l'evento non possiamo usare Sender in quanto, anzichè il controllo stesso ci verrebbe restituito lo StackPanel, ma bensì la proprietà Source passatoci attraverso il parametro e.
Per non propagare ulteriormente un determinato evento, sia esso Bubbling o Tunneling è sufficiente impostare all'interno del gestore e.Handled=True;

void NoPropagateClick(object sender, RoutedEventArgs e)
{
   ...
   e.Handled=true; //L'evento non viene propagato alla OnClick
}

anche se il tutto deve essere gestito accuratamente perchè potrebbe impedire il corretto funzionamento di alcuni gestori sopratutto se l'interruzione avviene durante il tunneling, ad esempio la Label indicata nello snippet gestisce sia l'evento bubbling (OnMouseDown) che il tunneling (OnLblPreviewMouseDn) e lo stesso evento è gestito anche dallo StackPanel parent questo significa che i gestori evento in presenza di un MouseDown sulla label verranno eseguiti in quest'ordine.

1-OnSPPreviewMouseDn
2-OnLblPreviewMouseDn
3-OnMouseDn
4-OnSPMouseDn


e che un eventuale e.Handled=True all'interno del gestore OnSPPreviewMouseDn non farebbe mai giungere l'evento al gestore abbinato all'evento MouseDown della label stessa.
Il motivo per il quale è conveniente non propagare un determinato evento è quando lo si vuole sostituire con un altro, magari più consono o con maggiori informazioni come nel caso della classe Button la quale intercetta l'evento MouseLeftButtonDown sostituendolo con l'evento Click.