Confusioni possibili coi Routed event
Il presente intervento fa seguito al dibattito da me avviato in un forum specifico, onestamente con un’incertezza che alla fine ho superato. Ritengo comunque di riportarlo in questo blog, renderlo un po’ più organico, a beneficio di quanti tuttora non riescono a chiarirsi le idee sul tema Routed Event.
Questa novità concettuale, a mio avviso, è stretta parente delle Dependency property, una volta che Microsoft ha deciso che tutte o quasi le proprietà di un oggetto Wpf derivano dal contesto (come è evidente per la sottospecie Attached. Sia come sia, non sempre a mio discutibilissimo parere conviene sfruttare i routed event, evitando così strani e non di rado indesiderati effetti.
Vengo al dunque con un esempio classico di evento “dirottato”:
<StackPanel Name="stkPnl" Grid.Row="0" Button.Click="commonRout">
<Button Name="puls1" Content="Pulsante 1" Height="50" Margin="20" />
<Button Name="puls2" Content="Pulsante 2" Height="50" Margin="20" />
<Button Name="puls3" Content="Pulsante 3" Height="50" Margin="20" />
<Button Name="puls4" Content="Pulsante 4" Height="50" Margin="20" />
</StackPanel>
Ove la routine comunitaria commonRout in questione è:
Private Sub commonRout(ByVal sender As Object, ByVal e As RoutedEventArgs) Handles puls1.Click, puls2.Click, puls3.Click, puls4.Click
Dim Msg = "Sender = " & sender.ToString & _
Constants.vbLf
If Not TypeOf (sender) Is Button Then
Msg &= "Stackpanel "
Else
Msg &= "Pulsante "
End If
Msg &= CType(sender, FrameworkElement).Name.ToString
MessageBox.Show(Msg)
End Sub
Cliccando su uno dei Button dello StackPanel la commonRout è lanciata ricorsivamente due volte con meccanismo “bubbling”, per cui la MessageBox segnala prima i dati del pulsante cliccato poi quelli del contenitore. Chi è alle prime armi si domanda: a che diavolo serve tutto ciò? E, se è un tipo prudente, risponde a se stesso che un giorno o l’altro questa novità magari torna utile, ma nei casi normali converrà togliere quel Button.Click=”commonRout” nella tag <StackPanel… > e semmai, laddove serva, ricordarsi dell’opzione Handles con eventi plurimi – Handles puls1.Click, puls2.Click eccetera - che può far risparmiare codice evitando di indicare la fatidica Sub comunitaria su tutti i Button come segue:
<Button Name="puls1" Click="commonRout" Content="Pulsante 1"... />
<Button Name="puls2" Click="commonRout" Content="Pulsante 2"... />
Eccetera.
Ma adesso attenzione. In un sacro testo ho trovato l’affermazione che il predetto codice XAML funziona allo stesso modo anche IN ASSENZA dell’Handles plurimo testé citato. In un primo momento tale affermazione appare falsa, perché la solita commonRout stavolta segnala soltanto l’oggetto contenitore. Dunque non c’è ricorsione? Giusto! Pertanto una siffatta impostazione non serve? Sbagliato! Perché a ben riflettere, o almeno col senno di poi, si deve prendere atto che la routine commonRout si fa pur sempre viva cliccando su uno dei Button facenti parte del contenitore.
Prtima di proseguire si provi anche a sostituire la commonRout con quest’altra banale miaRout priva di argomenti:
Private Sub miaRout()
Dim puls = ActualHeight.ToString
MsgBox("Altezza oggetto: " & puls)
End Sub
Ovviamente l’altezza presente ActualHeight segnalata è sempre e solo quella dello StackPanel. Inoltre non solo qui ma pure nel caso precedente sempre chi è all’inizio può pensare che non sia possibile individuare da quale controllo è partito il clic.
Il segreto svelato
È presto detto:
sia l’oggetto da cui parte il messaggio( sender, ovvero lo StackPanel nel nostro caso) sia l’oggetto che ha scatenato (OriginalSource, uno dei sei Button del nostro esempio) si possono conoscere tramite l‘argomento sender e, rispettivamente, e, precisamente con la sua proprietà OriginalSource,
La faccenda si sperimenta e spiega con la seguente variante della solita routine:
Private Sub commonRout(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
MessageBox.Show("Oggetto padre: " & sender.ToString & Constants.vbLf & _
Constants.vbLf & "Origine click: " & e.OriginalSource.ToString)
Dim Msg = "Hai premuto il pulsante "
Dim Origine = CType(e.OriginalSource, Button)
If Origine Is puls1 Then
MessageBox.Show(Msg & "1")
ElseIf Origine Is puls2 Then
MessageBox.Show(Msg & "2")
ElseIf Origine Is puls3 Then
MessageBox.Show(Msg & "3")
End If
End Sub
Nota. Va da sé che la prima istruzione MessageBox ha valenza didattica, serve per capire il meccanismo del routing, e potrebbe essere eliminata. In tal modo operano in modo trasparente solo le altre MessageBox che nei casi pratici saranno le serie di istruzioni specifiche di questo o quel pulsante.
Conclusione provvisoria. Limitandoci all’esempio trattato possiamo dire che in pratica questa soluzione, più aderente al mondo Wpf, equivale alla penultima, che adotta la clausola Handles plurima e funziona pure nel vecchio mondo delle Windows Form.
A buon intenditore potrebbe infine bastare la seguente, eloquente figura:
v.