// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Controls.Presenters { using System; using System.Collections; using System.Collections.Specialized; using Perspex.Controls.Generators; using Perspex.Controls.Templates; using Perspex.Input; using Perspex.Styling; /// /// Displays items inside an . /// public class ItemsPresenter : Control, IItemsPresenter, ITemplatedControl { /// /// Defines the property. /// public static readonly PerspexProperty ItemsProperty = ItemsControl.ItemsProperty.AddOwner(); /// /// Defines the property. /// public static readonly PerspexProperty ItemsPanelProperty = ItemsControl.ItemsPanelProperty.AddOwner(); private bool createdPanel; private IItemContainerGenerator generator; /// /// Initializes static members of the class. /// static ItemsPresenter() { KeyboardNavigation.TabNavigationProperty.OverrideDefaultValue( typeof(ItemsPresenter), KeyboardNavigationMode.Once); ItemsProperty.Changed.AddClassHandler(x => x.ItemsChanged); } /// /// Initializes a new instance of the class. /// public ItemsPresenter() { } /// /// Gets the used to generate item container /// controls. /// public IItemContainerGenerator ItemContainerGenerator { get { if (this.generator == null) { var i = this.TemplatedParent as ItemsControl; this.generator = i?.ItemContainerGenerator ?? new ItemContainerGenerator(this); } return this.generator; } set { if (this.generator != null) { throw new InvalidOperationException("ItemContainerGenerator is already set."); } this.generator = value; } } /// /// Gets or sets the items to be displayed. /// public IEnumerable Items { get { return this.GetValue(ItemsProperty); } set { this.SetValue(ItemsProperty, value); } } /// /// Gets or sets a template which creates the used to display the items. /// public ItemsPanelTemplate ItemsPanel { get { return this.GetValue(ItemsPanelProperty); } set { this.SetValue(ItemsPanelProperty, value); } } /// /// Gets the panel used to display the items. /// public Panel Panel { get; private set; } /// public override sealed void ApplyTemplate() { if (!this.createdPanel) { this.CreatePanel(); } } /// protected override Size MeasureOverride(Size availableSize) { this.Panel.Measure(availableSize); return this.Panel.DesiredSize; } /// protected override Size ArrangeOverride(Size finalSize) { this.Panel.Arrange(new Rect(finalSize)); return finalSize; } /// /// Creates the when is called for the first /// time. /// private void CreatePanel() { this.ClearVisualChildren(); this.Panel = this.ItemsPanel.Build(); this.Panel.SetValue(TemplatedParentProperty, this.TemplatedParent); if (!this.Panel.IsSet(KeyboardNavigation.DirectionalNavigationProperty)) { KeyboardNavigation.SetDirectionalNavigation(this.Panel, KeyboardNavigationMode.Contained); } this.AddVisualChild(this.Panel); var logicalHost = this.FindReparentingHost(); if (logicalHost != null) { ((IReparentingControl)this.Panel).ReparentLogicalChildren( logicalHost, logicalHost.LogicalChildren); } KeyboardNavigation.SetTabNavigation(this.Panel, KeyboardNavigation.GetTabNavigation(this)); this.createdPanel = true; this.CreateItemsAndListenForChanges(this.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) { if (items != null) { this.Panel.Children.AddRange( this.ItemContainerGenerator.CreateContainers(0, this.Items, null)); INotifyCollectionChanged incc = items as INotifyCollectionChanged; if (incc != null) { incc.CollectionChanged += this.ItemsCollectionChanged; } } } /// /// Called when the property changes. /// /// The event args. private void ItemsChanged(PerspexPropertyChangedEventArgs e) { if (this.createdPanel) { var generator = this.ItemContainerGenerator; if (e.OldValue != null) { generator.ClearContainers(); this.Panel.Children.Clear(); INotifyCollectionChanged incc = e.OldValue as INotifyCollectionChanged; if (incc != null) { incc.CollectionChanged -= this.ItemsCollectionChanged; } } if (this.Panel != null) { this.CreateItemsAndListenForChanges((IEnumerable)e.NewValue); } } } /// /// Called when the collection changes. /// /// The sender. /// The event args. private void ItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (this.createdPanel) { var generator = this.ItemContainerGenerator; // TODO: Handle Move and Replace etc. switch (e.Action) { case NotifyCollectionChangedAction.Add: this.Panel.Children.AddRange( generator.CreateContainers(e.NewStartingIndex, e.NewItems, null)); break; case NotifyCollectionChangedAction.Remove: this.Panel.Children.RemoveAll( generator.RemoveContainers(e.OldStartingIndex, e.OldItems)); break; } this.InvalidateMeasure(); } } } }