WPF page menu change button colour - wpf

I would like to achieve something that when thinking about it seems very straightforward.
I have a MVVM application of sorts, where I have a window with multiple pages/views and along the top I have an itemscontrol of buttons which take you to the various pages. Now I'd like the current pages button to change colour and stay that way while you're on the page.
Here is some of my code:
<DockPanel>
<Border DockPanel.Dock="Top" BorderBrush="#FAAA" BorderThickness="0,0,0,3" Background="#FDDD">
<ItemsControl ItemsSource="{Binding PageViewModels}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button Width="75"
Height="30"
Content="{Binding Name}"
Command="{Binding DataContext.ChangePageCommand, RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
CommandParameter="{Binding }"
Style="{StaticResource MenuButton}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Border>
<ContentControl Content="{Binding CurrentPageViewModel}" />
</DockPanel>
I was told that Data triggers may help but I don't know how to implement that into an itemscontrol. Will I have to break it apart, display all the buttons manually and then set it based on name or something?
Thanks

WPF already have a control that does the described behavior called TabControl. It can be styled to look like buttons if you prefer instead of regular tabs, just modify the control template for tab items. Below is some sample code making the selected tab red.
XAML:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<LinearGradientBrush x:Key="LightBrush" StartPoint="0,0" EndPoint="0,1">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="#FFF" Offset="0.0"/>
<GradientStop Color="#EEE" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<SolidColorBrush x:Key="SolidBorderBrush" Color="#888" />
<SolidColorBrush x:Key="DisabledBackgroundBrush" Color="#EEE" />
<SolidColorBrush x:Key="DisabledBorderBrush" Color="#AAA" />
<SolidColorBrush x:Key="DisabledForegroundBrush" Color="#888" />
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border
Name="Border"
Margin="0,0,-4,0"
Background="{StaticResource LightBrush}"
BorderBrush="{StaticResource SolidBorderBrush}"
BorderThickness="1,1,1,1"
CornerRadius="2,12,0,0" >
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="12,2,12,2"
RecognizesAccessKey="True"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="100" />
<Setter TargetName="Border" Property="Background" Value="Red" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="Border" Property="Background" Value="{StaticResource DisabledBackgroundBrush}" />
<Setter TargetName="Border" Property="BorderBrush" Value="{StaticResource DisabledBorderBrush}" />
<Setter Property="Foreground" Value="{StaticResource DisabledForegroundBrush}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel>
<TabControl
ItemsSource="{Binding Path=PageViewModels}">
<TabControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</StackPanel>
</Window>
Code behind:
using System.Collections.Generic;
using System.Windows;
namespace WpfApplication
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
public List<PageViewModel> PageViewModels
{
get
{
return new List<PageViewModel>() { new PageViewModel() { Header = "A", Content = "AAA" }, new PageViewModel { Header = "B", Content = "BBB" } };
}
}
}
public class PageViewModel
{
public string Header { get; set; }
public string Content { get; set; }
}
}

Related

WPF TreeView displaying data

Here is my treeview code :
<TreeView Name="UIListAddedTaches" Grid.Column="2" Grid.RowSpan="5" Background="#FFE4E4E4" Foreground="Gray">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="SnapsToDevicePixels" Value="True"/>
<Setter Property="BorderThickness" Value="1"/>
<!--working style-->
<!--When skip this 'template' setter , the data are displayed-->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TreeViewItem}">
<Border Name="Border" CornerRadius="1" BorderThickness="1">
<Border.BorderBrush>
<SolidColorBrush Color="Red"/>
</Border.BorderBrush>
<Border.Background>
<SolidColorBrush Color="Green" Opacity="0.5" />
</Border.Background>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<DataTemplate>
<Canvas>
<TextBlock Canvas.Bottom="-20" Canvas.Left="570" FontWeight="Bold" Text="{Binding UserID}">
<TextBlock.ToolTip>
<ToolTip Content="{Binding Author}" ContentStringFormat="{}Auteur : {0}" />
</TextBlock.ToolTip>
</TextBlock>
<!--...-->
</Canvas>
</DataTemplate>
</TreeView.ItemTemplate>
</TreeView>
The problem comes cause of the "Template" setter. When I put this style setter, the data are "hidden" by the background : even with the opacity. Datas are still in the list. Doing this in this way in this exemple is overkill but i need to do it like that to apply conditionnal triggers and stuff.
A solution to get datas AND template prop ?

apply an existed style for ListBoxItem template

I'm trying to give a style to items in a ListBox, I made this style previously for ListViewItem which about TextBlock, Image and a Border which changes its color when an item event raised (IsSelected, IsMouseOver, IsSelectionActive), Now I want to keep this style and apply it to any item added to a ListBox
<Style x:Key="ListBoxPCInfoStyle" TargetType="{x:Type ListBoxItem}">
<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 ListBoxItem}">
<Grid HorizontalAlignment="Left" Height="74" VerticalAlignment="Top" Width="68">
<Image x:Name="Img" Width="56" Height="56" Margin="6,0,6,18" Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path= ActualHeight}"/>
<Border x:Name="border" BorderBrush="{x:Null}" BorderThickness="1" HorizontalAlignment="Left" Height="74" VerticalAlignment="Top" Width="68" CornerRadius="2.5"/>
<TextBlock HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="{TemplateBinding Name}" VerticalAlignment="Bottom" Width="Auto" Height="17" TextAlignment="Center" Margin="4,0"/>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="border">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#33C1DEFF" Offset="0"/>
<GradientStop Color="#41A5CDFF" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="BorderBrush" TargetName="border" Value="#FF7DA2CE"/>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="BorderBrush" TargetName="border" Value="#FF7DA2CE"/>
<Setter Property="Background" TargetName="border">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#97C1DEFF" Offset="0"/>
<GradientStop Color="#A7A5CDFF" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="Selector.IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="BorderBrush" TargetName="border" Value="#FFB4B4B4"/>
<Setter Property="Background" TargetName="border">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#7FE5E5E5" Offset="0"/>
<GradientStop Color="#B2CCCCCC" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And I have this ListBox
<ListBox x:Name="ListHosts" Background="{x:Null}" BorderBrush="{x:Null}">
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Border x:Name="border" BorderBrush="{x:Null}" BorderThickness="1" HorizontalAlignment="Left" Height="20" VerticalAlignment="Top" Width="50" CornerRadius="2.5"/>
<Image x:Name="Img" Source="BtnImg/Computer.png" Stretch="None" Margin="3,0,10,0"/>
<TextBlock x:Name="PCName" Margin="0,7" TextWrapping="Wrap" Height="16" HorizontalAlignment="Left"><Run Text="{Binding Name}"/></TextBlock>
</StackPanel>
</DataTemplate>
</ListBox>
I feel like I'm missing something simple here... can someone help me spot it?
2 Options:
Either remove the x:Key from the style:
<Style TargetType="{x:Type ListBoxItem}">
<!-- ... -->
this will make the style apply to all ListBoxItems in the resource scope.
or
Explicitly reference the style in your ListBox:
<ListBox ItemContainerStyle="{StaticResource ListBoxPCInfoStyle}">
<!-- ... -->
-------------------------------------------------------------------------------------------
Anyways, all your XAML is wrong. You're defining the ListBoxItem.Template like this:
<Grid HorizontalAlignment="Left" Height="74" VerticalAlignment="Top" Width="68">
<Image x:Name="Img" Width="56" Height="56" Margin="6,0,6,18" Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path= ActualHeight}"/>
<Border x:Name="border" BorderBrush="{x:Null}" BorderThickness="1" HorizontalAlignment="Left" Height="74" VerticalAlignment="Top" Width="68" CornerRadius="2.5"/>
<TextBlock HorizontalAlignment="Stretch" TextWrapping="Wrap" Text="{TemplateBinding Name}" VerticalAlignment="Bottom" Width="Auto" Height="17" TextAlignment="Center" Margin="4,0"/>
</Grid>
Which leaves no chance for custom DataTemplates to be introduced anywhere. You need to leave a ContentPresenter somewhere, so that WPF has a chance to put DataTemplated content inside that.
And the TextBlock makes no sense. You're binding against the ListBoxItem.Name property, which is completely irrelevant and makes no sense to be shown in the UI, and which you have no control over, anyways.
Data does not belong into ControlTemplates, only DataTemplates.
Change your template like so:
<Grid HorizontalAlignment="Left" Height="74" VerticalAlignment="Top" Width="68">
<Image x:Name="Img" Width="56" Height="56" Margin="6,0,6,18" Source="{Binding RelativeSource={RelativeSource TemplatedParent}, Path= ActualHeight}"/>
<Border x:Name="border" BorderBrush="{x:Null}" BorderThickness="1" HorizontalAlignment="Left" Height="74" VerticalAlignment="Top" Width="68" CornerRadius="2.5"/>
<ContentPresenter ContentSource="Content"/>
</Grid>
And the ListBox XAML is also wrong:
By doing this:
<ListBox ...>
<DataTemplate>
<!-- ... -->
</DataTemplate>
</ListBox>
You're putting the DataTemplate as an item inside the ListBox, which is not what you want.
You need to specifically assign the DataTemplate as the ItemTemplate for the ListBox:
<ListBox ...>
<ListBox.ItemTemplate>
<DataTemplate>
<!-- ... -->
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
And why is the Image.Source bound to a property of type double? That makes no sense.
Either put a specific resource there:
<Image Source="/resources/somepic.png"/>
or
if you want to dynamically change the image dependending on certain data, then that belongs into the DataTemplate, not the ControlTemplate.
-------------------------------------------------------------------------------------------
I suggest you read up the following material:
MSDN: WPF Content Model
MSDN: WPF Controls Content Model
Dr. WPF: ItemsControls A to Z

WPF - Checkbox in cell row readonly possible?

I have a ListView which is bind dynamically to a list of object of the same type.
The object have a boolean value.
There a ListView column which display a checkbox instead of the "true" and "false" normal value for that specific property.
Is there a way to set that checkbox readonly ? otherwise is there a way to tell that the click is coming from this specific row in the events "checked" and "unchecked" which execute a method in code behind ?
Thanks!
You can make any control readonly by setting IsHitTestVisible and Focusable to false.
XAML:
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication1="clr-namespace:WpfApplication1">
<StackPanel>
<ListView ItemsSource="{Binding}">
<ListView.View>
<GridView>
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Path=Name}" />
<GridViewColumn Header="Is Valid">
<GridViewColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsValid}" IsHitTestVisible="False" Focusable="False" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel>
</Window>
Code behind:
using System.Collections.Generic;
namespace WpfApplication1
{
public partial class Window1
{
public Window1()
{
InitializeComponent();
List<DataItem> data = new List<DataItem>();
data.Add(new DataItem() { Name = "AAA", IsValid = true });
data.Add(new DataItem() { Name = "BBB" });
DataContext = data;
}
public class DataItem
{
public string Name { get; set; }
public bool IsValid { get; set; }
}
}
}
You can restyle the ControlTemplate of CheckBox to remove RenderPressed and bind IsChecked to your DataContext property instead of TemplateBinding. Here is the edited template, look for IsChecked="{Binding MyBoolean}" and change that to your property.
<LinearGradientBrush x:Key="CheckRadioFillNormal">
<GradientStop Color="#FFD2D4D2" Offset="0"/>
<GradientStop Color="#FFFFFFFF" Offset="1"/>
</LinearGradientBrush>
<LinearGradientBrush x:Key="CheckRadioStrokeNormal">
<GradientStop Color="#FF004C94" Offset="0"/>
<GradientStop Color="#FF003C74" Offset="1"/>
</LinearGradientBrush>
<Style x:Key="EmptyCheckBoxFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="1" SnapsToDevicePixels="true" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CheckRadioFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="14,0,0,0" SnapsToDevicePixels="true" Stroke="Black" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="ReadonlyCheckBox" TargetType="{x:Type CheckBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{StaticResource CheckRadioFillNormal}"/>
<Setter Property="BorderBrush" Value="{StaticResource CheckRadioStrokeNormal}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource EmptyCheckBoxFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<BulletDecorator Background="Transparent" SnapsToDevicePixels="true">
<BulletDecorator.Bullet>
<Microsoft_Windows_Themes:BulletChrome BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" IsChecked="{Binding MyBoolean}" RenderMouseOver="{TemplateBinding IsMouseOver}"/>
</BulletDecorator.Bullet>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="HasContent" Value="true">
<Setter Property="FocusVisualStyle" Value="{StaticResource CheckRadioFocusVisual}"/>
<Setter Property="Padding" Value="2,0,0,0"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Place transparent TextBlock on CheckBox.
Disable CheckBox by setting "IsEnabled" fnd use the ToolTip Content on this TextBlock.
Example below:
<DataGridTemplateColumn Header="Hdr" Width="34" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<CheckBox VerticalAlignment="Center" HorizontalAlignment="Center" IsEnabled="False" IsChecked="{Binding IS_CHECKED}"/>
<TextBlock>
<ToolTipService.ToolTip>
<ToolTip Content="{Binding TOOL_TP}"/>
</ToolTipService.ToolTip>
</TextBlock>
</Grid>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

WPF Button Image only showing in last control

I am fairly new to WPF and am probably missing something simple here. If I have 3 controls, only the last control will show the OriginalImage that I specify.
Any help would be most appreciated. Thanks
Ryan
Main Window
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="200*"/>
<RowDefinition Height="60" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="85" />
<ColumnDefinition Width="85" />
<ColumnDefinition Width="85" />
<ColumnDefinition Width="85" />
<ColumnDefinition Width="300" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="1">
<but:ListButton OriginalImage="/CustomItemsPanel;component/ListBox/Images/add.png"
DisableImage="/CustomItemsPanel;component/ListBox/Images/addunselect.png"
/>
</Grid >
<Grid Grid.Row="1" Grid.Column="1" >
<but:ListButton OriginalImage="/CustomItemsPanel;component/ListBox/Images/add.png"
DisableImage="/CustomItemsPanel;component/ListBox/Images/addunselect.png"
/>
</Grid >
<Grid Grid.Row="1" Grid.Column="2" >
<but:ListButton OriginalImage="/CustomItemsPanel;component/ListBox/Images/add.png"
DisableImage="/CustomItemsPanel;component/ListBox/Images/addunselect.png"
/>
</Grid>
</Grid>
Control XAML
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomItemsPanel.ListButton">
<LinearGradientBrush x:Key="ButtonBackground" EndPoint="0.5,1" StartPoint="0.5,0">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#FF0E3D70"/>
<GradientStop Color="#FF001832" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ButtonBackgroundMouseOver" EndPoint="0.5,1" StartPoint="0.5,0">
<LinearGradientBrush.GradientStops>
<GradientStop Color="#FF1E62A1" />
<GradientStop Color="#FF0A3C6D" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
<LinearGradientBrush x:Key="ButtonBackgroundSelected" EndPoint="0.5,1" StartPoint="0.5,0">
<LinearGradientBrush.GradientStops>
<GradientStop Color="Red" />
<GradientStop Color="#FF0A2A4C" Offset="1"/>
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
<Style x:Key="Toggle" TargetType="{x:Type Button}">
<Setter Property="Content">
<Setter.Value>
<Image>
<Image.Style>
<Style TargetType="{x:Type Image}">
<Setter Property="Source" Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ListButton}}, Path=OriginalImage}"/>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Source" Value="{Binding Path=DisableImage, RelativeSource={RelativeSource TemplatedParent}}"/>
</Trigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Cursor="Hand">
<Border Name="back" Margin="0,1,0,0" Background="{StaticResource ButtonBackground}">
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center" x:Name="content" />
</Border>
<Border BorderThickness="1" BorderBrush="#FF004F92">
<Border BorderThickness="0,0,1,0" BorderBrush="#FF101D29" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True" >
<Setter TargetName="back" Property="Background" Value="{StaticResource ButtonBackgroundMouseOver}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:ListButton}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ListButton}">
<Button Style="{StaticResource Toggle}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Control Code Behind
public class ListButton : Control
{
public static readonly DependencyProperty MouseOverImageProperty;
public static readonly DependencyProperty OriginalImageProperty;
public static readonly DependencyProperty DisableImageProperty;
static ListButton() {
DefaultStyleKeyProperty.OverrideMetadata(typeof(ListButton), new FrameworkPropertyMetadata(typeof(ListButton)));
MouseOverImageProperty = DependencyProperty.Register("MouseOverImage", typeof(ImageSource), typeof(ListButton), new UIPropertyMetadata(null));
OriginalImageProperty = DependencyProperty.Register("OriginalImage", typeof(ImageSource), typeof(ListButton), new UIPropertyMetadata(null));
DisableImageProperty = DependencyProperty.Register("DisableImage", typeof(ImageSource), typeof(ListButton), new UIPropertyMetadata(null));
}
public ImageSource MouseOverImage {
get { return (ImageSource)GetValue(MouseOverImageProperty); }
set { SetValue(MouseOverImageProperty, value); }
}
public ImageSource OriginalImage {
get { return (ImageSource)GetValue(OriginalImageProperty); }
set { SetValue(OriginalImageProperty, value); }
}
public ImageSource DisableImage
{
get { return (ImageSource)GetValue(DisableImageProperty); }
set { SetValue(DisableImageProperty, value); }
}
}
This happened because of your "Toggle" style for the Button. The Image you use there is created only once (because the style is only evaluated once) and the Image can not be added to multiple buttons (in WPF each Visual can only have one parent). So the last Button you apply the style to wins and steals the image from the previous button.
If you want to modify the VisualTree in a style you should do this in a ControlTemplate.
I am going to answer my own question. Bitbonk has a great explanation for what I was doing wrong and how styles work. Thanks!
<Style x:Key="Toggle" TargetType="{x:Type Button}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Grid Cursor="Hand">
<Border Name="back" Margin="0,1,0,0" Background="{StaticResource ButtonBackground}">
<Image Name="imgBut" Source="{Binding Path=(OriginalImage), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ListButton}}}" />
</Border>
<Border BorderThickness="1" BorderBrush="#FF004F92">
<Border BorderThickness="0,0,1,0" BorderBrush="#FF101D29" />
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True" >
<Setter TargetName="back" Property="Background" Value="{StaticResource ButtonBackgroundMouseOver}"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False" >
<Setter TargetName="imgBut" Property="Source" Value="{Binding Path=(DisableImage), RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:ListButton}}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
use x:Shared="False" on your style
<Style x:Key="Toggle" x:Shared="False" TargetType="{x:Type Button}">
......
</Style>

WPF Customized TabControl

I have to develop a customized tab control and decided to create it with WPF/XAML, because I planned to learn it anyway. It should look like this when it's finished:
I made good progress so far, but there are two issues left:
Only the first/last tab item should have a rounded upper-left/bottom-left corner. Is it possible to modify the style of these items, similar to the way I did with the selected tab item?
The selected tab item should not have a border on its right side. I tried to accomplish this with z-index and overlapping, but the results were rather disappointing. Is there any other way to do this?
XAML:
<Window x:Class="MyProject.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TestWindow" Height="350" Width="500" Margin="5" Background="LightGray">
<Window.Resources>
<LinearGradientBrush x:Key="SelectedBorderBrush" StartPoint="0,0" EndPoint="1,0">
<GradientBrush.GradientStops>
<GradientStopCollection>
<GradientStop Color="Gray" Offset="0.965"/>
<GradientStop Color="WhiteSmoke" Offset="1.0"/>
</GradientStopCollection>
</GradientBrush.GradientStops>
</LinearGradientBrush>
<Style TargetType="{x:Type TabControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabControl}">
<DockPanel>
<Border
Panel.ZIndex="50"
Margin="0,100,-1,0"
Background="#FFAAAAAA"
BorderBrush="Gray"
CornerRadius="7,0,0,7"
BorderThickness="1">
<TabPanel
Margin="0,0,0,0"
IsItemsHost="True" />
</Border>
<Border
Background="WhiteSmoke"
BorderBrush="Gray"
BorderThickness="1"
CornerRadius="7,7,7,0" >
<ContentPresenter
ContentSource="SelectedContent" />
</Border>
</DockPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type TabItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Grid>
<Border Name="Border"
Background="#FFAAAAAA"
CornerRadius="7,0,0,0"
BorderBrush="Gray"
BorderThickness="0,0,0,1"
Panel.ZIndex="50"
Margin="0,0,0,0"
>
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Left"
ContentSource="Header"
Margin="10,10,10,10"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="Panel.ZIndex" Value="100" />
<Setter Property="Margin" Value="0,0,-2,0" />
<Setter TargetName="Border"
Property="BorderBrush"
Value="{StaticResource SelectedBorderBrush}"/>
<Setter TargetName="Border"
Property="Background"
Value="WhiteSmoke" />
<Setter TargetName="Border"
Property="CornerRadius"
Value="0,0,0,0" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<TabControl Name="_menuTabControl" TabStripPlacement="Left" Margin="5">
<TabItem Name="_tabItem1" Header="First Tab Item" ></TabItem>
<TabItem Name="_tabItem2" Header="Second Tab Item" >
<Grid />
</TabItem>
<TabItem Name="_tabItem3" Header="Third Tab Item" >
<Grid />
</TabItem>
</TabControl>
</Grid>
Edit: Thanks to Vlad, I could fix the second problem with a gradient border brush. See updates XAML for the solution.
Edit: Vlad fixed the first problem.
For the second problem, you should perhaps try to remove the clipping? Beware however of the possible issues.
For the first problem, you should try style trigger on property IsSelected. (Edit: I see, you are doing it exactly this way.) Have a look how this is implemented at the default template at MSDN. Note that they are using ZIndex, too.
Edit:
I found a workaround for your first/last tab problem. You need to use attached properties to designate the first/last tab:
In your TestWindow class you define attached property:
public static bool GetIsFirstTab(DependencyObject obj)
{
return (bool)obj.GetValue(IsFirstTabProperty);
}
public static void SetIsFirstTab(DependencyObject obj, bool value)
{
obj.SetValue(IsFirstTabProperty, value);
}
public static readonly DependencyProperty IsFirstTabProperty =
DependencyProperty.RegisterAttached("IsFirstTab", typeof(bool),
typeof(TestWindow), new UIPropertyMetadata(false));
Then, in your first tab you set this property:
<Window x:Class="MyProject.TestWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyProject"
...
/>
...
<TabItem Name="_tabItem1" Header="First Tab Item"
local:TestWindow.IsFirstTab="true">
</TabItem>
Then, you should define a trigger for it:
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border"
Property="Background"
Value="WhiteSmoke" />
</Trigger>
<Trigger Property="local:Window1.IsFirstTab" Value="True">
<Setter TargetName="Border"
Property="Background"
Value="Red" />
</Trigger>
This must help.
The same trick would work with last tab. Or you can have a number instead of bool as attached property.

Resources