diff --git a/src/Perspex.Controls/ItemsControl.cs b/src/Perspex.Controls/ItemsControl.cs index 27028cba4b..20344af928 100644 --- a/src/Perspex.Controls/ItemsControl.cs +++ b/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); } diff --git a/src/Perspex.Controls/Perspex.Controls.csproj b/src/Perspex.Controls/Perspex.Controls.csproj index 8c62f1d66c..96481a1959 100644 --- a/src/Perspex.Controls/Perspex.Controls.csproj +++ b/src/Perspex.Controls/Perspex.Controls.csproj @@ -66,6 +66,7 @@ + diff --git a/src/Perspex.Controls/Presenters/CarouselPresenter.cs b/src/Perspex.Controls/Presenters/CarouselPresenter.cs index cde7bcf823..3ba9d14589 100644 --- a/src/Perspex.Controls/Presenters/CarouselPresenter.cs +++ b/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 /// /// Displays pages inside an . /// - public class CarouselPresenter : Control, IItemsPresenter + public class CarouselPresenter : ItemsPresenterBase { /// /// Defines the property. @@ -26,24 +27,6 @@ namespace Perspex.Controls.Presenters public static readonly StyledProperty IsVirtualizedProperty = Carousel.IsVirtualizedProperty.AddOwner(); - /// - /// Defines the property. - /// - public static readonly DirectProperty ItemsProperty = - ItemsControl.ItemsProperty.AddOwner(o => o.Items, (o, v) => o.Items = v); - - /// - /// Defines the property. - /// - public static readonly StyledProperty> ItemsPanelProperty = - ItemsControl.ItemsPanelProperty.AddOwner(); - - /// - /// Defines the property. - /// - public static readonly StyledProperty MemberSelectorProperty = - ItemsControl.MemberSelectorProperty.AddOwner(); - /// /// Defines the property. /// @@ -58,10 +41,7 @@ namespace Perspex.Controls.Presenters public static readonly StyledProperty TransitionProperty = Carousel.TransitionProperty.AddOwner(); - 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(x => x.SelectedIndexChanged); - TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); - } - - /// - /// Gets the used to generate item container - /// controls. - /// - 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; - } } /// @@ -114,33 +65,6 @@ namespace Perspex.Controls.Presenters set { SetValue(IsVirtualizedProperty, value); } } - /// - /// Gets or sets the items to display. - /// - public IEnumerable Items - { - get { return _items; } - set { SetAndRaise(ItemsProperty, ref _items, value); } - } - - /// - /// Gets or sets the panel used to display the pages. - /// - public ITemplate ItemsPanel - { - get { return GetValue(ItemsPanelProperty); } - set { SetValue(ItemsPanelProperty, value); } - } - - /// - /// Selects a member from to use as the list item. - /// - public IMemberSelector MemberSelector - { - get { return GetValue(MemberSelectorProperty); } - set { SetValue(MemberSelectorProperty, value); } - } - /// /// Gets or sets the index of the selected page. /// @@ -164,15 +88,6 @@ namespace Perspex.Controls.Presenters } } - /// - /// Gets the panel used to display the pages. - /// - public IPanel Panel - { - get; - private set; - } - /// /// Gets or sets a transition to use when switching pages. /// @@ -183,29 +98,16 @@ namespace Perspex.Controls.Presenters } /// - public override sealed void ApplyTemplate() + protected override void CreatePanel() { - if (!_createdPanel) - { - CreatePanel(); - } + base.CreatePanel(); + var task = MoveToPage(-1, SelectedIndex); } - /// - /// Creates the . - /// - private void CreatePanel() + /// + 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. } /// @@ -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().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().ElementAt(index); + var materialized = ItemContainerGenerator.Materialize( + index, + new[] { item }, + MemberSelector); + container = materialized.First().ContainerControl; + Panel.Children.Add(container); + } + + return container; + } + /// /// Called when the property changes. /// @@ -305,10 +214,5 @@ namespace Perspex.Controls.Presenters } } } - - private void TemplatedParentChanged(PerspexPropertyChangedEventArgs e) - { - (e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this); - } } -} +} \ No newline at end of file diff --git a/src/Perspex.Controls/Presenters/ItemsPresenter.cs b/src/Perspex.Controls/Presenters/ItemsPresenter.cs index 909a1e011f..e60fa846dc 100644 --- a/src/Perspex.Controls/Presenters/ItemsPresenter.cs +++ b/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 { /// /// Displays items inside an . /// - public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl + public class ItemsPresenter : ItemsPresenterBase { - /// - /// Defines the property. - /// - public static readonly DirectProperty ItemsProperty = - ItemsControl.ItemsProperty.AddOwner(o => o.Items, (o, v) => o.Items = v); - - /// - /// Defines the property. - /// - public static readonly StyledProperty> ItemsPanelProperty = - ItemsControl.ItemsPanelProperty.AddOwner(); - - /// - /// Defines the property. - /// - public static readonly StyledProperty MemberSelectorProperty = - ItemsControl.MemberSelectorProperty.AddOwner(); - - private IEnumerable _items; - private bool _createdPanel; - private IItemContainerGenerator _generator; - /// /// Initializes static members of the class. /// @@ -48,112 +22,12 @@ namespace Perspex.Controls.Presenters KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue( typeof(ItemsPresenter), KeyboardNavigationMode.Once); - ItemsProperty.Changed.AddClassHandler(x => x.ItemsChanged); - TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); - } - - /// - /// Initializes a new instance of the class. - /// - public ItemsPresenter() - { - } - - /// - /// Gets the used to generate item container - /// controls. - /// - 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; - } - } - - /// - /// Gets or sets the items to be displayed. - /// - public IEnumerable Items - { - get { return _items; } - set { SetAndRaise(ItemsProperty, ref _items, value); } - } - - /// - /// Gets or sets a template which creates the used to display the items. - /// - public ITemplate ItemsPanel - { - get { return GetValue(ItemsPanelProperty); } - set { SetValue(ItemsPanelProperty, value); } - } - - /// - /// Selects a member from to use as the list item. - /// - public IMemberSelector MemberSelector - { - get { return GetValue(MemberSelectorProperty); } - set { SetValue(MemberSelectorProperty, value); } - } - - /// - /// Gets the panel used to display the items. - /// - public IPanel Panel - { - get; - private set; } /// - public override sealed void ApplyTemplate() + protected override void CreatePanel() { - if (!_createdPanel) - { - CreatePanel(); - } - } - - /// - protected override Size MeasureOverride(Size availableSize) - { - Panel.Measure(availableSize); - return Panel.DesiredSize; - } - - /// - protected override Size ArrangeOverride(Size finalSize) - { - Panel.Arrange(new Rect(finalSize)); - return finalSize; - } - - /// - /// Creates the when is called for the first - /// time. - /// - 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); } - /// - /// Creates the items for a collection and starts listening for changes on the collection. - /// - /// The items, may be null. - private void CreateItemsAndListenForChanges(IEnumerable items) + /// + 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; - /// - /// Called when the property changes. - /// - /// The event args. - 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; - /// - /// Called when the collection changes. - /// - /// The sender. - /// The event args. - 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 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 items) @@ -313,4 +145,4 @@ namespace Perspex.Controls.Presenters } } } -} +} \ No newline at end of file diff --git a/src/Perspex.Controls/Presenters/ItemsPresenterBase.cs b/src/Perspex.Controls/Presenters/ItemsPresenterBase.cs new file mode 100644 index 0000000000..1cc7d40bd7 --- /dev/null +++ b/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 +{ + /// + /// Base class for controls that present items inside an . + /// + public abstract class ItemsPresenterBase : Control, IItemsPresenter, ITemplatedControl + { + /// + /// Defines the property. + /// + public static readonly DirectProperty ItemsProperty = + ItemsControl.ItemsProperty.AddOwner(o => o.Items, (o, v) => o.Items = v); + + /// + /// Defines the property. + /// + public static readonly StyledProperty> ItemsPanelProperty = + ItemsControl.ItemsPanelProperty.AddOwner(); + + /// + /// Defines the property. + /// + public static readonly StyledProperty MemberSelectorProperty = + ItemsControl.MemberSelectorProperty.AddOwner(); + + private IEnumerable _items; + private bool _createdPanel; + private IItemContainerGenerator _generator; + + /// + /// Initializes static members of the class. + /// + static ItemsPresenterBase() + { + TemplatedParentProperty.Changed.AddClassHandler(x => x.TemplatedParentChanged); + } + + /// + /// Gets or sets the items to be displayed. + /// + 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)); + } + } + } + + /// + /// Gets the item container generator. + /// + 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; + } + } + + /// + /// Gets or sets a template which creates the used to display the items. + /// + public ITemplate ItemsPanel + { + get { return GetValue(ItemsPanelProperty); } + set { SetValue(ItemsPanelProperty, value); } + } + + /// + /// Selects a member from to use as the list item. + /// + public IMemberSelector MemberSelector + { + get { return GetValue(MemberSelectorProperty); } + set { SetValue(MemberSelectorProperty, value); } + } + + /// + /// Gets the panel used to display the items. + /// + public IPanel Panel + { + get; + private set; + } + + /// + public override sealed void ApplyTemplate() + { + if (!_createdPanel) + { + CreatePanel(); + } + } + + /// + protected override Size MeasureOverride(Size availableSize) + { + Panel.Measure(availableSize); + return Panel.DesiredSize; + } + + /// + protected override Size ArrangeOverride(Size finalSize) + { + Panel.Arrange(new Rect(finalSize)); + return finalSize; + } + + /// + /// Creates the when is called for the first + /// time. + /// + 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)); + } + + /// + /// Called when the items for the presenter change, either because + /// has been set, or the items collection has been modified. + /// + /// A description of the change. + protected abstract void ItemsChanged(NotifyCollectionChangedEventArgs e); + + /// + /// Called when the collection changes. + /// + /// The sender. + /// The event args. + private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (_createdPanel) + { + ItemsChanged(e); + } + } + + private void TemplatedParentChanged(PerspexPropertyChangedEventArgs e) + { + (e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this); + } + } +} \ No newline at end of file