Browse Source

Merged itemspresenter-base branch.

pull/508/head
Steven Kirk 11 years ago
parent
commit
2cfa50e211
  1. 2
      src/Perspex.Controls/ItemsControl.cs
  2. 1
      src/Perspex.Controls/Perspex.Controls.csproj
  3. 154
      src/Perspex.Controls/Presenters/CarouselPresenter.cs
  4. 278
      src/Perspex.Controls/Presenters/ItemsPresenter.cs
  5. 217
      src/Perspex.Controls/Presenters/ItemsPresenterBase.cs

2
src/Perspex.Controls/ItemsControl.cs

@ -239,7 +239,7 @@ namespace Perspex.Controls
{
// If the item is its own container, then it will be removed from the logical tree
// when it is removed from the Items collection.
if (container.ContainerControl != container.Item)
if (container?.ContainerControl != container?.Item)
{
toRemove.Add(container.ContainerControl);
}

1
src/Perspex.Controls/Perspex.Controls.csproj

@ -66,6 +66,7 @@
<Compile Include="Platform\PlatformManager.cs" />
<Compile Include="Presenters\IContentPresenterHost.cs" />
<Compile Include="Presenters\IItemsPresenterHost.cs" />
<Compile Include="Presenters\ItemsPresenterBase.cs" />
<Compile Include="Primitives\HeaderedSelectingControl.cs" />
<Compile Include="Primitives\IScrollable.cs" />
<Compile Include="Primitives\TabStripItem.cs" />

154
src/Perspex.Controls/Presenters/CarouselPresenter.cs

@ -3,6 +3,7 @@
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
@ -18,7 +19,7 @@ namespace Perspex.Controls.Presenters
/// <summary>
/// Displays pages inside an <see cref="ItemsControl"/>.
/// </summary>
public class CarouselPresenter : Control, IItemsPresenter
public class CarouselPresenter : ItemsPresenterBase
{
/// <summary>
/// Defines the <see cref="IsVirtualized"/> property.
@ -26,24 +27,6 @@ namespace Perspex.Controls.Presenters
public static readonly StyledProperty<bool> IsVirtualizedProperty =
Carousel.IsVirtualizedProperty.AddOwner<CarouselPresenter>();
/// <summary>
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly DirectProperty<CarouselPresenter, IEnumerable> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<CarouselPresenter>(o => o.Items, (o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
/// </summary>
public static readonly StyledProperty<ITemplate<IPanel>> ItemsPanelProperty =
ItemsControl.ItemsPanelProperty.AddOwner<CarouselPresenter>();
/// <summary>
/// Defines the <see cref="MemberSelector"/> property.
/// </summary>
public static readonly StyledProperty<IMemberSelector> MemberSelectorProperty =
ItemsControl.MemberSelectorProperty.AddOwner<CarouselPresenter>();
/// <summary>
/// Defines the <see cref="SelectedIndex"/> property.
/// </summary>
@ -58,10 +41,7 @@ namespace Perspex.Controls.Presenters
public static readonly StyledProperty<IPageTransition> TransitionProperty =
Carousel.TransitionProperty.AddOwner<CarouselPresenter>();
private IEnumerable _items;
private int _selectedIndex = -1;
private bool _createdPanel;
private IItemContainerGenerator _generator;
private Task _currentTransition;
private int _queuedTransitionIndex = -1;
@ -71,35 +51,6 @@ namespace Perspex.Controls.Presenters
static CarouselPresenter()
{
SelectedIndexProperty.Changed.AddClassHandler<CarouselPresenter>(x => x.SelectedIndexChanged);
TemplatedParentProperty.Changed.AddClassHandler<CarouselPresenter>(x => x.TemplatedParentChanged);
}
/// <summary>
/// Gets the <see cref="IItemContainerGenerator"/> used to generate item container
/// controls.
/// </summary>
public IItemContainerGenerator ItemContainerGenerator
{
get
{
if (_generator == null)
{
var i = TemplatedParent as ItemsControl;
_generator = i?.ItemContainerGenerator ?? new ItemContainerGenerator(this);
}
return _generator;
}
set
{
if (_generator != null)
{
throw new InvalidOperationException("ItemContainerGenerator is already set.");
}
_generator = value;
}
}
/// <summary>
@ -114,33 +65,6 @@ namespace Perspex.Controls.Presenters
set { SetValue(IsVirtualizedProperty, value); }
}
/// <summary>
/// Gets or sets the items to display.
/// </summary>
public IEnumerable Items
{
get { return _items; }
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
/// <summary>
/// Gets or sets the panel used to display the pages.
/// </summary>
public ITemplate<IPanel> ItemsPanel
{
get { return GetValue(ItemsPanelProperty); }
set { SetValue(ItemsPanelProperty, value); }
}
/// <summary>
/// Selects a member from <see cref="Items"/> to use as the list item.
/// </summary>
public IMemberSelector MemberSelector
{
get { return GetValue(MemberSelectorProperty); }
set { SetValue(MemberSelectorProperty, value); }
}
/// <summary>
/// Gets or sets the index of the selected page.
/// </summary>
@ -164,15 +88,6 @@ namespace Perspex.Controls.Presenters
}
}
/// <summary>
/// Gets the panel used to display the pages.
/// </summary>
public IPanel Panel
{
get;
private set;
}
/// <summary>
/// Gets or sets a transition to use when switching pages.
/// </summary>
@ -183,29 +98,16 @@ namespace Perspex.Controls.Presenters
}
/// <inheritdoc/>
public override sealed void ApplyTemplate()
protected override void CreatePanel()
{
if (!_createdPanel)
{
CreatePanel();
}
base.CreatePanel();
var task = MoveToPage(-1, SelectedIndex);
}
/// <summary>
/// Creates the <see cref="Panel"/>.
/// </summary>
private void CreatePanel()
/// <inheritdoc/>
protected override void ItemsChanged(NotifyCollectionChangedEventArgs e)
{
Panel = ItemsPanel.Build();
Panel.SetValue(TemplatedParentProperty, TemplatedParent);
LogicalChildren.Clear();
VisualChildren.Clear();
LogicalChildren.Add(Panel);
VisualChildren.Add(Panel);
_createdPanel = true;
var task = MoveToPage(-1, SelectedIndex);
// TODO: Handle items changing.
}
/// <summary>
@ -224,24 +126,13 @@ namespace Perspex.Controls.Presenters
if (fromIndex != -1)
{
from = generator.ContainerFromIndex(fromIndex);
from = ItemContainerGenerator.ContainerFromIndex(fromIndex);
}
if (toIndex != -1)
{
var item = Items.Cast<object>().ElementAt(toIndex);
to = generator.ContainerFromIndex(toIndex);
if (to == null)
{
to = generator.Materialize(toIndex, new[] { item }, MemberSelector)
.FirstOrDefault()?.ContainerControl;
if (to != null)
{
Panel.Children.Add(to);
}
}
to = GetOrCreateContainer(toIndex);
}
if (Transition != null && (from != null || to != null))
@ -268,6 +159,24 @@ namespace Perspex.Controls.Presenters
}
}
private IControl GetOrCreateContainer(int index)
{
var container = ItemContainerGenerator.ContainerFromIndex(index);
if (container == null)
{
var item = Items.Cast<object>().ElementAt(index);
var materialized = ItemContainerGenerator.Materialize(
index,
new[] { item },
MemberSelector);
container = materialized.First().ContainerControl;
Panel.Children.Add(container);
}
return container;
}
/// <summary>
/// Called when the <see cref="SelectedIndex"/> property changes.
/// </summary>
@ -305,10 +214,5 @@ namespace Perspex.Controls.Presenters
}
}
}
private void TemplatedParentChanged(PerspexPropertyChangedEventArgs e)
{
(e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this);
}
}
}
}

278
src/Perspex.Controls/Presenters/ItemsPresenter.cs

@ -1,45 +1,19 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using Perspex.Controls.Generators;
using Perspex.Controls.Templates;
using Perspex.Controls.Utils;
using Perspex.Input;
using Perspex.Styling;
namespace Perspex.Controls.Presenters
{
/// <summary>
/// Displays items inside an <see cref="ItemsControl"/>.
/// </summary>
public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl
public class ItemsPresenter : ItemsPresenterBase
{
/// <summary>
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly DirectProperty<ItemsPresenter, IEnumerable> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<ItemsPresenter>(o => o.Items, (o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
/// </summary>
public static readonly StyledProperty<ITemplate<IPanel>> ItemsPanelProperty =
ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenter>();
/// <summary>
/// Defines the <see cref="MemberSelector"/> property.
/// </summary>
public static readonly StyledProperty<IMemberSelector> MemberSelectorProperty =
ItemsControl.MemberSelectorProperty.AddOwner<ItemsPresenter>();
private IEnumerable _items;
private bool _createdPanel;
private IItemContainerGenerator _generator;
/// <summary>
/// Initializes static members of the <see cref="ItemsPresenter"/> class.
/// </summary>
@ -48,112 +22,12 @@ namespace Perspex.Controls.Presenters
KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue(
typeof(ItemsPresenter),
KeyboardNavigationMode.Once);
ItemsProperty.Changed.AddClassHandler<ItemsPresenter>(x => x.ItemsChanged);
TemplatedParentProperty.Changed.AddClassHandler<ItemsPresenter>(x => x.TemplatedParentChanged);
}
/// <summary>
/// Initializes a new instance of the <see cref="ItemsPresenter"/> class.
/// </summary>
public ItemsPresenter()
{
}
/// <summary>
/// Gets the <see cref="IItemContainerGenerator"/> used to generate item container
/// controls.
/// </summary>
public IItemContainerGenerator ItemContainerGenerator
{
get
{
if (_generator == null)
{
var i = TemplatedParent as ItemsControl;
_generator = i?.ItemContainerGenerator ?? new ItemContainerGenerator(this);
}
return _generator;
}
set
{
if (_generator != null)
{
throw new InvalidOperationException("ItemContainerGenerator is already set.");
}
_generator = value;
}
}
/// <summary>
/// Gets or sets the items to be displayed.
/// </summary>
public IEnumerable Items
{
get { return _items; }
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
/// <summary>
/// Gets or sets a template which creates the <see cref="Panel"/> used to display the items.
/// </summary>
public ITemplate<IPanel> ItemsPanel
{
get { return GetValue(ItemsPanelProperty); }
set { SetValue(ItemsPanelProperty, value); }
}
/// <summary>
/// Selects a member from <see cref="Items"/> to use as the list item.
/// </summary>
public IMemberSelector MemberSelector
{
get { return GetValue(MemberSelectorProperty); }
set { SetValue(MemberSelectorProperty, value); }
}
/// <summary>
/// Gets the panel used to display the items.
/// </summary>
public IPanel Panel
{
get;
private set;
}
/// <inheritdoc/>
public override sealed void ApplyTemplate()
protected override void CreatePanel()
{
if (!_createdPanel)
{
CreatePanel();
}
}
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
Panel.Measure(availableSize);
return Panel.DesiredSize;
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
Panel.Arrange(new Rect(finalSize));
return finalSize;
}
/// <summary>
/// Creates the <see cref="Panel"/> when <see cref="ApplyTemplate"/> is called for the first
/// time.
/// </summary>
private void CreatePanel()
{
Panel = ItemsPanel.Build();
Panel.SetValue(TemplatedParentProperty, TemplatedParent);
base.CreatePanel();
if (!Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty))
{
@ -162,123 +36,81 @@ namespace Perspex.Controls.Presenters
KeyboardNavigationMode.Contained);
}
LogicalChildren.Clear();
VisualChildren.Clear();
LogicalChildren.Add(Panel);
VisualChildren.Add(Panel);
KeyboardNavigation.SetTabNavigation(
(InputElement)Panel,
KeyboardNavigation.GetTabNavigation(this));
_createdPanel = true;
CreateItemsAndListenForChanges(Items);
}
/// <summary>
/// Creates the items for a collection and starts listening for changes on the collection.
/// </summary>
/// <param name="items">The items, may be null.</param>
private void CreateItemsAndListenForChanges(IEnumerable items)
/// <inheritdoc/>
protected override void ItemsChanged(NotifyCollectionChangedEventArgs e)
{
if (items != null)
{
AddContainers(ItemContainerGenerator.Materialize(0, Items, MemberSelector));
INotifyCollectionChanged incc = items as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged += ItemsCollectionChanged;
}
}
}
var generator = ItemContainerGenerator;
/// <summary>
/// Called when the <see cref="Items"/> property changes.
/// </summary>
/// <param name="e">The event args.</param>
private void ItemsChanged(PerspexPropertyChangedEventArgs e)
{
if (_createdPanel)
// TODO: Handle Move and Replace etc.
switch (e.Action)
{
var generator = ItemContainerGenerator;
if (e.OldValue != null)
{
generator.Clear();
Panel.Children.Clear();
INotifyCollectionChanged incc = e.OldValue as INotifyCollectionChanged;
if (incc != null)
case NotifyCollectionChangedAction.Add:
if (e.NewStartingIndex + e.NewItems.Count < Items.Count())
{
incc.CollectionChanged -= ItemsCollectionChanged;
generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count);
}
}
if (Panel != null)
{
CreateItemsAndListenForChanges((IEnumerable)e.NewValue);
}
}
}
AddContainers(generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector));
break;
/// <summary>
/// Called when the <see cref="Items"/> collection changes.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The event args.</param>
private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_createdPanel)
{
var generator = ItemContainerGenerator;
// TODO: Handle Move and Replace etc.
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
if (e.NewStartingIndex + e.NewItems.Count < this.Items.Count())
{
generator.InsertSpace(e.NewStartingIndex, e.NewItems.Count);
}
AddContainers(generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector));
break;
case NotifyCollectionChangedAction.Remove:
RemoveContainers(generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count));
break;
case NotifyCollectionChangedAction.Remove:
RemoveContainers(generator.RemoveRange(e.OldStartingIndex, e.OldItems.Count));
break;
case NotifyCollectionChangedAction.Replace:
RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count));
var containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector);
AddContainers(containers);
case NotifyCollectionChangedAction.Replace:
RemoveContainers(generator.Dematerialize(e.OldStartingIndex, e.OldItems.Count));
var containers = generator.Materialize(e.NewStartingIndex, e.NewItems, MemberSelector);
AddContainers(containers);
var i = e.NewStartingIndex;
var i = e.NewStartingIndex;
foreach (var container in containers)
{
Panel.Children[i++] = container.ContainerControl;
}
foreach (var container in containers)
{
Panel.Children[i++] = container.ContainerControl;
}
break;
break;
case NotifyCollectionChangedAction.Move:
// TODO: Implement Move in a more efficient manner.
case NotifyCollectionChangedAction.Reset:
RemoveContainers(generator.Clear());
case NotifyCollectionChangedAction.Move:
// TODO: Implement Move in a more efficient manner.
case NotifyCollectionChangedAction.Reset:
RemoveContainers(generator.Clear());
if (Items != null)
{
AddContainers(generator.Materialize(0, Items, MemberSelector));
break;
}
}
InvalidateMeasure();
break;
}
InvalidateMeasure();
}
private void TemplatedParentChanged(PerspexPropertyChangedEventArgs e)
private void AddContainersToPanel(IEnumerable<ItemContainer> items)
{
(e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this);
foreach (var i in items)
{
if (i.ContainerControl != null)
{
if (i.Index < this.Panel.Children.Count)
{
// HACK: This will insert at the wrong place when there are null items,
// but all of this will need to be rewritten when we implement
// virtualization so hope no-one notices until then :)
this.Panel.Children.Insert(i.Index, i.ContainerControl);
}
else
{
this.Panel.Children.Add(i.ContainerControl);
}
}
}
}
private void AddContainers(IEnumerable<ItemContainer> items)
@ -313,4 +145,4 @@ namespace Perspex.Controls.Presenters
}
}
}
}
}

217
src/Perspex.Controls/Presenters/ItemsPresenterBase.cs

@ -0,0 +1,217 @@
// Copyright (c) The Perspex Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections;
using System.Collections.Specialized;
using Perspex.Controls.Generators;
using Perspex.Controls.Templates;
using Perspex.Styling;
namespace Perspex.Controls.Presenters
{
/// <summary>
/// Base class for controls that present items inside an <see cref="ItemsControl"/>.
/// </summary>
public abstract class ItemsPresenterBase : Control, IItemsPresenter, ITemplatedControl
{
/// <summary>
/// Defines the <see cref="Items"/> property.
/// </summary>
public static readonly DirectProperty<ItemsPresenterBase, IEnumerable> ItemsProperty =
ItemsControl.ItemsProperty.AddOwner<ItemsPresenterBase>(o => o.Items, (o, v) => o.Items = v);
/// <summary>
/// Defines the <see cref="ItemsPanel"/> property.
/// </summary>
public static readonly StyledProperty<ITemplate<IPanel>> ItemsPanelProperty =
ItemsControl.ItemsPanelProperty.AddOwner<ItemsPresenterBase>();
/// <summary>
/// Defines the <see cref="MemberSelector"/> property.
/// </summary>
public static readonly StyledProperty<IMemberSelector> MemberSelectorProperty =
ItemsControl.MemberSelectorProperty.AddOwner<ItemsPresenterBase>();
private IEnumerable _items;
private bool _createdPanel;
private IItemContainerGenerator _generator;
/// <summary>
/// Initializes static members of the <see cref="ItemsPresenter"/> class.
/// </summary>
static ItemsPresenterBase()
{
TemplatedParentProperty.Changed.AddClassHandler<ItemsPresenterBase>(x => x.TemplatedParentChanged);
}
/// <summary>
/// Gets or sets the items to be displayed.
/// </summary>
public IEnumerable Items
{
get
{
return _items;
}
set
{
if (_createdPanel)
{
INotifyCollectionChanged incc = _items as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged -= ItemsCollectionChanged;
}
}
if (_createdPanel && value != null)
{
INotifyCollectionChanged incc = value as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged += ItemsCollectionChanged;
}
}
SetAndRaise(ItemsProperty, ref _items, value);
if (_createdPanel)
{
ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
}
/// <summary>
/// Gets the item container generator.
/// </summary>
public IItemContainerGenerator ItemContainerGenerator
{
get
{
if (_generator == null)
{
var i = TemplatedParent as ItemsControl;
_generator = (i?.ItemContainerGenerator) ?? new ItemContainerGenerator(this);
}
return _generator;
}
internal set
{
if (_generator != null)
{
throw new InvalidOperationException("ItemContainerGenerator already created.");
}
_generator = value;
}
}
/// <summary>
/// Gets or sets a template which creates the <see cref="Panel"/> used to display the items.
/// </summary>
public ITemplate<IPanel> ItemsPanel
{
get { return GetValue(ItemsPanelProperty); }
set { SetValue(ItemsPanelProperty, value); }
}
/// <summary>
/// Selects a member from <see cref="Items"/> to use as the list item.
/// </summary>
public IMemberSelector MemberSelector
{
get { return GetValue(MemberSelectorProperty); }
set { SetValue(MemberSelectorProperty, value); }
}
/// <summary>
/// Gets the panel used to display the items.
/// </summary>
public IPanel Panel
{
get;
private set;
}
/// <inheritdoc/>
public override sealed void ApplyTemplate()
{
if (!_createdPanel)
{
CreatePanel();
}
}
/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
Panel.Measure(availableSize);
return Panel.DesiredSize;
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
Panel.Arrange(new Rect(finalSize));
return finalSize;
}
/// <summary>
/// Creates the <see cref="Panel"/> when <see cref="ApplyTemplate"/> is called for the first
/// time.
/// </summary>
protected virtual void CreatePanel()
{
Panel = ItemsPanel.Build();
Panel.SetValue(TemplatedParentProperty, TemplatedParent);
LogicalChildren.Clear();
VisualChildren.Clear();
LogicalChildren.Add(Panel);
VisualChildren.Add(Panel);
_createdPanel = true;
INotifyCollectionChanged incc = Items as INotifyCollectionChanged;
if (incc != null)
{
incc.CollectionChanged += ItemsCollectionChanged;
}
ItemsChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
/// <summary>
/// Called when the items for the presenter change, either because <see cref="Items"/>
/// has been set, or the items collection has been modified.
/// </summary>
/// <param name="e">A description of the change.</param>
protected abstract void ItemsChanged(NotifyCollectionChangedEventArgs e);
/// <summary>
/// Called when the <see cref="Items"/> collection changes.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The event args.</param>
private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (_createdPanel)
{
ItemsChanged(e);
}
}
private void TemplatedParentChanged(PerspexPropertyChangedEventArgs e)
{
(e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this);
}
}
}
Loading…
Cancel
Save