Apply style to first child? - c#

Is there some way to apply styles to the first (or last or nth) child of a container (anything that contains children)? I am trying to customize the look of tab items so that the first one has different border radius than the others.
This is what I have now:
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Name="Border" BorderBrush="#666" BorderThickness="1,1,1,0" CornerRadius="8,8,0,0" Margin="0,0,0,-1">
<TextBlock x:Name="TabItemText" Foreground="#444" Padding="6 2" TextOptions.TextFormattingMode="Display">
<ContentPresenter x:Name="ContentSite" VerticalAlignment="Center" HorizontalAlignment="Center" ContentSource="Header" Margin="12,2,12,2"/>
</TextBlock>
</Border>
</Grid>
</ControlTemplate>

For ItemsControl derived classes (such as TabControl), you can use the ItemContainerStyleSelector dependency property. When this dependency property is set, ItemsControl will call StyleSelector.SelectStyle() for each item in the control. This will allow you to use different styles for different items.
The following example changes the last tab item in a TabControl so its text is bold and a bit larger than the other tabs.
First, the new StyleSelector class:
class LastItemStyleSelector : StyleSelector
{
public override Style SelectStyle(object item, DependencyObject container)
{
var itemsControl = ItemsControl.ItemsControlFromItemContainer(container);
var index = itemsControl.ItemContainerGenerator.IndexFromContainer(container);
if (index == itemsControl.Items.Count - 1)
{
return (Style)itemsControl.FindResource("LastItemStyle");
}
return base.SelectStyle(item, container);
}
}
This style selector will return the style with the key "LastItemStyle" but only for the last item in the control. The other items will use the default style. (Note, that this function only uses members from ItemsControl. It could also be used for other ItemsControl derived classes.) Next, in your XAML, you first need to create two resources. The first resource will be to this LastItemStyleSelector and the second resource is the style.
<Window.Resources>
<local:LastItemStyleSelector x:Key="LastItemStyleSelector" />
<Style x:Key="LastItemStyle" TargetType="TabItem">
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="FontSize" Value="16" />
</Style>
</Window.Resources>
Then finally your TabControl:
<TabControl ItemContainerStyleSelector="{StaticResource LastItemStyleSelector}">
<TabItem Header="First" />
<TabItem Header="Second" />
<TabItem Header="Third" />
</TabControl>
For more information see the MSDN documentation:
ItemsControl.ItemContainerStyleSelector Property
StyleSelector Class

Unlike HTML and CSS, there's not a simple way to determine and trigger that type of change.
You could potentially write a trigger and use a value converter to do something like that using this forum post as inspiration potentially.
Much simpler would be to apply a custom style to the tabitem that you want to look different. Have you tried that?
<TabItem Header="TabItem" Style="{DynamicResource FirstTabStyle}">
<Grid Background="#FFE5E5E5"/>
</TabItem>

Related

Combining DataTemplates at runtime

I have a ListBox that presents a databound list of objects via its ItemSource. Because each object has special display needs I’m defining an ItemTemplateSelector that returns the appropriate DataTemplate depending on the object. That all works without a hitch.
The DataTemplates for each object follow a common formula, but contains custom elements in the middle. For example:
<DataTemplate x:Key="collectibleTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border BorderBrush="LightGray" BorderThickness="1">
<Expander IsExpanded="True" Header="{Binding ComponentName}" Background="WhiteSmoke">
<StackPanel>
<TextBlock Margin="5,5,5,0" Text="{Binding EditDescription}" TextWrapping="Wrap" />
<!-- This is the only custom part of each template -->
<StackPanel Margin="0,10,5,0" Orientation="Horizontal">
<Label Content="Type:" />
<ComboBox Height="22" HorizontalAlignment="Left" SelectedItem="{Binding Path=CollectibleType, Mode=TwoWay}"
ItemsSource="{Binding Source={StaticResource collectibleTypeFromEnum}}" />
</StackPanel>
<!-- End custom part -->
<StackPanel Margin="0,0,0,5">
<Label Content="Available Actions:" >
<Label.Style>
<Style TargetType="Label">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding EditActions.Count}" Value="0">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
<ItemsControl ItemsSource="{Binding EditActions}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Command="{Binding}" Content="{Binding Title}" ToolTip="{Binding ToolTip}" Margin="5,0,5,0"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</StackPanel>
</Expander>
</Border>
</Grid>
</DataTemplate>
As you can see there’s lots of shared XAML, wrapping a small custom section in the middle.
Additional data templates will be written by other engineers (they’ll want to create one for each new object type that they add), so I’m interested in making the creation of a new DataTemplate as fool-proof and painless as possible. No copying of the entire DataTemplate with the custom “stuff” added in the middle, of course – but I’m also not partial to extracting parts of the template as reusable parts and referencing them in because it still leads to lots of duplicate code in each new DataTemplate, and that means possible errors and hard maintainability. I.e., this right here is a more maintainable approach but still feels suboptimal:
<DataTemplate x:Key="collectibleTemplate">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border BorderBrush="LightGray" BorderThickness="1">
<Expander IsExpanded="True" Header="{Binding ComponentName}" Background="WhiteSmoke">
<StackPanel>
<TextBlock Margin="5,5,5,0" Text="{Binding EditDescription}" TextWrapping="Wrap" />
<!-- This is the only custom part of each template -->
[...]
<!-- End custom part -->
<ContentPresenter Content="{StaticResource AvailableActions}" />
</StackPanel>
</Expander>
</Border>
</Grid>
</DataTemplate>
<StackPanel Margin="0,0,0,5" x:Key="AvailableActions" x:Shared="false">
<Label Content="Available Actions:" >
<Label.Style>
<!--
[Bottom half of shared XAML from the first example, offloaded here]
-->
</StackPanel>
So: what is my best strategy to solve this? AFAIK I’m stuck with using DataTemplates because that’s the only element that a ListBox ItemTemplateSelector accepts. Is there a way to create a compound DataTemplate in the DataTemplateSelector? I'd provide the stock DataTemplate that is shared by all objects, and the DataTemplateSelector references in the bit of custom XAML needed for each object type. Other engineers would hook into that generalized code behavior.
Not sure, fumbling a bit in the dark here as whether there is a pattern that allows me to solve this elegantly.
And, just for reference: my current DataTemplateSelector is super straightforward. This is where I would expect to construct the final DataTemplate, rather than simply returning one that's hardcoded in XAML.
public class NodeComponentDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
FrameworkElement element = container as FrameworkElement;
if (element != null && item != null)
{
if (item is CollectibleComponent)
return element.FindResource("collectibleTemplate") as DataTemplate;
// [...]
}
}
}
You could create the DataTemplate dynamically using the XamlReader.Parse or XamlReader.Load method, e.g.:
string template = "<DataTemplate xmlns =\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\" xmlns:x =\"http://schemas.microsoft.com/winfx/2006/xaml\"><StackPanel>[PLACEHOLDER]</StackPanel></DataTemplate>".Replace("[PLACEHOLDER]", "...custom code...");
return System.Windows.Markup.XamlReader.Parse(template) as DataTemplate;
The custom parts could be defined as UserControls.
I am afraid there is no way to base a DataTemplate on another one in pure XAML though.
You could create a new CustomControl that fits your needs. It will apply the style by itself and you can give additional DepdendencyProperties to make it more convinient. In the end you can still put it in a DataTemplate to use it with your DataTemplateSelector.

AutomationElement for WPF ToggleButton - have ClassName Button

I am using ToggleButtons in the ToolBar and I want to get and use them in the UI Automation tests, but when I check AutomationElement.Current for these buttons, its ClassName property is Button, while I would expect ToggleButton
The xaml is not starightforward, so I mention it here :
<ToolBar ItemsSource="{Binding}"/>
for the type that's in the ItemsSource I have a DataTemplate:
<DataTemplate DataType="{x:Type myViewModelType}">
<ContentPresenter Content="{Binding}" ContentTemplate="{StaticResource MyToolBarElementTemplate}">
<ContentPresenter.Resources>
<Style TargetType="{x:Type ToggleButton}" BasedOn="{StaticResource ThisStyleSetsWidthAndHeight}"/>
</ContentPresenter.Resources>
</ContentPresenter>
</DataTemplate>
the style is defined as follows:
<Style TargetType="{x:Type ButtonBase}" x:Key="ThisStyleSetsWidthAndHeight">
<Setter Property="styles:AttachedProperties.ContentWidth" Value="32"/>
<Setter Property="styles:AttachedProperties.ContentHeight" Value="32"/>
</Style>
and the content template looks like this:
<DataTemplate x:Key="MyToolBarElementTemplate" DataType="{x:Type myViewModelType}">
<ToggleButton x:Name="AutomationIdThatIGetOk">
...
</ToggleButton>
</DataTemplate>
I am a bit new to Automation Framework, I guess it has to do with all these templates and styles, but is there any way to get the proper AutomationPeer instance created for this ToggleButton?
...but when I check AutomationElement.Current for these buttons, its ClassName property is Button, while I would expect ToggleButton
Your expectation is wrong because the ToggleButtonAutomationPeer class actually returns the string "Button" from its GetClassNameCore() method: https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/Windows/Automation/Peers/ToggleButtonAutomationPeer.cs,a58abe77888c16cd
So you are getting the proper instance.

No Style Inheritance Inside ItemsControl / DataTemplate in WPF?

can anyone explain why the TextBlock inside my DataTemplate does not apply the style defined in my UserControl.Resources element, but the second TextBlock ('Test B') does?
I think it may have to do with a dependency property somewhere set to not inherit, but I can't be sure.
<UserControl.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Padding" Value="8 2" />
</Style>
</UserControl.Resources>
<StackPanel>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<!--Padding does not apply-->
<TextBlock>Test A</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<!--Padding applies-->
<TextBlock>Test B</TextBlock>
</StackPanel>
Templates are considered as a boundary. Elements within the templates falls in this boundary range, and look up for the style with a matching target type ends within this range at runtime as a result the TextBlock outside will pickup the style and the one inside wont. like adminSoftDK said you should give the style an x:Key and then apply it as static resource it will work.

want to create Group Template user control in silverlight

I want to create multiple groups of control(s) within a rectangular border. where each group will be containing control within it, surrounded by rectangular border and a header (optional) is to be placed over each child group's top-left above its border.
So, I created a class GroupLayout, each child element within this have to create its own new group. I created Header as an attached property.
Syntax making use of template is as:-
<GroupLayout Orientation = "Vertical">
<DataGrid GroupLayout.Header= "Group 1" />
<Grid GroupLayout.Header= "Group 2" />
-------So On--------
</GroupLayout>
as above given, DataGrid and Grid both should form there own two groups with vertical orientation. each child element should create its own new group.
So, I tried this as User Control:-
<Style TargetType = "GroupLayout">
<Setter.Property>
<ControlTemplate TargetType="GroupLayout">
<StackPanel>
<Border x:Name="MainParentGroupBorder">
<StackPanel>
<ContentPresenter Content = "{TemplateBinding HeaderLabel}" />
<Border x:Name="ChildGroupBorder">
<ContentPresenter Content = "{TemplateBinding Content}" />
</Border>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Property>
</Style>
In code behind I'm driving from ItemsControl.
But, this is not working as required. Now after a lot of efforts, I think I have to implement ItemTemplate in Xaml here. but I'm not able to do so to get the required result. Please help me.
Thanks,
GK Prajapati
It looks to me like you're re-inventing the wheel.
each group will be containing control within it, surrounded by rectangular border and a header (optional) is to be placed over each child group's top-left above its border
This is perfectly covered by an existing control: HeaderedContentControl. All you have to do is provide an appropriate control template for it. I suggest something like this:
<ItemsControl>
<controls:HeaderedContentControl Header="Group 1">
<DataGrid />
</controls:HeaderedContentControl>
<controls:HeaderedContentControl Header="Group 2">
<Grid />
</controls:HeaderedContentControl>
</ItemsControl>
Now, give the HeaderedContentControl the appropriate template:
<Style TargetType="controls:HeaderedContentControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="controls:HeaderedContentControl">
<StackPanel>
<Border x:Name="MainParentGroupBorder">
<StackPanel>
<ContentPresenter Content="{TemplateBinding Header}" />
<Border x:Name="ChildGroupBorder">
<ContentPresenter Content="{TemplateBinding Content}" />
</Border>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Edit
Expanding on the above -- if you need the specific syntax of using an attached property GroupLayout.Header, then I suggest having your GroupLayout class override the ItemsControl.GetContainerForItem method. Have it return an instance of HeaderedContentControl:
protected override DependencyObject GetContainerForItemOverride()
{
return new HeaderedContentControl();
}
Now, you can use another ItemsControl override -- PrepareContainerForItemOverride -- to pass along your attached property:
protected virtual void PrepareContainerForItemOverride(DependencyObject element, Object item)
{
// get the attached property from the ItemsControl item
string header = ((FrameworkElement)item).GetValue(GroupLayout.Header) as string;
// set the container's "Header"
((HeaderedContentControl)element).Header = header;
}
Now you can use the exact XAML syntax you need:
<GroupLayout Orientation = "Vertical">
<DataGrid GroupLayout.Header= "Group 1" />
<Grid GroupLayout.Header= "Group 2" />
</GroupLayout>

WPF: Multiple content presenters in a custom control?

I'm trying to have a custom control that requires 2 or more areas of the XAML to be defined by a child control - that inherits from this control. I'm wondering if there's a way to define multiple contentpresenters and one which acts as the default content presenter
<MyControl>
<MyControl.MyContentPresenter2>
<Button Content="I am inside the second content presenter!"/>
</MyControl.MyContentPresenter2>
<Button Content="I am inside default content presenter" />
</MyControl>
Is this possible, how do I define this in the custom control's template?
The template can just bind the separate ContentPresenter instances like this (I've only set one property here but you'll likely want to set others):
<ContentPresenter Content="{TemplateBinding Content1}"/>
<ContentPresenter Content="{TemplateBinding Content2}"/>
The control itself should expose two properties for content and set the default using the ContentPropertyAttribute:
[ContentProperty("Content1")]
public class MyControl : Control
{
// dependency properties for Content1 and Content2
// you might also want Content1Template, Content2Template, Content1TemplateSelector, Content2TemplateSelector
}
You can use an "ItemsControl" with a custom template.
<ItemsControl>
<ItemsControl.Style>
<Style TargetType="ItemsControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[0]}"/>
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[1]}"/>
<ContentControl Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items[2]}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Style>
<TextBlock Text="Item 1"/>
<TextBlock Text="Item 2"/>
<TextBlock Text="Item 3"/>
</ItemsControl>
Here's another option that doesn't require making a custom control and is more typesafe than doing the ItemsControl thing (if type safety is something you want..perhaps not):
...Use an attached property!
Create an attached property of the appropriate type. We happened to need a text control so I did a string TextContent attached property. Then create a TemplateBinding to it from the template, and when instantiating in Xaml set it there as well. Works nicely.

Categories