Difference between Control Template and DataTemplate in WPF - 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.

Related

WPF how to set stackpanel as resources and reuse it in TabControls

I am new to C# and WPF so please give me some ideas:
I have an WPF app used to display some stack panels,all stack panels default Visibility is set to collapsed and they will switch to visible according to the received data.
Now I want to make all these stack panels to resources so I can reuse it in some new added tab controls and stack panels.
<StackPanel x:Name="ColorOption" Visibility="Collapsed">
<TextBlock Text="Line Color" Style="{StaticResource ItemNameTextBlockStyle}"/>
<Button Style="{StaticResource ColorButtonStyle}" Click="Color_Click">
<Button.Content>
<Rectangle x:Name="LineColorRect" Style="{StaticResource ColorSelectionRectangleStyle}" />
</Button.Content>
</Button>
</StackPanel>
Above is one example of stack panels I am using. In the code behind the function "Color_Click" will change this "ColorOption" stack panel state and do something.
However after I try to put this stack panel into Windows.Resources
<Window.Resources>
<StackPanel x:Name="ColorOption" Visibility="Collapsed" x:Key="ColorOption">
<TextBlock Text="Line Color" Style="{StaticResource ItemNameTextBlockStyle}"/>
<Button Style="{StaticResource ColorButtonStyle}" Click="Color_Click">
<Button.Content>
<Rectangle x:Name="LineColorRect" Style="{StaticResource ColorSelectionRectangleStyle}" />
</Button.Content>
</Button>
</StackPanel>
</Window.Resources> (I also put the style files inside)
In the tab controls I did
<TabControl>
<TabItem Header="Tab 1" Content="{StaticResource ColorOption}"/>
</TabControl>
The visual studio shows error in the code behind says "ColorOption does not exist in the current context"
How can I fix this? Is any way to set the context? thank you
You can simply wrap the StackPanel in ContentControl and make it ControlTemplate.
<ControlTemplate x:Key="ColorOptionTemplate" TargetType="{x:Type ContentControl}">
<StackPanel x:Name="ColorOption" Visibility="Collapsed">
<TextBlock Text="Line Color" Style="{StaticResource ItemNameTextBlockStyle}"/>
<Button Style="{StaticResource ColorButtonStyle}" Click="Color_Click">
<Button.Content>
<Rectangle x:Name="LineColorRect" Style="{StaticResource ColorSelectionRectangleStyle}"/>
</Button.Content>
</Button>
</StackPanel>
</ControlTemplate>
However, you will need to change properties of controls inside the ContentControl and it would be cumbersome. So the StackPanel could be wrapped in UserControl instead.
<UserControl x:Class="WpfApp1.ColorOptionControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel x:Name="ColorOption" Visibility="Collapsed">
<TextBlock Text="Line Color" Style="{StaticResource ItemNameTextBlockStyle}"/>
<Button Style="{StaticResource ColorButtonStyle}" Click="Color_Click">
<Button.Content>
<Rectangle x:Name="LineColorRect" Style="{StaticResource ColorSelectionRectangleStyle}"/>
</Button.Content>
</Button>
</StackPanel>
</UserControl>
This is a common way in WPF. The downside is that you will need to add dependency properties to UserControl and wire up them with dependency properties of internal controls so that you can set their values at the level of UserControl and bridge them with external controls and window. This could be complicated and cumbersome as well.
So I think ideally it would be better to find an existing control which has similar functionalities you want and create a custom control deriving from the existing one.

Presenting an ItemsControl

simple question!
I want to present an itemscontrol inside of an expander and grid which contains a textbox. I want to do this multiple times so I wrapped it in a ControlTemplate.
<ControlTemplate x:Key="ArrayPresenter">
<Expander Template="{StaticResource ArrayTemplate}">
<Grid>
<ContentPresenter/>
<TextBlock FontWeight="Bold" Text="Empty" Margin="3" HorizontalAlignment="Center" Foreground="#66C9C9C9" FontSize="15" Visibility="{quickConverter:Binding '$P.Count == 0 ? Visibility.Visible : Visibility.Collapsed', P={Binding Array}}" />
</Grid>
</Expander>
</ControlTemplate>
This is what I want to present. Unfortunately whenever an item is added to the itemscontrol, nothing happens and it doens't display the new items!
<ContentControl Template="{StaticResource ArrayPresenter}">
<ItemsControl Style="{StaticResource ArrayItemsStyle}" Margin="5" ItemTemplate="{StaticResource StructureFieldTemplate}"/>
</ContentControl>
As mentioned in the comment you need to target type of your ControlTemplate
<ControlTemplate ... TargetType="{x:Type ContentControl}">
Without that ControlTemplate targets System.Windows.Controls.Control type and that does not have Content to present so ContentPresenter does not know what to show.

WPF: Manage margin for all controls in a ContentPresenter

Is it possible to define the "presenting behavior" of a ContentPresenter so that it applies padding to its content?
Now I have a ContentPresenter and define the Margin on all UserControls that can be part of this ContentPresenter.
The downside of this, is that it calls for repeating definitions of Margins, and the UserControls are kind of dedicated to "fit" in the ContentPresenter.
E.g. XAML that contains the content presenter:
<ContentPresenter
x:Name="SettingsContentPanel"
Grid.Row="0"
Grid.Column="2"
Grid.ColumnSpan="2"
Content="{Binding ElementName=SettingsGroupSelector, Path=SelectedItem.Tag}" />
And the user controls are defined as follows:
<UserControl
<!-- left out irrelevant definitions -->
Margin="5,5,5,5">
You should be able to get the effect you're after by setting the Margin on your ContentPresenter element itself e.g:
<ContentPresenter
x:Name="SettingsContentPanel"
Grid.Row="0"
Grid.Column="2"
Grid.ColumnSpan="2"
Content="{Binding ElementName=SettingsGroupSelector, Path=SelectedItem.Tag}"
Margin="5,5,5,5" />
Also, if the Margin is the same on all sides, you could use the shorthand Margin="5".
Hope that helps =D

ItemsControl items bindings called when collapsed

I have an ItemsControl which displays a list of messages. It's defined as ...
<ItemsControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{Binding Messages}" >
</ItemsControl>
I then have a DataTemplate which handles the display for each message. It's defined as...
<DataTemplate DataType="{x:Type vm:MessageViewModel}">
<Button Command="{Binding CommandOpenPage}">
<Button.Template>
<ControlTemplate>
<Border Margin="2" BorderThickness="1"
BorderBrush="{Binding Flags, Converter={StaticResource msgFlagConverter}}"
Background="{Binding Flags, Converter={StaticResource msgFlagConverter}, ConverterParameter=1}" >
<TextBlock Text="{Binding Path=Message}" Style="{StaticResource ActionItem}" TextWrapping="Wrap" />
</Border>
</ControlTemplate>
</Button.Template>
</Button>
</DataTemplate>
Everything displays OK. My problem is when the parent controls are set to Visibility=Collapsed my ItemsControl still goes through the DataTemplate and calls the converters for BorderBrush and BackgroundBrush for each MessageViewModel.
This is bothersome because when the list is very large the bindings are set and converters are executed when they shouldn't. This list is only visible when the user chooses to see it. I understood the binding engine ignores elements under a collapsed parent. Is there an exception to this rule? Or am I just missing something?
I found my problem. The above ItemsControl and DataTemplate were in a UserControl. The visibility was originally handled inside the usercontrol itself by binding the main layout grid to a visibility property. By simply setting the user controls visibility in the parent XAML all bindings started behaving as expected.
This fixes my problem but I still don't understand the difference between setting the visibility of the main layout grid vs the visibility of the usercontrol itself.
<c:ApplicationMenuView Grid.Column="1" Grid.Row="4"
HorizontalAlignment="Left" Margin="1"
VerticalAlignment="Stretch"
DataContext="{Binding Menu}"
Visibility="{Binding IsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"/>

Cannot access drag adorner template

I've used the sample code provided by Bea Stollnitz (http://bea.stollnitz.com/blog/?p=53), in order to enable drag and drop in my application, and drag adorner, etc.
Everything works fine, my drag adorner is well displayed, I have all the behavior I want.
But (yes there is always a but), I cannot access the DataTemplate of the Drag Adorner, in order to display different data depending on the dragged data.
I have simplified the code, but the basics are still there.
This is the DataTemplate of my DragAdorner
<DataTemplate x:Key="DragAndDropTemplate" DataType="{x:Type MyType}">
<Grid>
<Grid Opacity="0.5">
<Border x:Name="HeaderBorder" CornerRadius="2" BorderThickness="1" Margin="5,2,5,2">
<Border x:Name="InsideBorder" CornerRadius="2" BorderThickness="1">
<TextBlock x:Name="number" Text="{Binding Name}" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
</Border>
</Border>
</Grid>
<Border Width="17" Height="17" BorderBrush="White" HorizontalAlignment="Center" VerticalAlignment="Center" CornerRadius="1" x:Name="numberContainer" Visibility="Collapsed">
<TextBlock x:Name="number" Text="80" HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="White"/>
</Border>
</Grid>
</DataTemplate>
This is the code that create the Adorner :
if (this.draggedAdorner == null)
{
var adornerLayer = AdornerLayer.GetAdornerLayer(source);
this.draggedAdorner = new DraggedAdorner(draggedData, dataTemplate, source, adornerLayer);
}
And this is the code that init an adorner
public DraggedAdorner(List dragDropData, DataTemplate dragDropTemplate, FrameworkElement adornedElement, AdornerLayer adornerLayer)
: base(adornedElement)
{
this.adornerLayer = adornerLayer;
this.contentPresenter = new ContentPresenter();
this.contentPresenter.Content = dragDropData[0];
this.contentPresenter.ContentTemplate = dragDropTemplate;
this.adornerLayer.Add(this);
}
The draggedData, will be a list of MyType, I get the first item as the content of the ContentPresenter of my DraggedAdorner, so the DataTemplate can apply.
The problem is, I want to access the numberContainer and number control of the DataTemplate, in order to display the number of dragged object, in the adorner. But I cannot manage to access it, whatever I try, It ends with the "This operation is valid only on elements that have this template applied." message.
I have tought I could do something like this :
this.contentPresenter.ContentTemplate.FindName("number", this.contentPresenter);
Since the DataTemplate should apply to the ContentPresenter, but nope...
For information the adornedElement is the ListViewItem from which the drag occurs.
If you have any idea...
Ok, so I have found how to achieve what I wanted.
I don't know why it didn't comes to mind earlier, and why I didn't found anything about this before.
I have just added a single line before trying to access the template :
this.UpdateLayout()
Looks like it forces the ContentPresenter and DataTemplate object to be update and "re-rederend" so the ContentPresenter is really templated by my DataTemplate.

Resources