Browse Source

Added ItemsControl.ItemTemplate property.

pull/540/head
Steven Kirk 10 years ago
parent
commit
5cd9b98057
  1. 5
      src/Avalonia.Controls/DropDown.cs
  2. 5
      src/Avalonia.Controls/Generators/IItemContainerGenerator.cs
  3. 7
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  4. 16
      src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
  5. 4
      src/Avalonia.Controls/Generators/TreeItemContainerGenerator.cs
  6. 32
      src/Avalonia.Controls/ItemsControl.cs
  7. 5
      src/Avalonia.Controls/ListBox.cs
  8. 15
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  9. 5
      src/Avalonia.Controls/Primitives/TabStrip.cs
  10. 1
      src/Avalonia.Controls/TreeView.cs
  11. 1
      src/Avalonia.Controls/TreeViewItem.cs
  12. 1
      src/Avalonia.Themes.Default/ContextMenu.xaml
  13. 1
      src/Avalonia.Themes.Default/DropDown.xaml
  14. 1
      src/Avalonia.Themes.Default/ItemsControl.xaml
  15. 1
      src/Avalonia.Themes.Default/ListBox.xaml
  16. 1
      src/Avalonia.Themes.Default/Menu.xaml
  17. 2
      src/Avalonia.Themes.Default/MenuItem.xaml
  18. 3
      src/Avalonia.Themes.Default/TabStrip.xaml
  19. 2
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTypedTests.cs
  20. 16
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  21. 22
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  22. 2
      tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs
  23. 5
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

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))

4
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);

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/>

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/>

1
src/Avalonia.Controls/TreeView.cs

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

1
src/Avalonia.Controls/TreeViewItem.cs

@ -80,6 +80,7 @@ namespace Avalonia.Controls
return new TreeItemContainerGenerator<TreeViewItem>(
this,
TreeViewItem.HeaderProperty,
null,
TreeViewItem.ItemsProperty,
TreeViewItem.IsExpandedProperty,
_treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex());

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>

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

@ -38,6 +38,7 @@
BorderThickness="1">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemTemplate="{TemplateBinding ItemTemplate}"
MemberSelector="{TemplateBinding MemberSelector}"/>
</Border>
</Popup>

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/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/Menu.xaml

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

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

@ -53,6 +53,7 @@
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
Margin="2"
MemberSelector="{TemplateBinding MemberSelector}"/>
<Rectangle Name="iconSeparator"
@ -99,6 +100,7 @@
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
Margin="2"
MemberSelector="{TemplateBinding MemberSelector}"/>
<Rectangle Name="iconSeparator"

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">

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);
}
}
}

Loading…
Cancel
Save