Questo frammento di XAML non compila:
<StackPanel>
<Button Width="100" Height="50" x:Name="btn1" Content="B1" />
<Button Width="100" Height="50" x:Name="btn1" Content="B2" />
</StackPanel>
il motivo è evidente: Non è possibile avere due elementi con lo stesso nome nel LogicalTree, proviamo quindi con questa alternativa:
<StackPanel x:Name="sp1">
<Button Width="100" Height="50" x:Name="btn1" Content="B1" Click="button1_Click" />
</StackPanel>
private void button1_Click(object sender, RoutedEventArgs e)
{
Button btn1 = new Button() { Name = "btn1", Width = 100, Height = 50, Content="B1" };
sp1.Children.Add(btn1);
}
e in questo caso invece non abbiamo problemi. Com’è possibile avere due pulsanti con lo stesso nome?
In realtà le cose non stanno proprio cosi, in quanto se riscriviamo il contenuto di button1_Click in questo modo:
private void button1_Click(object sender, RoutedEventArgs e)
{
Button btn2 = new Button() { Name = "btn2", Width = 100, Height = 50, Content="B2" };
sp1.Children.Add(btn2);
//Search for buttons...
bool btn1Found = this.FindName("btn1") != null; // true
bool btn2Found = this.FindName("btn2") != null; // false
}
notiamo che btn2 non è stato trovato anche se visibile nella finestra.
Il motivo di questa a prima vista stranezza sopratutto se paragonata ai WinForms, deriva dal fatto che i nomi dei vari elementi presenti nella finestra vengono registrati all’interno di un apposito NameScope e il fatto di aggiungere un qualsiasi elemento nel LogicalTree non ne implica la relativa registrazione.
Se volessimo, nel nostro caso, rendere btn2 visibile è necessario modificare il codice in questo modo:
private void button1_Click(object sender, RoutedEventArgs e)
{
Button btn2 = new Button() { Name = "btn2", Width = 100, Height = 50, Content="B2" };
this.RegisterName(btn2.Name, btn2);
sp1.Children.Add(btn2);
//Search for buttons...
bool btn1Found = this.FindName("btn1") != null; // true
bool btn2Found = this.FindName("btn2") != null; // true
}
Tutto questo per specificare che lo scope di visibilità dei nomi è totalmente indipendente dal relativo LogicalTree, è infatti possibile creare dei NameScopes indipendenti dal root Namescope normalmente utilizzato quando viene creato il LogicalTree allo startup come in questo esempio:
<StackPanel x:Name="sp1">
<Button Width="100" Height="50" x:Name="btn1" Content="B1" Click="button1_Click" />
<StackPanel x:Name="sp2">
</StackPanel>
</StackPanel>
e in button1_Click scrivere:
private void button1_Click(object sender, RoutedEventArgs e)
{
Button btn1 = new Button() { Name = "btn1", Width = 100, Height = 50, Content="B2" };
NameScope.SetNameScope(sp2, new NameScope());
sp2.RegisterName(btn1.Name, btn1);
sp2.Children.Add(btn1);
//Search for buttons...
bool btn1Found = this.FindName("btn1") != null; // true
bool btn2FoundOnSp2 = sp2.FindName("btn1") != null; // true
}
riuscendo a inserire, a tutti gli effetti, due pulsanti con lo stesso nome nel LogicalTree. Questo spiega com’è possibile che una cosa del genere riesca a funzionare (da MSDN):
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
>
<Page.Resources>
<ControlTemplate x:Key="MyButtonTemplate" TargetType="{x:Type Button}">
<Border BorderBrush="Red" Name="TheBorder" BorderThickness="2">
<ContentPresenter/>
</Border>
</ControlTemplate>
</Page.Resources>
<StackPanel>
<Button Template="{StaticResource MyButtonTemplate}">My first button</Button>
<Button Template="{StaticResource MyButtonTemplate}">My second button</Button>
</StackPanel>
</Page>
dove in teoria avremmo due oggetti Border con lo stesso nome.
La spiegazione sta nel fatto che ogni Template (e per completezza cito anche gli stili) ha il proprio Namescope indipendente da quello del controllo al quale viene applicato.
Sebbene normalmente non si interagisce coi NameScopes è importante conoscere questo diverso livello di visibilità soprattutto quando si cerca di capire il motivo percui un semplice Binding via ElementName, inspiegabilmente, non funziona.
Technorati Tags:
WPF NameScopes