Style TabItems, don't affect nested TabControls - wpf

I'm styling TabItems of a TabControl. The problem is, the style affects items of nested TabControls. Propagating styles are the only way I know to get to the TabItems. Anyone know how to style the TabItems on just the outer TabControl?
In my case, the inner tabs are defined in plugins, so I can't access them to try this answer.
Demo app
Here's a demo app of my situation.
MainWindow.xaml
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TabControl ItemsSource="{Binding}">
<TabControl.Resources>
<Style TargetType="TabItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border Name="Border" Background="Red">
<ContentPresenter ContentSource="Header"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding TabName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentControl Content="{Binding TabConent}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
namespace WpfApplication1
{
public partial class MainWindow : Window
{
public class TabData
{
public string TabName { get; set; }
public Label TabConent
{
get
{
// In real case, this TabControl from someone else's plugin
var content = new TabControl();
content.Items.Add(new TabItem() { Header = "Nested Tab Item" });
return new Label() { Content = content };
}
}
}
public MainWindow()
{
DataContext = new ObservableCollection<TabData>() { new TabData() { TabName = "Tab Item" } }; ;
InitializeComponent();
}
}
}

Use this, and tell if this is what you want :
<TabControl.Resources>
<Style TargetType="TabItem">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=TabControl, AncestorLevel=2}}" Value="{x:Null}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TabItem">
<Border Name="Border" Background="Red">
<ContentPresenter ContentSource="Header"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</TabControl.Resources>

Related

Can't set both ContentTemplateSelector and Template properties on a DataGridColumnHeader

In short, the question title says it all. For those that want more detail, here is the crux of my problem: I need to apply a custom ControlTemplate to the DataGridColumnHeader elements in my DataGrid control, but I also need to style them differently, depending on the cell data nearest the header. However, when I set both the ContentTemplateSelector and Template properties on a DataGridColumnHeader element, the DataTemplateSelector that is set as the value of the ContentTemplateSelector property is not called. Commenting out the Template property setting confirms this to be the case, as the DataTemplateSelector element will now be called.
Yes, I know that you guys love to see some code, but I have completely templated the whole DataGrid control to look like Excel, so as you can imagine, I have far too much code to display here. But just to please you code hungry devs, I've recreated my problem in a much simpler example... let's first see the XAML:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Local="clr-namespace:WpfApp1"
xmlns:System="clr-namespace:System;assembly=mscorlib"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Grid>
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
<DataGrid.Items>
<System:String>One</System:String>
<System:String>Two</System:String>
<System:String>Three</System:String>
</DataGrid.Items>
<DataGrid.Resources>
<Local:StringDataTemplateSelector x:Key="StringDataTemplateSelector" />
<Style TargetType="{x:Type DataGridColumnHeader}" BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="ContentTemplateSelector" Value="{StaticResource StringDataTemplateSelector}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" />
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.Resources>
</DataGrid>
</Grid>
</Window>
Now the most simple DataTemplateSelector class:
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
namespace WpfApp1
{
public class StringDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
Debugger.Break();
return null;
}
}
}
In the XAML, we see a DataGrid, with just one DataGridTemplateColumn and three string values, one on each row, and some resources. There is a Style for the DataGridColumnHeader element in the Resource section, with the most simple ControlTemplate set up for it, that only includes the required named parts from the default ControlTemplate.
If you run the application as it is, then it will NOT currently break at the Debugger.Break() method in the StringDataTemplateSelector class. This is unexpected. If you now comment out the setting of the Template property in the Style and run the application again, then you will now see that program execution will now break at the Debugger.Break() method, as expected.
Further information:
In the Remarks section of the ContentControl.ContentTemplateSelector Property page of MSDN, it states that
If both the ContentTemplateSelector and the ContentTemplate properties are set, then this property is ignored.
However, it does not mention the Template property and there is also no mention of this on the Control.Template Property page on MSDN.
Furthermore, I tried this same setup using a simple Button control and can confirm that setting both the ContentTemplateSelector and the ContentTemplate properties on that does NOT stop the StringDataTemplateSelector class from being called:
<ItemsControl>
<ItemsControl.Resources>
<Local:StringDataTemplateSelector x:Key="StringDataTemplateSelector" />
<Style TargetType="{x:Type Button}">
<Setter Property="ContentTemplateSelector" Value="{StaticResource StringDataTemplateSelector}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Ellipse Stroke="Red" StrokeThickness="1" Width="{TemplateBinding ActualWidth}" Height="{TemplateBinding Height}" />
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.Resources>
<Button Content="One" />
<Button Content="Two" />
<Button Content="Three" />
</ItemsControl>
So, what I'm after is a way to apply a custom ControlTemplate element to the DataGridColumnHeader objects, yet still be able to have the DataTemplateSelector class called during the rendering process.
add a content presenter in your controltemplate?
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Grid>
<Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" />
<Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" />
<ContentPresenter></ContentPresenter>
</Grid>
</ControlTemplate>

How to apply different styles to datagrid columns

i'm quite new to WPF and i don't know how to use two different control template for the columns of a datagrid (that will always have 2 columns).
This is the XAML of the DataGrid:
<DataGrid x:Name="HomeSoftwareGrid"
CanUserAddRows="false"
ItemsSource="{Binding CollectedSoftwares}"
AutoGenerateColumns="True"
FontSize="15"
ColumnWidth="*"
IsReadOnly="True"
AutoGeneratingColumn="OnAutoGeneratingColumn"
CellEditEnding="OnCellEditEnding"
HorizontalAlignment="Center"
MaxWidth="600">
</DataGrid>
I'm using the property AutoGeneratingColumn to remove a specific column and edit the columns' headers
private void OnAutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
PropertyDescriptor propertyDescriptor = (PropertyDescriptor)e.PropertyDescriptor;
e.Column.Header = propertyDescriptor.DisplayName;
if (propertyDescriptor.DisplayName == "Resources")
{
e.Cancel = true;
}
else if (propertyDescriptor.DisplayName == "SoftwareStatus")
{
e.Column.Header = "Software Status";
}
else if (propertyDescriptor.DisplayName == "SoftwareName")
{
e.Column.Header = "Software Name";
}
}
These are the content templates i want to use, the first for the first columns and the second for second one obviously :D :
<!-- first column style -->
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<ContentPresenter Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- second column style -->
<Style x:Key="SoftwareStatusDataGridColumn" TargetType="{x:Type DataGridCell}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridCell}">
<Grid HorizontalAlignment="Center">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Rectangle x:Name="ImgPartially" Grid.Column="0" Width="20" Height="20" Fill="Yellow">
<Rectangle.OpacityMask>
<VisualBrush Stretch="Uniform" Visual="{StaticResource appbar_warning}" />
</Rectangle.OpacityMask>
</Rectangle>
<ContentPresenter Grid.Column="1" VerticalAlignment="Center" HorizontalAlignment="Center" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
How can i achieve this?
sender of event is DataGrid, and DataGrid can find cell style in its visual tree. Then you can assign that style to columns individually:
else if (propertyDescriptor.DisplayName == "SoftwareName")
{
e.Column.Header = "Software Name";
e.Column.CellStyle = (sender as FrameworkElement).FindResource("SoftwareStatusDataGridColumn") as Style;
}
<Style TargetType="{x:Type DataGridCell}">
this Style uses Type as a key, it will be assigned to DataGridCells by default, no need to set it explicitly from code behind

Eror setting Header for TabItem: Specified element is already the logical child of another element

I'm trying to set the header for a TabItem that is bound to an ObservableCollection. When adding the second item to the list I get the error Specified element is already the logical child of another element. From reading some of the answers in SO I understand that the construct I've chosen will add the TextBlock twice. But I have no clue how I would do it correctly.
Here is the XAML code:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<TabControl ItemsSource="{Binding Tabs}">
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<!--This works: <Setter Property="Header" Value="{Binding}" />-->
<Setter Property="Header">
<Setter.Value>
<TextBlock Text="{Binding}"></TextBlock>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
</Window>
Here the C# part:
public partial class MainWindow : Window
{
public ObservableCollection<string> Tabs { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
Tabs = new<string>();
Tabs.Add("Hello");
Tabs.Add("World"); // Error: "Specified element is already the logical child of another element."
}
}
Each Visual can be used only in one place in the visual tree. By adding another TabItem you try to use it more then one place. You can use HeaderTemplate instead of setting Header directly which will create Header content from the DataTemplate for each TabItem
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="{x:Type TabItem}">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>
You are technically creating two TextBlock when you call Tabs.Add, because you are not using DataTemplates. It is technically reusing the Header value which is connected to the first item tab and then the second tab will try to reuse that one.
It will be better to use DataTemplates so that it will just create a new UIElement value to attach to the parent.
<TabControl>
<TabControl.ItemContainerStyle>
<Style TargetType="TabItem">
<Setter Property="HeaderTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.ItemContainerStyle>
</TabControl>

Why does setting ListView.View blow away my custom ControlTemplate?

I have a custom control that inherits from ListView. I've defined a custom template for it and it works the way it's supposed to. However, when I define a view for my list view the custom template does not seem to be applied.
Here's what I'm doing:
My control:
public class TestListView : ListView
{
static TestListView()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(TestListView), new FrameworkPropertyMetadata(typeof(TestListView)));
}
public TestListView() : base(){}
}
My custom template:
<Style x:Key="{x:Type local:TestListView}" TargetType="{x:Type local:TestListView}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListView">
<DockPanel>
<TextBlock Text="foo!" DockPanel.Dock="Top"/>
<ScrollViewer x:Name="PART_ContentScrollViewer" Style="{DynamicResource {x:Static GridView.GridViewScrollViewerStyleKey}}">
<ItemsPresenter />
</ScrollViewer>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
My XAML:
<StackPanel>
<GroupBox Header="With View">
<local:TestListView>
<ListViewItem>One</ListViewItem>
<ListViewItem>Two</ListViewItem>
<ListViewItem>Three</ListViewItem>
<local:TestListView.View>
<GridView>
<GridViewColumn Header="MyColumn" />
</GridView>
</local:TestListView.View>
</local:TestListView>
</GroupBox>
<GroupBox Header="With No View">
<local:TestListView>
<ListViewItem>One</ListViewItem>
<ListViewItem>Two</ListViewItem>
<ListViewItem>Three</ListViewItem>
</local:TestListView>
</GroupBox>
</StackPanel>
Here's what happens:
I can get the expected results by explicitly declaring the style (Style="{StaticResource {x:Type local:TestListView}}") or if the style is defined in the same file as the control (not in Generic.xaml or some other resource dictionary).
Can anyone help me understand why this is happening?

how to change button template dynamically WPF

How can I change a Button template dynamically?
I have a ComboBox where by changing his selected value I want to change a Button Template.
This is what I have been trying to do:
<Window.Resources>
<ControlTemplate x:Key="ButtonControlTemplate1" TargetType="{x:Type Button}">
<Grid>
<Rectangle Fill="#FF2D2D7A" Margin="7.5,9.5,8.5,11" Stroke="Black"
RadiusX="45" RadiusY="45" StrokeThickness="6"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ButtonControlTemplate2" TargetType="{x:Type Button}">
<Grid>
<ed:RegularPolygon Fill="#FFE7F9C9" Height="Auto" InnerRadius="0.47211"
Margin="20.5,16,15.5,8" PointCount="5" Stretch="Fill"
Stroke="Black" StrokeThickness="6" Width="Auto"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<ComboBox Name="GroupBoxHeaderComboBox" ItemsSource="{Binding Path=collection}"
DisplayMemberPath="Key" Height="52" Margin="211.5,60,230.5,0"
VerticalAlignment="Top" SelectedIndex="1"/>
<Button Content="Button" HorizontalAlignment="Left" Height="102" Margin="47.5,0,0,91"
VerticalAlignment="Bottom" Width="132"
Template="{DynamicResource ButtonControlTemplate2}"/>
<Button Content="Button" HorizontalAlignment="Right" Height="112.5" Margin="0,0,27.5,85"
VerticalAlignment="Bottom" Width="153"
Template="{DynamicResource ButtonControlTemplate1}"/>
<Button Content="Button" Height="102" Margin="239.5,0,252.5,13.5"
VerticalAlignment="Bottom"
Template="{Binding ElementName=GroupBoxHeaderComboBox, Path=SelectedItem.Value}"/>
</Grid>
And here are the associated Templates:
<Window.Resources>
<ControlTemplate x:Key="ButtonControlTemplate1" TargetType="{x:Type Button}">
<Grid>
<Rectangle Fill="#FF2D2D7A" Margin="7.5,9.5,8.5,11" Stroke="Black"
RadiusX="45" RadiusY="45" StrokeThickness="6"/>
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="ButtonControlTemplate2" TargetType="{x:Type Button}">
<Grid>
<ed:RegularPolygon Fill="#FFE7F9C9" Height="Auto" InnerRadius="0.47211"
Margin="20.5,16,15.5,8" PointCount="5" Stretch="Fill"
Stroke="Black" StrokeThickness="6" Width="Auto"/>
</Grid>
</ControlTemplate>
</Window.Resources>
And the code behind:
public partial class MainWindow : Window
{
public Dictionary<string, string> collection
{
get;
private set;
}
public MainWindow()
{
this.InitializeComponent();
DataContext = this;
collection = new Dictionary<string, string>()
{
{ "DynamicResource ButtonControlTemplate2", "{DynamicResource ButtonControlTemplate2}"},
{ "DynamicResource ButtonControlTemplate1", "{DynamicResource ButtonControlTemplate2}"},
};
// Insert code required on object creation below this point.
}
}
Is there another genric way to acomplish this?... I want that most of the code would be xaml.
EDIT:
Is there a point to do it using a style? Let's say I want more then one object to act, otherwise is there a point to change the style and to do it all from there?
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public Dictionary<string, ControlTemplate> collection
{
get
{
Dictionary<string, ControlTemplate> controlTemplates = new Dictionary<string, ControlTemplate>();
controlTemplates.Add("ButtonControlTemplate1", FindResource("ButtonControlTemplate1") as ControlTemplate);
controlTemplates.Add("ButtonControlTemplate2", FindResource("ButtonControlTemplate2") as ControlTemplate);
return controlTemplates;
}
}
}
Create a ControlTemplate in Windows resource,
<Window.Resources>
<ControlTemplate x:Key="GreenTemplate" TargetType="{x:Type Button}">
<Grid>
<Ellipse Fill="Green"/>
<ContentPresenter Content="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Window.Resources>
Now in run time you can change the template property of button.
private void Button_Clicked(object sender, RoutedEventArgs e)
{
Button btn = e.OriginalSource as Button;
if (btn != null)
{
btn.Template = FindResource("GreenTemplate") as ControlTemplate;
}
}
You can use a data trigger and do it all in xaml.
This uses a tree but the concept is the same
<Window x:Class="WpfBindingTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfBindingTest"
Title="Window3" Height="300" Width="300" Name="win3" >
<Window.Resources>
<XmlDataProvider x:Key="treeData" XPath="*">
<x:XData>
<Items Name="Items" xmlns="">
<Item1/>
<Item2>
<Item22/>
<Item12/>
<Item13>
<Item131/>
<Item131/>
</Item13>
</Item2>
</Items>
</x:XData>
</XmlDataProvider>
<HierarchicalDataTemplate ItemsSource="{Binding XPath=child::*}" x:Key="template">
<TextBlock Name="textBlock" Text="{Binding Name}"/>
</HierarchicalDataTemplate>
</Window.Resources>
<StackPanel>
<TreeView ItemTemplate="{StaticResource template}"
Name="treeView"
ItemsSource="{Binding Source={StaticResource treeData}}">
<TreeView.ItemContainerStyle>
<!--Using style setter to set the TreeViewItem.IsExpanded property to true, this will be applied
to all TreeViweItems when they are generated-->
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
<Button Width="120" Height="30">
<Button.Style>
<Style TargetType="Button">
<Setter Property="Content" Value="Default" />
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=treeView, Path=SelectedItem.Name}" Value="Item12">
<Setter Property="Content" Value="Now changed" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
from here.
(I just googled to get an example faster)

Resources