How to override the ControlTemplate of a UserControl - wpf

How can I know the default template element of a usercontrol when I trying to override it?
For example somebody have overrided the TabControl's template like this.
<TabControl>
<TabControl.Template>
<ControlTemplate TargetType="TabControl">
<StackPanel>
<ScrollViewer HorizontalScrollBarVisibility="Visible" VerticalScrollBarVisibility="Disabled">
<TabPanel x:Name="HeaderPanel"
Panel.ZIndex ="1"
KeyboardNavigation.TabIndex="1"
Grid.Column="0"
Grid.Row="0"
Margin="2,2,2,0"
IsItemsHost="true"/>
</ScrollViewer>
<ContentPresenter x:Name="PART_SelectedContentHost"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
Margin="{TemplateBinding Padding}"
ContentSource="SelectedContent"/>
</StackPanel>
</ControlTemplate>
</TabControl.Template>
<TabItem Header="TabItem1">TabItem1 Content</TabItem>
<TabItem Header="TabItem2">TabItem2 Content</TabItem>
</TabControl>
How does he know there is <StackPanel> and <ContentPresenter> in the TabControl's template?

The TabControl class has a [TemplatePart] attribute that indicates mandatory parts of the template:
[StyleTypedPropertyAttribute(Property = "ItemContainerStyle", StyleTargetType = typeof(TabItem))]
[TemplatePartAttribute(Name = "PART_SelectedContentHost", Type = typeof(ContentPresenter))]
public class TabControl : Selector
In this case the template must contain a ContentPresenter named PART_SelectedContentHost. Everything else is optional, you can put anything you like in the template (as long as it makes sense of course).

When you are overriding a ControlTemplate for any control, you are defining how it will look. The <StackPanel> is just the layout control you are using, it could be a grid or any other layout control.
However the is something it needs to be there. If you look at the WPF control hierarchy, you can see several types of controls at the base levels, after Control, FrameworkElement, etc:
<ContentControl>
<HeaderedContentControl>
<ItemsControl>
<HeaderedItemsControl>
Each one of these have specific rendering options and parts. In your case a <TabControl> is an <Selector> which is a special type of an <ItemsControl>. This Selector has a Content and a TabPanel, thus the <TabPanel> and the <ContentPresenter> (which tells WPF where to render the Content).
The best way to aquire this knowledge is by looking at the default WPF templates for each control, for example the TabControl default template for WPF4 is here

See MSDN for default templates and styles.

Related

UserControl within a ControlTemplate

I have a ControlTemplate for a Telerik Tile and I am overriding like below:
<ControlTemplate TargetType="{x:Type ctrl:Tile}">
<Border>
<local:UserControl>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</local:UserControl>
</Border>
</ControlTemplate>
My user control looks like:
<DockPanel>
<!-- some content -->
<ContentPresenter/>
</DockPanel>
The ControlTemplate does not display the content of the UserControl.
If I change my control template to:
<ControlTemplate TargetType="{x:Type ctrl:Tile}">
<Border>
<StackPanel>
<local:UserControl/>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</StackPanel>
</Border>
</ControlTemplate>
It will find the content and place it appropriately. It seems like the ControlTemplate cannot find the content once it's nested inside my UserControl. Is there anything I could be doing wrong?
Note that these ControlTemplate Items are appearing in an ItemsPresenter.
You're treating the UserControl as if it is a basic ContentControl (like a Button) which is a little different than what it actually is. Using Button as an example, when you add a child (i.e. a TextBlock) into a Button element that's actually setting that TextBlock as the Button's Content property. The way it gets rendered is through the Button's ControlTemplate, which includes a ContentPresenter to inject Content into. The Visual Tree ends up like this:
<Button>
-start Template
<Border>
<ContentPresenter>
-start Content
<TextBlock>
So far that's basically the model your code is following. The problem is that you're using a (still ContentControl derived) UserControl instead, which rather than using a ControlTemplate is most often defined with a XAML+code-behind model, where the XAML defines the Content of the UserControl. (It is possible to switch these models and template a UserControl or make a Button derived class with XAML+code-behind but not common)
If you want to both define the look of your UserControl in XAML as normal and still be able to inject other content you can add another DependencyProperty that mirrors the setup of the Content property and set your content to that. This approach is used with HeaderedContentControl derivatives (i.e. Expander) which essentially has 2 content properties, Content and Header. Using the new property would look like this:
<Border>
<local:UserControl>
<local:UserControl.OtherContent>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}"/>
</local:UserControl.OtherContent>
</local:UserControl>
</Border>
And then inside the UserControl's XAML you need to explicitly set up the ContentPresenter Bindings (you only get them for free inside templates of ContentControls):
<DockPanel>
<!-- some content -->
<ContentPresenter Content="{Binding Path=OtherContent, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UserControl}}"/>
</DockPanel>
If you want a ContentTemplate, ContentTemplateSelector, or ContentStringFormat you'll also need to add properties and bindings for those.

Partially templated ListBox.ItemTemplate

I'm creating a custom control and I'm trying to create partially specified template for list box items. The template has some predefined parts and there should be another part that can be templated when using the control.
For this I have created a dependency property named SuggestionItemTemplate like so:
public static readonly DependencyProperty SuggestionItemTemplateProperty =
DependencyProperty.Register("SuggestionItemTemplate",
typeof(DataTemplate),
typeof(AutoSuggestTextBox),
new PropertyMetadata(null));
In my custom controls' generic.xaml I have:
<Style TargetType="local:AutoSuggestTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AutoSuggestTextBox">
<Grid>
<ListBox x:Name="ItemsControl">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
ContentTemplate="{TemplateBinding SuggestionItemTemplate}"
Content="{Binding}" />
<ToggleButton Grid.Column="1"
x:Name="DetailsHover"
ClickMode="Hover"
Style="{StaticResource DetailsToggleButtonStyle}" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Unfortunatelly, this does not work as it's not possible to use TemplateBinding from inside ContentPresenter nested into DataTemplate. (The member "SuggestionItemTemplate" is not recognized or is not accessible.)
I also tried to use ancestor binding (available in Silverlight 5) like:
<ContentPresenter Grid.Column="0"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:AutoSuggestTextBox}, Path=SuggestionItemTemplate}"
Content="{Binding}" />
But this results in binding error:
Error: System.Exception: BindingExpression_CannotFindAncestor
I suppose this happens because I'm inside ControlTemplate of my custom control and "local:AutoSuggestTextBox" is not defined anywhere in the style.
The third option that I tried was to apply ContentTemplate in OnApplyTemplate override but this also doesn't work:
var cp = itemsControlElement.ItemTemplate.LoadContent() as ContentPresenter;
cp.ContentTemplate = SuggestionItemTemplate;
In all cases, I get my grid with two columns, toggle button is visible but content presenter simple prints out view model's type name. (I believe this is the default behavior if the ContentTemplate is null).
Is this even possible to do? Are there any other ways to specify a partial template and then only add customized template part when necessary?
As a workaround for now, I can specify
ItemTemplate="{TemplateBinding SuggestionItemTemplate}"
for the list box and then copy/paste the generic template everywhere I use this control. But this is the behavior I'm hoping to avoid in the first place.
Thanks!
edit: I used the code tags for all blocks of code, but they're not highlighted for some reason. :/
It is possible to walk through Visual Ancestors in the OnApplyTemplate method, find your ContentPresenter(s) and set the ItemTemplate on that. To my mind, this is fine for a single item, but not so much in an ItemsControl scenario.
You could achieve what you are after using your own custom Control. Just give it a Content dependency property of type Object, and a Template DP of type DataTemplate (and multiples of the two if you fancy), and you can set up the root visual style and templates in the default style for your Control.
In this specific case, I would suggest that the best approach is to put your ToggleButton in the ListBoxItem template instead by customising the ListBox.ItemContainerStyle. It is easy to modify the default Control Template using Expression Blend, and the DataContext of the ToggleButton will not change, so the changes to your own logic should be minimal.
Edit: If you mean to use a number of different data templates, perhaps Implicit Data Templates will be more suitable.
I managed to solve this using a different approach. I used ancestor binding but instead of trying to reach the root control (my AutoSuggestTextBox) from the DataTemplate, I ask for a reference to my ListBox (here named ItemsControl).
However, since the ListBox doesn't have the SuggestionItemTemplate property, I sub-classed it to my own CustomListBox where I implemented that property. It all comes down to this code snippet:
<Style TargetType="local:AutoSuggestTextBox">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:AutoSuggestTextBox">
<Grid>
<local:CustomizableListBox x:Name="ItemsControl"
SuggestionItemTemplate="{TemplateBinding SuggestionItemTemplate}">
<local:CustomizableListBox.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Grid.Column="0"
ContentTemplate="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType=local:CustomizableListBox}, Path=SuggestionItemTemplate}"
Content="{Binding}" />
<ToggleButton Grid.Column="1"
x:Name="DetailsHover"
ClickMode="Hover"
Style="{StaticResource DetailsToggleButtonStyle}" />
</Grid>
</DataTemplate>
</local:CustomizableListBox.ItemTemplate>
</local:CustomizableListBox>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Please teach me how to use ControlTemplate properly?

Ok, I think I am pretty noob on using ControlTemplate lol...
All I want is to template my every UserControl so every UserControl will have Delete, Save, Cancel buttons.
So now I am writting some test......
Try to make a border around the everything...
In App.xaml
<Application.Resources>
<ControlTemplate x:Key="DeleteSaveCancelTemplate">
<Grid VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Border BorderThickness="5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Black">
<ContentPresenter/>
</Border>
</Grid>
</ControlTemplate>
</Application.Resources>
Then in UserControl:
<UserControl ... Height="150" Width="300" Template="{StaticResource DeleteSaveCancelTemplate}" >
But all I see is just ...black... what did I do wrong? I thought I should use ContentPresenter to shows the content?
For most controls you use ContentPresenter. ItemsPresenter only applies to elements that extend ItemsControl.
So... I found the problem lol From this page
The ControlPresenter doens't bind to Content by default...
I need to write
<ContentPresenter Content="{TemplateBinding Property=ContentControl.Content}" />
Unless you have specify the TargetType, then you it will bind by default.

Customize WPF databinding: How to add custom logic?

i have a question regarding some complex data-binding.
I want to be able to update a grid (which has the property "IsItemsHost" set to true)
dynamically whenever a data-binding occurs.
Actually i am using a CustomControl which is an ItemsControl and this
has the Grid in its ControlTemplate.
To be more specific, i bind the grid to some items and i want to
change the number of grid rows depending on these items,
add something like a header (one row containing some text),
and set the items' Grid.Row and Grid.Column using some custom logic.
What is the easiest way to apply such behaviour
whenever the bound data is updated?
Do i have to use a viewmodel that also contains the header data?
Thanks in advance.
Code of the CustomControl Generic.xaml:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TimeTableControl">
<Style TargetType="{x:Type local:TimeTableControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:TimeTableControl}">
<Border Width="Auto" Height="Auto" BorderBrush="#FF4B5A9B" BorderThickness="4" CornerRadius="4" Margin="2" Padding="0" Background="White">
<Grid Width="Auto">
<Grid.RowDefinitions>
<RowDefinition Height="0.1*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Viewbox>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DayCaption}"/>
</Viewbox>
<Border Grid.Row="1" BorderThickness="0,2,0,0" BorderBrush="#FF4B5A9B">
<Grid Name="ContentGrid" IsItemsHost="True">
</Grid>
</Border>
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
Grid is used for layout. If you have a changing number of items in some collection, what you really want is ItemsControl, or more specific ListBox (if you want item selection, etc).
If you still want Grid-like behavior of individual rows, you may want to define a Grid in ItemsControl.ItemTemplate and play with Grid.IsSharedSizeScope at ItemsControl level. Alternatively, you may just use ListView instead to get the grid look and item selection in a package.
Update: I got it working by creating a custom panel that uses MeasureOverride and ArrangeOverride to update itself. This lets me adjust the panel to the children and I don't even need to use a grid. This also makes the control lookless.

Difference between Control Template and DataTemplate in WPF

What is difference between a ControlTemplate and a DataTemplate in WPF?
Typically a control is rendered for its own sake, and doesn't reflect underlying data. For example, a Button wouldn't be bound to a business object - it's there purely so it can be clicked on. A ContentControl or ListBox, however, generally appear so that they can present data for the user.
A DataTemplate, therefore, is used to provide visual structure for underlying data, while a ControlTemplate has nothing to do with underlying data and simply provides visual layout for the control itself.
A ControlTemplate will generally only contain TemplateBinding expressions, binding back to the properties on the control itself, while a DataTemplate will contain standard Binding expressions, binding to the properties of its DataContext (the business/domain object or view model).
Very basically a ControlTemplate describes how to display a Control while a DataTemplate describes how to display Data.
For example:
A Label is a control and will include a ControlTemplate which says the Label should be displayed using a Border around some Content (a DataTemplate or another Control).
A Customer class is Data and will be displayed using a DataTemplate which could say to display the Customer type as a StackPanel containing two TextBlocks one showing the Name and the other displaying the phone number. It might be helpful to note that all classes are displayed using DataTemplates, you will just usually use the default template which is a TextBlock with the Text property set to the result of the Object's ToString method.
Troels Larsen has a good explanation on MSDN forum
<Window x:Class="WpfApplication7.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="ButtonContentTemplate">
<StackPanel Orientation="Horizontal">
<Grid Height="8" Width="8">
<Path HorizontalAlignment="Stretch"
Margin="0,0,1.8,1.8"
VerticalAlignment="Stretch" Stretch="Fill" Stroke="#FF000000"
Data="M0.5,5.7 L0.5,0.5 L5.7,0.5"/>
<Path HorizontalAlignment="Stretch"
Margin="2,3,0,0"
VerticalAlignment="Stretch" Stretch="Fill" Stroke="#FFFFFFFF"
Data="M3.2,7.5 L7.5,7.5 L7.5,3.5"/>
<Path HorizontalAlignment="Stretch"
Margin="1.2,1.4,0.7,0.7"
VerticalAlignment="Stretch" Fill="#FFFFFFFF" Stretch="Fill" Stroke="#FF000000"
Data="M2.5,2.5 L7.5,7.5"/>
<Path HorizontalAlignment="Stretch"
Margin="1.7,2.0,1,1"
VerticalAlignment="Stretch" Stretch="Fill" Stroke="#FF000000"
Data="M3,7.5 L7.5,7.5 L7.5,3.5"/>
<Path HorizontalAlignment="Stretch"
Margin="1,1,1,1"
VerticalAlignment="Stretch" Stretch="Fill" Stroke="#FFFFFFFF"
Data="M1.5,6.5 L1.5,1 L6.5,1.5"/>
</Grid>
<ContentPresenter Content="{Binding}"/>
</StackPanel>
</DataTemplate>
<ControlTemplate TargetType="Button" x:Key="ButtonControlTemplate">
<Grid>
<Ellipse Fill="{TemplateBinding Background}"/>
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Window.Resources>
<StackPanel>
<Button Template="{StaticResource ButtonControlTemplate}" ContentTemplate="{StaticResource ButtonContentTemplate}" Content="1"/>
<Button Template="{StaticResource ButtonControlTemplate}" ContentTemplate="{StaticResource ButtonContentTemplate}" Content="2"/>
<Button Template="{StaticResource ButtonControlTemplate}" ContentTemplate="{StaticResource ButtonContentTemplate}" Content="3"/>
</StackPanel>
</Window>
(Templates blatently stolen from
http://msdn.microsoft.com/en-us/library/system.windows.controls.controltemplate.aspx
and
http://msdn.microsoft.com/en-us/library/system.windows.controls.contentcontrol.contenttemplate%28VS.95%29.aspx
respectively)
Anyway, the ControlTemplate decides how the Button itself looks, while
the ContentTemplate decides how the Content of the button looks. So
you could bind the content to one of you data classes and have it
present itself however you wanted it.
ControlTemplate: Represents control style.
DataTemplate: Represents data style(How would you like to show your data).
All controls are using default control template that you can override through template property.
For example
Button template is a control template.
Button content template is a data template
<Button VerticalAlignment="Top" >
<Button.Template>
<ControlTemplate >
<Grid>
<Rectangle Fill="Blue" RadiusX="20" RadiusY="20"/>
<Ellipse Fill="Red" />
<ContentPresenter Content="{Binding}">
<ContentPresenter.ContentTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Height="50">
<TextBlock Text="Name" Margin="5"/>
<TextBox Text="{Binding UserName, Mode=TwoWay}" Margin="5" Width="100"/>
<Button Content="Show Name" Click="OnClickShowName" />
</StackPanel>
</DataTemplate>
</ContentPresenter.ContentTemplate>
</ContentPresenter>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
public String UserName
{
get { return userName; }
set
{
userName = value;
this.NotifyPropertyChanged("UserName");
}
}
ControlTemplate - Changing the appearance of element. For example Button can contain image and text
DataTemplate - Representing the underlying data using the elements.
ControlTemplate DEFINES the visual appearance, DataTemplate REPLACES the visual appearance of a data item.
Example: I want to show a button from rectangular to circle form => Control Template.
And if you have complex objects to the control, it just calls and shows ToString(), with DataTemplate you can get various members and display and change their values of the data object.
All of the above answers are great but there is a key difference that was missed. That helps make better decisions about when to use what. It is ItemTemplate property:
DataTemplate is used for elements that provide ItemTemplate property for you to replace its items' content using DataTemplates you define previously according to bound data through a selector that you provide.
But if your control does not provide this luxury for you then you still can use a ContentView that can display its content from predefined ControlTemplate. Interestingly, you can change the ControlTemplate property of your ContentView at runtime. One more thing to note that unlike controls with ItemTemplate property, you cannot have a TemplateSelector for this (ContentView) control. However, you still can create triggers to change the ControlTemplate at runtime.

Resources