ItemTemplate Wrapper in UserControl Based on ItemsControl - c#

I am working on my own user control which derives from ItemsControl. To give a brief description of what I want to achieve here is what the XAML looks like right now:
<ItemsControl x:Class="MyApp.MyUserControl"
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:MyApp"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="600">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<Grid>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPresenter/>
</Border>
</Grid>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
This presents my data the way I want to, in a horizontal StackPanel, nothing fancy. In the visual tree you'll see this:
ItemsControl
[...Other Visual Item in The Tree...]
ItemsPresenter
ContentPresenter
Item1 (ItemTemplate)
ContentPresenter
Item2 (ItemTemplate)
Now I would like to modify the XAML of my user control so that the tree would look like this:
ItemsControl
[...Other Visual Item in The Tree...]
ItemsPresenter
ContentPresenter
SomeContainerDefinedInMyUserControlXAML
Item1 (ItemTemplate)
ContentPresenter
SomeContainerDefinedInMyUserControlXAML
Item2 (ItemTemplate)
The goal here is to have a wrapper container around the templated item. Its behavior would be bound to internal properties of the UserControl, allowing me to define item behaviors immune to the ItemTemplate choice made by the user of my control.
I was trying to add DataTemplates on ContentPresenter in ItemsControl.Resources, but that failed. Could someone help me out here? :)

You may create a derived ContentPresenter or ContentControl in the ItemControl's Get​Container​For​Item​Override method:
public class MyContainer : ContentControl
{
}
public class MyItemsControl : ItemsControl
{
protected override DependencyObject GetContainerForItemOverride()
{
return new MyContainer();
}
protected override bool IsItemItsOwnContainerOverride(object item)
{
return item is MyContainer;
}
}
I'd also suggest to move the derived ItemsControl's XAML to a default Style in Themes\Generic.xaml (as done by default for custom controls):
<Style TargetType="local:MyItemsControl">
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:MyItemsControl">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Also add
static MyItemsControl()
{
DefaultStyleKeyProperty.OverrideMetadata(
typeof(MyItemsControl),
new FrameworkPropertyMetadata(typeof(MyItemsControl)));
}
to the control's code.

Related

Custom control including wpf TabControl fails bindings with ElementName

I've a CustomControl which has an Items property. An internal TabControl is bound to it like this:
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<TabControl Name="PART_TabControl"
>
</TabControl>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
OnApplyTemplate:
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
TabControl tabControl = Template.FindName("PART_TabControl", this) as TabControl;
foreach (var item in Items)
{
Dispatcher.BeginInvoke(new Action(() => tabControl.Items.Add(item)), System.Windows.Threading.DispatcherPriority.ContextIdle);
}
}
To demonstrate the problem I've added "Dispatcher.BeginInvoke". The problem occurs when tabitems are added after a while. The usage below results in a binding failure:
<ccl:CustomControl1 Name="cc1">
<ccl:TabItemCollection>
<TabItem Header="Tab1">
<TextBox Name="txtData" />
</TabItem>
<TabItem Header="Tab2">
<TextBlock Text="{Binding ElementName=txtData, Path=Text}" />
</TabItem>
</ccl:TabItemCollection>
</ccl:CustomControl1>
I noticed that after snooping the textblock, the problem is fixed. And from the source code I see this code does the job:
BindingExpression expression = BindingOperations.GetBindingExpression(dObj, property);
if (expression != null && !expression.HasError && expression.Status != BindingStatus.Active)
{
dObj.ClearValue(property);
BindingOperations.SetBinding(dObj, property, expression.ParentBindingBase);
}
As I've many controls in tab items this kind of "fix binding" code causes a lot of performance issues.
Is there any workaround for that?

WPF Template.FindName return always null

Template
<Style TargetType="{x:Type local:Viewport}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:Viewport}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ItemsPresenter/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas x:Name="PART_Canvas" IsItemsHost="True"/>
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
And the code in OnApplyTemplate
content = this.Template.FindName("PART_Canvas", this) as FrameworkElement;
the content returns always null, why it doesn't work?
if I replace with this, the program quits directly
content = this.ItemsPanel.FindName("PART_Canvas", this) as FrameworkElement;
With FindName you can find only elements declared in a Template. ItemsPanel is not part of that template. ItemsControl puts ItemsPanel into ItemsPresenter place holder via which you can access your Canvas but first you need to name ItemsPresenter in your template:
<ControlTemplate TargetType="{x:Type local:Viewport}">
<Border>
<ItemsPresenter x:Name="PART_ItemsPresenter"/>
</Border>
</ControlTemplate>
then, using VisualTreeHelper get your Canvas, but I think earliest place when you can call code below is when FrameWorkElement is Loaded. This is my example:
public class MyListBox : ListBox
{
public MyListBox()
{
AddHandler(FrameworkElement.LoadedEvent, new RoutedEventHandler(ControlIsLoaded));
}
private void ControlIsLoaded(object sender, RoutedEventArgs e)
{
var canvas = VisualTreeHelper.GetChild(this.Template.FindName("PART_ItemsPresenter", this) as DependencyObject, 0);
}
}

How to get ContentPresenter in code behind?

I have the code below under Themes\Generic.xaml
Now I need to get cPresenter in code behind how I can do it?
In fact I try to convert Silverlight implementation into WPF code.
And I want to use something like Silverlight has:
FrameworkElement cp = this.GetTemplateChild("cPresenter") as FrameworkElement;
Themes\Generic.xaml
<Style TargetType="local:Marquee">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:Marquee">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer Width="Auto" Height="Auto" HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch" VerticalScrollBarVisibility="Disabled">
<Canvas>
<ContentPresenter x:Name="cPresenter" VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Canvas>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
There's no reason that shouldn't work in WPF also.
Put this in your code for Marquee.
private ContentPresenter cPresenter;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
this.cPresenter = this.GetTemplateChild("cPresenter") as ContentPresenter;
}

Changing ListBox ItemsPanelTemplate has gotten me into trouble?

I have a CustomControl (say CC) that has been inherited from ContentControl and contains a ScrollViewer which includes a ContentPresenter. When I put a ListBox into the CC it works without any problem. But when I set the ItemsPanelTemplate of the ListBox it doesn't notify CC to scroll into the ListBox selected item.
What's the reason for it? -Thanks
UPDATE:
I'll encounter the problem described above only if I set HorizontalScrollBarVisibility or VerticalScrollBarVisibility to Hidden and customize the ItemsPanelTemplate of the ListBox simultaneously. (I need to hide scollbars.)
I wonder if hiding Scrollbars prevents ScrollViewer contents from notifying it to bring selected item into view, why this issue doesn't happen when I don't change items panel???
Generic.xaml:
<ResourceDictionary ...>
<Style TargetType="{x:Type local:CustomControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomControl1}">
<Border ...>
<ScrollViewer ...
CanContentScroll="True"
HorizontalScrollBarVisibility="Hidden" « PROBLEM
VerticalScrollBarVisibility="Hidden"> «
<ContentPresenter Content="{TemplateBinding Content}"/>
</ScrollViewer>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
MainWindow.xaml:
<Window x:Class="MyNamespace1.MainWindow"
...
xmlns:proj="clr-namespace:MyNamespace0;assembly=...">
<Grid>
<proj:CustomControl1 x:Name="CC">
<ListBox>
<ListBox.ItemsPanel> «
<ItemsPanelTemplate> «
<StackPanel Orientation="Horizontal"/> « PROBLEM
</ItemsPanelTemplate> «
</ListBox.ItemsPanel> «
<!--content goes here-->
</ListBox>
</proj:CustomControl1>
</Grid>
</Window>
Have you set the IsItemsHost property for the Panel in the ItemsPanelTemplate to True?
E.g. if the itemspaneltemplate should use a Canvas:
<ItemsPanelTemplate>
<Canvas IsItemsHost="True" />
</ItemsPanelTemplate>
Related
StackPanel treats its content has having infinite space..
You will have to limit its size explicitly, or change it to other panel, Grid for example.
Try this:
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
Or:
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" Width="100" Height="100"/>
</ItemsPanelTemplate>

Style me an ItemsControl subclass please - stuch with ItemsPresenter

I have an control that is subclassed from ItemsControl, called WorkSheet:
public sealed class WorkSheet : ItemsControl {
Its elements are forced to be WorkTiles:
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item) {
return (item is WorkTile);
}
/// <inheritdoc />
protected override DependencyObject GetContainerForItemOverride() {
return new WorkTile();
}
So far - so good. I want the WorkSheet to use a Canvas for presenting the WorkItems, position being determined by overriing ArrangeOverride, which is called and positions properly determined. The exact psosition is being determined in overrides. I am pretty lost in the styling, though. I simply can not get the items to appear. In The Generic.xaml, I have defined the styles. They work, but not as they should:
<Style TargetType="{x:Type local:WorkSheet}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border>
<ItemsPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemTemplate">
<Setter.Value>
<DataTemplate>
<ContentPresenter />
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Here,pretty much, in the presentation tree, the ItemsPresenter is the lowest element. The subelements of ItemsPresenter never appear. I also tried putting a Canvas into the ControlTemplate with IsItemsHost="true" - again, no items. Anyone any idea what I am doing wrong here?
Again, as explanation - I put a breakpoint into a button handler on the form and use the Visualizer to see the visual tree. The hierarchy I get is:
WorkSheet -> Border -> ItemsPresenter ... and nothing below.
Obviously this means the ControlTemplate is used, but the ItemsPanel is never invoked.
Place a TargetType in your ControlTemplate as well:
<ControlTemplate TargetType="{x:Type local:WorkSheet}">
Does not work ;)
Update 2:
I replicated your things in a side project and you have two problems:
First problem is the ItemTemplate setter in your Style which will trigger a StackOverflow exception (how ironic ;)). Remove the ContentPresenter, remove the whole template or use keys.
Second problem is the GetContainerForItemOverride method. Removing this method will give me stuff on screen!
Here is my code:
public sealed class WorkSheet : ItemsControl
{
/// <inheritdoc />
protected override bool IsItemItsOwnContainerOverride(object item)
{
return (item is WorkTile);
}
}
And the xaml of Window with the Style:
<Window x:Class="WpfApplication8.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:WpfApplication8="clr-namespace:WpfApplication8"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style TargetType="{x:Type WpfApplication8:WorkSheet}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate >
<Border>
<ItemsPresenter />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="ItemsPanel">
<Setter.Value>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<WpfApplication8:WorkSheet x:Name="sheet" />
</Grid>
</Window>

Categories

Resources