How to Stretch WPF Tab Item Headers to Parent Control Width - wpf

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 ...

Related

Visual indication of last focused item when application loses focus / alternative to IsKeyboardFocusWithin

In an application with multiple panels or documents to interact with, one needs a clear indication of which area of the app has focus. Visual Studio itself is a good example of this.
The following MCV Example is close to achieving the desired effect.
However, because it uses IsKeyboardFocusWithin, the most recently focused item in the application is not maintained when the application itself loses focus.
Desired Behavior: The focused item indicated by blue "SelectedColor" is maintained when the application loses focus. Visual Studio does this.
How can the indication of focus be maintained when the app loses focus?
Code
Note: There is no code-behind. This is the complete example.
<Window x:Class="TrackFocusMCVE.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="150" Width="300">
<Window.Resources>
<SolidColorBrush x:Key="MouseOverColor" Color="#FF1C97EA"/>
<SolidColorBrush x:Key="SelectedColor" Color="#FF007ACC"/>
<SolidColorBrush x:Key="InactiveColor" Color="#19FFFFFF"/>
<SolidColorBrush x:Key="BackgroundColor" Color="#FF44454B"/>
<Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="Margin" Value="5,15,5,7"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0,2,0,0"/>
<Setter Property="BorderBrush" Value="{DynamicResource InactiveColor}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="{StaticResource BackgroundColor}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Style.Triggers>
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="TabControl.BorderBrush" Value="{StaticResource SelectedColor}"/>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="TabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="15,2"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Name="TabBorder" MinWidth="40" MinHeight="20"
BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
Margin="{TemplateBinding Margin}" Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}">
<ContentPresenter ContentSource="Header" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true" />
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="TabBorder" Property="Background" Value="{StaticResource MouseOverColor}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource Self}}" Value="false" />
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="TabBorder" Property="Background" Value="{StaticResource InactiveColor}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsKeyboardFocusWithin, RelativeSource={RelativeSource Self}}" Value="true" />
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="TabBorder" Property="Background" Value="{StaticResource SelectedColor}"/>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Background="DimGray">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TabControl Grid.Column="0" x:Name="tc1" Style="{DynamicResource TabControlStyle}">
<TabItem Header="1" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
<TabItem Header="2" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
<TabItem Header="3" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
</TabControl>
<TabControl Grid.Column="1" x:Name="tc2" Style="{DynamicResource TabControlStyle}">
<TabItem Header="4" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
<TabItem Header="5" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
<TabItem Header="6" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
</TabControl>
</Grid>
</Window>
Partial answer:
You can make your focused tab stay blue when the window loses focus by changing your MultiDataTrigger Condition to use IsFocused instead of IsKeyboardFocusWithin like this:
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFocused, RelativeSource={RelativeSource Self}}" Value="true" />
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true" />
</MultiDataTrigger.Conditions>
<Setter TargetName="TabBorder" Property="Background" Value="{StaticResource SelectedColor}" />
</MultiDataTrigger>
However, there is no straight forward way to know when a child of the TabControl is focused so that you can pain the border blue.
Taking #Andy 's advice, I created a LastFocusedControl property and trigger off of that.
This solution works exactly as desired, albiet a tad verbose.
Edit: This solution falls apart as soon as you allow focus to shift to a control inside the tab content. Leaving the answer here, so I can update it if I find a work-around.
If anyone has a better solution, please share!
MainWindow.xaml
<Window x:Class="TrackFocusMCVE.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="150" Width="300">
<Window.Resources>
<SolidColorBrush x:Key="MouseOverColor" Color="#FF1C97EA"/>
<SolidColorBrush x:Key="SelectedColor" Color="#FF007ACC"/>
<SolidColorBrush x:Key="InactiveColor" Color="#33FFFFFF"/>
<SolidColorBrush x:Key="BackgroundColor" Color="#FF44454B"/>
<Style x:Key="TabControlStyle" TargetType="{x:Type TabControl}">
<Setter Property="Margin" Value="5,15,5,7"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0,2,0,0"/>
<Setter Property="BorderBrush" Value="{DynamicResource InactiveColor}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="{StaticResource BackgroundColor}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Style.Triggers>
<DataTrigger Binding="{Binding LastFocusedControl}" Value="1">
<Setter Property="TabControl.BorderBrush" Value="{StaticResource SelectedColor}"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="TabControlStyle2" TargetType="{x:Type TabControl}">
<Setter Property="Margin" Value="5,15,5,7"/>
<Setter Property="Padding" Value="0"/>
<Setter Property="BorderThickness" Value="0,2,0,0"/>
<Setter Property="BorderBrush" Value="{DynamicResource InactiveColor}"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="{StaticResource BackgroundColor}"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Style.Triggers>
<DataTrigger Binding="{Binding LastFocusedControl}" Value="2">
<Setter Property="TabControl.BorderBrush" Value="{StaticResource SelectedColor}"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="TabItemStyle" TargetType="{x:Type TabItem}">
<Setter Property="Padding" Value="15,2"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border Name="TabBorder" MinWidth="40" MinHeight="20"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Margin="{TemplateBinding Margin}"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}">
<ContentPresenter ContentSource="Header" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
</Border>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsMouseOver, RelativeSource={RelativeSource Self}}" Value="true" />
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="false"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="TabBorder" Property="Background" Value="{StaticResource MouseOverColor}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFocused, RelativeSource={RelativeSource Self}}" Value="false" />
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="TabBorder" Property="Background" Value="{StaticResource InactiveColor}"/>
</MultiDataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsFocused, RelativeSource={RelativeSource Self}}" Value="true" />
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="true"/>
</MultiDataTrigger.Conditions>
<Setter TargetName="TabBorder" Property="Background" Value="{StaticResource SelectedColor}"/>
</MultiDataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid Background="DimGray">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TabControl Grid.Column="0" x:Name="tc1" Style="{DynamicResource TabControlStyle}" GotFocus="Tc1_GotFocus" LostFocus="Tc1_LostFocus">
<TabItem Header="1" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
<TabItem Header="2" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
<TabItem Header="3" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
</TabControl>
<TabControl Grid.Column="1" x:Name="tc2" Style="{DynamicResource TabControlStyle2}" GotFocus="Tc2_GotFocus" LostFocus="Tc2_LostFocus">
<TabItem Header="4" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
<TabItem Header="5" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
<TabItem Header="6" Content="Tab Content" Style="{DynamicResource TabItemStyle}"/>
</TabControl>
</Grid>
</Window>
MainWindow.xaml.cs
namespace TrackFocusMCVE
{
using System.Windows;
using System.ComponentModel;
using System.Windows.Controls;
public partial class MainWindow : Window, INotifyPropertyChanged
{
private int _lastFocusedControl = 0;
public int LastFocusedControl
{
get { return _lastFocusedControl; }
set { _lastFocusedControl = value; RaisePropertyChanged(nameof(LastFocusedControl)); }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
TabItem item = (TabItem)tc1.Items[0];
item.Focus(); // Default Focus
}
private void Tc1_GotFocus(object sender, RoutedEventArgs e) { LastFocusedControl = 1; }
private void Tc1_LostFocus(object sender, RoutedEventArgs e) { LastFocusedControl = 0; }
private void Tc2_GotFocus(object sender, RoutedEventArgs e) { LastFocusedControl = 2; }
private void Tc2_LostFocus(object sender, RoutedEventArgs e) { LastFocusedControl = 0; }
private void RaisePropertyChanged(string propName) {
PropertyChanged(this, new PropertyChangedEventArgs(propName)); }
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}

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 to create object of wpf control from View model

Hi I have created converter to change the behaviour of Treeview control and this is working fine.this is my 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 DevLake.OrgChart.UI.View
{
/// <summary>
/// Interaction logic for TreeView.xaml
/// </summary>
public partial class TreeView : UserControl
{
public TreeView()
{
InitializeComponent();
}
}
class TreeViewLineConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
TreeViewItem item = (TreeViewItem)value;
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
return ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
}
}
But now I want move this code behind in to view model.But I am not able to create the object of TreeViewItem from view model.Please help me how do this in MVVM pattern
my UserControl:
<UserControl x:Class="DevLake.OrgChart.UI.View.TreeView"
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:local="clr-namespace:DevLake.OrgChart.UI.View"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<UserControl.Resources>
<local:TreeViewLineConverter x:Key="LineConverter"/>
<SolidColorBrush x:Key="GlyphBrush" Color="#444" />
<!--=================================================================
TreeViewItem
==================================================================-->
<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="White">
<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}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19"
Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- Connecting Lines -->
<Rectangle x:Name="HorLn" Height="1" Stroke="#8888" Margin="10,0,0,0" SnapsToDevicePixels="true"/>
<Rectangle x:Name="VerLn" Width="1" Stroke="#8888" Grid.RowSpan="2" SnapsToDevicePixels="true"/>
<ToggleButton x:Name="Expander"
Style="{StaticResource ExpandCollapseToggleStyle}"
IsChecked="{Binding Path=IsExpanded,
RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"/>
<Border Name="Bd"
Grid.Column="1"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<ContentPresenter x:Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Border>
<ItemsPresenter x:Name="ItemsHost"
Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"/>
</Grid>
<ControlTemplate.Triggers>
<!-- This trigger changes the connecting lines if the item is the last in the list -->
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LineConverter}}" Value="true">
<Setter TargetName="VerLn"
Property="Height"
Value="6"/>
<Setter TargetName="VerLn"
Property="VerticalAlignment"
Value="Top"/>
</DataTrigger>
<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>
</UserControl.Resources>
<StackPanel>
<TreeView x:Name="tvMain" ItemsSource="{Binding Root}" BorderThickness="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImagePath}" MaxHeight="40" MaxWidth="40" Margin="2"/>
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat=" {0} {1}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<StackPanel.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="true">
<Setter Property="StackPanel.Background" Value="LightBlue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</TreeView.ItemContainerStyle>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding SelectedCommand}"
CommandParameter="{Binding ElementName=tvMain, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
</StackPanel>
</UserControl>

Not able to change the wpf default node linking line for treeview control

I am trying to change the default node linking line for wpf treeview HierarchicalDataTemplate with help of converter.But still I am not able change the default look of tree view node linking .I tried to achieve this with help of below resource code but it is not changing the node default look even though it does not throw any error . Please help me to change the default look of node linking .I want to show the node linking with line
<UserControl x:Class="DevLake.OrgChart.UI.View.TreeView"
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:local="clr-namespace:DevLake.OrgChart.UI.View"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity">
<UserControl.Resources>
<local:TreeViewLineConverter x:Key="LineConverter"/>
<SolidColorBrush x:Key="GlyphBrush" Color="#444" />
<!--=================================================================
TreeViewItem
==================================================================-->
<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="White">
<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}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="19"
Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<!-- Connecting Lines -->
<Rectangle x:Name="HorLn" Height="1" Stroke="#8888" Margin="10,0,0,0" SnapsToDevicePixels="true"/>
<Rectangle x:Name="VerLn" Width="1" Stroke="#8888" Grid.RowSpan="2" SnapsToDevicePixels="true"/>
<ToggleButton x:Name="Expander"
Style="{StaticResource ExpandCollapseToggleStyle}"
IsChecked="{Binding Path=IsExpanded,
RelativeSource={RelativeSource TemplatedParent}}"
ClickMode="Press"/>
<Border Name="Bd"
Grid.Column="1"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}">
<ContentPresenter x:Name="PART_Header"
ContentSource="Header"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"/>
</Border>
<ItemsPresenter x:Name="ItemsHost"
Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="2"/>
</Grid>
<ControlTemplate.Triggers>
<!-- This trigger changes the connecting lines if the item is the last in the list -->
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource LineConverter}}" Value="true">
<Setter TargetName="VerLn"
Property="Height"
Value="6"/>
<Setter TargetName="VerLn"
Property="VerticalAlignment"
Value="Top"/>
</DataTrigger>
<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>
</UserControl.Resources>
<StackPanel>
<TreeView x:Name="tvMain" ItemsSource="{Binding Root}" BorderThickness="0">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Source="{Binding ImagePath}" MaxHeight="40" MaxWidth="40" Margin="2"/>
<TextBlock VerticalAlignment="Center">
<TextBlock.Text>
<MultiBinding StringFormat=" {0} {1}">
<Binding Path="FirstName"/>
<Binding Path="LastName"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<StackPanel.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected}" Value="true">
<Setter Property="StackPanel.Background" Value="LightBlue"/>
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</TreeView.ItemContainerStyle>
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectedItemChanged">
<i:InvokeCommandAction Command="{Binding SelectedCommand}"
CommandParameter="{Binding ElementName=tvMain, Path=SelectedItem}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TreeView>
</StackPanel>
</UserControl>
and my converter code is :
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 DevLake.OrgChart.UI.View
{
/// <summary>
/// Interaction logic for TreeView.xaml
/// </summary>
public partial class TreeView : UserControl
{
public TreeView()
{
InitializeComponent();
}
}
class TreeViewLineConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
TreeViewItem item = (TreeViewItem)value;
ItemsControl ic = ItemsControl.ItemsControlFromItemContainer(item);
return ic.ItemContainerGenerator.IndexFromContainer(item) == ic.Items.Count - 1;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new Exception("The method or operation is not implemented.");
}
}
}
you have an overriden TreeView.ItemContainerStyle. use BasedOn property to apply a base style where lines are defined
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="IsExpanded" Value="True"/>
<Setter Property="IsSelected" Value="{Binding IsSelected}"/>
</Style>
</TreeView.ItemContainerStyle>

(WPF) Editable Listbox with Add and Remove buttons

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 :)

Resources