diff --git a/Perspex.Base/Collections/PerspexReadOnlyListView.cs b/Perspex.Base/Collections/PerspexReadOnlyListView.cs index dd5f8af61b..58def8bd32 100644 --- a/Perspex.Base/Collections/PerspexReadOnlyListView.cs +++ b/Perspex.Base/Collections/PerspexReadOnlyListView.cs @@ -13,29 +13,208 @@ namespace Perspex.Collections using System.ComponentModel; using System.Linq; + public class PerspexReadOnlyListView : IReadOnlyPerspexList, IDisposable + { + private IReadOnlyPerspexList source; + + public PerspexReadOnlyListView() + : this(null) + { + } + + public PerspexReadOnlyListView(IReadOnlyPerspexList source) + { + this.source = source; + + if (source != null) + { + this.source.CollectionChanged += this.SourceCollectionChanged; + } + } + + public T this[int index] + { + get { return this.source[index]; } + } + + public int Count + { + get { return this.source.Count; } + } + + public IReadOnlyPerspexList Source + { + get + { + return this.source; + } + + set + { + if (this.source != null) + { + this.source.CollectionChanged -= this.SourceCollectionChanged; + + if (this.CollectionChanged != null) + { + var ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Remove, + this.source, + 0); + this.CollectionChanged(this, ev); + } + } + + this.source = value; + + if (this.source != null) + { + this.source.CollectionChanged += this.SourceCollectionChanged; + + if (this.CollectionChanged != null) + { + var ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + this.source, + 0); + this.CollectionChanged(this, ev); + } + } + } + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public event PropertyChangedEventHandler PropertyChanged; + + public void Dispose() + { + this.source.CollectionChanged -= this.SourceCollectionChanged; + } + + public IEnumerator GetEnumerator() + { + return this.source.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + private void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (this.CollectionChanged != null) + { + NotifyCollectionChangedEventArgs ev; + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + e.NewItems, + e.NewStartingIndex); + break; + case NotifyCollectionChangedAction.Remove: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Remove, + e.OldItems, + e.NewStartingIndex); + break; + case NotifyCollectionChangedAction.Replace: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Replace, + e.NewItems, + e.OldItems, + e.OldStartingIndex); + break; + + default: + throw new NotSupportedException("Action not yet implemented."); + } + + this.CollectionChanged(this, ev); + } + } + } + public class PerspexReadOnlyListView : IReadOnlyPerspexList, IDisposable { - private IReadOnlyPerspexList inner; + private IReadOnlyPerspexList source; private Func convert; - public PerspexReadOnlyListView( - IReadOnlyPerspexList inner, - Func convert) + public PerspexReadOnlyListView(Func convert) + : this(null, convert) { - this.inner = inner; + } + + public PerspexReadOnlyListView(IReadOnlyPerspexList source, Func convert) + { + this.source = source; this.convert = convert; - this.inner.CollectionChanged += this.InnerCollectionChanged; + + if (source != null) + { + this.source.CollectionChanged += this.SourceCollectionChanged; + } } public TOut this[int index] { - get { return this.convert(this.inner[index]); } + get + { + return (this.convert != null) ? + this.convert(this.source[index]) : + (TOut)(object)this.source[index]; + } } public int Count { - get { return this.inner.Count; } + get { return this.source.Count; } + } + + public IReadOnlyPerspexList Source + { + get + { + return this.source; + } + + set + { + if (this.source != null) + { + this.source.CollectionChanged -= this.SourceCollectionChanged; + + if (this.CollectionChanged != null) + { + var ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Remove, + this.ConvertList(this.source), + 0); + this.CollectionChanged(this, ev); + } + } + + this.source = value; + + if (this.source != null) + { + this.source.CollectionChanged += this.SourceCollectionChanged; + + if (this.CollectionChanged != null) + { + var ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Add, + this.ConvertList(this.source), + 0); + this.CollectionChanged(this, ev); + } + } + } } public event NotifyCollectionChangedEventHandler CollectionChanged; @@ -44,12 +223,22 @@ namespace Perspex.Collections public void Dispose() { - this.inner.CollectionChanged -= this.InnerCollectionChanged; + if (this.source != null) + { + this.source.CollectionChanged -= this.SourceCollectionChanged; + } } public IEnumerator GetEnumerator() { - return this.inner.Select(convert).GetEnumerator(); + if (this.source != null) + { + return this.source.Select(this.convert).GetEnumerator(); + } + else + { + return Enumerable.Empty().GetEnumerator(); + } } IEnumerator IEnumerable.GetEnumerator() @@ -57,12 +246,12 @@ namespace Perspex.Collections return this.GetEnumerator(); } - private IList ConvertList(IList list) + private IList ConvertList(IEnumerable list) { return list.Cast().Select(this.convert).ToList(); } - private void InnerCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void SourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (this.CollectionChanged != null) { @@ -82,6 +271,14 @@ namespace Perspex.Collections this.ConvertList(e.OldItems), e.NewStartingIndex); break; + case NotifyCollectionChangedAction.Replace: + ev = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Replace, + this.ConvertList(e.NewItems), + this.ConvertList(e.OldItems), + e.OldStartingIndex); + break; + default: throw new NotSupportedException("Action not yet implemented."); } @@ -90,4 +287,5 @@ namespace Perspex.Collections } } } + } diff --git a/Perspex.Base/Collections/SingleItemPerspexList.cs b/Perspex.Base/Collections/SingleItemPerspexList.cs index 5af1e68caa..72f5fbd834 100644 --- a/Perspex.Base/Collections/SingleItemPerspexList.cs +++ b/Perspex.Base/Collections/SingleItemPerspexList.cs @@ -66,7 +66,7 @@ namespace Perspex.Collections NotifyCollectionChangedEventArgs e = null; bool countChanged = false; - if (value == null && this.item != null ) + if (value == null && this.item != null) { e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, this.item, 0); this.item = null; diff --git a/Perspex.Controls/ItemsControl.cs b/Perspex.Controls/ItemsControl.cs index ec19b52882..9bf3e8d948 100644 --- a/Perspex.Controls/ItemsControl.cs +++ b/Perspex.Controls/ItemsControl.cs @@ -32,7 +32,8 @@ namespace Perspex.Controls private ItemContainerGenerator itemContainerGenerator; - private PerspexReadOnlyListView logicalChildren; + private PerspexReadOnlyListView logicalChildren = + new PerspexReadOnlyListView(x => (ILogical)x); private ItemsPresenter presenter; @@ -82,19 +83,11 @@ namespace Perspex.Controls protected override void OnTemplateApplied() { - if (this.logicalChildren != null) - { - this.logicalChildren.Dispose(); - this.logicalChildren = null; - } - this.presenter = this.FindTemplateChild("itemsPresenter"); if (this.presenter != null) { - this.logicalChildren = new PerspexReadOnlyListView( - ((IVisual)presenter.Panel).VisualChildren, - x => (ILogical)x); + this.logicalChildren.Source = ((IVisual)this.presenter).VisualChildren; } } diff --git a/Tests/Perspex.Controls.UnitTests/ItemsControlTests.cs b/Tests/Perspex.Controls.UnitTests/ItemsControlTests.cs index 39905133e6..ab21dcc766 100644 --- a/Tests/Perspex.Controls.UnitTests/ItemsControlTests.cs +++ b/Tests/Perspex.Controls.UnitTests/ItemsControlTests.cs @@ -218,6 +218,26 @@ namespace Perspex.Controls.UnitTests Assert.True(called); } + [Fact] + public void LogicalChildren_Should_Not_Change_Instance_When_Template_Changed() + { + var target = new ItemsControl() + { + Template = this.GetTemplate(), + }; + + var before = ((ILogical)target).LogicalChildren; + + target.Template = null; + target.Template = this.GetTemplate(); + + var after = ((ILogical)target).LogicalChildren; + + Assert.NotNull(before); + Assert.NotNull(after); + Assert.Same(before, after); + } + private ControlTemplate GetTemplate() { return ControlTemplate.Create(parent =>