Dev Logging

printf("does this work\n");
in

Cascading Implicit Styles in WPF

I was all set to write a post detailing a cumbersome workaround for getting implicit styles to cascade in WPF, when I figured out there’s an easier way. Actually, I think it’s 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 couldn’t find anything about this in the Sells book. It’s hinted at on p. 315 of WPF Unleashed, but not stated explicitly.)

Implicit style in WPF are styles you don’t need to refer to by name. They simply apply to all elements in scope belonging to the specified type. An example will help.

Suppose I’m 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 don’t need to. We do need to make sure to specify the target type; otherwise, WPF can’t know which elements we want this style to apply to. Let’s 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:

image

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>
I’ve created another implicit style to increase the font size of any TextBlocks I place in the top Grid. Let’s try running this and see what happens.

image

Hmm. That’s 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, it’s not clear how our implicit styles get applied, since we didn’t 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? What’s 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 element’s 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>

We’ll run the app to make sure it works:

image

Neat.

This isn’t a perfect solution, since we still need to be aware when we’re stomping on our previous styles, but I think the benefits of implicit styles are more than worth the price.

Like I said, I’m sure a lot of people know about this already, but I was oblivious until now, so I thought I’d 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.

Comments

Implizite Styles in WPF « Murratore’s Weblog said:

Pingback from  Implizite Styles in WPF &laquo; Murratore&#8217;s Weblog

# August 26, 2009 2:46 AM

David Dikman said:

Splendid! Absolutely splendid!

I´ve been searching for this solution a long time now. Knew it must've been possible to build styles on top of implicit styles.

Say you wouldn't happen to know if this is possible with implicit styles from theme packages linked in using merged resource directories?

Say I link in the Aero theme using a merged directory and then which to add an implicit margin to all buttons, could i do something like

<Style TargetType="{x:Type Button}" BasedOn="{StaticResource {x:Type Button}}">

...

</Style>

And place this below the merged resource, or would this mess something up since there are two implicit styles on the 'same' level in the hierarchy?

Best Regards

# September 24, 2009 10:59 AM

sdevlin said:

Thanks for the comment, David.

As to your question, I don't have a lot of experience with merged resource dictionaries, but my gut tells me it would blow up when you try to add another style with the same key. For example, the following code blows up:

   <Application.Resources>

       <Style x:Key="{x:Type TextBlock}" TargetType="{x:Type TextBlock}">

           <Setter Property="Foreground" Value="Red" />

       </Style>

       <Style TargetType="{x:Type TextBlock}" BasedOn="{StaticResource {x:Type TextBlock}}" >

           <Setter Property="Foreground" Value="Blue" />

       </Style>

   </Application.Resources>

With the following exception message:

Item has already been added. Key in dictionary: 'System.Windows.Controls.TextBlock'  Key being added: 'System.Windows.Controls.TextBlock'

If merged dictionaries simulate the above, then I think it won't work. My best advice would be to give it a shot and see what happens. If it doesn't work, maybe you can find some way to fit your style in somewhere else in the hierarchy?

Hope this helps.

# September 24, 2009 12:51 PM