(WPF) Editable Listbox with Add and Remove buttons - wpf

May be I'm missing something, but I think there should be a nice way to add items to ListBoxes whose contents are bound to a data source, ether than having separate button near that ListBox to trigger add_new_item() from the code.
I guess I can figure out how to add a delete button near currently selected button through style. I'm not yet sure which parameter will trigger this. But I'm more concerned with adding items like shown in the image
My Awesome Custom Listbox
I'm not yet advanced into styling. What I do is, I take somebody's style and take it apart, then create what I actually need. I don't even know where to look at to see all styling techniques like looking at a class reference.
I had an idea of adding a button hanging in a corner of a ListBox or ComboBox, but how should I declare a new Event for it so I could assign unique functions to then other than one for everything.
I'm not asking for a complete solution, just a hint of what to do. I'll post an answer when I'll figure it out.
And after a year I've figured these things out.

Just bind to an ObservableCollection and add the value
If that is not what you are looking for please ask a more specific question

You have to create the button yourself and assign a command (MVVM) or click (code behind) to it. Your listbox itemssource should be observablecollection which will notify the UI anytime you add or remove an item from the listbox. If you know that you want a listbox with a button in many places, then you can create a usercontrol out of these two controls. If you want a button inside the lixtbox or combobox then you have to modify the template of these controls, slightly more complicated.

With time I figured these things (UVMCommand is my ICommand interface implementation, it is easier for me to use it this way):
public class Game : Notifiable {
public Game() {
Players = new ObservableCollection<Player>();
AddNewPlayer = new UVMCommand("AddNewPlayer", p => {
var pl = new Player() { Text = "New Player", IsSelected = true, IsEdited = true };
pl.RemoveThis = new UVMCommand("RemoveThis", pp => Players.Remove(pl));
Players.Add(pl);
});
}
private ObservableCollection<Player> _players;
public ObservableCollection<Player> Players { get { return _players; } set { _players = value; OnPropertyChanged("Players"); } }
private UVMCommand _addNewPlayer;
public UVMCommand AddNewPlayer { get { return _addNewPlayer; } set { _addNewPlayer = value; OnPropertyChanged("AddNewPlayer"); } }
}
public class Player : Notifiable {
public Player() {}
private UVMCommand _removeThis;
public UVMCommand RemoveThis { get { return _removeThis; } set { _removeThis = value; OnPropertyChanged("RemoveThis"); } }
}
public class Notifiable : INotifyPropertyChanged {
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And then making a user control in code-behind declare:
public partial class EditableListBox : System.Windows.Controls.ListBox, INotifyPropertyChanged {
public EditableListBox() {
InitializeComponent();
//var s = FindResource("EditableListBoxStyle") as Style;
//Style = s;
}
[Category("Common")]
public UVMCommand AddItem {
get { return (UVMCommand)GetValue(AddItemProperty); }
set {
SetValue(AddItemProperty, value);
}
}
// Using a DependencyProperty as the backing store for AddItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty AddItemProperty =
DependencyProperty.Register("AddItem", typeof(UVMCommand), typeof(EditableListBox),
new PropertyMetadata(
new UVMCommand("Default Command", p => { Debug.WriteLine("EditableListBox.AddITem not defined"); })));
[Category("Layout")]
public Orientation Orientation {
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
// Using a DependencyProperty as the backing store for Orientation. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation), typeof(EditableListBox), new PropertyMetadata(Orientation.Vertical));
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName) {
// take a copy to prevent thread issues
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And in XAML I define a Style with an add button in a template at the end of a list and a delete button in an item template:
<ListBox x:Name="listBox" x:Class="MTCQuest.CustomControls.EditableListBox"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MTCQuest"
xmlns:zq="clr-namespace:MTCQuest.ViewModel.zQuest;assembly=MTCQuest.ViewModel"
xmlns:xctk="http://schemas.xceed.com/wpf/xaml/toolkit"
xmlns:ccon="clr-namespace:MTCQuest.CustomControls"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Style="{DynamicResource EditableListBoxStyle}"
ItemContainerStyle="{DynamicResource EditableListBoxItemStyle}" d:DataContext="{DynamicResource TestTheme}" ItemsSource="{Binding Questions}">
<ListBox.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../Resources/StyleRes.xaml"/>
<ResourceDictionary Source="../Resources/QuestSpacificControlStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
<ccon:BindingExists x:Key="BindingExists"/>
<ccon:ColorToSolidColorBrushConverter x:Key="ColorToSolidColorBrushConverter"/>
<zq:Theme x:Key="TestTheme">
<zq:Theme.Questions>
<zq:Question IsEdited="True" IsSelected="True" Text="Some Question" Color="#FF2E00FF"/>
<zq:Question Text="Another Question"/>
</zq:Theme.Questions>
</zq:Theme>
<Style x:Key="EditableListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{StaticResource Pallete.Divider}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="{StaticResource Pallete.PrimaryText}"/>
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled"/>
<Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Auto"/>
<Setter Property="ScrollViewer.CanContentScroll" Value="true"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="True"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="ItemsPanel" Value="{DynamicResource OrientedItemsPanelTemplate}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBox}">
<ScrollViewer Background="{TemplateBinding Background}" SnapsToDevicePixels="true" Focusable="false" Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}">
<StackPanel Orientation="{Binding Orientation, ElementName=listBox}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<ccon:zButton x:Name="AddButton" Command="{Binding AddItem, RelativeSource={RelativeSource TemplatedParent}}"
HorizontalContentAlignment="Left"
Background="Transparent" Foreground="{DynamicResource Pallete.PrimaryText}"
BorderThickness="0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
<Rectangle Width="16" Height="16" Margin="5,0"
Fill="{DynamicResource Pallete.Accent}"
OpacityMask="{DynamicResource Icon_PlusSign}"/>
<TextBlock Text="Add" Foreground="{StaticResource Pallete.PrimaryText}"/>
</StackPanel>
</ccon:zButton>
</StackPanel>
</ScrollViewer>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="AddButton" Property="Visibility" Value="Visible"/>
</Trigger>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Effect">
<Setter.Value>
<ccon:DesaturateEffect DesaturationFactor=".25"/>
</Setter.Value>
</Setter>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsGrouping" Value="true"/>
<Condition Property="VirtualizingPanel.IsVirtualizingWhenGrouping" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="ScrollViewer.CanContentScroll" Value="false"/>
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="EditableListBoxItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource FindAncestor, AncestorLevel=1, AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Margin" Value="5,0"/>
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
<Setter Property="FocusVisualStyle">
<Setter.Value>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="2" SnapsToDevicePixels="True" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="True">
<Grid Height="33" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox x:Name="TB" Text="{Binding Text}"
VerticalContentAlignment="Center"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="5, 0" BorderThickness="0" GotFocus="TB_GotFocus"
Visibility="Collapsed"/>
<Label x:Name="Lb" Content="{Binding Text}"
VerticalContentAlignment="Center"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="5, 0" BorderThickness="0" Margin="2,1,0,0" />
<xctk:ColorPicker x:Name="CB" Grid.Column="1" Width="48" Visibility="Collapsed"
SelectedColor="{Binding Color}"
ShowRecentColors="True" ShowDropDownButton="False" ShowStandardColors="False"/>
<!--<ccon:zButton x:Name="CB" OpacityMask="{DynamicResource Icon_Edit}"
Width="16" Height="16" Panel.ZIndex="19"
Background="{Binding Color, Converter={StaticResource ColorToSolidColorBrushConverter}}" BorderThickness="0"
Visibility="Collapsed" Margin="2" Grid.Column="1"/>-->
<ccon:zButton x:Name="DB" OpacityMask="{DynamicResource Icon_MinusSign}"
Command="{Binding RemoveThis}"
Width="16" Height="16" Background="#FFD12929" BorderThickness="0"
Visibility="Collapsed" Margin="2" Grid.Column="2"/>
</Grid>
</Border>
<ControlTemplate.Triggers>
<!--<DataTrigger Binding="{Binding IsEdited}" Value="true">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=TB}" />
</DataTrigger>-->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="#1F26A0DA"/>
<Setter Property="BorderBrush" TargetName="Bd" Value="#A826A0DA"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="#3DDADADA"/>
<Setter Property="BorderBrush" TargetName="Bd" Value="#FFDADADA"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="#3D26A0DA"/>
<Setter Property="BorderBrush" TargetName="Bd" Value="#FF26A0DA"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsSelected" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="Visibility" TargetName="Lb" Value="Collapsed"/>
<Setter Property="Visibility" TargetName="TB" Value="Visible"/>
<Setter Property="Visibility" TargetName="DB" Value="Visible"/>
<Setter Property="Visibility" TargetName="CB" Value="Visible"/>
</MultiTrigger>
<DataTrigger Binding="{Binding Color, Converter={StaticResource BindingExists}, FallbackValue=false}" Value="false">
<Setter Property="Visibility" TargetName="CB" Value="Collapsed"/>
</DataTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="TextElement.Foreground" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<ItemsPanelTemplate x:Key="OrientedItemsPanelTemplate">
<VirtualizingStackPanel IsItemsHost="True"
Orientation="{Binding Orientation, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ccon:EditableListBox}}}"/>
</ItemsPanelTemplate>
</ResourceDictionary>
</ListBox.Resources>
And now it's just a:
<ccon:EditableListBox ItemsSource="{Binding Players}" AddItem="{Binding AddNewPlayer}" Orientation="Horizontal" HorizontalContentAlignment="Center" ScrollViewer.VerticalScrollBarVisibility="Disabled"/>
And Hell Yeagh! I can make it either vertical or horizontal.
I can't belive that when I was just learning WPF I've tried to do something that takes so many things to know and be able to write them (Commands, Templates, Triggers, DependencyProperties, INotifyPropertyChanged and many more), that I had no idea even existed. Just a yaer ago :)

Related

Show ContextMenu on whole TreeViewItem line in WPF

I have a TreeView with heterogeneuos node types. Each node type is configuered in XAML using a HierarchicalDataTemplate.
Some of the nodes have a ContextMenu, which is dependent on the type of the node. The ContextMenus are defined as static resources in XAML and attached to a DockPanel in the HierarchicalDataTemplate.
In addition, I am using the ControlTemplate for a TreeViewItem described by bendewey in the following StackOverflow question https://stackoverflow.com/a/672123/1626109. This ControlTemplate is defined so that the complete TreeViewItem is highlighted when it is selected.
A left click on any part of the line, selects the item.
In contrast, the context menus, which are defined in the HierarchicalDataTemplate, only work on the right hand part of the line.
I am looking for a way to configure the ContextMenus so that they are also available on the complete line.
This seems to require attaching the context menu to an element in the ControlTemplate, with a TemplateBinding to something defined in the HierarchicalDataTemplate, but I can't figure out how to do it.
By the way, the solution explorer in Visual Studio has exactly this behavior. The context menu is dependent on the node type, and it is available on the complete item, including to the left of the expand/collapse button.
(Edited) You need to get rid of the two columns of the Grid in TreeViewItem Style:
<Window.Resources>
<local:LeftMarginMultiplierConverter Length="19" x:Key="lengthConverter" />
<SolidColorBrush x:Key="GlyphBrush" Color="#444" />
<Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToggleButton">
<Grid Width="15" Height="13"
Background="Transparent">
<Path x:Name="ExpandPath"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="1,1,1,1"
Fill="{StaticResource GlyphBrush}"
Data="M 4 0 L 8 4 L 4 8 Z"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked"
Value="True">
<Setter Property="Data"
TargetName="ExpandPath"
Value="M 0 4 L 8 4 L 4 8 Z"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TreeViewItemFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Border>
<Rectangle Margin="0,0,0,0"
StrokeThickness="5"
Stroke="Black"
StrokeDashArray="1 2"
Opacity="0"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="{x:Type TreeViewItem}"
TargetType="{x:Type TreeViewItem}">
<Setter Property="Background"
Value="Transparent"/>
<Setter Property="HorizontalContentAlignment"
Value="{Binding Path=HorizontalContentAlignment,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment"
Value="{Binding Path=VerticalContentAlignment,
RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding"
Value="1,0,0,0"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="FocusVisualStyle"
Value="{StaticResource TreeViewItemFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<ControlTemplate.Resources>
<local:LeftMarginMultiplierConverter Length="19" x:Key="lengthConverter" />
</ControlTemplate.Resources>
<StackPanel>
<Border Name="Bd"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<Grid>
<ToggleButton Panel.ZIndex="2" x:Name="Expander"
HorizontalAlignment="Left"
Style="{StaticResource ExpandCollapseToggleStyle}"
IsChecked="{Binding Path=IsExpanded,
RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"
Margin="{Binding Converter={StaticResource lengthConverter}, ConverterParameter=0,
RelativeSource={RelativeSource TemplatedParent}}"/>
<ContentPresenter x:Name="PART_Header" ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}">
</ContentPresenter>
</Grid>
</Border>
<ItemsPresenter x:Name="ItemsHost" />
</StackPanel>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded"
Value="false">
<Setter TargetName="ItemsHost"
Property="Visibility"
Value="Collapsed"/>
</Trigger>
<Trigger Property="HasItems"
Value="false">
<Setter TargetName="Expander"
Property="Visibility"
Value="Hidden"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="HasHeader"
Value="false"/>
<Condition Property="Width"
Value="Auto"/>
</MultiTrigger.Conditions>
<Setter TargetName="PART_Header"
Property="MinWidth"
Value="75"/>
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="HasHeader"
Value="false"/>
<Condition Property="Height"
Value="Auto"/>
</MultiTrigger.Conditions>
<Setter TargetName="PART_Header"
Property="MinHeight"
Value="19"/>
</MultiTrigger>
<Trigger Property="IsSelected"
Value="true">
<Setter TargetName="Bd"
Property="Background"
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected"
Value="true"/>
<Condition Property="IsSelectionActive"
Value="false"/>
</MultiTrigger.Conditions>
<Setter TargetName="Bd"
Property="Background"
Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled"
Value="false">
<Setter Property="Foreground"
Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
However, this will distort the Header-Items arrangements. Therefore, you need to adjust the Margin in the HierarchicalDataTemplate part:
<TreeView Margin="50" HorizontalContentAlignment="Stretch" DataContext="{Binding}" ItemsSource="{Binding Models}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:Model}" ItemsSource="{Binding Models}">
<Border Background="Transparent">
<TextBlock Margin="{Binding Converter={StaticResource lengthConverter}, ConverterParameter=1,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=TreeViewItem}}"
Text="{Binding Name}" />
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="dddd"/>
</ContextMenu>
</Border.ContextMenu>
</Border>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
Note that you should adjusted the converter to take the required extra margin into account:
public class LeftMarginMultiplierConverter : IValueConverter
{
public double Length { get; set; }
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var item = value as TreeViewItem;
if (item == null)
return new Thickness(0);
int extra = int.Parse(parameter.ToString());
var t = item.GetDepth();
return new Thickness(Length * (item.GetDepth() + extra), 0, 0, 0);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new System.NotImplementedException();
}
}
If converter parameter is 1, it will add one point to the depth of the item. I use it for the Header part.
Another approach might be to add a DataTemplate to the ContentPresenter of the TreeViewItem Style. While I don't know how you can bind the ContextMenu, I prefer this one.

How can you share a resource between ListBox item instances?

We have a custom-rendered ListBox which maintains an instance of a StreamGeometry object that is based on its width. The control needs to then share that StreamGeometry instance with all of its items for rendering purposes.
Only way we can think is putting that StreamGeometry instance in the ViewModel for the ListBox, then binding to it in the individual DataTemplates, which just feels dirty to me considering that is a view-only thing and therefore shouldn't be in the ViewModel at all.
Note: We could also just store it via an attached property on the ListBox (or subclass the ListBox), but we're still left with binding of a view-only thing which seems wrong to me for something like this.
Any thoughts?
You can make the StreamGeometry a dependency property on your custom listview, then refer to it through Binding MyGeometry, RelativeSource={RelativeSource AncestorType=ListView}.
This way, there is no ViewModel involved.
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"
xmlns:s="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<!-- default lsitviewitem style except for added path -->
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="2,0,0,0"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<StackPanel Orientation="Horizontal">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
<!-- added path-->
<Path Stretch="Uniform" Stroke="DarkBlue" Fill="DarkOrchid" Data="{Binding MyGeometry, RelativeSource={RelativeSource AncestorType=ListView}}" />
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="Selector.IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid >
<local:CustomListView Margin="20" >
<local:CustomListView.Items>
<ListViewItem Content="ListViewItem1" />
<ListViewItem Content="ListViewItem2" />
<ListViewItem Content="ListViewItem3" />
</local:CustomListView.Items>
</local:CustomListView>
</Grid>
</Window>
CustomListView:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace WpfApplication1
{
public class CustomListView : ListView
{
public StreamGeometry MyGeometry { get { return (StreamGeometry)GetValue(MyGeometryProperty); } set { SetValue(MyGeometryProperty, value); } }
public static readonly DependencyProperty MyGeometryProperty = DependencyProperty.Register("MyGeometry", typeof(StreamGeometry), typeof(CustomListView), new PropertyMetadata(null));
protected override void OnRender(DrawingContext drawingContext)
{
StreamGeometry geometry = new StreamGeometry(); // directly opening MyGeometry results in "must have isfrozen set to false to modify" error
using (StreamGeometryContext context = geometry.Open())
{
Point p1 = new Point(this.ActualWidth * (2d / 5d), 0);
Point p2 = new Point(this.ActualWidth / 2d, -10);
Point p3 = new Point(this.ActualWidth * (3d / 5d), 0);
context.BeginFigure(p1, true, true);
List<Point> points = new List<Point>() { p2, p3 };
context.PolyLineTo(points, true, true);
}
drawingContext.DrawGeometry(Brushes.DarkOrchid, new Pen(Brushes.DarkBlue, 1), geometry);
this.MyGeometry = geometry;
base.OnRender(drawingContext);
}
}
}

WPF TreeView with right alignment values

I have a problem with my wpf treeview. It's code goes like this:
<TreeView ItemsSource="{Binding Path=Items}" Grid.RowSpan="2" Grid.ColumnSpan="2">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type m:MyTreeItem}" ItemsSource="{Binding Items}">
<DockPanel LastChildFill="True">
<TextBlock Text="{Binding Path=Value}" DockPanel.Dock="Right"/>
<TextBlock Text="{Binding Path=Display}" DockPanel.Dock="Left"/>
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
Where MyTreeItem is a simple class with 2 String properties (Display and Value) and a List of MyTreeItem called Items.
I need the tree view to display all 'Values' aligned to the right, while maintaining the tree tabulation of the items 'Display', based on it's deepth.
I have tryed to set the dockPanel to a fixed width, but that didn't work.
I want to do this without any c# code, just xaml.
Thanks in advance.
If I understand your question correctly you want to Stretch the TreeViewItems and align Value to the right. To get the TreeViewItems to Stretch Horizontaly you'll need to edit the Template for TreeViewItem. The problem is explained here: http://leecampbell.blogspot.com/2009/01/horizontal-stretch-on-treeviewitems.html
Here is an edited version of the TreeViewItem which does this. Note that you'll have to add a reference to PresentationFramework.Aero for this to work
Sample
<TreeView ItemsSource="{Binding Path=Items}"
Grid.RowSpan="2"
Grid.ColumnSpan="2"
ItemContainerStyle="{StaticResource StretchTreeViewItemStyle}">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type m:MyTreeItem}" ItemsSource="{Binding Items}">
<DockPanel LastChildFill="True">
<TextBlock Text="{Binding Path=Value}" DockPanel.Dock="Right"/>
<TextBlock Text="{Binding Path=Display}" DockPanel.Dock="Left"/>
</DockPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
StretchTreeViewItemStyle
<Style x:Key="TreeViewItemFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Padding="5,5,5,5" Width="16">
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="Transparent" Stroke="#FF989898">
<Path.RenderTransform>
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
</Path.RenderTransform>
</Path>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Stroke" TargetName="ExpandPath" Value="#FF1BBBFA"/>
<Setter Property="Fill" TargetName="ExpandPath" Value="Transparent"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="RenderTransform" TargetName="ExpandPath">
<Setter.Value>
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
</Setter.Value>
</Setter>
<Setter Property="Fill" TargetName="ExpandPath" Value="#FF595959"/>
<Setter Property="Stroke" TargetName="ExpandPath" Value="#FF262626"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="StretchTreeViewItemStyle"
TargetType="{x:Type TreeViewItem}"
xmlns:Microsoft_Windows_Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="1,0,0,0"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<ItemsPresenter x:Name="ItemsHost" Grid.Column="1" Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="VirtualizingStackPanel.IsVirtualizing" Value="true">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
class StretchedTreeView : TreeView {
protected override void OnItemContainerStyleChanged(Style oldItemContainerStyle, Style newItemContainerStyle) {
base.OnItemContainerStyleChanged(oldItemContainerStyle, newItemContainerStyle);
newItemContainerStyle.Setters
.Add(new EventSetter(LoadedEvent, new RoutedEventHandler(TreeViewItem_Loaded)));
}
void TreeViewItem_Loaded(object sender, RoutedEventArgs e) {
TreeViewItem item = sender as TreeViewItem;
if (item == null) {
return;
}
ContentPresenter cp = FindVisualChild<ContentPresenter>(item);
cp.HorizontalAlignment = HorizontalAlignment.Stretch;
Border border = FindVisualChild<Border>(item, "Bd");
border.SetValue(Grid.ColumnSpanProperty, 2);
}
static T FindVisualChild<T>(DependencyObject obj, string name = null) where T : DependencyObject {
for (int i = 0 ; i < VisualTreeHelper.GetChildrenCount(obj) ; i++) {
DependencyObject child = VisualTreeHelper.GetChild(obj, i);
if (child != null && child is T
&& (name == null || !(child is FrameworkElement) || name == ((FrameworkElement) child).Name)) {
return (T) child;
}
else {
T childOfChild = FindVisualChild<T>(child, name);
if (childOfChild != null) {
return childOfChild;
}
}
}
return null;
}
}

WPF - Setting different ToggleButton image for each TreeViewItem root node with styles

I am new to using styles, resources and templates in WPF. What I need to do is override the ToggleButton +/- in the TreeView to be an image, with a different image for each TreeViewItem root Node. For Instance, I would want an image of a car for the "Car" node, and an image of an airplane for the "Plane" node. I have a colorful and a grayscale image of each (for expanded/collapsed).
I've found styles to override the treeview and get the image set for the toggle button, but I am not sure the best way to style each item differently.
the style code for an item is quite long, so I'm pretty sure there's a better way than copy/pasting the full style just to change the source property.
Could someone please point me in the right direction on the best way to do this?
Thank you.
Here's the style I've been playing with, which was copied from a different post and altered for my images.
<Style x:Key="TreeViewItemFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Width="16" Height="16" Background="Transparent">
<Border Width="16" Height="16" SnapsToDevicePixels="true" Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}" BorderBrush="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" BorderThickness="1">
<Image x:Name="ExpandImg" Width="16" Height="16" Source="/MyApp;component/Images/Icons/Grayscale/car.ico" />
</Border>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Source" TargetName="ExpandImg" Value="/MyApp;component/Images/Icons/Color/car.ico"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TreeViewItemStyle1" TargetType="{x:Type TreeViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="1,0,0,0"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander" Style="{StaticResource ExpandCollapseToggleStyle}" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"/>
<Border x:Name="Bd" SnapsToDevicePixels="true" Grid.Column="1" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
<ContentPresenter x:Name="PART_Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" ContentSource="Header"/>
</Border>
<ItemsPresenter x:Name="ItemsHost" Grid.Column="1" Grid.ColumnSpan="2" Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="VirtualizingStackPanel.IsVirtualizing" Value="true">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
Well, unfortunately, that's the way WPF works. If you want to change the Style of the default controls, you're most likely bound to work with a bunch of Style declarations (as you've posted above) and make some changes on it.
Using Expression Blend makes it a bit easier since you'll have a UI designer that you can use to easily modify your styles. But behind the scenes, it still has the Style declarations. As an example, here's what I got doing your requirements using Blend:
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="WpfApplication1.MainWindow"
x:Name="Window"
Title="MainWindow"
Width="640" Height="480">
<Window.Resources>
<Style x:Key="TreeViewItemFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
<Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Padding="5,5,5,5" Width="16">
<Image x:Name="PART_Image" Source="Image1.jpg"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="False"/>
<Trigger Property="IsMouseOver" Value="True"/>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Source" TargetName="PART_Image" Value="Image2.jpg"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="1,0,0,0"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="VirtualizingStackPanel.IsVirtualizing" Value="true">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid x:Name="LayoutRoot">
<TreeView HorizontalAlignment="Left" Margin="8,8,0,105" Width="196">
<TreeViewItem Header="Item1">
<TreeViewItem Header="Item2"/>
</TreeViewItem>
</TreeView>
</Grid>
</Window>
Anyway, I hope this helps.
EDIT:
Ok, I noticed that the code I posted is much like what you posted in your example. But I assure you, I used expression blend to make the modifications above. And I guess that just proves the point: you're bound to work with a bunch of Style declarations, whether using Blend or not. =)
EDIT2:
Window.xaml
<Window x:Class="WpfApplication2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication2"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow" Height="300" Width="300">
<Window.Resources>
<HierarchicalDataTemplate x:Key="treeTemplate"
ItemsSource="{Binding SubItems}">
<TextBlock Text="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
<Style x:Key="ExpandCollapseToggleStyle" TargetType="{x:Type ToggleButton}">
<Setter Property="Focusable" Value="False"/>
<Setter Property="Width" Value="16"/>
<Setter Property="Height" Value="16"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border Background="Transparent" Height="16" Width="16">
<Image x:Name="PART_Image" Source="{Binding ImageSource}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="customTreeItemStyle" TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="VerticalContentAlignment" Value="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
<Setter Property="Padding" Value="1,0,0,0"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19" Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>
<Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
<ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsExpanded" Value="false">
<Setter Property="Visibility" TargetName="ItemsHost" Value="Collapsed"/>
</Trigger>
<Trigger Property="HasItems" Value="false">
<Setter Property="Visibility" TargetName="Expander" Value="Hidden"/>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="VirtualizingStackPanel.IsVirtualizing" Value="true">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<VirtualizingStackPanel/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<TreeView x:Name="tree"
ItemTemplate="{StaticResource treeTemplate}"
ItemContainerStyle="{StaticResource customTreeItemStyle}"/>
</Window>
Code-behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfApplication2
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private List<ViewModel> items = new List<ViewModel>();
public MainWindow()
{
InitializeComponent();
ViewModelImpl1 vm1 = new ViewModelImpl1() { Name = "VM1" };
vm1.SubItems.Add("SubItem1");
vm1.SubItems.Add("SubItem2");
vm1.SubItems.Add("SubItem3");
ViewModelImpl1 vm2 = new ViewModelImpl1() { Name = "VM2" };
vm2.SubItems.Add("SubItem1");
vm2.SubItems.Add("SubItem2");
vm2.SubItems.Add("SubItem3");
vm2.SubItems.Add("SubItem4");
ViewModelImpl2 vm3 = new ViewModelImpl2() { Name = "VM3" };
vm3.SubItems.Add("SubItem1");
items.Add(vm1);
items.Add(vm2);
items.Add(vm3);
tree.ItemsSource = items;
}
}
}
ViewModels
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace WpfApplication2
{
public abstract class ViewModel : INotifyPropertyChanged
{
private List<string> subItems = new List<string>();
public List<string> SubItems
{
get { return this.subItems; }
}
private string name;
public string Name
{
get { return this.name; }
set
{
this.name = value;
this.OnPropertyChanged("Name");
}
}
private bool isExpanded;
public bool IsExpanded
{
get { return this.isExpanded; }
set
{
this.isExpanded = value;
this.OnPropertyChanged("IsExpanded");
UpdateImageSource();
}
}
private string imageSource;
public string ImageSource
{
get
{
if (this.imageSource == null)
UpdateImageSource();
return this.imageSource;
}
protected set
{
this.imageSource = value;
this.OnPropertyChanged("ImageSource");
}
}
protected abstract void UpdateImageSource();
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
#endregion
}
public class ViewModelImpl1 : ViewModel
{
protected override void UpdateImageSource()
{
if (this.IsExpanded)
this.ImageSource = "pack://application:,,,/Images/bell1.png";
else
this.ImageSource = "pack://application:,,,/Images/bell2.png";
}
}
public class ViewModelImpl2 : ViewModel
{
protected override void UpdateImageSource()
{
if (this.IsExpanded)
this.ImageSource = "pack://application:,,,/Images/star1.png";
else
this.ImageSource = "pack://application:,,,/Images/star2.png";
}
}
}

How to Stretch WPF Tab Item Headers to Parent Control Width

Is there a way in XAML to cause the tab item headers to stretch across the width of the tab control?
For example, I have three tabs: red, blue and green. If I have a tab control with its width set to auto, the tab headers will only fill up part of the space above the tab content, but I want them to fill up all the space. For my three tab example, red should take up the first third of the control, blue should take up the center third, and green the final third.
I have an idea how to do this in a code behind which I am working on now, but I am interested in doing this the easiest way possible.
I took Jordan's example and made some changes to it. This version should work for any number of tabs:
namespace WpfApplication1.Converters
{
public class TabSizeConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
TabControl tabControl = values[0] as TabControl;
double width = tabControl.ActualWidth / tabControl.Items.Count;
//Subtract 1, otherwise we could overflow to two rows.
return (width <= 1) ? 0 : (width - 1);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
}
}
Same namespace in the xaml:
xmlns:local="clr-namespace:WpfApplication1.Converters"
And this will make all tabs use it:
<Window.Resources>
<local:TabSizeConverter x:Key="tabSizeConverter" />
<Style TargetType="{x:Type TabItem}">
<Setter Property="Width">
<Setter.Value>
<MultiBinding Converter="{StaticResource tabSizeConverter}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType={x:Type TabControl}}" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor,
AncestorType={x:Type TabControl}}" Path="ActualWidth" />
</MultiBinding>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
Everyone seems to be going the converter route, but it really is as simple as using a UniformGrid with Rows set to 1 in the TabControl template, in place of the TabPanel. Of course, you will have to re-template it, but this isn't too bad.
I am an old school style guy. and prefer this kind of functionality to encapsulate into the code of the control itself. My derived control looks like following:
public class CustomTabControl :TabControl
{
protected override void OnRenderSizeChanged(System.Windows.SizeChangedInfo sizeInfo)
{
foreach (TabItem item in this.Items)
{
double newW = (this.ActualWidth / Items.Count) - 1;
if (newW < 0) newW = 0;
item.Width = newW;
}
}
}
and my XAML looks like
</infrastructure:CustomTabControl>
<TabItem />
<TabItem />
</infrustracture:CustomControl>
Can someone explain why everyone prefers styling control instead of deriving.
I was able to do this using a Converter like so:
namespace WpfApplication1.Converters
{
public class SizeConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
double width = Double.Parse(value.ToString());
//Subtract 1, otherwise we could overflow to two rows.
return .25 * width - 1;
}
public object ConvertBack(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
}
Then adding the namespace to my xaml:
xmlns:local="clr-namespace:WpfApplication1.Converters"
Then making all of the TabItems use the converter:
<Window.Resources>
<local:SizeConverter x:Key="sizeConverter" />
<Style TargetType="{x:Type TabItem}">
<Setter Property="Width" Value="{Binding ElementName=x_Grid, Path=ActualWidth, Converter={StaticResource sizeConverter}}" />
</Style>
</Window.Resources>
x_Grid is the x:Name of the parent element I want the tabs to be 1/4 of, if that makes sense.
It is possible by binding the width to the ActualWidth of the parent tab control as shown below.
I have wrapped it in a style to apply to all tab pages.
<Grid>
<Grid.Resources>
<Style TargetType="TabItem">
<Setter Property="Width" Value="{Binding
Path=ActualWidth,
RelativeSource={RelativeSource
Mode=FindAncestor,
AncestorType={x:Type TabControl}}}"/>
</Style>
</Grid.Resources>
<TabControl>
<TabItem Header="Page3"/>
<TabItem Header="Page2"/>
<TabItem Header="Page3"/>
</TabControl>
</Grid>
This is the most swift solution:
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<DockPanel>
<UniformGrid IsItemsHost="True" Rows="1" DockPanel.Dock="Top"></UniformGrid>
<ContentPresenter ContentSource="SelectedContent"></ContentPresenter>
</DockPanel>
</ControlTemplate>
</TabControl.Template>
I have solved this problem by creating a special converter:
public class TabItemWidthAdjustmentConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
Double lTabControlWidth = value is Double ? (Double)value : 50; // 50 just to see something, in case of error
Int32 lTabsCount = (parameter != null && parameter is String) ? Int32.Parse((String)parameter) : 1;
return lTabControlWidth / lTabsCount;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And I calculate the value of one tab item in the Tag element of the TabControl, to avoid calculating it for each tab separately. Here is the sample code (Note that In my case I needed a horizontal ScrollViewer, because I have multiple tab items, and with a minimum width):
<TabControl Name="tabControl" VerticalAlignment="Stretch" SelectionChanged="TabControl_SelectionChanged"
Tag="{Binding RelativeSource={RelativeSource Self}, Path=ActualWidth, Converter={StaticResource tabItemWidthAdjustmentConverter}, ConverterParameter=15}"><!-- Here 15 because I have 15 tabs -->
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
<TabPanel x:Name="HeaderPanel"
Panel.ZIndex="1"
KeyboardNavigation.TabIndex="1"
IsItemsHost="True"/>
</ScrollViewer>
<ContentPresenter x:Name="PART_SelectedContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"/>
</StackPanel>
</ControlTemplate>
</TabControl.Template>
<TabItem Header="Tab1" MinWidth="115" VerticalAlignment="Stretch" Width="{Binding ElementName=tabControl, Path=Tag}">
<ContentControl ContentTemplate="{StaticResource My_TemplateTab1}">
<ContentPresenter />
</ContentControl>
</TabItem>
<TabItem Header="Tab2" MinWidth="115" Height="50" Width="{Binding ElementName=tabControl, Path=Tag}">
<ContentControl ContentTemplate="{StaticResource My_TemplateTab2}">
<ContentPresenter />
</ContentControl>
</TabItem>
<!-- Here another 13 tabs which I skipped -->
</TabControl>
I can say that this works like a charm in my case :)
Hope someone will find it useful!
P.S. I did not need/want any style in my case.
Here is a painless solution that uses Templates only:
<Window x:Class="Window1"
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:EffectLibrary="clr-namespace:EffectLibrary;assembly=EffectLibrary"
mc:Ignorable="d"
Title="Window1" Height="300" Width="300">
<TabControl Style="{DynamicResource TabControlStyle}" ItemContainerStyle="{DynamicResource TabItemStyle}" BorderBrush="{DynamicResource Pallete.Primary}" Foreground="{DynamicResource Pallete.Primary}" Background="Transparent" Margin="0" d:LayoutOverrides="Height">
<TabControl.Resources>
<Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="Padding" Value="0"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="#093A5F"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="Foreground" Value="#001423"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<Border x:Name="Bg" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}">
<Grid x:Name="templateRoot" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0"/>
<ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
<RowDefinition x:Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<UniformGrid x:Name="headerPanel" IsItemsHost="True" Margin="0">
<UniformGrid.Style>
<Style TargetType="{x:Type UniformGrid}">
<Setter Property="Rows" Value="1"/>
<Style.Triggers>
<DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource TemplatedParent}}" Value="Right">
<Setter Property="Columns" Value="1"/>
<Setter Property="Rows" Value="0"/>
</DataTrigger>
<DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource TemplatedParent}}" Value="Left">
<Setter Property="Columns" Value="1"/>
<Setter Property="Rows" Value="0"/>
</DataTrigger>
</Style.Triggers>
</Style>
</UniformGrid.Style>
</UniformGrid>
<Border x:Name="contentPanel" Grid.Column="0" KeyboardNavigation.DirectionalNavigation="Contained" Grid.Row="1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local" BorderThickness="0,1,0,0" BorderBrush="{TemplateBinding BorderBrush}">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="TabStripPlacement" Value="Bottom">
<Setter Property="Grid.Row" TargetName="headerPanel" Value="1"/>
<Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
<Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
<Setter Property="Height" TargetName="RowDefinition1" Value="Auto"/>
</Trigger>
<Trigger Property="TabStripPlacement" Value="Left">
<Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
<Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
<Setter Property="Grid.Column" TargetName="headerPanel" Value="0"/>
<Setter Property="Grid.Column" TargetName="contentPanel" Value="1"/>
<Setter Property="Width" TargetName="ColumnDefinition0" Value="Auto"/>
<Setter Property="Width" TargetName="ColumnDefinition1" Value="*"/>
<Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
<Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
</Trigger>
<Trigger Property="TabStripPlacement" Value="Right">
<Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
<Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
<Setter Property="Grid.Column" TargetName="headerPanel" Value="1"/>
<Setter Property="Grid.Column" TargetName="contentPanel" Value="0"/>
<Setter Property="Width" TargetName="ColumnDefinition0" Value="*"/>
<Setter Property="Width" TargetName="ColumnDefinition1" Value="Auto"/>
<Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
<Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Effect" TargetName="templateRoot">
<Setter.Value>
<EffectLibrary:DesaturateEffect DesaturationFactor=".25"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="TabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="FocusVisualStyle" Value="{StaticResource FocusVisual}"/>
<Setter Property="Foreground" Value="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}"/>
<Setter Property="Background" Value="{Binding Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}"/>
<Setter Property="BorderBrush" Value="{Binding BorderBrush, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TabControl}}}"/>
<Setter Property="Margin" Value="0"/>
<Setter Property="Padding" Value="0,5"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
<Setter Property="VerticalContentAlignment" Value="Stretch"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid x:Name="templateRoot" SnapsToDevicePixels="true" Background="{TemplateBinding Background}">
<Border x:Name="mainBorder" BorderBrush="{TemplateBinding BorderBrush}">
<Border x:Name="highlightBorder"/>
</Border>
<ContentPresenter x:Name="contentPresenter" ContentSource="Header" Focusable="False" HorizontalAlignment="{Binding HorizontalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{Binding VerticalContentAlignment, RelativeSource={RelativeSource AncestorType={x:Type ItemsControl}}}"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Effect" TargetName="templateRoot">
<Setter.Value>
<EffectLibrary:DesaturateEffect DesaturationFactor=".25"/>
</Setter.Value>
</Setter>
</Trigger>
<DataTrigger Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true">
<Setter TargetName="highlightBorder" Property="Background" Value="#0B79CE"/>
</DataTrigger>
<DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Top">
<Setter TargetName="mainBorder" Property="BorderThickness" Value="0,0,1,0"/>
<Setter TargetName="highlightBorder" Property="Height" Value="2"/>
<Setter TargetName="highlightBorder" Property="VerticalAlignment" Value="Bottom"/>
</DataTrigger>
<DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Bottom">
<Setter TargetName="mainBorder" Property="BorderThickness" Value="0,0,1,0"/>
<Setter TargetName="highlightBorder" Property="Height" Value="2"/>
<Setter TargetName="highlightBorder" Property="VerticalAlignment" Value="Top"/>
</DataTrigger>
<DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Left">
<Setter TargetName="mainBorder" Property="BorderThickness" Value="0,0,0,1"/>
<Setter TargetName="highlightBorder" Property="Width" Value="2"/>
<Setter TargetName="highlightBorder" Property="HorizontalAlignment" Value="Right"/>
</DataTrigger>
<DataTrigger Binding="{Binding TabStripPlacement, RelativeSource={RelativeSource AncestorType={x:Type TabControl}}}" Value="Right">
<Setter TargetName="mainBorder" Property="BorderThickness" Value="0,0,0,1"/>
<Setter TargetName="highlightBorder" Property="Width" Value="2"/>
<Setter TargetName="highlightBorder" Property="HorizontalAlignment" Value="Left"/>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TabControl.Resources>
<TabItem Header="Years">
<ListBox Background="{DynamicResource Pallete.Primary.Brightest}" Foreground="{DynamicResource Pallete.Primary}">
<TextBlock Text="2015"/>
<TextBlock Text="2016"/>
<TextBlock Text="2017"/>
</ListBox>
</TabItem>
<TabItem Header="Tables">
<ListBox Background="{DynamicResource Pallete.Primary.Brightest}" Foreground="{DynamicResource Pallete.Primary}">
<TextBlock Text="Table1..."/>
<TextBlock Text="Table2..."/>
<TextBlock Text="Table3..."/>
</ListBox>
</TabItem>
</TabControl>
</Window>
Hope I've included all colors and it will work for you. Ahh... Snap! My Desaturation Effect! My WPF startup project you can grab that effect from there if you want (It's easier to plop the effect in trigger, than to recolor all the thing, same with highlights).
Yes, that's a lot of code, but I simply changed ItemsContainer to look better and replaced standard Header control with UniformGrid and set Rows or Columns to 1 depending on TabStripPlacement. Now I can collapse this code, or hide it somewhere. :)
I followed Charlie's suggestion and went on re-templating route. Here is a simple implementation of TabControl that divides available space equally among its TabItems, using UniformGrid:
Control's XAML
<TabControl x:Class="YourNamespace.Views.BigTabsTabControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:YourNamespace.Views"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
Padding="2" HorizontalContentAlignment="Center" VerticalContentAlignment="Center"
BorderThickness="1" Foreground="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}">
<TabControl.Resources>
<SolidColorBrush x:Key="TabItem.Selected.Background" Color="#FFFFFF"/>
<SolidColorBrush x:Key="TabItem.Selected.Border" Color="#ACACAC"/>
</TabControl.Resources>
<TabControl.Style>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Background" Value="{StaticResource TabItem.Selected.Background}"/>
<Setter Property="BorderBrush" Value="{StaticResource TabItem.Selected.Border}"/>
</Style>
</TabControl.Style>
<TabControl.Template>
<ControlTemplate TargetType="{x:Type TabControl}">
<Grid x:Name="templateRoot" ClipToBounds="true" SnapsToDevicePixels="true" KeyboardNavigation.TabNavigation="Local">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="ColumnDefinition0"/>
<ColumnDefinition x:Name="ColumnDefinition1" Width="0"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition x:Name="RowDefinition0" Height="Auto"/>
<RowDefinition x:Name="RowDefinition1" Height="*"/>
</Grid.RowDefinitions>
<UniformGrid x:Name="headerPanel" Background="Transparent" Grid.Column="0" IsItemsHost="true" Grid.Row="0" KeyboardNavigation.TabIndex="1" Panel.ZIndex="1" />
<Border x:Name="contentPanel" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="0" KeyboardNavigation.DirectionalNavigation="Contained" Grid.Row="1" KeyboardNavigation.TabIndex="2" KeyboardNavigation.TabNavigation="Local">
<ContentPresenter x:Name="PART_SelectedContentHost" ContentSource="SelectedContent" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="TabStripPlacement" Value="Bottom">
<Setter Property="Grid.Row" TargetName="headerPanel" Value="1"/>
<Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
<Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
<Setter Property="Height" TargetName="RowDefinition1" Value="Auto"/>
<Setter Property="Margin" TargetName="headerPanel" Value="2,0,2,2"/>
</Trigger>
<Trigger Property="TabStripPlacement" Value="Left">
<Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
<Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
<Setter Property="Grid.Column" TargetName="headerPanel" Value="0"/>
<Setter Property="Grid.Column" TargetName="contentPanel" Value="1"/>
<Setter Property="Width" TargetName="ColumnDefinition0" Value="Auto"/>
<Setter Property="Width" TargetName="ColumnDefinition1" Value="*"/>
<Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
<Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
<Setter Property="Margin" TargetName="headerPanel" Value="2,2,0,2"/>
</Trigger>
<Trigger Property="TabStripPlacement" Value="Right">
<Setter Property="Grid.Row" TargetName="headerPanel" Value="0"/>
<Setter Property="Grid.Row" TargetName="contentPanel" Value="0"/>
<Setter Property="Grid.Column" TargetName="headerPanel" Value="1"/>
<Setter Property="Grid.Column" TargetName="contentPanel" Value="0"/>
<Setter Property="Width" TargetName="ColumnDefinition0" Value="*"/>
<Setter Property="Width" TargetName="ColumnDefinition1" Value="Auto"/>
<Setter Property="Height" TargetName="RowDefinition0" Value="*"/>
<Setter Property="Height" TargetName="RowDefinition1" Value="0"/>
<Setter Property="Margin" TargetName="headerPanel" Value="0,2,2,2"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="TextElement.Foreground" TargetName="templateRoot" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</TabControl.Template>
</TabControl>
Control's Code-Behind
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
namespace YourNamespace.Views
{
/// <summary>
/// A TabControl with large tabs.
/// </summary>
public partial class BigTabsTabControl : TabControl
{
public BigTabsTabControl()
{
InitializeComponent();
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if (this.Template != null)
{
UniformGrid X = this.Template.FindName("headerPanel", this) as UniformGrid;
if (X != null) X.Columns = this.Items.Count;
}
}
}
}
That's it. You can now add TabItems to this control and they'll adjust their width automatically. No need to specify Grid.Column for these TabItems either, they work fine without it, even at design time.
I don't know if it will work for tabs, but whenever I've needed to stretch anything to fill a container I've used a ViewBox. Is that the kind of thing you're looking for?
I am using the following solution:
In the main window i use a window re sized event and on tabcontrol Initialized event to set the Width of each Tab. The number '5' corresponds to my number of Tabs.
private void tabchanger_Initialized(object sender, EventArgs e)
{
foreach (TabItem item in tabchanger.Items)
{
double newW = (tabchanger.ActualWidth / 5) - 1;
if (newW < 0) newW = 0;
item.Width = newW;
}
}
private void Window_SizeChanged(object sender, SizeChangedEventArgs e)
{
foreach (TabItem item in tabchanger.Items)
{
double newW = (tabchanger.ActualWidth / 5) - 1;
if (newW < 0) newW = 0;
item.Width = newW;
}
}
In addition to Ryan Versaw's accepted solution which gives equal tabItem header widths, I found the following way to make it dependent on the length of each header.
First we get the string of each tabItem header by adding this line to the xaml multibinding. Thus it becomes:
<MultiBinding Converter="{StaticResource tabSizeConverter}">
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType={x:Type TabControl}}" Path="ActualWidth" />
<Binding Path="Header" RelativeSource="{RelativeSource Self}"/>
</MultiBinding>
And a bit more of code in the Converter (values[] gets also the tabItem Header):
public object Convert(object[] values, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
TabControl tabControl = values[0] as TabControl;
string AllHeaders = "";
for (int i = 0; i < tabControl.Items.Count; i++)
{
int index = tabControl.Items[i].ToString().IndexOf("Header:") + "Header:".Length;
string currentHeader = tabControl.Items[i].ToString().Substring(index);
currentHeader = currentHeader.Substring(0, currentHeader.Length - " Content:".Length);
AllHeaders += currentHeader;
}
//Normalize width according to header length
double width = values[2].ToString().Length * tabControl.ActualWidth / AllHeaders.Length;
//Subtract 1, otherwise we could overflow to two rows.
var retVal = (width <= 1) ? 0 : (width - 1);
return retVal;
}
I suspect there might be a more efficient way to get the AllHeaders string of all the headers, but it works fine as it is ...

Resources