WPF: Why are nested styles not always working? - wpf

I'm trying to apply a nested WPF style to a Toolbar. I'd like to have all children of the Toolbar (MenuItems, Buttons, ToggleButtons etc.) to have the specified style.
The problem is, that the nested style definition is applied correctly to some controls like MenuItems, but not to Buttons.
What am I doing wrong?
The MenuItem is correctly placed at the bottom of the Toolbar, but the ToggleButton is in the middle:
<Window.Resources>
<Style x:Key="MyToolbarStyle" TargetType="ToolBar">
<!-- Setters for Toolbar properties -->
<Setter Property="Height" Value="80" />
<!-- Nested setters for children of the Toolbar -->
<Style.Resources>
<Style TargetType="MenuItem">
<Setter Property="VerticalAlignment" Value="Bottom" />
</Style>
<Style TargetType="ToggleButton">
<Setter Property="VerticalAlignment" Value="Bottom" />
</Style>
</Style.Resources>
</Style>
</Window.Resources>
<Grid >
<ToolBar VerticalAlignment="Top" Style="{StaticResource MyToolbarStyle}">
<MenuItem Header="MyMenuItem" /> <!-- Appears on the bottom like defined in the style-->
<ToggleButton Content="MyToggleButton" /> <!-- Nested style does not seem to be applied-->
</ToolBar>
</Grid>

The WPF ToolBar is a special type of control that defines some custom styles for some WPF controls like Button, ToggleButton... full list here, you can identify them by ElementName + StyleKey property name. If you'd like to change a default style for a specific control you will have to modify one of these styles.
Try replacing your style for the ToggleButton with the following:
<Style x:Key="{x:Static ToolBar.ToggleButtonStyleKey}" TargetType="ToggleButton">
<Setter Property="VerticalAlignment" Value="Bottom" />
</Style>

What you are doing wrong is thinking that WPF Styles are like CSS styles. In WPF, Styles are just not used that way. Sure, if we could, we'd probably save a few lines of XAML, but we can't. The best that we can do is what you have done... I'm assuming that you've created a Style for a top level element like Control. As you have seen, not all controls will extend the Control class, so the Style won't be applied to all of them.
Instead, Styles in WPF are more like the .class styles in CSS... one Style per type and then we can apply a further Style per UI element. There are lots of situations like this in WPF where we wish we could write less code, but it is how it is and the sooner that everybody realises it, the better.
UPDATE >>>
In response to your first comment, you seem to be mistaken. Just to clarify, if what you are calling nested Styles are the Styles that you defined in the outer Style.Resources section, then there is nothing wrong with that... no problem what-so-ever. Just take those inner Styles out of the Resources section and you will see the same UI.
Now you're probably thinking of changing your question title to something like 'Why isn't my default ToggleButton Style being applied inside a ToolBar control?'. While I can't say for sure, I can only assume that this behaviour is caused by a Style that has been defined within the ToolBar ControlTemplate.
I'm thinking that because of the following points:
A custom implicit Style (no x:Key) will not work inside the ToolBar control.
A custom explicit Style (named) will work as expected inside the ToolBar control.
A Style property set on the element will work as expected inside the ToolBar control.

Related

WPF styling deep nested elements

I can't override a default style attribute of a third-party component that I use in a clean way. The visual tree looks something like this:
A
--B
...
------------Z
--------------TextBlock
I try to override the style of TextBlock like this:
<Style
TargetType="A">
<Style.Resources>
<Style
TargetType="TextBlock">
<Setter
Property="TextWrapping"
Value="Wrap" />
</Style>
</Style.Resources>
</Style>
But this doesn't work. In live visual tree, I confirm that component A sees my custom style but TextBlock doesn't see it. However, when I try:
<Style
TargetType="Z">
<Style.Resources>
<Style
TargetType="TextBlock">
<Setter
Property="TextWrapping"
Value="Wrap" />
</Style>
</Style.Resources>
</Style>
It works though. Therefore, it seems to me that WPF forces me to add all of the child components one by one as Style.Resources to be able to edit the innermost child. But I don't want to define all of the resources between component A to Z just to add one simple style. What are my options?
I'm not sure if I fully understand your question, as you've provided a working answer, and I can't see why would WPF force you to 'define all of the resources between component A to Z'.
However, here is a tip:
You can create a custom control that inherits Z and overrides OnApplyTemplate(). When OnApplyTemplate() is called you're guaranteed that the template was applied, so you can find any child control (your textbox) you want. (Just google for e.g 'wpf find child control by name' if you don't know how.) Once you have your textbox, you can change its' wrapping from code.
I had to customize a Ribbon control once, and that approach greatly simplified the task.

Reset inherited WPF style?

In the App.xaml portion of my application I have a ResourceDictionary element that targets things like DataGridColumnHeader and DataGridCell and applies custom styling to them. These definitions are global (in that they don't use a x:key - they apply to all instances of the type).
Is it possible to completely reset the style of these types to the 'base' Aero theme (or whatever theme is currently in use) for use in a subsection of my application?
I've tried using
<Style TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
</Style>
Which doesn't seem to do anything, as I'm guessing its just inheriting itself.
There are 2 ways to reset the style:
Set the style property of the element to {x:Null}. You will have to do this on every element.
<Button Height="50" Style="{x:Null}"></Button>
Define an empty style in the resources of the element containing the elements you want to reset. Don't specify BasedOn, otherwise it will inherit all properties and does not reset them to standard (as you correctly noticed).
<Style TargetType="{x:Type DataGridColumnHeader}">
</Style>

WPF TreeView style template resetting IsExpanded on style switch

I have a basic TreeView on a window that has a style applied from a resource in a dll. The style dll is capable of switching between two styles. When I don't have a style for the TreeView in the dll I am able to expand the TreeViewItems, switch styles (of other controls), and the TreeViewItems remain expanded. However, as soon as I add a style for the TreeView, the TreeViewItems that have been expanded collapse as soon as the style is switched.
All fancy animations and TreeViewItem styling has been removed in order to track down the problem. The remaining style is simply:
<Style x:Key="{x:Type TreeView}" TargetType="{x:Type TreeView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeView">
<Border Name="Border"
Background="Transparent"
BorderThickness="1"
CornerRadius="1">
<ItemsPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Any suggestions would be greatly appreciated. I've ran out of ideas!
When you take away a template or replace by another some dependency properties will reset to their default value. The reason for that is dependency property precedence. Take a look at this here: http://msdn.microsoft.com/library/ms743230
Think of it like this: A dependency property may contain many values which are layered. The value in top most layer is always the current value. If you take a style away, you take layers away too. If you take all layers away, the dependency property will take default value as current value.
When you replace the style of your TreeView, all underneath styles will be updated/reinitalized/changed/resetted...
To fix this try keep same template and only change colors, borders and stuff like that.. or use Binding

What is the most economical way to implement your own window border and title bar?

I am pretty new to WPF and am sitting here with my book trying to figure out the best approach to this application.
The title bar is not part of the client area so I am making my own title bar.
Which way would it be easiest to make this into some sort of resource to apply to all new windows I create?
<Application.Resources>
<Style x:Key="WindowTheme">
<Setter Property="Window.WindowStyle" Value="None"/>
</Style>
<!--Would I create a user control here for the title bar/border and title bar buttons? Or would it be a style?-->
</Application.Resources>
In WPF, there are two ways to use styles: Named styles and typed styles. A named style has an x:Key="..." attribute. A typed style doesn't have a name, but a TargetType="..." attribute (Rem: Named styles can and very often do have a TargetType as well, so named styles and unnamed styles would be more precise). Typed styles automatically get applied to all controls in the scope, which are of type TargetType (not a derived type).
<Style TargetType="{x:Type Window}">
<Setter Property="Background" Value="Blue" />
</Style>
To create your own window, you can set it's template property to a UserControl in the style:
<Style TargetType="{x:Type Window}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The professional way to implement the control template is to implement it 'from scratch', this means not using a UserControl which derives from Window. To do this, you define the visual tree of the Window, and use the WPF feature TemplateParts to define what part of your control template is responsible for what functionality of the window.
Here is a tutorial which describes pretty exactly what you want to do:
CodeProject tutorial

Toolbar button styles get ignored?

I have buttons on a toolbar in WPF.
When I do the XAML:
<ToolBar.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="Width" Value="21"></Setter>
<Setter Property="Height" Value="21"></Setter>
</Style>
</ToolBar.Resources>
None of the buttons on the toolbar set their sizes accordingly.
I have to go to each button and manually set their widths and heights to the desired values.
Any idea why the Style on the toolbar does not work?
This occurs because ToolBar applies the style identified by ToolBar.ButtonStyleKey to buttons, instead of leaving them with the default style. (That's why buttons in Toolbars are flat even though the default style is raised.) Reference.
You need to "hijack" this style, instead of the default style:
<ToolBar.Resources>
<Style x:Key="{x:Static ToolBar.ButtonStyleKey}" TargetType="Button">
<Setter Property="Width" Value="100" />
</Style>
</ToolBar.Resources>
Note the x:Key in the Style declaration.
If you are adding hardcoded buttons to your toolbar, you can set ToolBar.ItemContainerStyle to a custom style to get the effect you want.
<ToolBar.ItemContainerStyle>
<Style
TargetType="Button">
<Setter
Property="Width"
Value="21" />
<Setter
Property="Height"
Value="21" />
</Style>
</ToolBar.ItemContainerStyle>
If you are using ToolBar.ItemsSource you can instead use ToolBar.ItemTemplate to define a template for your toolbar data.
<ToolBar.ItemTemplate>
<DataTemplate>
<Button
Width="21"
Height="21"
Content="{Binding}" />
</DataTemplate>
</ToolBar.ItemTemplate>
Note that in some cases, both of these can be used at the same time for additional flexibility.
This applies not only to toolbar, but to all derivatives of ItemsControl.
Best of luck,
As noted in the other answer the Toolbar will automatically apply its own styles to many/most typical controls added to it.
As an alternative to hijacking its style keys or applying your own styles to the controls manually, you can instead override its method which sets its internal styles in the first place. Simple example:
public class LessIsMoreToolbar : ToolBar
{
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
{
// Nada
}
}
and then use <local:LessIsMoreToolbar> in your XAML instead of <Toolbar>.
Note that here PrepareContainerForItemOverride() specifically does NOT call base.PrepareContainerForItemOverride()`. This is what eliminates the setting of styles. You can view the base's version of this method yourself to verify this doesn't eliminate anything you need.
One caveat is that PrepareContainerForItemOverride is defined by ItemsControl, which is like a great-grandparent of Toolbar. Its version of this method kicks off some other Prepare... cases which you also should be careful won't break anything. You can't (or perhaps shouldn't) just call that version of the method directly.
But in the end if it works for you then this is a nice simple approach.

Resources