Styling for direct children - wpf

Given this example XAML:
<TabControl>
<TabItem Header="Test">
<Grid> <!-- outer grid that should receive the styles -->
<Grid.RowDefinitions><!-- ... --></Grid.RowDefinitions>
<Grid.ColumnDefinitions><!-- ... --></Grid.ColumnDefinitions>
<Grid Grid.Row="1" Grid.Column="1">
<!-- inner grid, should NOT receive the styles -->
</Grid>
</Grid>
</TabItem>
</TabControl>
How can I style all direct Grid children of the TabItem and no other Grid's deeper in the hierarchy?
Here is what I tried (I put this in the the App.xml):
<Style TargetType="TabItem">
<Style.Resources>
<Style TargetType="Grid">
<Setter Property="Margin" Value="10" />
</Style>
</Style.Resources>
</Style>
(I know that I can assign certain styles using Style={StaticResource ...} but than I would have to apply it to all Grid's individually which seems lots of unnecessary code ...)

How can I style all direct Grid children of the TabItem and no other Grid's deeper in the hierarchy?
By explicitly setting the Style property for all outer Grid elements one way or another.
You could for example create a custom Grid type that you apply the Style to:
public class OuterGrid : Grid { }
XAML:
<Style TargetType="local:OuterGrid">
<Setter Property="Margin" Value="10" />
</Style>
...
<local:OuterGrid>
<!-- outer grid that should receive the styles -->
<Grid Grid.Row="1" Grid.Column="1">
<!-- inner grid, should NOT receive the styles -->
<TextBlock>inner</TextBlock>
</Grid>
</local:OuterGrid>
Or specify the default values of the custom Grid without using a Style:
public class OuterGrid : Grid
{
public OuterGrid()
{
Margin = new Thickness(10);
}
}
I am afraid there is no concept of CSS child selectors (>) in XAML.

Related

WPF Custom Control Display the Content

I'm creating a custom control in WPF, and I would like to be able to display whatever I put inside it.
A good example would be a Grid, StackPanel, DockPanel
Where you may do something like:
<StackPanel>
<TextBox />
<Button/>
</StackPanel>
And the StackPanel knows about the TextBox and the Button displays them and reacts accordingly.
Question:
How can I display what I put inside my control?
I would like to be able to do something like:
<controls:MyControl>
<Grid>
<TextBox />
<Button />
</Grid>
</controls:MyControl>
Update
Code behind looks like:
public class MyControl:ContentControl
{
static MyControl()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyControl),
new FrameworkPropertyMetadata(typeof(MyControl)));
}
}
<Style TargetType="{x:Type local:MyControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border BorderThickness="1" BorderBrush="Black">
<ContentPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Extend ContentControl:
public class MyControl : ContentControl {}
It has a Content property that you can set to any object, including a Grid or any other Panel.

WPF ControlTemplate breaks style

The stuff that does work
I need to style controls of a certain type that are children of a StackPanel. I'm using:
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">...</Style>
</StackPanel.Resources>
<TextBlock ...>
...
</StackPanel>
And this works fine! Each TextBlock looks to the resources of it's parent (the StackPanel) to find out how it should be styled. It doesn't matter how far down you nest the TextBlock down a StackPanel... if it doesn't find a style in its direct parent, it will look at its parent's parent and so on, until it finds something (in this case, the style that was defined in ).
The stuff that doesn't work
I ran into a problem when I nested a TextBlock inside a ContentControl, which had a Template (see code below). The ControlTemplate seems to disrupt the way a TextBlock retrieves its style from its parents, grandparents,...
The use of a ControlTemplate effectively seems to knock out cold the TextBlock's means of finding its rightful style (the one in StackPanel.Resources). When it encounters a ControlTemplate, it stops looking for its style in the resources up the tree, and instead defaults to the style in MergedDictionaries of the Application itself.
<StackPanel Orientation="Vertical" Background="LightGray">
<StackPanel.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Green" />
</Style>
</StackPanel.Resources>
<TextBlock Text="plain and simple in stackpanel, green" />
<ContentControl>
<TextBlock Text="inside ContentControl, still green" />
</ContentControl>
<ContentControl>
<ContentControl.Template>
<ControlTemplate TargetType="{x:Type ContentControl}">
<StackPanel Orientation="Vertical">
<ContentPresenter />
<TextBlock Text="how come this one - placed in the template - is not green?" />
</StackPanel>
</ControlTemplate>
</ContentControl.Template>
<TextBlock Text="inside ContentControl with a template, this one is green as well" />
</ContentControl>
</StackPanel>
Is there a way - besides duplicating the Style in StackPanel.Resources to ControlTemplate.Resources - to make the TextBlock inside that ControlTemplate find the defined style?
Thanks...
WPF considers ControlTemplates to be a boundry, and will not apply implicit styles (styles without an x:Key) inside of templates.
But there is one exception to this rule: anything that inherits from Control will apply implicit styles.
So you could use a Label instead of a TextBlock, and it would apply the implicit style defined further up your XAML hierarchy, however since TextBlock inherits from FrameworkElement instead of Control, it won't apply the implicit style automatically and you have to add it manually.
My most common way to get around this is to add an implicit style in the ControlTemplate.Resources that is BasedOn the existing implicit TextBlock style
<ControlTemplate.Resources>
<Style TargetType="{x:Type TextBlock}"
BasedOn="{StaticResource {x:Type TextBlock}}" />
<ControlTemplate.Resources>
Other common ways of getting around this are:
Place the implicit style in <Application.Resources>. Styles placed here will apply to your entire application, regardless of template boundaries. Be careful with this though, as it will apply the style to TextBlocks inside of other controls as well, like Buttons or ComboBoxes
<Application.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Green" />
</Style>
</Application.Resources>
Use a Label instead of a TextBlock since it's inherited from Control, so will apply implicit Styles defined outside the ControlTemplate
Give the base style an x:Key and use it as the base style for an implicit TextBlock styles inside the ControlTemplate. It's pretty much the same as the top solution, however it's used for base styles that have an x:Key attribute
<Style x:Key="BaseTextBlockStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="Green" />
</Style>
...
<ControlTemplate.Resources>
<Style TargetType="{x:Type TextBlock}"
BasedOn="{StaticResource BaseTextBlockStyle}" />
<ControlTemplate.Resources>

TabControl Styles

I am learning to use styles in wpf and I am creating a style for a Tab Control. I was wandering if someone can please tell how I can stop a style propagating down, for example I have a Tab control that where one of the tabitems holds another tabcontrol, of closable tabitems, (yes Nested TabControl O.o).
So in my first UserControl it holds the "Master" TabControl this UserControl also has a UserControl.Rescource that has a style for this TabControl. This style propogates down to the nested tabcontrol, how can I stop this from happening?
The other tab control is kept in a seperate usercontrol class.
Looks Something like this:
<UserControl.Resources>
<Style TargetType="{x:Type TabControl}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="Template">
...
<!-- The Style -->
...
</UserControl.Resources>
<TabControl SelectedIndex="{Binding Path=TabIndexFocus}">
<TabItem Header="Tab1" IsEnabled="{Binding Path=IsEnabled_WorkSpace}" >
<View:NestedTabControl/>
</TabItem>
<TabItem Header="Tab2">
<View:SomeOtherView />
</TabItem>
.....
</TabControl>
Thanks All :D
Make a copy of the entire default Style Template, then I would recommend putting it in a separate resource dictionary but either way you will give the style template a unique x:Key name so like;
<Style x:Key="NonDefaultTabControlStyle" Target="{x:Type TabControl}">
Then in your tab control itself call your specific Style template like;
<TabControl Style="{StaticResource NonDefaultTabControlStyle}" ....>
When you specify the uniquely named Style template it will use it, when you don't it will use the default. Hope this helps and best of luck!

Specify ControlTemplate for ItemsControl.ItemContainerStyle

The following is similar to what I'm trying to accomplish. However, I get the error
Invalid PropertyDescriptor value.
on the Template Setter. I suspect it's because I didn't specify a TargetType for the Style; however, I don't know the container type for ItemsControl.
<ItemsControl>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel>
<TextBlock Text="Some Content Here" />
<ContentPresenter />
<Button Content="Edit" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
<!-- heterogenous controls -->
<ItemsControl.Items>
<Button Content="Content 1" />
<TextBox Text="Content 2" />
<Label Content="Content 3" />
</ItemsControl.Items>
</ItemsControl>
You can qualify the property name with the type name:
<Setter Property="Control.Template">
The container for ItemsControl is normally a ContentPresenter, but if the child is a UIElement then it won't use a container. In this case, all of the children are Controls, so the ItemContainerStyle will apply to them directly. If you added an item other than a UIElement, that setter would set the Control.Template property on the ContentPresenter, which would succeed but have no effect.
Actually, it sounds like what you want is to wrap each child in a container, even if they are already a UIElement. To do that, you will have to use a subclass of ItemsControl. You could use an existing one like ListBox, or you could subclass ItemsControl and override GetContainerForItemOverride and IsItemItsOwnContainerOverride to wrap the items in your own container. You could wrap them in a ContentControl and then use that as the TargetType for the Style.
public class CustomItemsControl
: ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new ContentControl();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
// Even wrap other ContentControls
return false;
}
}
You will also need to set the TargetType on the ControlTemplate so that the ContentPresenter will bind to the Content property:
<ControlTemplate TargetType="ContentControl">
Also if you only want to do all of it with XAML you can simply use ListBox instead of ItemsControl and define a style for ListBoxItem:
<ListBox ItemsSource="{Binding Elements.ListViewModels}">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<StackPanel>
<TextBlock>Some Content Here</TextBlock>
<ContentPresenter Content="{TemplateBinding Content}" />
<Button>Edit</Button>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.Resources>
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
</ListBox>
Note that because I am using ListBox the container is ListBoxItem(Generally the container for WPF's default list control is always named the Item) so we create a style for ListBoxItem:
<Style TargetType="ListBoxItem">
Then we create a new ControlTemplate for ListBoxItem. Please note that ContentPresenter is not used as it always appears in articles and tutorials, you need to template-bind it to Content property of ListBoxItem, so it will show the content for that item.
<ContentPresenter Content="{TemplateBinding Content}" />
I just had the same problem and fixed it this way. I dont wanted some functionalities of ListBox ( item selection ) and by using this technique the item selection does not work anymore.

Underline the implicit Textblock created in Silverlight for a ContentPresenter when Content is a string?

I am trying to create a template for a content control such as Button or HeaderedContentControl etc. where the text is underlined.
I just want to underline the text when Content="This text is underlined" is specified.
It must continue to work as normal if Content is another UIElement.
Most posts asking this same question are satisfied with modifying the template to only work for a string as content. Scott Gu has a good article about styling buttons but doesn't address this issue.
The following sample will work if you actually pass in Content as an instance of type TextBlock but not as a string. Surely the visual tree has a TextBlock so it should style it. Perhaps this is a Sivlerlight limitation.
This example shows black text and big red text when I want it to display both as big red text.
<navigation:Page.Resources>
<Style TargetType="TextBlock" x:Key="style123">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontSize" Value="72"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="TextDecorations" Value="Underline"/>
</Style>
</navigation:Page.Resources>
<StackPanel>
<!-- This doesn't work and shows black text -->
<ContentPresenter Content="Small black text">
<ContentPresenter.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource style123}"/>
</ContentPresenter.Resources>
</ContentPresenter>
<!-- This works and shows red text -->
<ContentPresenter>
<ContentPresenter.Content>
<TextBlock Text="This is big red text"/>
</ContentPresenter.Content>
<ContentPresenter.Resources>
<Style TargetType="TextBlock" BasedOn="{StaticResource style123}"/>
</ContentPresenter.Resources>
</ContentPresenter>
</StackPanel>
You could subclass whatever actual ContentControl (i.e. Button) you are using and override OnContentChanged in order to reset the Content property to an underlined TextBlock if the newContent is a string. In the case that the newContent is not a string it would perform in the usual way.
public class UnderlineButton : Button
{
protected override void OnContentChanged(object oldContent, object newContent)
{
if (newContent is string)
{
TextBlock textBlock = new TextBlock();
textBlock.Text = newContent as string;
textBlock.TextDecorations = TextDecorations.Underline;
this.Content = textBlock;
}
base.OnContentChanged(oldContent, newContent);
}
}
It's kind of annoying to subclass just to accomplish this but it avoids messy style templates and subclassing ContentPresenter.
Try this example, using a DataTemplate to custom-render string content (I've just set the background to red):
<ContentControl Content="{Binding YourData}" >
<ContentControl.Resources>
<DataTemplate DataType="{x:Type s:String}">
<TextBlock Text="{Binding}" Background="Red" />
</DataTemplate>
</ContentControl.Resources>
</ContentControl>
EDIT: just as a note, you could pull this out into a ContentControl style rather than applying it inline each time, if you need better reusability...

Resources