Reputation: 15
I have a very peculiar and annoying issue that I can't get my head around. It's a bit difficult to explain so bear with me.
The background is that I have an app with 2 views and I can switch back-and-forth between the 2. This is implemented using MVVM, so that I have a main window with a ContentControl. The Content of the ContentControl is bound to CurrentViewModel property in the MainViewModel. Switching is by using commandbindings that set the CurrentViewModel to one or the other ViewModel of my 2 different views. The 2 ViewModels are linked to the Views by DataTemplate definitions in App.xaml. I believe this is a fairly common setup. (Note, in the current implementation I use MvvmLight and the ViewModels are managed using its SimpleIOC)
The problem is now if I have a ToggleButton in one view with some custom content (rectangle and circle) drawn for toggled / untoggled state. This is achieved using style triggers (or DataTriggers). There's still no issue until here, but if I bind the Fill property to the Foreground color of the ToggleButton (see code sample below) something strange happens:
EDIT: The error I get when switching back: System.Windows.Data Error: 4 : Cannot find source for binding with reference 'ElementName=TB1'. BindingExpression:Path=Foreground; DataItem=null; target element is 'Ellipse' (Name=''); target property is 'Fill' (type 'Brush')
Anybody has a clue where the issue comes from and how to fix this? I was thinking to simplify the test app, exclude all MvvmLight / IOC, but I doubt that issue is related to this.
<ToggleButton x:Name="TB1" Grid.Row="0" Grid.Column="1" Margin="5" Width="30"
Command="{Binding Toggle1}"
IsChecked="{Binding IsToggled1}">
<ToggleButton.Style>
<Style TargetType="{x:Type ToggleButton}">
<Setter Property="Content" >
<Setter.Value>
<Rectangle Width="17" Height="17" Fill="{Binding ElementName=TB1, Path=Foreground}" />
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsToggled1}" Value="False">
<Setter Property="Content">
<Setter.Value>
<Ellipse Width="17" Height="17" Fill="{Binding ElementName=TB1, Path=Foreground}" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ToggleButton.Style>
Upvotes: 1
Views: 803
Reputation: 452
You have trouble with XAML namescopes. It's like variable scope, just for XAML names. XAML namescope is created on a root element of XAML page, but Styles and Templates have their own namescopes because they are reusable. Let's say that Rectangle or Ellipse from your Style had a Name, and you had 5 ToggleButtons in your view using that Style. If Styles didn't have their own namescopes you would have 5 elements with the same name in same namescope. When XAML is parsed, one of the Content property values is applied (depending on IsChecked state of ToggleButton). It enters the namescope of TogglButton and that ElementName is resolved, but the other stays outside of namescope and can't be resolved.
So what can you do? You can use x:Reference
markup extension. So, instead of ElementName=TB1
you could use Source={x:Reference TB1}
. But then XAML parser will throw a cyclical dependency exception, because your reference would point to an element containing the reference (your Style is defined within your ToggleButton). To remedy this, define your style as a resource:
<Window.Resources>
<Style x:key="TBStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Content">
<Setter.Value>
<Rectangle Width="17" Height="17"
Fill="{Binding Foreground, Source={x:Reference TB1}}"/>
</Setter.Value>
</Setter>
<Style.Triggers>
<DataTrigger Binding="{Binding IsToggled1}" Value="False">
<Setter Property="Content">
<Setter.Value>
<Ellipse Width="17" Height="17"
Fill="{Binding Foreground, Source={x:Reference TB1}}}" />
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
....
<ToggleButton x:Name="TB1"
Margin="5"
Width="30"
Height="30"
IsChecked="{Binding IsToggled1}"
Style="{StaticResource TBStyle}"/>
If you applied this style to 5 ToggleButtons, they would all pick Foreground color from that one button named "TB1", but that is completely different issue.
Upvotes: 1