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

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

Related

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

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

WPF subclass Control without adding logic, just to be able to customize style and template?

I'm searching for a good way to create style-able, reusable controls in WPF. For example, I've got a twitter feed that could look something like this (much simplified):
<ItemsControl ItemsSource={Binding tweets}>
<ItemsControl.DataTemplate>
<DataTemplate TargetType="tweet">
<StackPanel>
<Image Source="{Binding user.image}" />
<TextBlock Text="{Binding text}" />
</StackPanel>
</DataTemplate>
</ItemsControl.DataTemplate>
</ItemsControl>
To make this a reusable control, I could put this in a UserControl. But doing just that would make it impossible to change the way the image part is displayed for example.
So what I find myself doing now is creating a control for the user's image, like this:
public class UserImage : Control
{
// Empty class..
}
<Style TargetType="{x:Type UserImage}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type UserImage}">
<Image Source="{Binding}" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
// The itemscontrol datatemplate now looks like this
<DataTemplate TargetType="tweet">
<StackPanel>
<UserImage DataContext="{Binding user.image}" />
<TextBlock Text="{Binding text}" />
</StackPanel>
</DataTemplate>
Now I could do something like this to customize the appearance of the user image:
<Style TargetType="{x:Type UserImage}" BasedOn="{StaticResource {x:Type UserImage}}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type UserImage}">
<Ellipse>
<Ellipse.Fill>
<ImageBrush ImageSource="{Binding}">
</Ellipse.Fill>
</Ellipse>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Even though this works, it does feel a bit inefficient and well ... wrong to have to create an "empty" control for each and every component in the custom control. Is this the way to go, or is there a cleaner way?

WPF Style: Generalizing

Following being WPF style, is there a way to generalize the hard-coded column names (Name and Code), so that I could specify them when actually applying this style on a ComboBox? Even better, if I could even modify the number of columns?
<Style TargetType="ComboBox" x:Key="MultiColumnComboBoxStyle">
<Style.Resources>
<Style TargetType="ComboBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Border>
<Grid HorizontalAlignment="Stretch" TextElement.FontWeight="Normal">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" SharedSizeGroup="Code" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding Path=Name}" />
<Rectangle Grid.Column="1" Width="1" Fill="Black" />
<TextBlock Grid.Column="2" Text="{Binding Path=Code}" Margin="5,0,5,0" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Style.Resources>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Grid.IsSharedSizeScope="True" IsItemsHost="True" />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
instead of using a style you could consider craeting a custom control with a dependency property for your columns.
a little bit of setup involved but It will better meet your needs, especialy if you want to reuse it.
an example would be something like the following. Some of this is psuedo code you should be able to fill out.
<!-- In your generic.xaml file -->
<Style TargetType="MyCustomComboBox" >
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate TargetType="MyCustomComboBox" >
<!-- your template code goes here -->
<Grid x:Name="_myCustomGrid />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
//then in a cs file inherit from the combo box
public class MyCustomColumnComboBox : ComboBox //get all the combobox functionality
{
public IList ComboColumns
{
get { return (IList)GetValue(ComboColumnsProperty);}
set { SetValue(ComboColumnsProperty,value);}
}
public static readonly DependencyProperty ComboColumnsProperty = DependencyProperty.RegisterProperty(...);
private Grid _grid;
public override OnApplyTemplate()
{
//pull your template grid info here, then use that when setting the columns.
_grid = GetTemplateChild("_myCustomGrid") as Grid;
//from here you can check to see if you have your list yet,
//if you don't then you maintain the grid for when you do have your list.
// This can behave different depending on if you are in wpf or silverlight,
// and depending on how you were to add the items to the dependency property.
}
}
In Summary, for you, add the custom control with the custom dependency property, then in your theme/generic.xaml drop in your template and name the grid to what you want to pull into your template in the on apply template function. from there you are either ready to set up or can set up your columns that you specified in the dependency property.
NOTE : The dependency property isn't actually necessary but it can help to buy you a little bit more flexibility later on using things like the dependency properties on change callback to update if necessary.

Expander inside ListViewItem

I have a listview for which i have implemented grouping and have defined an expander inside the groupstyle as shown below :
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" >
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/>
<TextBlock FontWeight="Bold" Text=" Items"/>
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</ListView.GroupStyle>
I want to modify the expander such that only one is expanded and the rest are collapsed if more than one are there and also if i want to programmatically expand any one of the expanders i.e. suppose i add an object from cs and want to show that expander opened, it should be possible, any suggestions ??
Thanks in advance
Edit : The code to bind the listview and the group :
CollectionViewSource viewSource = new CollectionViewSource { Source = TO_DOlst };
viewSource.GroupDescriptions.Add(new PropertyGroupDescription("timeCategory"));
lstView.ItemsSource = viewSource.View;
timecategory is a member of the class
You can Bind IsExpanded property by creating a boolean property in your class just like Name and define all Business rules in CS directly.
if(ItemsSource.Count() > 1)
//set IsExpanded = false;
else
// set only first item and so on.
Regards.

How can I use a custom TabItem control when databinding a TabControl in WPF?

I have a custom control that is derived from TabItem, and I want to databind that custom TabItem to a stock TabControl. I would rather avoid creating a new TabControl just for this rare case.
This is what I have and I'm not having any luck getting the correct control to be loaded. In this case I want to use my ClosableTabItem control instead of the stock TabItem control.
<TabControl x:Name="tabCases" IsSynchronizedWithCurrentItem="True"
Controls:ClosableTabItem.TabClose="TabClosed" >
<TabControl.ItemTemplate>
<DataTemplate DataType="{x:Type Controls:ClosableTabItem}" >
<TextBlock Text="{Binding Path=Id}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type Entities:Case}">
<CallLog:CaseReadOnlyDisplay DataContext="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
EDIT: This is what I ended up with, rather than trying to bind a custom control.
The "CloseCommand" im getting from a previous question.
<Style TargetType="{x:Type TabItem}" BasedOn="{StaticResource {x:Type TabItem}}" >
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border
Name="Border"
Background="LightGray"
BorderBrush="Black"
BorderThickness="1"
CornerRadius="25,0,0,0"
SnapsToDevicePixels="True">
<StackPanel Orientation="Horizontal">
<ContentPresenter x:Name="ContentSite"
VerticalAlignment="Center"
HorizontalAlignment="Center"
ContentSource="Header"
Margin="20,1,5,1"/>
<Button
Command="{Binding Path=CloseCommand}"
Cursor="Hand"
DockPanel.Dock="Right"
Focusable="False"
Margin="1,1,5,1"
Background="Transparent"
BorderThickness="0">
<Image Source="/Russound.Windows;component/Resources/Delete.png" Height="10" />
</Button>
</StackPanel>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="Bold" />
<Setter TargetName="Border" Property="Background" Value="LightBlue" />
<Setter TargetName="Border" Property="BorderThickness" Value="1,1,1,0" />
<Setter TargetName="Border" Property="BorderBrush" Value="DarkBlue" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
found a way,
derive a class from TabControl and override this function, in my case I want the items of the tab control (when bound) to be CloseableTabItems
public class CloseableTabControl : TabControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new CloseableTabItem();
}
}
HTH Someone
Sam
You don't want to set the DataType of the DataTemplate in this case. The value of the ItemTemplate property is used whenever a new item needs to be added, and in the case of a tab control it will be used to create a new TabItem. You should declare an instance of your class within the DataTemplate itself:
<TabControl x:Name="tabCases" IsSynchronizedWithCurrentItem="True" Controls:ClosableTabItem.TabClose="TabClosed">
<TabControl.ItemTemplate>
<DataTemplate>
<Controls:ClosableTabItem>
<TextBlock Text="{Binding Path=Id}" />
</Controls:ClosableTabItem>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type Entities:Case}">
<CallLog:CaseReadOnlyDisplay DataContext="{Binding}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
This will cause a new ClosableTabItem to be created whenever a new tab is added to the TabControl.
Update; From your comment, it sounds like that the ItemTemplate controls what is created within the TabItem, rather than changing the TabItem itself. To do what you want to do, but for a TreeView, you would set the HeaderTemplate. Unfortunately, I don't see a HeaderTemplate property of TabControl.
I did some searching, and this tutorial modifies the contents of the tab headers by adding controls to TabItem.Header. Maybe you could create a Style for your TabItems that would add the close button that your class is currently adding?

Resources