Browse Source

Tidied up tree item container generators.

In doing so fixed a bug in DevTools where tree items would become
unselectable.
Steven Kirk 10 years ago
parent
commit
78abef1551

+ 2 - 17
src/Perspex.Controls/Generators/ITreeItemContainerGenerator.cs

@@ -11,23 +11,8 @@ namespace Perspex.Controls.Generators
     public interface ITreeItemContainerGenerator : IItemContainerGenerator
     {
         /// <summary>
-        /// Gets the item container for the root of the tree, or null if this generator is itself 
-        /// the root of the tree.
+        /// Gets the container index for the tree.
         /// </summary>
-        ITreeItemContainerGenerator RootGenerator { get; }
-
-        /// <summary>
-        /// Gets the item container for the specified item, anywhere in the tree.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>The container, or null if not found.</returns>
-        IControl TreeContainerFromItem(object item);
-
-        /// <summary>
-        /// Gets the item for the specified item container, anywhere in the tree.
-        /// </summary>
-        /// <param name="container">The container.</param>
-        /// <returns>The item, or null if not found.</returns>
-        object TreeItemFromContainer(IControl container);
+        TreeContainerIndex Index { get; }
     }
 }

+ 87 - 0
src/Perspex.Controls/Generators/TreeContainerIndex.cs

@@ -0,0 +1,87 @@
+// 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.Collections.Generic;
+
+namespace Perspex.Controls.Generators
+{
+    /// <summary>
+    /// Maintains an index of all item containers currently materialized by a <see cref="TreeView"/>.
+    /// </summary>
+    /// <remarks>
+    /// Each <see cref="TreeViewItem"/> has its own <see cref="TreeItemContainerGenerator{T}"/> 
+    /// that maintains the list of its direct children, but they also share an instance of this
+    /// class in their <see cref="TreeItemContainerGenerator{T}.Index"/> property which tracks 
+    /// the containers materialized for the entire tree.
+    /// </remarks>
+    public class TreeContainerIndex
+    {
+        private readonly Dictionary<object, IControl> _itemToContainer = new Dictionary<object, IControl>();
+        private readonly Dictionary<IControl, object> _containerToItem = new Dictionary<IControl, object>();
+
+        /// <summary>
+        /// Gets the currently materialized containers.
+        /// </summary>
+        public IEnumerable<IControl> Items => _containerToItem.Keys;
+
+        /// <summary>
+        /// Adds an entry to the index.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <param name="container">The item container.</param>
+        public void Add(object item, IControl container)
+        {
+            _itemToContainer.Add(item, container);
+            _containerToItem.Add(container, item);
+        }
+
+        /// <summary>
+        /// Removes a container from the index.
+        /// </summary>
+        /// <param name="container">The item container.</param>
+        public void Remove(IControl container)
+        {
+            var item = _containerToItem[container];
+            _containerToItem.Remove(container);
+            _itemToContainer.Remove(item);
+        }
+
+        /// <summary>
+        /// Removes a set of containers from the index.
+        /// </summary>
+        /// <param name="containers">The item containers.</param>
+        public void Remove(IEnumerable<ItemContainer> containers)
+        {
+            foreach (var container in containers)
+            {
+                var item = _containerToItem[container.ContainerControl];
+                _containerToItem.Remove(container.ContainerControl);
+                _itemToContainer.Remove(item);
+            }
+        }
+
+        /// <summary>
+        /// Gets the container for an item.
+        /// </summary>
+        /// <param name="item">The item.</param>
+        /// <returns>The container, or null of not found.</returns>
+        public IControl ContainerFromItem(object item)
+        {
+            IControl result;
+            _itemToContainer.TryGetValue(item, out result);
+            return result;
+        }
+
+        /// <summary>
+        /// Gets the item for a container.
+        /// </summary>
+        /// <param name="container">The container.</param>
+        /// <returns>The item, or null of not found.</returns>
+        public object ItemFromContainer(IControl container)
+        {
+            object result;
+            _containerToItem.TryGetValue(container, out result);
+            return result;
+        }
+    }
+}

+ 17 - 89
src/Perspex.Controls/Generators/TreeItemContainerGenerator.cs

@@ -1,6 +1,7 @@
 // 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 Perspex.Controls.Templates;
@@ -14,9 +15,6 @@ namespace Perspex.Controls.Generators
     public class TreeItemContainerGenerator<T> : ItemContainerGenerator<T>, ITreeItemContainerGenerator
         where T : class, IControl, new()
     {
-        private readonly Dictionary<object, T> _itemToContainer;
-        private readonly Dictionary<IControl, object> _containerToItem;
-
         /// <summary>
         /// Initializes a new instance of the <see cref="TreeItemContainerGenerator{T}"/> class.
         /// </summary>
@@ -24,34 +22,30 @@ namespace Perspex.Controls.Generators
         /// <param name="contentProperty">The container's Content property.</param>
         /// <param name="itemsProperty">The container's Items property.</param>
         /// <param name="isExpandedProperty">The container's IsExpanded property.</param>
-        /// <param name="rootGenerator">
-        /// The item container for the root of the tree, or null if this generator is itself the
-        /// root of the tree.
-        /// </param>
+        /// <param name="index">The container index for the tree</param>
         public TreeItemContainerGenerator(
             IControl owner,
             PerspexProperty contentProperty,
             PerspexProperty itemsProperty,
             PerspexProperty isExpandedProperty,
-            ITreeItemContainerGenerator rootGenerator)
+            TreeContainerIndex index)
             : base(owner, contentProperty)
         {
+            Contract.Requires<ArgumentNullException>(owner != null);
+            Contract.Requires<ArgumentNullException>(contentProperty != null);
+            Contract.Requires<ArgumentNullException>(itemsProperty != null);
+            Contract.Requires<ArgumentNullException>(isExpandedProperty != null);
+            Contract.Requires<ArgumentNullException>(index != null);
+
             ItemsProperty = itemsProperty;
             IsExpandedProperty = isExpandedProperty;
-            RootGenerator = rootGenerator;
-
-            if (rootGenerator == null)
-            {
-                _itemToContainer = new Dictionary<object, T>();
-                _containerToItem = new Dictionary<IControl, object>();
-            }
+            Index = index;
         }
 
         /// <summary>
-        /// Gets the item container for the root of the tree, or null if this generator is itself 
-        /// the root of the tree.
+        /// Gets the container index for the tree.
         /// </summary>
-        public ITreeItemContainerGenerator RootGenerator { get; }
+        public TreeContainerIndex Index { get; }
 
         /// <summary>
         /// Gets the item container's Items property.
@@ -63,30 +57,6 @@ namespace Perspex.Controls.Generators
         /// </summary>
         protected PerspexProperty IsExpandedProperty { get; }
 
-        /// <summary>
-        /// Gets the item container for the specified item, anywhere in the tree.
-        /// </summary>
-        /// <param name="item">The item.</param>
-        /// <returns>The container, or null if not found.</returns>
-        public IControl TreeContainerFromItem(object item)
-        {
-            T result;
-            _itemToContainer.TryGetValue(item, out result);
-            return result;
-        }
-
-        /// <summary>
-        /// Gets the item for the specified item container, anywhere in the tree.
-        /// </summary>
-        /// <param name="container">The container.</param>
-        /// <returns>The item, or null if not found.</returns>
-        public object TreeItemFromContainer(IControl container)
-        {
-            object result;
-            _containerToItem.TryGetValue(container, out result);
-            return result;
-        }
-
         /// <inheritdoc/>
         protected override IControl CreateContainer(object item)
         {
@@ -114,7 +84,7 @@ namespace Perspex.Controls.Generators
                     result.DataContext = item;
                 }
 
-                AddToIndex(item, result);
+                Index.Add(item, result);
 
                 return result;
             }
@@ -122,59 +92,17 @@ namespace Perspex.Controls.Generators
 
         public override IEnumerable<ItemContainer> Clear()
         {
-            ClearIndex();
-            return base.Clear();
+            var items = base.Clear();
+            Index.Remove(items);
+            return items;
         }
 
         public override IEnumerable<ItemContainer> Dematerialize(int startingIndex, int count)
         {
-            RemoveFromIndex(GetContainerRange(startingIndex, count));
+            Index.Remove(GetContainerRange(startingIndex, count));
             return base.Dematerialize(startingIndex, count);
         }
 
-        private void AddToIndex(object item, T container)
-        {
-            if (RootGenerator != null)
-            {
-                ((TreeItemContainerGenerator<T>)RootGenerator).AddToIndex(item, container);
-            }
-            else
-            {
-                _itemToContainer.Add(item, container);
-                _containerToItem.Add(container, item);
-            }
-        }
-
-        private void RemoveFromIndex(IEnumerable<ItemContainer> containers)
-        {
-            if (RootGenerator != null)
-            {
-                ((TreeItemContainerGenerator<T>)RootGenerator).RemoveFromIndex(containers);
-            }
-            else
-            {
-                foreach (var container in containers)
-                {
-                    var item = _containerToItem[container.ContainerControl];
-                    _containerToItem.Remove(container.ContainerControl);
-                    _itemToContainer.Remove(item);
-                }
-            }
-        }
-
-        private void ClearIndex()
-        {
-            if (RootGenerator != null)
-            {
-                ((TreeItemContainerGenerator<T>)RootGenerator).ClearIndex();
-            }
-            else
-            {
-                _containerToItem.Clear();
-                _itemToContainer.Clear();
-            }
-        }
-
         /// <summary>
         /// Gets the data template for the specified item.
         /// </summary>

+ 1 - 0
src/Perspex.Controls/Perspex.Controls.csproj

@@ -46,6 +46,7 @@
     <Compile Include="DockPanel.cs" />
     <Compile Include="Expander.cs" />
     <Compile Include="Generators\ItemContainer.cs" />
+    <Compile Include="Generators\TreeContainerIndex.cs" />
     <Compile Include="HotkeyManager.cs" />
     <Compile Include="INameScope.cs" />
     <Compile Include="IPseudoClasses.cs" />

+ 4 - 4
src/Perspex.Controls/TreeView.cs

@@ -58,7 +58,7 @@ namespace Perspex.Controls
                 TreeViewItem.HeaderProperty,
                 TreeViewItem.ItemsProperty,
                 TreeViewItem.IsExpandedProperty,
-                null);
+                new TreeContainerIndex());
         }
 
         /// <inheritdoc/>
@@ -101,13 +101,13 @@ namespace Perspex.Controls
             bool rangeModifier = false,
             bool toggleModifier = false)
         {
-            var item = ItemContainerGenerator.TreeItemFromContainer(container);
+            var item = ItemContainerGenerator.Index.ItemFromContainer(container);
 
             if (item != null)
             {
                 if (SelectedItem != null)
                 {
-                    var old = ItemContainerGenerator.TreeContainerFromItem(SelectedItem);
+                    var old = ItemContainerGenerator.Index.ContainerFromItem(SelectedItem);
                     MarkContainerSelected(old, false);
                 }
 
@@ -162,7 +162,7 @@ namespace Perspex.Controls
 
             if (item != null)
             {
-                if (item.ItemContainerGenerator.RootGenerator == this.ItemContainerGenerator)
+                if (item.ItemContainerGenerator.Index == this.ItemContainerGenerator.Index)
                 {
                     return item;
                 }

+ 1 - 1
src/Perspex.Controls/TreeViewItem.cs

@@ -79,7 +79,7 @@ namespace Perspex.Controls
                 TreeViewItem.HeaderProperty,
                 TreeViewItem.ItemsProperty,
                 TreeViewItem.IsExpandedProperty,
-                _treeView?.ItemContainerGenerator);
+                _treeView?.ItemContainerGenerator.Index ?? new TreeContainerIndex());
         }
 
         /// <inheritdoc/>

+ 3 - 2
tests/Perspex.Controls.UnitTests/TreeViewTests.cs

@@ -3,6 +3,7 @@
 
 using System.Collections.Generic;
 using System.Linq;
+using Perspex.Collections;
 using Perspex.Controls.Presenters;
 using Perspex.Controls.Templates;
 using Perspex.Input;
@@ -65,7 +66,7 @@ namespace Perspex.Controls.UnitTests
 
             ApplyTemplates(target);
 
-            var container = target.ItemContainerGenerator.TreeContainerFromItem(
+            var container = target.ItemContainerGenerator.Index.ContainerFromItem(
                 tree[0].Children[1].Children[0]);
 
             Assert.NotNull(container);
@@ -92,7 +93,7 @@ namespace Perspex.Controls.UnitTests
             ApplyTemplates(target);
 
             var item = tree[0].Children[1].Children[0];
-            var container = (TreeViewItem)target.ItemContainerGenerator.TreeContainerFromItem(item);
+            var container = (TreeViewItem)target.ItemContainerGenerator.Index.ContainerFromItem(item);
 
             Assert.NotNull(container);