diff --git a/Perspex.Base/IReadOnlyPerspexList.cs b/Perspex.Base/IReadOnlyPerspexList.cs new file mode 100644 index 0000000000..87b7074bd2 --- /dev/null +++ b/Perspex.Base/IReadOnlyPerspexList.cs @@ -0,0 +1,16 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex +{ + using System.Collections.Generic; + using System.Collections.Specialized; + using System.ComponentModel; + + public interface IReadOnlyPerspexList : IReadOnlyList, INotifyCollectionChanged, INotifyPropertyChanged + { + } +} \ No newline at end of file diff --git a/Perspex.Base/Perspex.Base.csproj b/Perspex.Base/Perspex.Base.csproj index 48a0db4c96..637a4d6120 100644 --- a/Perspex.Base/Perspex.Base.csproj +++ b/Perspex.Base/Perspex.Base.csproj @@ -38,6 +38,7 @@ + diff --git a/Perspex.Base/PerspexList.cs b/Perspex.Base/PerspexList.cs index 209699a388..80fdaeb37d 100644 --- a/Perspex.Base/PerspexList.cs +++ b/Perspex.Base/PerspexList.cs @@ -7,44 +7,252 @@ namespace Perspex { using System; + using System.Collections; using System.Collections.Generic; - using System.Collections.ObjectModel; using System.Collections.Specialized; - using System.Reactive.Linq; + using System.ComponentModel; + using System.Linq; - public class PerspexList : ObservableCollection + public class PerspexList : IList, IList, IReadOnlyPerspexList, INotifyCollectionChanged, INotifyPropertyChanged { + private List inner; + public PerspexList() + : this(Enumerable.Empty()) { - this.Initialize(); } public PerspexList(IEnumerable items) - : base(items) { - this.Initialize(); + this.inner = new List(items); + } + + public event NotifyCollectionChangedEventHandler CollectionChanged; + + public event PropertyChangedEventHandler PropertyChanged; + + public T this[int index] + { + get + { + return this.inner[index]; + } + + set + { + T old = this.inner[index]; + this.inner[index] = value; + + if (this.CollectionChanged != null) + { + var e = new NotifyCollectionChangedEventArgs( + NotifyCollectionChangedAction.Replace, + value, + old); + this.CollectionChanged(this, e); + } + } + } + + public int Count + { + get { return this.inner.Count; } + } + + public bool IsReadOnly + { + get { return false; } + } + + bool IList.IsFixedSize + { + get { return false; } + } + + bool IList.IsReadOnly + { + get { return false; } } - public IObservable Changed + object IList.this[int index] { - get; - private set; + get { return this[index]; } + + set { this[index] = (T)value; } + } + + int ICollection.Count + { + get { return this.inner.Count; } + } + + bool ICollection.IsSynchronized + { + get { return false; } + } + + object ICollection.SyncRoot + { + get { return null; } + } + + public void Add(T item) + { + int index = this.inner.Count; + this.inner.Add(item); + this.NotifyAdd(new[] { item }); } public void AddRange(IEnumerable items) { - foreach (T item in items) + int index = this.inner.Count; + this.inner.AddRange(items); + this.NotifyAdd((items as IList) ?? items.ToList()); + } + + public void Clear() + { + var old = this.inner; + this.inner = new List(); + this.NotifyRemove(old); + } + + public bool Contains(T item) + { + return this.inner.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + this.inner.CopyTo(array, arrayIndex); + } + + public IEnumerator GetEnumerator() + { + return this.inner.GetEnumerator(); + } + + public int IndexOf(T item) + { + return this.inner.IndexOf(item); + } + + public void Insert(int index, T item) + { + this.inner.Insert(index, item); + this.NotifyAdd(new[] { item }); + } + + public void InsertRange(int index, IEnumerable items) + { + this.inner.InsertRange(index, items); + this.NotifyAdd((items as IList) ?? items.ToList()); + } + + public bool Remove(T item) + { + bool result = this.inner.Remove(item); + this.NotifyRemove(new[] { item }); + return result; + } + + public void RemoveAll(IEnumerable items) + { + List removed = new List(); + + foreach (var i in items) + { + if (this.inner.Remove(i)) + { + removed.Add(i); + } + } + + this.NotifyRemove(removed); + } + + public void RemoveAt(int index) + { + T item = this.inner[index]; + this.inner.RemoveAt(index); + this.NotifyRemove(new[] { item }); + } + + int IList.Add(object value) + { + int index = this.Count; + this.Add((T)value); + return index; + } + + bool IList.Contains(object value) + { + return this.Contains((T)value); + } + + void IList.Clear() + { + this.Clear(); + } + + int IList.IndexOf(object value) + { + return this.IndexOf((T)value); + } + + void IList.Insert(int index, object value) + { + this.Insert(index, (T)value); + } + + void IList.Remove(object value) + { + this.Remove((T)value); + } + + void IList.RemoveAt(int index) + { + this.RemoveAt(index); + } + + void ICollection.CopyTo(Array array, int index) + { + this.inner.CopyTo((T[])array, index); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.inner.GetEnumerator(); + } + + private void NotifyAdd(IList t) + { + if (this.CollectionChanged != null) { - this.Add(item); + var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, t); + this.CollectionChanged(this, e); } + + this.NotifyCountChanged(); } - private void Initialize() + private void NotifyCountChanged() { - this.Changed = Observable.FromEvent( - handler => (sender, e) => handler(e), - handler => this.CollectionChanged += handler, - handler => this.CollectionChanged -= handler); + if (this.PropertyChanged != null) + { + this.PropertyChanged(this, new PropertyChangedEventArgs("Count")); + } + } + + private void NotifyRemove(IList t) + { + if (this.CollectionChanged != null) + { + var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t); + this.CollectionChanged(this, e); + } + + this.NotifyCountChanged(); } } } \ No newline at end of file diff --git a/Perspex.Controls/Control.cs b/Perspex.Controls/Control.cs index 75f68f838a..62271e5fa9 100644 --- a/Perspex.Controls/Control.cs +++ b/Perspex.Controls/Control.cs @@ -188,53 +188,5 @@ namespace Perspex.Controls } }); } - - protected virtual DataTemplate FindDataTemplate(object content) - { - // TODO: This needs to traverse the logical tree, not the visual. - foreach (var i in this.GetSelfAndVisualAncestors().OfType()) - { - foreach (DataTemplate dt in i.DataTemplates.Reverse()) - { - if (dt.Match(content)) - { - return dt; - } - } - } - - IGlobalDataTemplates global = Locator.Current.GetService(); - - if (global != null) - { - foreach (DataTemplate dt in global.DataTemplates.Reverse()) - { - if (dt.Match(content)) - { - return dt; - } - } - } - - return null; - } - - protected Control ApplyDataTemplate(object content) - { - DataTemplate result = this.FindDataTemplate(content); - - if (result != null) - { - return result.Build(content); - } - else if (content is Control) - { - return (Control)content; - } - else - { - return DataTemplate.Default.Build(content); - } - } } } diff --git a/Perspex.Controls/DataTemplateExtensions.cs b/Perspex.Controls/DataTemplateExtensions.cs new file mode 100644 index 0000000000..48e6c51c82 --- /dev/null +++ b/Perspex.Controls/DataTemplateExtensions.cs @@ -0,0 +1,62 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls +{ + using System.Linq; + using Splat; + + public static class DataTemplateExtensions + { + public static Control ApplyDataTemplate(this Control control, object data) + { + DataTemplate result = control.FindDataTemplate(data); + + if (result != null) + { + return result.Build(data); + } + else if (data is Control) + { + return (Control)data; + } + else + { + return DataTemplate.Default.Build(data); + } + } + + public static DataTemplate FindDataTemplate(this Control control, object data) + { + // TODO: This needs to traverse the logical tree, not the visual. + foreach (var i in control.GetSelfAndVisualAncestors().OfType()) + { + foreach (DataTemplate dt in i.DataTemplates.Reverse()) + { + if (dt.Match(data)) + { + return dt; + } + } + } + + IGlobalDataTemplates global = Locator.Current.GetService(); + + if (global != null) + { + foreach (DataTemplate dt in global.DataTemplates.Reverse()) + { + if (dt.Match(data)) + { + return dt; + } + } + } + + return null; + } + } +} diff --git a/Perspex.Controls/Generators/IItemContainerGenerator.cs b/Perspex.Controls/Generators/IItemContainerGenerator.cs new file mode 100644 index 0000000000..29c6f4a57f --- /dev/null +++ b/Perspex.Controls/Generators/IItemContainerGenerator.cs @@ -0,0 +1,38 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.Generators +{ + using System; + using System.Collections; + using System.Collections.Generic; + + public enum ItemContainerGeneratorState + { + NoStarted, + Generating, + Generated, + } + + public interface IItemContainerGenerator + { + event EventHandler StateChanged; + + ItemContainerGeneratorState State { get; } + + Control GetContainerForItem(object item); + + object GetItemForContainer(Control container); + + IEnumerable> GetAll(); + + IEnumerable Generate(IEnumerable items); + + IEnumerable Remove(IEnumerable item); + + void RemoveAll(); + } +} diff --git a/Perspex.Controls/Generators/ItemContainerGenerator.cs b/Perspex.Controls/Generators/ItemContainerGenerator.cs new file mode 100644 index 0000000000..129b52d3ba --- /dev/null +++ b/Perspex.Controls/Generators/ItemContainerGenerator.cs @@ -0,0 +1,126 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.Generators +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + + public class ItemContainerGenerator : IItemContainerGenerator + { + private Dictionary containersByItem = new Dictionary(); + + private Dictionary itemsByContainer = new Dictionary(); + + private ItemContainerGeneratorState state; + + public ItemContainerGenerator(ItemsControl owner) + { + this.Owner = owner; + } + + public event EventHandler StateChanged; + + public ItemContainerGeneratorState State + { + get + { + return this.state; + } + + set + { + if (this.state != value) + { + this.state = value; + + if (this.StateChanged != null) + { + this.StateChanged(this, EventArgs.Empty); + } + } + } + } + + protected ItemsControl Owner + { + get; + private set; + } + + public Control GetContainerForItem(object item) + { + Control result; + this.containersByItem.TryGetValue(item, out result); + return result; + } + + public object GetItemForContainer(Control container) + { + object result; + this.itemsByContainer.TryGetValue(container, out result); + return result; + } + + public IEnumerable> GetAll() + { + return this.containersByItem.Select(x => Tuple.Create(x.Key, x.Value)); + } + + IEnumerable IItemContainerGenerator.Generate(IEnumerable items) + { + List result = new List(); + + this.State = ItemContainerGeneratorState.Generating; + + try + { + foreach (object item in items) + { + Control container = this.CreateContainerOverride(item); + container.TemplatedParent = null; + this.containersByItem.Add(item, container); + this.itemsByContainer.Add(container, item); + result.Add(container); + } + } + finally + { + this.State = ItemContainerGeneratorState.Generated; + } + + return result; + } + + IEnumerable IItemContainerGenerator.Remove(IEnumerable items) + { + List result = new List(); + + foreach (var item in items) + { + Control container = this.containersByItem[item]; + this.containersByItem.Remove(item); + this.itemsByContainer.Remove(container); + result.Add(container); + } + + return result; + } + + void IItemContainerGenerator.RemoveAll() + { + this.containersByItem.Clear(); + this.itemsByContainer.Clear(); + } + + protected virtual Control CreateContainerOverride(object item) + { + return this.Owner.ApplyDataTemplate(item); + } + } +} diff --git a/Perspex.Controls/Generators/TreeItemContainerGenerator.cs b/Perspex.Controls/Generators/TreeItemContainerGenerator.cs new file mode 100644 index 0000000000..fe068e95d5 --- /dev/null +++ b/Perspex.Controls/Generators/TreeItemContainerGenerator.cs @@ -0,0 +1,56 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.Generators +{ + public class TreeItemContainerGenerator : ItemContainerGenerator where T : TreeViewItem, new() + { + public TreeItemContainerGenerator(ItemsControl owner) + : base(owner) + { + } + + protected override Control CreateContainerOverride(object item) + { + T result = item as T; + + if (result == null) + { + TreeDataTemplate template = this.GetTreeDataTemplate(item); + + System.Diagnostics.Debug.WriteLine("{0} created item for {1}", this.GetHashCode(), item); + + result = new T + { + Header = template.Build(item), + Items = template.ItemsSelector(item), + IsExpanded = template.IsExpanded(item), + }; + } + + return result; + } + + private TreeDataTemplate GetTreeDataTemplate(object item) + { + DataTemplate template = this.Owner.FindDataTemplate(item); + + if (template == null) + { + template = DataTemplate.Default; + } + + TreeDataTemplate treeTemplate = template as TreeDataTemplate; + + if (treeTemplate == null) + { + treeTemplate = new TreeDataTemplate(template.Build, x => null); + } + + return treeTemplate; + } + } +} diff --git a/Perspex.Controls/Generators/TypedItemContainerGenerator.cs b/Perspex.Controls/Generators/TypedItemContainerGenerator.cs new file mode 100644 index 0000000000..e486ccaf49 --- /dev/null +++ b/Perspex.Controls/Generators/TypedItemContainerGenerator.cs @@ -0,0 +1,29 @@ +// ----------------------------------------------------------------------- +// +// Copyright 2014 MIT Licence. See licence.md for more information. +// +// ----------------------------------------------------------------------- + +namespace Perspex.Controls.Generators +{ + public class TypedItemContainerGenerator : ItemContainerGenerator where T : ContentControl, new() + { + public TypedItemContainerGenerator(ItemsControl owner) + : base(owner) + { + } + + protected override Control CreateContainerOverride(object item) + { + T result = item as T; + + if (result == null) + { + result = new T(); + result.Content = this.Owner.ApplyDataTemplate(item); + } + + return result; + } + } +} diff --git a/Perspex.Controls/ItemsControl.cs b/Perspex.Controls/ItemsControl.cs index f097e58d09..6132932e66 100644 --- a/Perspex.Controls/ItemsControl.cs +++ b/Perspex.Controls/ItemsControl.cs @@ -8,10 +8,9 @@ namespace Perspex.Controls { using System; using System.Collections; - using System.Collections.Generic; - using System.Collections.Specialized; using System.ComponentModel; using System.Linq; + using Perspex.Controls.Generators; public class ItemsControl : TemplatedControl { @@ -24,73 +23,41 @@ namespace Perspex.Controls public static readonly PerspexProperty ItemsPanelProperty = PerspexProperty.Register("ItemsPanel", defaultValue: DefaultPanel); - private Dictionary controlsByItem = new Dictionary(); - - private Dictionary itemsByControl = new Dictionary(); + private ItemContainerGenerator itemContainerGenerator; public ItemsControl() { this.GetObservableWithHistory(ItemsProperty).Subscribe(this.ItemsChanged); } - public IEnumerable Items + public ItemContainerGenerator ItemContainerGenerator { - get { return this.GetValue(ItemsProperty); } - set { this.SetValue(ItemsProperty, value); } - } - - public ItemsPanelTemplate ItemsPanel - { - get { return this.GetValue(ItemsPanelProperty); } - set { this.SetValue(ItemsPanelProperty, value); } - } - - public Control GetControlForItem(object item) - { - Control result; - this.controlsByItem.TryGetValue(item, out result); - return result; - } - - public object GetItemForControl(Control control) - { - object result; - this.itemsByControl.TryGetValue(control, out result); - return result; - } - - public IEnumerable GetAllItemControls() - { - return this.controlsByItem.Values; - } + get + { + if (this.itemContainerGenerator == null) + { + this.itemContainerGenerator = this.CreateItemContainerGenerator(); + } - internal Control CreateItemControl(object item) - { - Control control = this.CreateItemControlOverride(item); - this.itemsByControl.Add(control, item); - this.controlsByItem.Add(item, control); - return control; + return this.itemContainerGenerator; + } } - internal void RemoveItemControls(IEnumerable items) + protected virtual ItemContainerGenerator CreateItemContainerGenerator() { - foreach (object i in items) - { - Control control = this.GetControlForItem(i); - this.controlsByItem.Remove(i); - this.itemsByControl.Remove(control); - } + return new ItemContainerGenerator(this); } - internal void ClearItemControls() + public IEnumerable Items { - this.controlsByItem.Clear(); - this.itemsByControl.Clear(); + get { return this.GetValue(ItemsProperty); } + set { this.SetValue(ItemsProperty, value); } } - protected virtual Control CreateItemControlOverride(object item) + public ItemsPanelTemplate ItemsPanel { - return this.ApplyDataTemplate(item); + get { return this.GetValue(ItemsPanelProperty); } + set { this.SetValue(ItemsPanelProperty, value); } } private void ItemsChanged(Tuple value) diff --git a/Perspex.Controls/ItemsPresenter.cs b/Perspex.Controls/ItemsPresenter.cs index 64bd0af37f..eedca327fb 100644 --- a/Perspex.Controls/ItemsPresenter.cs +++ b/Perspex.Controls/ItemsPresenter.cs @@ -8,9 +8,9 @@ namespace Perspex.Controls { using System; using System.Collections; - using System.Collections.Generic; using System.Collections.Specialized; - using System.Linq; + using System.Reactive.Linq; + using Perspex.Controls.Generators; public class ItemsPresenter : Control, IVisual { @@ -24,7 +24,7 @@ namespace Perspex.Controls public ItemsPresenter() { - this.GetObservableWithHistory(ItemsProperty).Subscribe(this.ItemsChanged); + this.GetObservableWithHistory(ItemsProperty).Skip(1).Subscribe(this.ItemsChanged); } public IEnumerable Items @@ -58,53 +58,16 @@ namespace Perspex.Controls this.ItemsChanged(Tuple.Create(default(IEnumerable), this.Items)); } - private Control CreateItemControl(object item) + private IItemContainerGenerator GetGenerator() { ItemsControl i = this.TemplatedParent as ItemsControl; - if (i != null) + if (i == null) { - return i.CreateItemControl(item); + throw new InvalidOperationException("ItemsPresenter must be part of an ItemsControl template."); } - else - { - return this.ApplyDataTemplate(item); - } - } - - private IEnumerable CreateItemControls(IEnumerable items) - { - if (items != null) - { - return items - .Cast() - .Select(x => this.CreateItemControl(x)) - .OfType(); - } - else - { - return Enumerable.Empty(); - } - } - - private void ClearItemControls() - { - ItemsControl i = this.TemplatedParent as ItemsControl; - - if (i != null) - { - i.ClearItemControls(); - } - } - - private void RemoveItemControls(IEnumerable items) - { - ItemsControl i = this.TemplatedParent as ItemsControl; - if (i != null) - { - i.RemoveItemControls(items); - } + return i.ItemContainerGenerator; } private Panel GetPanel() @@ -119,8 +82,12 @@ namespace Perspex.Controls private void ItemsChanged(Tuple value) { + var generator = this.GetGenerator(); + if (value.Item1 != null) { + this.panel.Children.RemoveAll(generator.Remove(value.Item1)); + INotifyCollectionChanged incc = value.Item1 as INotifyCollectionChanged; if (incc != null) @@ -129,24 +96,18 @@ namespace Perspex.Controls } } - this.ClearItemControls(); - if (this.panel != null) { - var controls = this.CreateItemControls(value.Item2).ToList(); - - foreach (var control in controls) + if (value.Item2 != null) { - control.TemplatedParent = null; - } + this.panel.Children.AddRange(generator.Generate(this.Items)); - this.panel.Children = new Controls(controls); + INotifyCollectionChanged incc = value.Item2 as INotifyCollectionChanged; - INotifyCollectionChanged incc = value.Item2 as INotifyCollectionChanged; - - if (incc != null) - { - incc.CollectionChanged += this.ItemsCollectionChanged; + if (incc != null) + { + incc.CollectionChanged += this.ItemsCollectionChanged; + } } } } @@ -155,28 +116,21 @@ namespace Perspex.Controls { if (this.panel != null) { - // TODO: Handle Move and Replace. + var generator = this.GetGenerator(); + + // TODO: Handle Move and Replace etc. switch (e.Action) { case NotifyCollectionChangedAction.Add: - var controls = this.CreateItemControls(e.NewItems).ToList(); - - foreach (var control in controls) - { - control.TemplatedParent = null; - } - - this.panel.Children.AddRange(controls); + this.panel.Children.AddRange(generator.Generate(e.NewItems)); break; case NotifyCollectionChangedAction.Remove: - this.RemoveItemControls(e.OldItems); - break; - - case NotifyCollectionChangedAction.Reset: - this.ItemsChanged(Tuple.Create(this.Items, this.Items)); + this.panel.Children.RemoveAll(generator.Remove(e.OldItems)); break; } + + this.InvalidateMeasure(); } } } diff --git a/Perspex.Controls/Perspex.Controls.csproj b/Perspex.Controls/Perspex.Controls.csproj index 7bc4f4d554..ece27dea29 100644 --- a/Perspex.Controls/Perspex.Controls.csproj +++ b/Perspex.Controls/Perspex.Controls.csproj @@ -45,7 +45,11 @@ + + + + @@ -56,6 +60,7 @@ + diff --git a/Perspex.Controls/TabControl.cs b/Perspex.Controls/TabControl.cs index cdd30d6186..230359729b 100644 --- a/Perspex.Controls/TabControl.cs +++ b/Perspex.Controls/TabControl.cs @@ -7,9 +7,9 @@ namespace Perspex.Controls { using System; - using System.Collections.Generic; using System.Linq; using System.Reactive.Linq; + using Perspex.Controls.Generators; public class TabControl : SelectingItemsControl { @@ -20,43 +20,23 @@ namespace Perspex.Controls public TabControl() { - this.GetObservable(SelectedItemProperty).Skip(1).Subscribe(this.SelectedItemChanged); + this.GetObservable(SelectedItemProperty).Subscribe(x => + { + ContentControl c = x as ContentControl; + object content = (c != null) ? c.Content : null; + this.SetValue(SelectedContentProperty, content); + }); } - protected override void OnTemplateApplied() + protected override ItemContainerGenerator CreateItemContainerGenerator() { - this.tabStrip = this.GetTemplateControls() - .OfType() - .FirstOrDefault(); - - if (this.tabStrip != null) - { - if (this.IsSet(SelectedItemProperty)) - { - this.SelectedItem = SelectedItem; - } - - this.tabStrip.GetObservable(TabStrip.SelectedItemProperty).Subscribe(x => - { - this.SelectedItem = x; - }); - } + return new TypedItemContainerGenerator(this); } - private void SelectedItemChanged(object item) + protected override void OnTemplateApplied() { - this.SelectedItem = item; - - ContentControl content = item as ContentControl; - - if (content != null) - { - this.SetValue(SelectedContentProperty, content.Content); - } - else - { - this.SetValue(SelectedContentProperty, item); - } + this.tabStrip = this.GetTemplateControls().OfType().FirstOrDefault(); + this.BindTwoWay(SelectedItemProperty, this.tabStrip, SelectedItemProperty); } } } diff --git a/Perspex.Controls/TabStrip.cs b/Perspex.Controls/TabStrip.cs index 5fc95f73a8..3e66396442 100644 --- a/Perspex.Controls/TabStrip.cs +++ b/Perspex.Controls/TabStrip.cs @@ -9,6 +9,7 @@ namespace Perspex.Controls using System; using System.Collections; using System.Linq; + using Perspex.Controls.Generators; using Perspex.Input; public class TabStrip : SelectingItemsControl @@ -24,38 +25,38 @@ namespace Perspex.Controls public TabStrip() { this.PointerPressed += this.OnPointerPressed; - this.GetObservable(ItemsProperty).Subscribe(this.ItemsChanged); this.GetObservable(SelectedItemProperty).Subscribe(this.SelectedItemChanged); } - protected override Control CreateItemControlOverride(object item) + protected override ItemContainerGenerator CreateItemContainerGenerator() { - TabItem result = item as TabItem; + TabControl tabControl = this.TemplatedParent as TabControl; + ItemContainerGenerator result; - if (result == null) + if (tabControl != null) { - result = new TabItem - { - Content = this.ApplyDataTemplate(item), - }; + result = tabControl.ItemContainerGenerator; + } + else + { + result = new TypedItemContainerGenerator(this); } - result.IsSelected = this.SelectedItem == item; + result.StateChanged += ItemsContainerGeneratorStateChanged; return result; } - private void ItemsChanged(IEnumerable items) + private void ItemsContainerGeneratorStateChanged(object sender, EventArgs e) { - if (items != null) + if (this.ItemContainerGenerator.State == ItemContainerGeneratorState.Generated) { - this.SelectedItem = - items.OfType().FirstOrDefault(x => x.IsSelected) ?? - items.OfType().FirstOrDefault(); - } - else - { - this.SelectedItem = null; + var tabs = this.ItemContainerGenerator.GetAll() + .Select(x => x.Item2) + .OfType() + .ToList(); + + this.SelectedItem = tabs.FirstOrDefault(x => x.IsSelected) ?? tabs.FirstOrDefault(); } } @@ -77,11 +78,11 @@ namespace Perspex.Controls } } - private void SelectedItemChanged(object selectedItem) + private void SelectedItemChanged(object selected) { - foreach (TabItem item in this.GetAllItemControls()) + foreach (TabItem item in this.ItemContainerGenerator.GetAll().Select(x => x.Item2)) { - item.IsSelected = item == selectedItem; + item.IsSelected = selected == item; } } } diff --git a/Perspex.Controls/TreeView.cs b/Perspex.Controls/TreeView.cs index 88665602e9..1bbd6b6dcd 100644 --- a/Perspex.Controls/TreeView.cs +++ b/Perspex.Controls/TreeView.cs @@ -10,6 +10,7 @@ namespace Perspex.Controls using System.Collections; using System.Linq; using System.Reactive.Linq; + using Perspex.Controls.Generators; using Perspex.Input; public class TreeView : SelectingItemsControl @@ -19,42 +20,9 @@ namespace Perspex.Controls this.PointerPressed += this.OnPointerPressed; } - protected override Control CreateItemControlOverride(object item) + protected override ItemContainerGenerator CreateItemContainerGenerator() { - TreeViewItem result = item as TreeViewItem; - - if (result == null) - { - TreeDataTemplate template = this.GetTreeDataTemplate(item); - - result = new TreeViewItem - { - Header = template.Build(item), - Items = template.ItemsSelector(item), - IsExpanded = template.IsExpanded(item), - }; - } - - return result; - } - - private TreeDataTemplate GetTreeDataTemplate(object item) - { - DataTemplate template = this.FindDataTemplate(item); - - if (template == null) - { - template = DataTemplate.Default; - } - - TreeDataTemplate treeTemplate = template as TreeDataTemplate; - - if (treeTemplate == null) - { - treeTemplate = new TreeDataTemplate(template.Build, x => null); - } - - return treeTemplate; + return new TreeItemContainerGenerator(this); } private void OnPointerPressed(object sender, PointerEventArgs e) @@ -75,7 +43,7 @@ namespace Perspex.Controls i.IsSelected = i == item; } - this.SelectedItem = this.GetItemForControl(item); + this.SelectedItem = this.ItemContainerGenerator.GetContainerForItem(item); } } diff --git a/Perspex.Controls/TreeViewItem.cs b/Perspex.Controls/TreeViewItem.cs index c8ebbb4f2b..69692faa7b 100644 --- a/Perspex.Controls/TreeViewItem.cs +++ b/Perspex.Controls/TreeViewItem.cs @@ -8,6 +8,7 @@ namespace Perspex.Controls { using System; using System.Linq; + using Perspex.Controls.Generators; public class TreeViewItem : HeaderedItemsControl { @@ -17,7 +18,7 @@ namespace Perspex.Controls public static readonly PerspexProperty IsSelectedProperty = PerspexProperty.Register("IsSelected"); - TreeView parent; + TreeView treeView; public TreeViewItem() { @@ -37,21 +38,33 @@ namespace Perspex.Controls set { this.SetValue(IsSelectedProperty, value); } } - protected override Control CreateItemControlOverride(object item) + protected override ItemContainerGenerator CreateItemContainerGenerator() { - if (this.parent != null) + if (this.treeView == null) { - return this.parent.CreateItemControl(item); - } - else - { - throw new InvalidOperationException("TreeViewItem must be added to TreeView."); + throw new InvalidOperationException( + "Cannot get the ItemContainerGenerator for a TreeViewItem " + + "before it is added to a TreeView."); } + + return this.treeView.ItemContainerGenerator; } protected override void OnVisualParentChanged(Visual oldParent) { - this.parent = this.GetVisualAncestors().OfType().FirstOrDefault(); + if (this.GetVisualParent() != null) + { + this.treeView = this.GetVisualAncestors().OfType().FirstOrDefault(); + + if (this.treeView == null) + { + throw new InvalidOperationException("TreeViewItems must be added to a TreeView."); + } + } + else + { + this.treeView = null; + } } } } diff --git a/Perspex.SceneGraph/IVisual.cs b/Perspex.SceneGraph/IVisual.cs index 0b256d5fac..2b6d68c2eb 100644 --- a/Perspex.SceneGraph/IVisual.cs +++ b/Perspex.SceneGraph/IVisual.cs @@ -51,7 +51,7 @@ namespace Perspex /// /// Gets the scene graph node's child nodes. /// - IEnumerable VisualChildren { get; } + IReadOnlyPerspexList VisualChildren { get; } /// /// Gets the scene graph node's parent node. diff --git a/Perspex.SceneGraph/Visual.cs b/Perspex.SceneGraph/Visual.cs index cbab16719e..e03737333c 100644 --- a/Perspex.SceneGraph/Visual.cs +++ b/Perspex.SceneGraph/Visual.cs @@ -30,7 +30,7 @@ namespace Perspex private Rect bounds; - private PerspexList visualChildren; + private PerspexList visualChildren; private Visual visualParent; @@ -68,7 +68,7 @@ namespace Perspex get { return this.bounds; } } - IEnumerable IVisual.VisualChildren + IReadOnlyPerspexList IVisual.VisualChildren { get { @@ -190,7 +190,7 @@ namespace Perspex { if (this.visualChildren == null) { - this.visualChildren = new PerspexList(); + this.visualChildren = new PerspexList(); this.visualChildren.CollectionChanged += VisualChildrenChanged; this.CreateVisualChildren(); } diff --git a/TestApplication/Program.cs b/TestApplication/Program.cs index 9dfc45901b..200798f7a9 100644 --- a/TestApplication/Program.cs +++ b/TestApplication/Program.cs @@ -195,24 +195,6 @@ namespace TestApplication Id = "treeView", Items = treeData, }, - new StackPanel - { - Orientation = Orientation.Vertical, - Gap = 2.0, - Children = new Controls - { - new TextBox - { - Id = "newTreeViewItemText", - Text = "New Item" - }, - new Button - { - Id = "addTreeViewItem", - Content = "Add", - }, - } - }, } }, }, @@ -220,21 +202,6 @@ namespace TestApplication } }; - //var treeView = window.FindControl("treeView"); - //var newTreeViewItemText = window.FindControl("newTreeViewItemText"); - //var addTreeViewItem = window.FindControl