Browse Source

Merge pull request #540 from AvaloniaUI/issue-524

Add ContentTemplate and ItemTemplate
pull/541/head
Steven Kirk 10 years ago
parent
commit
39ca1c2d6c
  1. 8
      samples/XamlTestApplicationPcl/Views/MainWindow.xaml
  2. 15
      src/Avalonia.Controls/ContentControl.cs
  3. 5
      src/Avalonia.Controls/DropDown.cs
  4. 5
      src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
  5. 7
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  6. 16
      src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
  7. 10
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  8. 6
      src/Avalonia.Controls/IContentControl.cs
  9. 32
      src/Avalonia.Controls/ItemsControl.cs
  10. 5
      src/Avalonia.Controls/ListBox.cs
  11. 17
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  12. 15
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  13. 5
      src/Avalonia.Controls/Primitives/TabStrip.cs
  14. 25
      src/Avalonia.Controls/Templates/DataTemplateExtensions.cs
  15. 1
      src/Avalonia.Controls/TreeView.cs
  16. 6
      src/Avalonia.Controls/TreeViewItem.cs
  17. 1
      src/Avalonia.Themes.Default/Button.xaml
  18. 1
      src/Avalonia.Themes.Default/CheckBox.xaml
  19. 3
      src/Avalonia.Themes.Default/ContentControl.xaml
  20. 1
      src/Avalonia.Themes.Default/ContextMenu.xaml
  21. 2
      src/Avalonia.Themes.Default/DropDown.xaml
  22. 1
      src/Avalonia.Themes.Default/DropDownItem.xaml
  23. 4
      src/Avalonia.Themes.Default/Expander.xaml
  24. 1
      src/Avalonia.Themes.Default/ItemsControl.xaml
  25. 1
      src/Avalonia.Themes.Default/LayoutTransformControl.xaml
  26. 1
      src/Avalonia.Themes.Default/ListBox.xaml
  27. 1
      src/Avalonia.Themes.Default/ListBoxItem.xaml
  28. 1
      src/Avalonia.Themes.Default/Menu.xaml
  29. 5
      src/Avalonia.Themes.Default/MenuItem.xaml
  30. 1
      src/Avalonia.Themes.Default/PopupRoot.xaml
  31. 1
      src/Avalonia.Themes.Default/RadioButton.xaml
  32. 3
      src/Avalonia.Themes.Default/TabStrip.xaml
  33. 1
      src/Avalonia.Themes.Default/TabStripItem.xaml
  34. 1
      src/Avalonia.Themes.Default/ToggleButton.xaml
  35. 1
      src/Avalonia.Themes.Default/ToolTip.xaml
  36. 5
      src/Avalonia.Themes.Default/Window.xaml
  37. 19
      tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
  38. 2
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs
  39. 16
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  40. 22
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  41. 2
      tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs
  42. 5
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
  43. 45
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

8
samples/XamlTestApplicationPcl/Views/MainWindow.xaml

@ -143,14 +143,14 @@
<TabItem Header="Lists">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
<ListBox Items="{Binding Items}" SelectionMode="Multiple">
<ListBox.DataTemplates>
<ListBox.ItemTemplate>
<DataTemplate DataType="vm:TestItem">
<StackPanel>
<TextBlock Text="{Binding Header}" FontSize="24" />
<TextBlock Text="{Binding SubHeader}" />
</StackPanel>
</DataTemplate>
</ListBox.DataTemplates>
</ListBox.ItemTemplate>
</ListBox>
<DropDown VerticalAlignment="Center" SelectedIndex="0">
<StackPanel>
@ -168,14 +168,14 @@
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/>
</Style>
</TreeView.Styles>
<TreeView.DataTemplates>
<TreeView.ItemTemplate>
<TreeDataTemplate DataType="vm:TestNode" ItemsSource="{Binding Children}">
<StackPanel>
<TextBlock Text="{Binding Header}" FontSize="24" />
<TextBlock Text="{Binding SubHeader}" />
</StackPanel>
</TreeDataTemplate>
</TreeView.DataTemplates>
</TreeView.ItemTemplate>
</TreeView>
<StackPanel Orientation="Vertical" Gap="4">
<Button Command="{Binding CollapseNodesCommand}">Collapse Nodes</Button>

15
src/Avalonia.Controls/ContentControl.cs

@ -21,6 +21,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<object> ContentProperty =
AvaloniaProperty.Register<ContentControl, object>(nameof(Content));
/// <summary>
/// Defines the <see cref="ContentTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate> ContentTemplateProperty =
AvaloniaProperty.Register<ContentControl, IDataTemplate>(nameof(ContentTemplate));
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
@ -51,6 +57,15 @@ namespace Avalonia.Controls
set { SetValue(ContentProperty, value); }
}
/// <summary>
/// Gets or sets the data template used to display the content of the control.
/// </summary>
public IDataTemplate ContentTemplate
{
get { return GetValue(ContentTemplateProperty); }
set { SetValue(ContentTemplateProperty, value); }
}
/// <summary>
/// Gets the presenter from the control's template.
/// </summary>

5
src/Avalonia.Controls/DropDown.cs

@ -68,7 +68,10 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<DropDownItem>(this, DropDownItem.ContentProperty);
return new ItemContainerGenerator<DropDownItem>(
this,
DropDownItem.ContentProperty,
DropDownItem.ContentTemplateProperty);
}
/// <inheritdoc/>

5
src/Avalonia.Controls/Generators/IItemContainerGenerator.cs

@ -18,6 +18,11 @@ namespace Avalonia.Controls.Generators
/// </summary>
IEnumerable<ItemContainer> Containers { get; }
/// <summary>
/// Gets or sets the data template used to display the items in the control.
/// </summary>
IDataTemplate ItemTemplate { get; set; }
/// <summary>
/// Signalled whenever new containers are materialized.
/// </summary>

7
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -37,6 +37,11 @@ namespace Avalonia.Controls.Generators
/// <inheritdoc/>
public event EventHandler<ItemContainerEventArgs> Dematerialized;
/// <summary>
/// Gets or sets the data template used to display the items in the control.
/// </summary>
public IDataTemplate ItemTemplate { get; set; }
/// <summary>
/// Gets the owner control.
/// </summary>
@ -156,7 +161,7 @@ namespace Avalonia.Controls.Generators
/// <returns>The created container control.</returns>
protected virtual IControl CreateContainer(object item)
{
var result = Owner.MaterializeDataTemplate(item);
var result = Owner.MaterializeDataTemplate(item, ItemTemplate);
if (result != null && !(item is IControl))
{

16
src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs

@ -19,15 +19,18 @@ namespace Avalonia.Controls.Generators
/// </summary>
/// <param name="owner">The owner control.</param>
/// <param name="contentProperty">The container's Content property.</param>
/// <param name="contentTemplateProperty">The container's ContentTemplate property.</param>
public ItemContainerGenerator(
IControl owner,
AvaloniaProperty contentProperty)
AvaloniaProperty contentProperty,
AvaloniaProperty contentTemplateProperty)
: base(owner)
{
Contract.Requires<ArgumentNullException>(owner != null);
Contract.Requires<ArgumentNullException>(contentProperty != null);
ContentProperty = contentProperty;
ContentTemplateProperty = contentTemplateProperty;
}
/// <summary>
@ -35,6 +38,11 @@ namespace Avalonia.Controls.Generators
/// </summary>
protected AvaloniaProperty ContentProperty { get; }
/// <summary>
/// Gets the container's ContentTemplate property.
/// </summary>
protected AvaloniaProperty ContentTemplateProperty { get; }
/// <inheritdoc/>
protected override IControl CreateContainer(object item)
{
@ -51,6 +59,12 @@ namespace Avalonia.Controls.Generators
else
{
var result = new T();
if (ContentTemplateProperty != null)
{
result.SetValue(ContentTemplateProperty, ItemTemplate);
}
result.SetValue(ContentProperty, item);
if (!(item is IControl))

10
src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs

@ -20,16 +20,18 @@ namespace Avalonia.Controls.Generators
/// </summary>
/// <param name="owner">The owner control.</param>
/// <param name="contentProperty">The container's Content property.</param>
/// <param name="contentTemplateProperty">The container's ContentTemplate property.</param>
/// <param name="itemsProperty">The container's Items property.</param>
/// <param name="isExpandedProperty">The container's IsExpanded property.</param>
/// <param name="index">The container index for the tree</param>
public TreeItemContainerGenerator(
IControl owner,
AvaloniaProperty contentProperty,
AvaloniaProperty contentTemplateProperty,
AvaloniaProperty itemsProperty,
AvaloniaProperty isExpandedProperty,
TreeContainerIndex index)
: base(owner, contentProperty)
: base(owner, contentProperty, contentTemplateProperty)
{
Contract.Requires<ArgumentNullException>(owner != null);
Contract.Requires<ArgumentNullException>(contentProperty != null);
@ -73,7 +75,7 @@ namespace Avalonia.Controls.Generators
}
else
{
var template = GetTreeDataTemplate(item);
var template = GetTreeDataTemplate(item, ItemTemplate);
var result = new T();
result.SetValue(ContentProperty, template.Build(item));
@ -121,9 +123,9 @@ namespace Avalonia.Controls.Generators
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The template.</returns>
private ITreeDataTemplate GetTreeDataTemplate(object item)
private ITreeDataTemplate GetTreeDataTemplate(object item, IDataTemplate primary)
{
var template = Owner.FindDataTemplate(item) ?? FuncDataTemplate.Default;
var template = Owner.FindDataTemplate(item, primary) ?? FuncDataTemplate.Default;
var treeTemplate = template as ITreeDataTemplate ??
new FuncTreeDataTemplate(typeof(object), template.Build, x => null);
return treeTemplate;

6
src/Avalonia.Controls/IContentControl.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Templates;
using Avalonia.Layout;
namespace Avalonia.Controls
@ -16,6 +17,11 @@ namespace Avalonia.Controls
/// </summary>
object Content { get; set; }
/// <summary>
/// Gets or sets the data template used to display the content of the control.
/// </summary>
IDataTemplate ContentTemplate { get; set; }
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>

32
src/Avalonia.Controls/ItemsControl.cs

@ -41,6 +41,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<ITemplate<IPanel>> ItemsPanelProperty =
AvaloniaProperty.Register<ItemsControl, ITemplate<IPanel>>(nameof(ItemsPanel), DefaultPanel);
/// <summary>
/// Defines the <see cref="ItemTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty =
AvaloniaProperty.Register<ItemsControl, IDataTemplate>(nameof(ItemTemplate));
/// <summary>
/// Defines the <see cref="MemberSelector"/> property.
/// </summary>
@ -65,6 +71,7 @@ namespace Avalonia.Controls
{
PseudoClasses.Add(":empty");
SubscribeToItems(_items);
ItemTemplateProperty.Changed.AddClassHandler<ItemsControl>(x => x.ItemTemplateChanged);
}
/// <summary>
@ -80,6 +87,7 @@ namespace Avalonia.Controls
if (_itemContainerGenerator != null)
{
_itemContainerGenerator.ItemTemplate = ItemTemplate;
_itemContainerGenerator.Materialized += (_, e) => OnContainersMaterialized(e);
_itemContainerGenerator.Dematerialized += (_, e) => OnContainersDematerialized(e);
}
@ -108,6 +116,15 @@ namespace Avalonia.Controls
set { SetValue(ItemsPanelProperty, value); }
}
/// <summary>
/// Gets or sets the data template used to display the items in the control.
/// </summary>
public IDataTemplate ItemTemplate
{
get { return GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
/// <summary>
/// Selects a member from <see cref="Items"/> to use as the list item.
/// </summary>
@ -354,7 +371,7 @@ namespace Avalonia.Controls
/// <summary>
/// Subscribes to an <see cref="Items"/> collection.
/// </summary>
/// <param name="items"></param>
/// <param name="items">The items collection.</param>
private void SubscribeToItems(IEnumerable items)
{
PseudoClasses.Set(":empty", items == null || items.Count() == 0);
@ -366,5 +383,18 @@ namespace Avalonia.Controls
incc.CollectionChanged += ItemsCollectionChanged;
}
}
/// <summary>
/// Called when the <see cref="ItemTemplate"/> changes.
/// </summary>
/// <param name="e">The event args.</param>
private void ItemTemplateChanged(AvaloniaPropertyChangedEventArgs e)
{
if (_itemContainerGenerator != null)
{
_itemContainerGenerator.ItemTemplate = (IDataTemplate)e.NewValue;
// TODO: Rebuild the item containers.
}
}
}
}

5
src/Avalonia.Controls/ListBox.cs

@ -41,7 +41,10 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<ListBoxItem>(this, ListBoxItem.ContentProperty);
return new ItemContainerGenerator<ListBoxItem>(
this,
ListBoxItem.ContentProperty,
ListBoxItem.ContentTemplateProperty);
}
/// <inheritdoc/>

17
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -48,6 +48,12 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<object> ContentProperty =
ContentControl.ContentProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="ContentTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate> ContentTemplateProperty =
ContentControl.ContentTemplateProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="CornerRadius"/> property.
/// </summary>
@ -140,6 +146,15 @@ namespace Avalonia.Controls.Presenters
set { SetValue(ContentProperty, value); }
}
/// <summary>
/// Gets or sets the data template used to display the content of the control.
/// </summary>
public IDataTemplate ContentTemplate
{
get { return GetValue(ContentTemplateProperty); }
set { SetValue(ContentTemplateProperty, value); }
}
/// <summary>
/// Gets or sets the radius of the border rounded corners.
/// </summary>
@ -200,7 +215,7 @@ namespace Avalonia.Controls.Presenters
{
var old = Child;
var content = Content;
var result = this.MaterializeDataTemplate(content);
var result = this.MaterializeDataTemplate(content, ContentTemplate);
if (old != null)
{

15
src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs

@ -27,6 +27,12 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<ITemplate<IPanel>> ItemsPanelProperty =
ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenterBase>();
/// <summary>
/// Defines the <see cref="ItemTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate> ItemTemplateProperty =
ItemsControl.ItemTemplateProperty.AddOwner<ItemsPresenterBase>();
/// <summary>
/// Defines the <see cref="MemberSelector"/> property.
/// </summary>
@ -122,6 +128,15 @@ namespace Avalonia.Controls.Presenters
set { SetValue(ItemsPanelProperty, value); }
}
/// <summary>
/// Gets or sets the data template used to display the items in the control.
/// </summary>
public IDataTemplate ItemTemplate
{
get { return GetValue(ItemTemplateProperty); }
set { SetValue(ItemTemplateProperty, value); }
}
/// <summary>
/// Selects a member from <see cref="Items"/> to use as the list item.
/// </summary>

5
src/Avalonia.Controls/Primitives/TabStrip.cs

@ -20,7 +20,10 @@ namespace Avalonia.Controls.Primitives
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<TabStripItem>(this, ContentControl.ContentProperty);
return new ItemContainerGenerator<TabStripItem>(
this,
ContentControl.ContentProperty,
ContentControl.ContentTemplateProperty);
}
/// <inheritdoc/>

25
src/Avalonia.Controls/Templates/DataTemplateExtensions.cs

@ -16,8 +16,15 @@ namespace Avalonia.Controls.Templates
/// </summary>
/// <param name="control">The control materializing the data template.</param>
/// <param name="data">The data.</param>
/// <param name="primary">
/// An optional primary template that can will be tried before the
/// <see cref="IControl.DataTemplates"/> in the tree are searched.
/// </param>
/// <returns>The data materialized as a control.</returns>
public static IControl MaterializeDataTemplate(this IControl control, object data)
public static IControl MaterializeDataTemplate(
this IControl control,
object data,
IDataTemplate primary = null)
{
if (data == null)
{
@ -33,7 +40,7 @@ namespace Avalonia.Controls.Templates
}
else
{
IDataTemplate template = control.FindDataTemplate(data);
IDataTemplate template = control.FindDataTemplate(data, primary);
IControl result;
if (template != null)
@ -57,9 +64,21 @@ namespace Avalonia.Controls.Templates
/// </summary>
/// <param name="control">The control searching for the data template.</param>
/// <param name="data">The data.</param>
/// <param name="primary">
/// An optional primary template that can will be tried before the
/// <see cref="IControl.DataTemplates"/> in the tree are searched.
/// </param>
/// <returns>The data template or null if no matching data template was found.</returns>
public static IDataTemplate FindDataTemplate(this IControl control, object data)
public static IDataTemplate FindDataTemplate(
this IControl control,
object data,
IDataTemplate primary = null)
{
if (primary?.Match(data) == true)
{
return primary;
}
foreach (var i in control.GetSelfAndLogicalAncestors().OfType<IControl>())
{
foreach (IDataTemplate dt in i.DataTemplates)

1
src/Avalonia.Controls/TreeView.cs

@ -96,6 +96,7 @@ namespace Avalonia.Controls
var result = new TreeItemContainerGenerator<TreeViewItem>(
this,
TreeViewItem.HeaderProperty,
TreeViewItem.ItemTemplateProperty,
TreeViewItem.ItemsProperty,
TreeViewItem.IsExpandedProperty,
new TreeContainerIndex());

6
src/Avalonia.Controls/TreeViewItem.cs

@ -80,6 +80,7 @@ namespace Avalonia.Controls
return new TreeItemContainerGenerator<TreeViewItem>(
this,
TreeViewItem.HeaderProperty,
TreeViewItem.ItemTemplateProperty,
TreeViewItem.ItemsProperty,
TreeViewItem.IsExpandedProperty,
_treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex());
@ -90,6 +91,11 @@ namespace Avalonia.Controls
{
base.OnAttachedToLogicalTree(e);
_treeView = this.GetLogicalAncestors().OfType<TreeView>().FirstOrDefault();
if (ItemTemplate == null && _treeView?.ItemTemplate != null)
{
ItemTemplate = _treeView.ItemTemplate;
}
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)

1
src/Avalonia.Themes.Default/Button.xaml

@ -14,6 +14,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

1
src/Avalonia.Themes.Default/CheckBox.xaml

@ -22,6 +22,7 @@
</Border>
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="4,0,0,0"
VerticalAlignment="Center"
Grid.Column="1"/>

3
src/Avalonia.Themes.Default/ContentControl.xaml

@ -5,7 +5,8 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

1
src/Avalonia.Themes.Default/ContextMenu.xaml

@ -13,6 +13,7 @@
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
KeyboardNavigation.TabNavigation="Continue"/>
</Border>
</ControlTemplate>

2
src/Avalonia.Themes.Default/DropDown.xaml

@ -11,6 +11,7 @@
BorderThickness="{TemplateBinding BorderThickness}">
<Grid ColumnDefinitions="*,Auto">
<ContentPresenter Content="{TemplateBinding SelectionBoxItem}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
@ -38,6 +39,7 @@
BorderThickness="1">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding MemberSelector}"/>
</Border>
</Popup>

1
src/Avalonia.Themes.Default/DropDownItem.xaml

@ -10,6 +10,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}"/>

4
src/Avalonia.Themes.Default/Expander.xaml

@ -17,6 +17,7 @@
Grid.Row="1"
IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
@ -34,6 +35,7 @@
Grid.Row="0"
IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
@ -51,6 +53,7 @@
Grid.Column="1"
IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>
@ -68,6 +71,7 @@
Grid.Column="0"
IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" />
</Grid>

1
src/Avalonia.Themes.Default/ItemsControl.xaml

@ -4,6 +4,7 @@
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding MemberSelector}"/>
</ControlTemplate>
</Setter>

1
src/Avalonia.Themes.Default/LayoutTransformControl.xaml

@ -6,6 +6,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

1
src/Avalonia.Themes.Default/ListBox.xaml

@ -11,6 +11,7 @@
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
Margin="{TemplateBinding Padding}"
MemberSelector="{TemplateBinding MemberSelector}"/>
</ScrollViewer>

1
src/Avalonia.Themes.Default/ListBoxItem.xaml

@ -7,6 +7,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

1
src/Avalonia.Themes.Default/Menu.xaml

@ -8,6 +8,7 @@
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
KeyboardNavigation.TabNavigation="Continue"/>
</Border>
</ControlTemplate>

5
src/Avalonia.Themes.Default/MenuItem.xaml

@ -13,6 +13,7 @@
<Grid ColumnDefinitions="22,13,*,20">
<ContentPresenter Name="icon"
Content="{TemplateBinding Icon}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Width="16"
Height="16"
Margin="3"
@ -26,6 +27,7 @@
VerticalAlignment="Center"/>
<ContentPresenter Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"
VerticalAlignment="Center"
Grid.Column="2">
@ -53,6 +55,7 @@
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
Margin="2"
MemberSelector="{TemplateBinding MemberSelector}"/>
<Rectangle Name="iconSeparator"
@ -81,6 +84,7 @@
<Panel>
<ContentPresenter Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}">
<ContentPresenter.DataTemplates>
<DataTemplate DataType="sys:String">
@ -99,6 +103,7 @@
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
Margin="2"
MemberSelector="{TemplateBinding MemberSelector}"/>
<Rectangle Name="iconSeparator"

1
src/Avalonia.Themes.Default/PopupRoot.xaml

@ -5,6 +5,7 @@
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

1
src/Avalonia.Themes.Default/RadioButton.xaml

@ -21,6 +21,7 @@
VerticalAlignment="Center"/>
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="4,0,0,0"
VerticalAlignment="Center"
Grid.Column="1"/>

3
src/Avalonia.Themes.Default/TabStrip.xaml

@ -5,7 +5,8 @@
<ItemsPresenter Name="PART_ItemsPresenter"
MemberSelector="{TemplateBinding MemberSelector}"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"/>
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"/>
</ControlTemplate>
</Setter>
<Setter Property="ItemsPanel">

1
src/Avalonia.Themes.Default/TabStripItem.xaml

@ -9,6 +9,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

1
src/Avalonia.Themes.Default/ToggleButton.xaml

@ -14,6 +14,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

1
src/Avalonia.Themes.Default/ToolTip.xaml

@ -10,6 +10,7 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Padding="{TemplateBinding Padding}"/>
</ControlTemplate>
</Setter>

5
src/Avalonia.Themes.Default/Window.xaml

@ -6,7 +6,10 @@
<ControlTemplate>
<Border Background="{TemplateBinding Background}">
<AdornerDecorator>
<ContentPresenter Name="PART_ContentPresenter" Content="{TemplateBinding Content}" Margin="{TemplateBinding Padding}"/>
<ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Margin="{TemplateBinding Padding}"/>
</AdornerDecorator>
</Border>
</ControlTemplate>

19
tests/Avalonia.Controls.UnitTests/ContentControlTests.cs

@ -116,6 +116,24 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { child }, target.GetLogicalChildren());
}
[Fact]
public void Should_Use_ContentTemplate_To_Create_Control()
{
var target = new ContentControl
{
Template = GetTemplate(),
ContentTemplate = new FuncDataTemplate<string>(_ => new Canvas()),
};
target.Content = "Foo";
target.ApplyTemplate();
((ContentPresenter)target.Presenter).UpdateChild();
var child = target.Presenter.Child;
Assert.IsType<Canvas>(child);
}
[Fact]
public void DataTemplate_Created_Control_Should_Be_Logical_Child_After_ApplyTemplate()
{
@ -266,6 +284,7 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_ContentPresenter",
[~ContentPresenter.ContentProperty] = parent[~ContentControl.ContentProperty],
[~ContentPresenter.ContentTemplateProperty] = parent[~ContentControl.ContentTemplateProperty],
}
};
});

2
tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs

@ -14,7 +14,7 @@ namespace Avalonia.Controls.UnitTests.Generators
{
var items = new[] { "foo", "bar", "baz" };
var owner = new Decorator();
var target = new ItemContainerGenerator<ListBoxItem>(owner, ListBoxItem.ContentProperty);
var target = new ItemContainerGenerator<ListBoxItem>(owner, ListBoxItem.ContentProperty, null);
var containers = target.Materialize(0, items, null);
var result = containers
.Select(x => x.ContainerControl)

16
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -14,6 +14,22 @@ namespace Avalonia.Controls.UnitTests
{
public class ItemsControlTests
{
[Fact]
public void Should_Use_ItemTemplate_To_Create_Control()
{
var target = new ItemsControl
{
Template = GetTemplate(),
ItemTemplate = new FuncDataTemplate<string>(_ => new Canvas()),
};
target.Items = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
Assert.IsType<Canvas>(target.Presenter.Panel.Children[0]);
}
[Fact]
public void Panel_Should_Have_TemplatedParent_Set_To_ItemsControl()
{

22
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@ -14,6 +14,27 @@ namespace Avalonia.Controls.UnitTests
{
public class ListBoxTests
{
[Fact]
public void Should_Use_ItemTemplate_To_Create_Item_Content()
{
var target = new ListBox
{
Template = new FuncControlTemplate(CreateListBoxTemplate),
ItemTemplate = new FuncDataTemplate<string>(_ => new Canvas()),
};
target.Items = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter.ApplyTemplate();
var container = (ListBoxItem)target.Presenter.Panel.Children[0];
container.Template = ListBoxItemTemplate();
container.ApplyTemplate();
((ContentPresenter)container.Presenter).UpdateChild();
Assert.IsType<Canvas>(container.Presenter.Child);
}
[Fact]
public void ListBox_Should_Find_ItemsPresenter_In_ScrollViewer()
{
@ -123,6 +144,7 @@ namespace Avalonia.Controls.UnitTests
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = parent[!ListBoxItem.ContentProperty],
[!ContentPresenter.ContentTemplateProperty] = parent[!ListBoxItem.ContentTemplateProperty],
});
}

2
tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs

@ -201,7 +201,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<TestItem>(this, TestItem.ContentProperty);
return new ItemContainerGenerator<TestItem>(this, TestItem.ContentProperty, null);
}
}
}

5
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

@ -54,7 +54,8 @@ namespace Avalonia.Controls.UnitTests.Presenters
target.ItemContainerGenerator = new ItemContainerGenerator<ListBoxItem>(
target,
ListBoxItem.ContentProperty);
ListBoxItem.ContentProperty,
null);
target.ApplyTemplate();
Assert.Equal(2, target.Panel.Children.Count);
@ -332,7 +333,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
{
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new ItemContainerGenerator<TestItem>(this, TestItem.ContentProperty);
return new ItemContainerGenerator<TestItem>(this, TestItem.ContentProperty, null);
}
}
}

45
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -35,6 +35,33 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { "Grandchild2a" }, ExtractItemHeader(target, 2));
}
[Fact]
public void Items_Should_Be_Created_Using_ItemTemplate_If_Present()
{
TreeView target;
var root = new TestRoot
{
Child = target = new TreeView
{
Template = CreateTreeViewTemplate(),
Items = CreateTestTreeData(),
ItemTemplate = new FuncTreeDataTemplate<Node>(
_ => new Canvas(),
x => x.Children),
}
};
ApplyTemplates(target);
var items = target.ItemContainerGenerator.Index.Items
.OfType<TreeViewItem>()
.ToList();
Assert.Equal(4, items.Count);
Assert.All(items, x => Assert.IsType<Canvas>(x.HeaderPresenter.Child));
}
[Fact]
public void Root_ItemContainerGenerator_Containers_Should_Be_Root_Containers()
{
@ -302,6 +329,7 @@ namespace Avalonia.Controls.UnitTests
control.Template = CreateTreeViewItemTemplate();
control.ApplyTemplate();
control.Presenter.ApplyTemplate();
control.HeaderPresenter.ApplyTemplate();
ApplyTemplates(control.Presenter.Panel.Children);
}
}
@ -354,10 +382,21 @@ namespace Avalonia.Controls.UnitTests
private IControlTemplate CreateTreeViewItemTemplate()
{
return new FuncControlTemplate<TreeViewItem>(parent => new ItemsPresenter
return new FuncControlTemplate<TreeViewItem>(parent => new Panel
{
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
Children = new Controls
{
new ContentPresenter
{
Name = "PART_HeaderPresenter",
[~ContentPresenter.ContentProperty] = parent[~TreeViewItem.HeaderProperty],
},
new ItemsPresenter
{
Name = "PART_ItemsPresenter",
[~ItemsPresenter.ItemsProperty] = parent[~ItemsControl.ItemsProperty],
}
}
});
}

Loading…
Cancel
Save