/// <summary> /// Handles the PreviewMouseLeftButtonDown event on the drag source. /// </summary> /// <param name="sender">The sender.</param> /// <param name="e">The event args.</param> private void DragSourcePreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { this.sourceItemsControl = (TreeListBox)sender; var visual = e.OriginalSource as Visual; this.topWindow = Window.GetWindow(this.sourceItemsControl); this.initialMousePosition = e.GetPosition(this.topWindow); this.sourceItemContainer = visual != null ? this.sourceItemsControl.ContainerFromElement(visual) as TreeListBoxItem : null; if (this.sourceItemContainer != null) { this.draggedData = new List <IDragSource>(); this.draggedData.Add(this.sourceItemContainer.DataContext); // todo: how to drag multiple items? // must set e.Handled = true to avoid items being deselected // foreach (var si in sourceItemsControl.SelectedItems) this.draggedData.Add(si); } else { this.draggedData = null; } }
/// <summary> /// Handles the PreviewMouseLeftButtonDown event on the drag source. /// </summary> /// <param name="sender"> /// The sender. /// </param> /// <param name="e"> /// The event args. /// </param> private void DragSourcePreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { this.sourceItemsControl = (TreeListBox)sender; var visual = e.OriginalSource as Visual; this.topWindow = Window.GetWindow(this.sourceItemsControl); this.initialMousePosition = e.GetPosition(this.topWindow); this.sourceItemContainer = visual != null ? this.sourceItemsControl.ContainerFromElement(visual) as TreeListBoxItem : null; if (this.sourceItemContainer != null) { this.draggedData = new List<IDragSource>(); this.draggedData.Add(this.sourceItemContainer.DataContext); // todo: how to drag multiple items? // must set e.Handled = true to avoid items being deselected // foreach (var si in sourceItemsControl.SelectedItems) this.draggedData.Add(si); } else { this.draggedData = null; } }
/// <summary> /// Decides the drop target. /// </summary> /// <param name="e">The event args.</param> private void DecideDropTarget(DragEventArgs e) { // If the types of the dragged data and ItemsControl's source are compatible, // there are 3 situations to have into account when deciding the drop target: // 1. mouse is over an items container // 2. mouse is over the empty part of an ItemsControl, but ItemsControl is not empty // 3. mouse is over an empty ItemsControl. // The goal of this method is to decide on the values of the following properties: // targetItemContainer, insertionIndex and isInFirstHalf. int targetItemsControlCount = this.targetItemsControl.Items.Count; var draggedItems = e.Data.GetData(this.format.Name) as IList; if (targetItemsControlCount > 0) { this.hasVerticalOrientation = true; this.targetItemContainer = this.targetItemsControl.ContainerFromElement((DependencyObject)e.OriginalSource) as TreeListBoxItem; if (this.targetItemContainer != null) { Point positionRelativeToItemContainer = e.GetPosition(this.targetItemContainer); double relativePosition = GetRelativePosition( this.targetItemContainer, positionRelativeToItemContainer, this.hasVerticalOrientation); this.isInFirstHalf = relativePosition < 0.5; if (relativePosition > 0.3 && relativePosition < 0.7) { this.dropPosition = DropPosition.Add; } else { this.dropPosition = relativePosition < 0.5 ? DropPosition.InsertBefore : DropPosition.InsertAfter; } } else { this.targetItemContainer = this.targetItemsControl.ContainerFromIndex(targetItemsControlCount - 1); this.isInFirstHalf = false; this.dropPosition = DropPosition.InsertAfter; } } else { this.targetItemContainer = null; this.dropPosition = DropPosition.InsertBefore; } if (this.targetItemContainer != null && draggedItems != null) { var dropTarget = this.targetItemContainer.DataContext as IDropTarget; foreach (var draggedItem in draggedItems) { var dragSource = draggedItem as IDragSource; if ((dragSource == null || !dragSource.IsDraggable) || (dropTarget == null || !dropTarget.CanDrop(dragSource, this.dropPosition, (DragDropEffect)e.Effects))) { this.targetItemContainer = null; e.Effects = DragDropEffects.None; return; } } } }
/// <summary> /// Expands the specified item. /// </summary> /// <param name="item"> /// The item. /// </param> internal void Expand(TreeListBoxItem item) { if (this.isPreparingContainer) { // cannot expand when preparing the container // add the item to a queue, and expand later this.expandItems.Enqueue(item); if (!this.isSubscribedToRenderingEvent) { CompositionTarget.Rendering += this.CompositionTargetRendering; this.isSubscribedToRenderingEvent = true; } return; } if (item.Content == null || item.Children == null) { return; } lock (this) { int i0 = this.Items.IndexOf(item.Content) + 1; foreach (var child in item.Children) { if (this.Items.Contains(child)) { Debug.WriteLine("TreeListBox bug."); continue; } this.Items.Insert(i0++, child); this.parentContainerMap[child] = item; } } }
/// <summary> /// Decides the drop target. /// </summary> /// <param name="e"> /// The event args. /// </param> private void DecideDropTarget(DragEventArgs e) { // If the types of the dragged data and ItemsControl's source are compatible, // there are 3 situations to have into account when deciding the drop target: // 1. mouse is over an items container // 2. mouse is over the empty part of an ItemsControl, but ItemsControl is not empty // 3. mouse is over an empty ItemsControl. // The goal of this method is to decide on the values of the following properties: // targetItemContainer, insertionIndex and isInFirstHalf. int targetItemsControlCount = this.targetItemsControl.Items.Count; var draggedItems = e.Data.GetData(this.format.Name) as IList; if (targetItemsControlCount > 0) { this.hasVerticalOrientation = true; this.targetItemContainer = this.targetItemsControl.ContainerFromElement((DependencyObject)e.OriginalSource) as TreeListBoxItem; if (this.targetItemContainer != null) { Point positionRelativeToItemContainer = e.GetPosition(this.targetItemContainer); double relativePosition = GetRelativePosition( this.targetItemContainer, positionRelativeToItemContainer, this.hasVerticalOrientation); this.isInFirstHalf = relativePosition < 0.5; if (relativePosition > 0.3 && relativePosition < 0.7) { this.dropPosition = DropPosition.Add; } else { this.dropPosition = relativePosition < 0.5 ? DropPosition.InsertBefore : DropPosition.InsertAfter; } } else { this.targetItemContainer = this.targetItemsControl.ContainerFromIndex(targetItemsControlCount - 1); this.isInFirstHalf = false; this.dropPosition = DropPosition.InsertAfter; } } else { this.targetItemContainer = null; this.dropPosition = DropPosition.InsertBefore; } if (this.targetItemContainer != null && draggedItems != null) { var dropTarget = this.targetItemContainer.DataContext as IDropTarget; bool copy = (e.Effects & DragDropEffects.Copy) != 0; foreach (var draggedItem in draggedItems) { var dragSource = draggedItem as IDragSource; if ((dragSource == null || !dragSource.IsDraggable) || (dropTarget == null || !dropTarget.CanDrop(dragSource, this.dropPosition, copy))) { this.targetItemContainer = null; e.Effects = DragDropEffects.None; return; } } } }
/// <summary> /// Handles child collection changes. /// </summary> /// <param name="parent"> /// The parent. /// </param> /// <param name="e"> /// The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data. /// </param> internal void ChildCollectionChanged(TreeListBoxItem parent, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Move: foreach (var item in e.OldItems) { var container = this.ContainerFromItem(item); if (container != null && container.IsExpanded) { container.IsExpanded = false; } this.Items.Remove(item); } break; case NotifyCollectionChangedAction.Reset: var itemsToRemove = new List<TreeListBoxItem>(); foreach (var item in this.Items) { var container = this.ContainerFromItem(item); if (container == null || !ReferenceEquals(container.ParentItem, parent)) { continue; } itemsToRemove.Add(container); } foreach (var container in itemsToRemove) { if (container.IsExpanded) { container.IsExpanded = false; } this.Items.Remove(container.Content); } parent.IsExpanded = false; parent.HasItems = false; break; case NotifyCollectionChangedAction.Replace: // todo: remove old items and child items throw new NotImplementedException(); } switch (e.Action) { case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Add: if (parent.IsExpanded) { int i; if (e.NewStartingIndex + e.NewItems.Count < parent.Children.Count) { // inserted items var followingChild = parent.Children[e.NewStartingIndex + e.NewItems.Count]; i = this.Items.IndexOf(followingChild); } else { // added items var parentSibling = parent.GetNextSibling(); if (parentSibling == null) { // No sibling found, so add at the end of the list. i = this.Items.Count; } else { // Found the sibling, so add the items before this item. i = this.Items.IndexOf(parentSibling.Content); //// int i0 = this.ItemContainerGenerator.IndexFromContainer(parentSibling); } } if (i < 0) { Debug.WriteLine("TreeListBox bug."); } else { foreach (var item in e.NewItems) { this.parentContainerMap[item] = parent; if (this.Items.Contains(item)) { Debug.WriteLine("TreeListBox bug."); } this.Items.Insert(i++, item); } } } break; case NotifyCollectionChangedAction.Replace: for (int i = 0; i < e.NewItems.Count; i++) { this.Items[e.NewStartingIndex + i] = e.NewItems[i]; } // todo: add new items and child items throw new NotImplementedException(); } parent.HasItems = parent.Children.Cast<object>().Any(); }
/// <summary> /// Collapses the specified item. /// </summary> /// <param name="item"> /// The item. /// </param> internal void Collapse(TreeListBoxItem item) { if (item.Children == null) { return; } foreach (var child in item.Children) { var childContainer = this.ContainerFromItem(child); if (childContainer != null) { if (childContainer.IsExpanded) { childContainer.IsExpanded = false; } if (childContainer.IsSelected) { childContainer.IsSelected = false; } } this.Items.Remove(child); } }
/// <summary> /// Inserts the specified items in the tree. /// </summary> /// <param name="parentContainer">The parent container.</param> /// <param name="newItems">The new items.</param> /// <param name="newStartingIndex">New index of the starting.</param> private void InsertItems(TreeListBoxItem parentContainer, IList newItems, int newStartingIndex) { // Find the index where the new items should be added int i; if (newStartingIndex + newItems.Count < parentContainer.Children.Count) { // inserted items var followingChild = parentContainer.Children[newStartingIndex + newItems.Count]; i = this.Items.IndexOf(followingChild); } else { // added items var parentSibling = parentContainer.GetNextSibling(); if (parentSibling == null) { // No sibling found, so add at the end of the list. i = this.Items.Count; } else { // Found the sibling, so add the items before this item. i = this.Items.IndexOf(parentSibling.Content); } } if (i < 0) { #if !DEBUG throw new InvalidOperationException("Could not get parent index in TreeListBox."); #else System.Diagnostics.Trace.WriteLine("Could not get parent index in TreeListBox."); #endif } foreach (var item in newItems) { this.InsertItem(i, item, parentContainer.Content); } }
/// <summary> /// Expands the specified container. /// </summary> /// <param name="container">The container to expand.</param> internal void Expand(TreeListBoxItem container) { if (this.isPreparingContainer) { // cannot expand when preparing the container // add the item to a queue, and expand later this.expandItemQueue.Enqueue(container); if (!this.isSubscribedToRenderingEvent) { CompositionTarget.Rendering += this.CompositionTargetRendering; this.isSubscribedToRenderingEvent = true; } return; } if (container.Content == null || container.Children == null) { return; } lock (this) { int i0 = this.Items.IndexOf(container.Content) + 1; var parent = container.Content; foreach (var child in container.Children) { this.InsertItem(i0++, child, parent); } } }
/// <summary> /// Handles child collection changes. /// </summary> /// <param name="parentContainer">The parent container.</param> /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs" /> instance containing the event data.</param> internal void ChildCollectionChanged(TreeListBoxItem parentContainer, NotifyCollectionChangedEventArgs e) { switch (e.Action) { case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Replace: this.RemoveItems(e.OldItems); break; case NotifyCollectionChangedAction.Reset: this.RemoveItems(this.Items); parentContainer.IsExpanded = false; parentContainer.HasItems = false; break; } switch (e.Action) { case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Replace: if (parentContainer.IsExpanded) { this.InsertItems(parentContainer, e.NewItems, e.NewStartingIndex); } break; } parentContainer.HasItems = parentContainer.Children.Cast<object>().Any(); }
/// <summary> /// Subscribes for changes on the specified collection. /// </summary> /// <param name="parent">The parent.</param> /// <param name="collection">The collection to observe.</param> private void SubscribeForCollectionChanges(TreeListBoxItem parent, IEnumerable collection) { if (this.ParentTreeListBox == null) { throw new InvalidOperationException("Parent not set."); } var cc = collection as INotifyCollectionChanged; if (cc != null) { var parentTreeListBox = this.ParentTreeListBox; this.collectionChangedHandler = (s, e) => parentTreeListBox.ChildCollectionChanged(parent, e); cc.CollectionChanged += this.collectionChangedHandler; } }
/// <summary> /// Subscribes for collection changes. /// </summary> /// <param name="parent"> /// The parent. /// </param> /// <param name="collection"> /// The collection. /// </param> private void SubscribeForCollectionChanges(TreeListBoxItem parent, IEnumerable collection) { var cc = collection as INotifyCollectionChanged; if (cc != null) { this.collectionChangedHandler = (s, e) => this.ParentTreeListBox.ChildCollectionChanged(parent, e); cc.CollectionChanged += this.collectionChangedHandler; } }