2012-04-05 02:21:23 +04:00
|
|
|
|
//
|
|
|
|
|
// ExTreeView.cs
|
|
|
|
|
//
|
|
|
|
|
// Author:
|
|
|
|
|
// Eric Maupin <ermau@xamarin.com>
|
2013-05-08 19:38:43 +04:00
|
|
|
|
// David Karlaš <david.karlas@gmail.com>
|
2012-04-05 02:21:23 +04:00
|
|
|
|
//
|
|
|
|
|
// Copyright (c) 2012 Xamarin, Inc.
|
|
|
|
|
//
|
|
|
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
|
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
|
|
|
// in the Software without restriction, including without limitation the rights
|
|
|
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
|
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
|
|
|
// furnished to do so, subject to the following conditions:
|
|
|
|
|
//
|
|
|
|
|
// The above copyright notice and this permission notice shall be included in
|
|
|
|
|
// all copies or substantial portions of the Software.
|
|
|
|
|
//
|
|
|
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
|
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
|
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
|
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
|
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
|
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
|
|
|
// THE SOFTWARE.
|
|
|
|
|
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.ObjectModel;
|
|
|
|
|
using System.Collections.Specialized;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
using System.Windows;
|
|
|
|
|
using System.Windows.Controls;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using SWC = System.Windows.Controls;
|
2013-05-08 19:38:43 +04:00
|
|
|
|
using WKey = System.Windows.Input.Key;
|
|
|
|
|
using System.Windows.Input;
|
2012-04-05 02:21:23 +04:00
|
|
|
|
|
|
|
|
|
namespace Xwt.WPFBackend
|
|
|
|
|
{
|
2013-10-07 20:19:37 +04:00
|
|
|
|
using Keyboard = System.Windows.Input.Keyboard;
|
|
|
|
|
|
2012-04-05 02:21:23 +04:00
|
|
|
|
public class ExTreeView
|
|
|
|
|
: SWC.TreeView, IWpfWidget
|
|
|
|
|
{
|
|
|
|
|
public ExTreeView()
|
|
|
|
|
{
|
|
|
|
|
SelectedItems = new ObservableCollection<object> ();
|
2013-05-08 19:38:43 +04:00
|
|
|
|
Loaded += new RoutedEventHandler(ExTreeView_Loaded);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ExTreeView_Loaded(object sender, RoutedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
if (SelectionMode == SWC.SelectionMode.Single)
|
|
|
|
|
{
|
|
|
|
|
if (Items.Count > 0)
|
|
|
|
|
SelectItem(Items[0]);
|
|
|
|
|
}
|
2012-04-05 02:21:23 +04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public event EventHandler SelectedItemsChanged;
|
|
|
|
|
|
|
|
|
|
public WidgetBackend Backend
|
|
|
|
|
{
|
|
|
|
|
get;
|
|
|
|
|
set;
|
|
|
|
|
}
|
|
|
|
|
|
2012-04-11 19:58:14 +04:00
|
|
|
|
private readonly GridView view = new GridView();
|
|
|
|
|
public GridView View
|
|
|
|
|
{
|
|
|
|
|
get { return this.view; }
|
|
|
|
|
}
|
2012-04-05 02:21:23 +04:00
|
|
|
|
|
|
|
|
|
public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register ("SelectionMode",
|
|
|
|
|
typeof (System.Windows.Controls.SelectionMode), typeof (ExTreeView),
|
|
|
|
|
new UIPropertyMetadata (System.Windows.Controls.SelectionMode.Single, OnSelectionModePropertyChanged));
|
|
|
|
|
|
|
|
|
|
public System.Windows.Controls.SelectionMode SelectionMode
|
|
|
|
|
{
|
|
|
|
|
get { return (System.Windows.Controls.SelectionMode) GetValue (SelectionModeProperty); }
|
|
|
|
|
set { SetValue (SelectionModeProperty, value); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register ("SelectedItems",
|
|
|
|
|
typeof (IList), typeof (ExTreeView),
|
|
|
|
|
new UIPropertyMetadata (OnSelectedItemsPropertyChanged));
|
|
|
|
|
|
|
|
|
|
public IList SelectedItems
|
|
|
|
|
{
|
|
|
|
|
get { return (IList) GetValue (SelectedItemsProperty); }
|
|
|
|
|
set { SetValue (SelectedItemsProperty, value); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SelectAllExpanded()
|
|
|
|
|
{
|
|
|
|
|
if (Items == null || Items.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
foreach (object item in Items) {
|
|
|
|
|
var treeItem = ((ExTreeViewItem) ItemContainerGenerator.ContainerFromItem (item));
|
|
|
|
|
treeItem.SelectChildren ((i,t) => t.IsExpanded);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void UnselectAll()
|
|
|
|
|
{
|
|
|
|
|
if (Items == null || Items.Count == 0)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
SelectedItems.Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ExTreeViewItem ContainerFromObject (object item)
|
|
|
|
|
{
|
|
|
|
|
ExTreeViewItem treeItem = null;
|
|
|
|
|
TraverseTree ((o,i) => {
|
|
|
|
|
if (o == item) {
|
|
|
|
|
treeItem = i;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return treeItem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override bool IsItemItsOwnContainerOverride (object item)
|
|
|
|
|
{
|
|
|
|
|
return item is ExTreeViewItem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override DependencyObject GetContainerForItemOverride()
|
|
|
|
|
{
|
|
|
|
|
return new ExTreeViewItem (this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override System.Windows.Size MeasureOverride (System.Windows.Size constraint)
|
|
|
|
|
{
|
|
|
|
|
var s = base.MeasureOverride (constraint);
|
|
|
|
|
if (ScrollViewer.GetHorizontalScrollBarVisibility (this) != ScrollBarVisibility.Hidden)
|
|
|
|
|
s.Width = 0;
|
|
|
|
|
if (ScrollViewer.GetVerticalScrollBarVisibility (this) != ScrollBarVisibility.Hidden)
|
|
|
|
|
s.Height = SystemParameters.CaptionHeight;
|
|
|
|
|
|
|
|
|
|
return Backend.MeasureOverride (constraint, s);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void OnSelectionModeChanged (DependencyPropertyChangedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
System.Windows.Controls.SelectionMode oldMode = (System.Windows.Controls.SelectionMode) e.OldValue;
|
|
|
|
|
|
|
|
|
|
object lastSelected = SelectedItem;
|
|
|
|
|
|
|
|
|
|
if (oldMode == System.Windows.Controls.SelectionMode.Multiple) {
|
|
|
|
|
UnselectAll ();
|
|
|
|
|
if (lastSelected != null)
|
|
|
|
|
SelectedItems.Add (lastSelected);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void OnSelectedItemsChanged (EventArgs e)
|
|
|
|
|
{
|
|
|
|
|
var handler = SelectedItemsChanged;
|
|
|
|
|
if (handler != null)
|
|
|
|
|
handler (this, e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void OnSelectedItemsPropertyChanged (DependencyPropertyChangedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
var oldNotifying = e.OldValue as INotifyCollectionChanged;
|
|
|
|
|
if (oldNotifying != null)
|
|
|
|
|
oldNotifying.CollectionChanged -= SelectedItemsCollectionChanged;
|
|
|
|
|
|
|
|
|
|
var newNotifying = e.NewValue as INotifyCollectionChanged;
|
|
|
|
|
if (newNotifying != null)
|
|
|
|
|
newNotifying.CollectionChanged += SelectedItemsCollectionChanged;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool TraverseTree (Func<object, ExTreeViewItem, bool> action, ExTreeViewItem parent = null)
|
|
|
|
|
{
|
|
|
|
|
ItemContainerGenerator g = ItemContainerGenerator;
|
|
|
|
|
IEnumerable items = Items;
|
|
|
|
|
if (parent != null) {
|
|
|
|
|
items = parent.Items;
|
|
|
|
|
g = parent.ItemContainerGenerator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach (object item in items) {
|
|
|
|
|
var treeItem = (ExTreeViewItem) g.ContainerFromItem (item);
|
|
|
|
|
if (treeItem == null)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!action (item, treeItem))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (!TraverseTree (action, treeItem))
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ExTreeViewItem GetTreeItem (object item)
|
|
|
|
|
{
|
|
|
|
|
return (ExTreeViewItem) ItemContainerGenerator.ContainerFromItem (item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static readonly PropertyInfo IsSelectionChangeActiveProperty =
|
|
|
|
|
typeof (System.Windows.Controls.TreeView).GetProperty ("IsSelectionChangeActive", BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
|
|
|
|
|
|
private void SelectedItemsCollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
bool changeActive = (bool)IsSelectionChangeActiveProperty.GetValue (this, null);
|
|
|
|
|
if (!changeActive)
|
|
|
|
|
IsSelectionChangeActiveProperty.SetValue (this, true, null);
|
|
|
|
|
|
|
|
|
|
switch (e.Action) {
|
|
|
|
|
case NotifyCollectionChangedAction.Reset:
|
|
|
|
|
var selectedItems = new HashSet<object> (SelectedItems.Cast<object> ());
|
|
|
|
|
TraverseTree ((o, i) => {
|
|
|
|
|
i.IsSelected = selectedItems.Remove (o);
|
|
|
|
|
return true;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
foreach (object item in SelectedItems)
|
|
|
|
|
GetTreeItem (item).IsSelected = true;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
if (!changeActive && SelectionMode == System.Windows.Controls.SelectionMode.Single) {
|
2012-04-05 03:21:27 +04:00
|
|
|
|
if (SelectedItems.Count > 0)
|
|
|
|
|
SelectedItems.Clear();
|
2012-04-05 02:21:23 +04:00
|
|
|
|
if (e.NewItems != null && e.NewItems.Count > 0)
|
|
|
|
|
SelectedItems.Add (e.NewItems [0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (e.NewItems != null || e.OldItems != null) {
|
|
|
|
|
HashSet<object> newItems = (e.NewItems != null)
|
|
|
|
|
? new HashSet<object> (e.NewItems.Cast<object> ())
|
|
|
|
|
: new HashSet<object> ();
|
|
|
|
|
|
|
|
|
|
HashSet<object> oldItems = (e.OldItems != null)
|
|
|
|
|
? new HashSet<object> (e.OldItems.Cast<object> ())
|
|
|
|
|
: new HashSet<object> ();
|
|
|
|
|
|
|
|
|
|
TraverseTree ((o, ti) => {
|
|
|
|
|
if (newItems.Remove (o))
|
|
|
|
|
ti.IsSelected = true;
|
|
|
|
|
else if (SelectionMode == SWC.SelectionMode.Single || oldItems.Remove (o))
|
|
|
|
|
ti.IsSelected = false;
|
|
|
|
|
|
|
|
|
|
return (newItems.Count + oldItems.Count > 0);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
OnSelectedItemsChanged (EventArgs.Empty);
|
|
|
|
|
|
|
|
|
|
if (!changeActive)
|
|
|
|
|
IsSelectionChangeActiveProperty.SetValue (this, changeActive, null);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void OnSelectedItemsPropertyChanged (DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
var view = (ExTreeView) dependencyObject;
|
|
|
|
|
view.OnSelectedItemsPropertyChanged (e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void OnSelectionModePropertyChanged (DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
var view = (ExTreeView) dependencyObject;
|
|
|
|
|
view.OnSelectionModeChanged (e);
|
|
|
|
|
}
|
2013-05-08 19:38:43 +04:00
|
|
|
|
|
|
|
|
|
public void SelectItem(object item)
|
|
|
|
|
{
|
|
|
|
|
SelectItem(ContainerFromObject(item));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ExTreeViewItem shiftStart = null;
|
|
|
|
|
ExTreeViewItem shiftEnd = null;
|
|
|
|
|
|
|
|
|
|
private bool ShiftPressed
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return Keyboard.IsKeyDown(WKey.RightShift) || Keyboard.IsKeyDown(WKey.LeftShift);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool CtrlPressed
|
|
|
|
|
{
|
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return Keyboard.IsKeyDown(WKey.RightCtrl) || Keyboard.IsKeyDown(WKey.LeftCtrl);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected override void OnGotFocus(RoutedEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
base.OnGotFocus(e);
|
|
|
|
|
FocusItem(focusedTreeViewItem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void SelectItem(ExTreeViewItem item)
|
|
|
|
|
{
|
|
|
|
|
FocusItem(item);
|
|
|
|
|
if (!CtrlPressed)
|
|
|
|
|
SelectedItems.Clear();
|
|
|
|
|
if (ShiftPressed)
|
|
|
|
|
{
|
|
|
|
|
if (shiftEnd != null)//Erase previous selection of shift
|
|
|
|
|
foreach (var forEachItem in GetItemsBetween(shiftStart, shiftEnd))
|
|
|
|
|
SelectedItems.Remove(forEachItem.DataContext);
|
|
|
|
|
shiftEnd = item;
|
|
|
|
|
foreach (var forEachItem in GetItemsBetween(shiftStart, shiftEnd))
|
|
|
|
|
SelectedItems.Add(forEachItem.DataContext);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
shiftEnd = null;
|
|
|
|
|
shiftStart = item;
|
|
|
|
|
if (CtrlPressed && SelectedItems.Contains(item.DataContext))
|
|
|
|
|
SelectedItems.Remove(item.DataContext);
|
|
|
|
|
else
|
|
|
|
|
SelectedItems.Add(item.DataContext);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerable<ExTreeViewItem> GetAllVisibleItems(ItemsControl itemsControl)
|
|
|
|
|
{
|
|
|
|
|
foreach (var item in itemsControl.Items)
|
|
|
|
|
{
|
|
|
|
|
ExTreeViewItem treeViewItem = (ExTreeViewItem)itemsControl.ItemContainerGenerator.ContainerFromItem(item);
|
|
|
|
|
if (treeViewItem == null)
|
|
|
|
|
continue;
|
|
|
|
|
if (treeViewItem.IsVisible && treeViewItem.Visibility == Visibility.Visible)
|
|
|
|
|
{
|
|
|
|
|
yield return treeViewItem;
|
|
|
|
|
if (treeViewItem.IsExpanded)
|
|
|
|
|
foreach (var childItem in GetAllVisibleItems(treeViewItem))
|
|
|
|
|
yield return childItem;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerable<ExTreeViewItem> GetItemsBetween(ExTreeViewItem startItem, ExTreeViewItem endItem)
|
|
|
|
|
{
|
|
|
|
|
var allVisibleItems = GetAllVisibleItems(this).ToList();
|
|
|
|
|
var startIndex = allVisibleItems.IndexOf(startItem);
|
|
|
|
|
var endIndex = allVisibleItems.IndexOf(endItem);
|
|
|
|
|
|
|
|
|
|
if (endIndex == startIndex)
|
|
|
|
|
return new List<ExTreeViewItem> { endItem };
|
|
|
|
|
var filteredItems = new List<ExTreeViewItem>();
|
|
|
|
|
if (endIndex > startIndex)
|
|
|
|
|
for (int i = startIndex; i <= endIndex; i++)
|
|
|
|
|
filteredItems.Add(allVisibleItems[i]);
|
|
|
|
|
else
|
|
|
|
|
for (int i = startIndex; i >= endIndex; i--)
|
|
|
|
|
filteredItems.Add(allVisibleItems[i]);
|
|
|
|
|
return filteredItems;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ExTreeViewItem focusedTreeViewItem = null;
|
|
|
|
|
protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e)
|
|
|
|
|
{
|
|
|
|
|
e.Handled = true;
|
|
|
|
|
if (e.Key == WKey.Down)
|
|
|
|
|
{
|
|
|
|
|
if (CtrlPressed && !ShiftPressed)
|
|
|
|
|
{
|
|
|
|
|
FocusItem(GetNextItem(focusedTreeViewItem));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SelectItem(GetNextItem(focusedTreeViewItem));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (e.Key == WKey.Up)
|
|
|
|
|
{
|
|
|
|
|
if (CtrlPressed && !ShiftPressed)
|
|
|
|
|
{
|
|
|
|
|
FocusItem(GetPrevItem(focusedTreeViewItem));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SelectItem(GetPrevItem(focusedTreeViewItem));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (e.Key == WKey.Space)
|
|
|
|
|
{
|
|
|
|
|
SelectItem(focusedTreeViewItem);
|
|
|
|
|
}
|
|
|
|
|
else if (e.Key == WKey.Right && !CtrlPressed)
|
|
|
|
|
{
|
|
|
|
|
focusedTreeViewItem.IsExpanded = true;
|
|
|
|
|
}
|
|
|
|
|
else if (e.Key == WKey.Left && !CtrlPressed)
|
|
|
|
|
{
|
|
|
|
|
focusedTreeViewItem.IsExpanded = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
e.Handled = false;
|
|
|
|
|
}
|
|
|
|
|
base.OnKeyDown(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void FocusItem(ExTreeViewItem exTreeViewItem)
|
|
|
|
|
{
|
|
|
|
|
if (exTreeViewItem != null)
|
|
|
|
|
{
|
|
|
|
|
focusedTreeViewItem = exTreeViewItem;
|
|
|
|
|
exTreeViewItem.Focus();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ExTreeViewItem GetPrevItem(ExTreeViewItem p)
|
|
|
|
|
{
|
|
|
|
|
var items = GetAllVisibleItems(this).ToList();
|
|
|
|
|
int indexOfP = items.IndexOf(p);
|
|
|
|
|
if (indexOfP == 0)
|
|
|
|
|
return p;
|
|
|
|
|
else
|
|
|
|
|
return items[indexOfP - 1];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ExTreeViewItem GetNextItem(ExTreeViewItem p)
|
|
|
|
|
{
|
|
|
|
|
var items = GetAllVisibleItems(this).ToList();
|
|
|
|
|
int indexOfP = items.IndexOf(p);
|
|
|
|
|
if (indexOfP + 1 == items.Count)
|
|
|
|
|
return p;
|
|
|
|
|
else
|
|
|
|
|
return items[indexOfP + 1];
|
|
|
|
|
}
|
2012-04-05 02:21:23 +04:00
|
|
|
|
}
|
|
|
|
|
}
|