Cascading Implicit Styles in WPF
Posted by: Clarity Blogs: ASP.NET,
on 27 Jul 2009 |
View original | Bookmarked: 0 time(s)
I was all set to write a post detailing a cumbersome workaround for getting implicit styles to cascade in WPF, when I figured out theres an easier way. Actually, I think its probably common knowledge to a lot of people, but I figured I should post it here in case anyone (like me) is in the dark. (After digging around a little, I couldnt find anything about this in the Sells book. Its hinted at on p. 315 of WPF Unleashed, but not stated explicitly.)
Implicit style in WPF are styles you dont need to refer to by name. They simply apply to all elements in scope belonging to the specified type. An example will help.
Suppose Im writing a new app, and I decide that every TextBlock should have red text. (Warning: I am not a designer.) I might create a style like this in my App.xaml:
<
Application.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Red" />
</Style>
</Application.Resources>
Note that we do not specify a key on the style. We dont need to. We do need to make sure to specify the target type; otherwise, WPF cant know which elements we want this style to apply to. Lets drop some code in to test that this works:
<StackPanel>
<Grid>
<TextBlock Text="where my implicit styles at" />
</Grid>
<Grid Grid.Row="1">
<TextBlock Text="where my implicit styles at" />
</Grid>
</StackPanel>
If we run the app, we can see that it works:
Perfect. Now, suppose I want to add another style to apply only to some of my text. I might scope it like this:
<StackPanel>
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="24" />
</Style>
</Grid.Resources>
<TextBlock Text="where my implicit styles at" />
</Grid>
<Grid Grid.Row="1">
<TextBlock Text="where my implicit styles at" />
</Grid>
</StackPanel>
Ive created another implicit style to increase the font size of any TextBlocks I place in the top Grid. Lets try running this and see what happens.
Hmm. Thats not quite what we wanted. Our new implicit style seems to have overridden the old one. To understand why, we need to think a little bit about how implicit styles actually work. When we specify a style explicitly, WPF walks up the chain of resource dictionaries and looks for one with the specified key. It returns the first (most local) one it finds. In light of this, its not clear how our implicit styles get applied, since we didnt specify any keys for them. If we think about it a little, we can work it out.
We know the styles have to have some keys; otherwise, how would they fit into the resource dictionaries? Whats not clear is how the key is chosen. One might suspect some randomly generated value to serve as the key, but the answer is much simpler: the target type is the key. (This is mentioned on p. 315 of WPF Unleashed.) Armed with this knowledge, we can easily understand implicit styles. For any elements where the style is not explicitly specified, look for the elements type in the resource dictionary chain and return the most local one you find. So in our example, we have two such styles: one at the application level and one scoped just to the Grid. It finds the local style first and stops looking.
Now that we have a good understanding of the problem, the solution is pretty obvious. Since we know our implicit style does have a key, we can use it as a base for our new style like this:
<StackPanel>
<Grid>
<Grid.Resources>
<Style TargetType="{x:Type TextBlock}"
BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="FontSize" Value="24" />
</Style>
</Grid.Resources>
<TextBlock Text="where my implicit styles at" />
</Grid>
<Grid Grid.Row="1">
<TextBlock Text="where my implicit styles at" />
</Grid>
</StackPanel>
Well run the app to make sure it works:
Neat.
This isnt a perfect solution, since we still need to be aware when were stomping on our previous styles, but I think the benefits of implicit styles are more than worth the price.
Like I said, Im sure a lot of people know about this already, but I was oblivious until now, so I thought Id share.
Hope this helps.
EDIT: Just as an addendum, I want to point out that you can have implicit styles with explicit keys. You just need to make sure that the key is the same as it would be if generated implicitly; that is, the key must be the target type. So when you see code like this:
<Application.Resources>
<Style x:Key="{x:Type TextBlock}" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Red" />
</Style>
</Application.Resources>
Understand that this is still an implicit type.
Hope this helps.