I have a custom control, here simplified:
<UserControl x:Class="WPF.TestControl"
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:WPF"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid>
<ContentPresenter Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
</Grid>
</UserControl>
With usage:
<local:TestControl Grid.Row="0">
<Border BorderBrush="Red" BorderThickness="1">
<TextBlock Text="{Binding Path=Message, Mode=OneWay}"/>
</Border>
</local:TestControl>
So far so good, that works.
However, when used as part of a DataTemplate for a ListView, the binding no longer works:
<ListView ItemsSource="{Binding Path=Items}" Grid.Row="2">
<ListView.Resources>
<DataTemplate DataType="{x:Type local:TestViewModel}">
<local:TestControl>
<TextBlock Text="{Binding Path=Message, Mode=OneWay}"/>
</local:TestControl>
</DataTemplate>
</ListView.Resources>
</ListView>
What incantations are needed to make the databinding above work?
You are creating an infinite loop.
The UserControl already is a ContentControl as it derives from it. So as long as you don't override its ControlTemplate, you should not add a ContentPresenter to it, which binds to the TemplatedParent while being used inside a template.
The trigger of this misery is the TemplatedParent binding. The TestControl is part of a template. The template is applied to the ListViewItem, which is therefore the templated parent of TestControl.
Now the value of the binding source of the ContentPresenter inside the TestControl is the value of the ListViewItem.Content property, which holds the TestControl itself. This way you add the templated parent ListViewItem.Content, which is the TestControl, to the ContentPresenter of TestControl, which now contains the same TestControl (itself), which binds the templated parent ListViewItem.Content, which is the TestControl, to the ContentPresenter of TestControl, which now contains the same TestControl (itself), which binds the templated parent... StackOverflow.
If you want to use the UserControl like a ContentControl i.e. to display the visuals of other "external" controls, then use it like the ContentCobtrol it is:
TestControl
<UserControl x:Class="TestControl">
</UserControl>
DataTemplate
<DataTemplate DataType="{x:Type local:TestViewModel}">
<local:TestControl>
<TextBlock Text="{Binding Path=Message, Mode=OneWay}"/>
</local:TestControl>
</DataTemplate>
Or if the content is more complex and advanced than simple presentation, you should override the ControlTemplate:
TestControl
<UserControl x:Class="TestControl">
<UserControl.Template>
<ControlTemplate TargetType="local:TreeIndex">
<Border>
<Grid>
...
<ContentPresenter Content="{TemplateBinding Content}" />
</Grid>
</Border>
</ControlTemplate>
</UserControl.Template>
</UserControl>
DataTemplate
<DataTemplate DataType="{x:Type local:TestViewModel}">
<local:TestControl>
<TextBlock Text="{Binding Path=Message, Mode=OneWay}"/>
</local:TestControl>
</DataTemplate>
Related
I would like to make a UserControl which have a DataTemplate, and inside that DataTemplate there are controls. I would like to bind to those nested (inside the DataTemplate) controls' properties so I can set them when I reuse this UserControl. The nested controls will use the ItemSource's properties but the property names of the ItemSource's properties could be different.
The UserControl:
<UserControl x:Class="ContextMenu.BaseFilterUserControl"
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"
mc:Ignorable="d"
x:Name="Self">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
<ColumnDefinition Width="auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="70" />
</Grid.RowDefinitions>
<TextBlock Grid.Column="0"
VerticalAlignment="Center"
HorizontalAlignment="Right"
Margin="10"
Text="Owners" />
<Button Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Center"
Margin="10"
Click="FilteButtonClicked"
Width="40"
Height="40"
x:Name="FilterButton">
<Popup x:Name="FilterBoxPopup"
PlacementTarget="{Binding ElementName=FilterButton}"
Placement="Bottom"
StaysOpen="False">
<Border BorderBrush="Black"
Background="White"
Margin="2">
<ListView ItemsSource="{Binding ElementName=Self, Path=FilterList}"
x:Name="FilterListView"
Height="300"
Width="150">
<ListView.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<CheckBox IsChecked="{Binding IsChecked}" />-->
<!--<TextBlock Text="{Binding Name}" />-->
<!--This is where I don't know how to properly bind eg. the above control, things I tried:-->
<!--<TextBlock Text="{Binding ElementName=FilterListView, Path=FilterElementName}" />-->
<!--<TextBlock Text="{Binding ElementName=Self, Path=DataContext.FilterElementName}" />-->
<!--<TextBlock Text="{Binding ElementName=FilterListView, Path=DataContext.FilterElementName}" />-->
</StackPanel>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Border>
</Popup>
</Button>
<TextBlock Grid.Column="3"
VerticalAlignment="Center"
HorizontalAlignment="Left"
Margin="10"
Text="{Binding ElementName=Self, Path=SelectedNames}" />
</Grid>
</UserControl>
This how the UserControl is used, the FilterElementName="Name" is what I would like to set, depending on the list binded with FilterList list:
<local:BaseFilterUserControl FilterList="{Binding Owners}"
FilterElementName="Name"
SelectedNames="{Binding SelectedNames}"/>
In this case the Owners is a simple IReadOnlyList of an Owner class. The Owner class has a string Name property. But I will use this UserControl again with different list eg. where I would like to use the Versions list's Release property (for the TextBlock inside the UserControl):
<local:BaseFilterUserControl FilterList="{Binding Versions}"
FilterElementName="Release"
SelectedNames="{Binding SelectedReleases}"/>
The ListView is properly populated with items, so the FilterList DependencyProperty is working. But the nested controls are only working when I hard code the bindings:
<TextBlock Text="{Binding Name}" />
For this to work you would need to bind the Path-property of your TextBlocks Text-Binding to the FilterElementName property of your UserControl. Unfortunately the Path property of the Binding class is not a DependencyProperty and therefore not bindable.
One way to to achieve your goal would be to use the DisplayMemberPath property of the ListView, which is bindable:
<ListView x:Name="FilterListView"
Width="150"
Height="300"
ItemsSource="{Binding ElementName=Self, Path=FilterList}"
DisplayMemberPath="{Binding ElementName=self, Path=FilterElementName}"/>
If this approach does not work because you need to specify a more complex ItemTemplate, another way would be create a property of type DataTemplate in your UserControl, use that as ItemTemplate in the ListView and specify it from outside like so:
<local:BaseFilterUserControl FilterList="{Binding Versions}"
SelectedNames="{Binding SelectedReleases}">
<local:BaseFilterUserControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Release}" />
</DataTemplate>
</local:BaseFilterUserControl.ItemTemplate>
</local:BaseFilterUserControl>
What in the world is wrong with this ListBox? It is showing items as plain strings, not using the template I have provided:
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="20" Height="20" Fill="LightBlue" />
<TextBlock Text="{TemplateBinding Content}" Foreground="Red" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Items>
<ListBoxItem>html</ListBoxItem>
<ListBoxItem>head</ListBoxItem>
<ListBoxItem>body</ListBoxItem>
<ListBoxItem>table</ListBoxItem>
<ListBoxItem>tr</ListBoxItem>
<ListBoxItem>td</ListBoxItem>
</ListBox.Items>
</ListBox>
From the Remarks section in the ItemTemplate MSDN page:
When you set an ItemTemplate on an ItemsControl, the UI is generated
as follows (using the ListBox as an example):
1.During content generation, the ItemsPanel initiates a request for the ItemContainerGenerator to create a container for each data item.
For ListBox, the container is a ListBoxItem. The generator calls back
into the ItemsControl to prepare the container.
2.Part of the preparation involves the copying of the ItemTemplate of the ListBox to be the ContentTemplate of the ListBoxItem.
3.Similar to all ContentControl types, the ControlTemplate of a ListBoxItem contains a ContentPresenter. When the template is applied,
it creates a ContentPresenter whose ContentTemplate is bound to the
ContentTemplate of the ListBoxItem.
4.Finally, the ContentPresenter applies that ContentTemplate to itself, and that creates the UI.
These steps are apparently not executed when you create ListBoxItem instances directly in XAML. It is however not strictly necessary to bind the ItemSource property. You may also directly set items like this:
<ListBox xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Width="20" Height="20" Fill="LightBlue" />
<TextBlock Text="{TemplateBinding Content}" Foreground="Red" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.Items>
<sys:String>html</sys:String>
<sys:String>head</sys:String>
<sys:String>body</sys:String>
<sys:String>table</sys:String>
<sys:String>tr</sys:String>
<sys:String>td</sys:String>
</ListBox.Items>
</ListBox>
public class MyViewModel
{
public List<String> Items
{
get { return new List<String> { "html", "head", "body","table","tr","td" }; }
}
}
//This can be done in the Loaded event of the page:
DataContext = new MyViewModel();
Your XAML
<ListBox Margin="20" ItemsSource="{Binding Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<Ellipse Width="20" Height="20" Fill="LightBlue" />
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
I am obviously missing something very basic here. I have found similar questions but from none of the answers I was able to comprehend what I am doing wrong.
When I set ControlTemplate, my DataTemplate is not picked up.
I have created a very simple example of my problem:
<Window x:Class="WpfTesterProject.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfTesterProject"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<DataTemplate DataType="{x:Type local:Person}">
<StackPanel>
<TextBlock Text="{Binding FirstName}" />
<TextBlock Text="{Binding LastName}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<ContentControl Content="{Binding Content}">
<ContentControl.Template>
<ControlTemplate>
<Border BorderBrush="Blue" BorderThickness="2">
<ContentPresenter />
</Border>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
</Window>
What I want to do is select user-defined data template loaded at runtime, but also I want to, for example, wrap every single element in border, no matter what the user template is or even if he did not specify any templates at all.
From what I have read from similar questions, I have to use <ContentPresenter /> in the ControlTemplate, but the result is the same as if I remove it - only border is shown.
I reproduced your application. It seems the problem is in the TargetType property of the ControlTemplate:
<ContentControl Content="{Binding}">
<ContentControl.Template>
<ControlTemplate TargetType="ContentControl">
<Border BorderBrush="Blue" BorderThickness="2">
<ContentPresenter />
</Border>
</ControlTemplate>
</ContentControl.Template>
</ContentControl>
I have a UserControl which is basically a DataGrid. The datagrid has one TemplateColumn which is bound to an object.
I want to add a property to the UserControl which will set the Borderbrush that is inside the DataTemplate.
Here is my Xaml:
<DataGrid.Columns>
<DataGridTemplateColumn Header="No" IsReadOnly="True">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Border x:Name="ElementBorder" BorderThickness="1">
<Viewbox Height="Auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<TextBlock Text="{Binding Name}" VerticalAlignment="Top"></TextBlock>
</Viewbox>
</Border>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
I would like to set the ElementBorder's BorderBrush property based on a "ElementBorderColor" property in my UserControl.
Another solution... point your BorderBrush to a resource in your control and, in code, change the resource. Something like this:
<SolidColorBrush x:Key="scb01"
Color="Red" />
<DataTemplate x:Key="dt01">
<Border x:Name="ElementBorder"
BorderThickness="1"
BorderBrush="{DynamicResource scb01}">
<Viewbox Height="Auto"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch">
<TextBlock Text="{Binding}"
VerticalAlignment="Top"></TextBlock>
</Viewbox>
</Border>
</DataTemplate>
In code:
(Resources["scb01"] as SolidColorBrush).Color = Colors.Green;
How can I pass the DataContext of LayoutRoot to the converter of the ContentControl inside the ListBox items template?
<Grid x:Name="LayoutRoot"
Background="White"
DataContext="{Binding Source={StaticResource myViewModel}}">
<StackPanel HorizontalAlignment="Left"
Margin="6,6,0,394"
Orientation="Vertical"
Width="200"
d:LayoutOverrides="Height">
<ListBox x:Name="listBox2"
ItemsSource="{Binding MyCollection, Mode=TwoWay}"
VerticalAlignment="Top"
Height="400">
<ListBox.ItemTemplate>
<DataTemplate>
<ContentControl Content="{Binding}"
ContentTemplate="{Binding Converter={StaticResource myConverter}}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Text="{Binding Value1, Mode=TwoWay}"/>
</StackPanel>
</Grid>
I want to be able to touch the objects inside the DataContext from within the Converter and use them for TwoWay binding on controls within the ListBox item's DataTemplate.
Any ideas? Any suggestions?
Thank you.
I just got the DataContext up to the converter using:
<ContentControl Content="{Binding}"
ContentTemplate="{Binding Converter={StaticResource stringToDataTemplateConverter}, ConverterParameter={StaticResource myViewModel}}" />
Now I have another problem my dynamic property binding is not working.
(sorry for my bad english)
I'm not sure about what you are trying to do here but with SL 5 you can use RelativeSource to get the DataContext:
{Binding DataContext,RelativeSource={RelativeSource AncestorLevel=1,AncestorType=Grid}}