418 lines
14 KiB
C#
418 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Controls.Primitives;
|
|
using System.Windows.Input;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using System.Windows.Media.Media3D;
|
|
using JD.WPF.Events;
|
|
using JD.Interfaces.Events;
|
|
using JD.Interfaces.Models;
|
|
|
|
namespace JD.WPF.Controls
|
|
{
|
|
public class TreeList : ListView
|
|
{
|
|
private bool m_unselectOnMouseUp;
|
|
private ITreeViewItemData m_lastSelection;
|
|
private Point m_mouseDownPosition;
|
|
private bool m_scrolling;
|
|
|
|
public event DragOperationStartedHandler DragStarted;
|
|
public event ValueEventHandler<ITreeViewItemData> TopReached;
|
|
public event ValueEventHandler<ITreeViewItemData> BottomReached;
|
|
|
|
public TreeList()
|
|
{
|
|
PreviewMouseDown += OnPreviewMouseDown;
|
|
PreviewMouseUp += OnPreviewMouseUp;
|
|
PreviewMouseMove += OnPreviewMouseMove;
|
|
PreviewKeyDown += OnPreviewKeyDown;
|
|
}
|
|
|
|
~TreeList()
|
|
{
|
|
PreviewMouseDown -= OnPreviewMouseDown;
|
|
PreviewMouseUp -= OnPreviewMouseUp;
|
|
PreviewMouseMove -= OnPreviewMouseMove;
|
|
PreviewKeyDown -= OnPreviewKeyDown;
|
|
}
|
|
|
|
protected override DependencyObject GetContainerForItemOverride()
|
|
{
|
|
return new TreeListItem();
|
|
}
|
|
|
|
protected override bool IsItemItsOwnContainerOverride(object item)
|
|
{
|
|
return item is TreeListItem;
|
|
}
|
|
|
|
private static bool IsCtrlPressed
|
|
{
|
|
get { return Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl); }
|
|
}
|
|
|
|
private static bool IsShiftPressed
|
|
{
|
|
get { return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift); }
|
|
}
|
|
|
|
private void OnPreviewMouseDown(object sender, MouseButtonEventArgs e)
|
|
{
|
|
// If clicking on a tree branch expander...
|
|
if(IsInButton(e.OriginalSource as DependencyObject))
|
|
return;
|
|
|
|
if (e.ChangedButton == MouseButton.Left)
|
|
m_mouseDownPosition = e.GetPosition(this);
|
|
|
|
// Clear all previous selected item states if ctrl is NOT being held down
|
|
var clickedItem = GetListViewItemClicked((FrameworkElement)e.OriginalSource);
|
|
if (clickedItem == null)
|
|
{
|
|
if (!IsMouseOverScrollbar(this, e.GetPosition(this as IInputElement)))
|
|
{
|
|
ClearSelection();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var clickedItemData = (clickedItem.DataContext as ITreeViewItemData);
|
|
if (!IsCtrlPressed && !IsShiftPressed)
|
|
{
|
|
if (clickedItem != null && clickedItem.IsSelected)
|
|
{
|
|
// Copy Windows Explorer behavior : if a selected item is clicked on, the selection is keep until the mouse button is released.
|
|
m_unselectOnMouseUp = true;
|
|
e.Handled = true;
|
|
}
|
|
else
|
|
{
|
|
ClearSelection();
|
|
}
|
|
m_lastSelection = clickedItem.DataContext as ITreeViewItemData;
|
|
}
|
|
|
|
// Is this an item range selection?
|
|
if (IsShiftPressed && m_lastSelection != null)
|
|
{
|
|
int beginIndex = Items.IndexOf(m_lastSelection);
|
|
int endIndex = Items.IndexOf(clickedItem.DataContext);
|
|
if (beginIndex > endIndex)
|
|
{
|
|
int temp = endIndex;
|
|
endIndex = beginIndex;
|
|
beginIndex = temp;
|
|
}
|
|
|
|
if(!IsCtrlPressed)
|
|
ClearSelection();
|
|
foreach (ITreeViewItemData item in Items.Cast<ITreeViewItemData>().Skip(beginIndex).Take(endIndex - beginIndex + 1))
|
|
item.IsSelected = true;
|
|
e.Handled = true;
|
|
Focus();
|
|
}
|
|
|
|
// Otherwise, individual selection
|
|
else if (!m_unselectOnMouseUp)
|
|
{
|
|
clickedItemData.IsSelected = !clickedItemData.IsSelected;
|
|
e.Handled = true;
|
|
m_lastSelection = clickedItem.DataContext as ITreeViewItemData;
|
|
Focus();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnPreviewMouseUp(object sender, MouseButtonEventArgs e)
|
|
{
|
|
m_scrolling = false;
|
|
|
|
if (m_unselectOnMouseUp)
|
|
{
|
|
if(e.ChangedButton == MouseButton.Left)
|
|
SetSelection(m_lastSelection);
|
|
m_unselectOnMouseUp = false;
|
|
}
|
|
}
|
|
|
|
private void OnPreviewMouseMove(object sender, MouseEventArgs e)
|
|
{
|
|
if (DragStarted == null)
|
|
return;
|
|
|
|
if (m_scrolling)
|
|
return;
|
|
|
|
// If clicking on a tree branch expander...
|
|
if (IsInButton(e.OriginalSource as DependencyObject))
|
|
return;
|
|
|
|
if (e.LeftButton == MouseButtonState.Pressed)
|
|
{
|
|
ReleaseMouseCapture();
|
|
|
|
if (IsMouseOverScrollbar(this, e.GetPosition(this as IInputElement)))
|
|
{
|
|
m_scrolling = true;
|
|
return;
|
|
}
|
|
|
|
if (!HasMouseMovedEnough(e.GetPosition(this)))
|
|
return;
|
|
|
|
m_unselectOnMouseUp = false;
|
|
List<object> selectedItems = new List<object>();
|
|
foreach (object item in SelectedItems)
|
|
selectedItems.Add(item);
|
|
|
|
DragStarted(this, new DragOperationStartedEventArgs(selectedItems));
|
|
}
|
|
}
|
|
|
|
private void OnPreviewKeyDown(object sender, KeyEventArgs e)
|
|
{
|
|
switch (e.Key)
|
|
{
|
|
case Key.Down: SelectNextItem(); e.Handled = true; break;
|
|
case Key.Up: SelectPreviousItem(); e.Handled = true; break;
|
|
case Key.Left: CollapseItem(); e.Handled = true; break;
|
|
case Key.Right: ExpandItem(); e.Handled = true; break;
|
|
}
|
|
}
|
|
|
|
protected override void OnSelectionChanged(SelectionChangedEventArgs e)
|
|
{
|
|
base.OnSelectionChanged(e);
|
|
|
|
if (SelectedItems.Count == 1)
|
|
m_lastSelection = SelectedItems[0] as ITreeViewItemData;
|
|
}
|
|
|
|
private static ListViewItem GetListViewItemClicked(DependencyObject sender)
|
|
{
|
|
while (sender != null && !(sender is ListViewItem))
|
|
sender = VisualTreeHelper.GetParent(sender);
|
|
return sender as ListViewItem;
|
|
}
|
|
|
|
private static bool IsInButton(DependencyObject sender)
|
|
{
|
|
while (sender != null && !(sender is ButtonBase))
|
|
sender = VisualTreeHelper.GetParent(sender);
|
|
return sender != null;
|
|
}
|
|
|
|
protected void ClearSelection()
|
|
{
|
|
foreach(ITreeViewItemData item in Items)
|
|
item.IsSelected = false;
|
|
|
|
// Note : Mandatory because of virtualization (otherwise, non-visible selected items won't be affected).
|
|
SelectedItems.Clear();
|
|
}
|
|
|
|
protected void SelectNextItem()
|
|
{
|
|
ITreeViewItemData data = GetLastSelectedItemData();
|
|
if (data == null)
|
|
return;
|
|
|
|
data = data.GetNextItem();
|
|
while (data != null)
|
|
{
|
|
if (!data.IsFiltered)
|
|
{
|
|
SetSelection(data);
|
|
break;
|
|
}
|
|
|
|
data = data.GetNextItem();
|
|
}
|
|
|
|
if (data == null && BottomReached != null)
|
|
BottomReached(this, new ValueEventArgs<ITreeViewItemData>(GetLastSelectedItemData()));
|
|
}
|
|
|
|
protected void SelectPreviousItem()
|
|
{
|
|
ITreeViewItemData data = GetLastSelectedItemData();
|
|
if (data == null)
|
|
return;
|
|
|
|
data = data.GetPreviousItem();
|
|
while (data != null)
|
|
{
|
|
if (!data.IsFiltered)
|
|
{
|
|
SetSelection(data);
|
|
break;
|
|
}
|
|
|
|
data = data.GetPreviousItem();
|
|
}
|
|
|
|
if (data == null && TopReached != null)
|
|
TopReached(this, new ValueEventArgs<ITreeViewItemData>(GetLastSelectedItemData()));
|
|
}
|
|
|
|
protected void CollapseItem()
|
|
{
|
|
ITreeViewItemData data = GetLastSelectedItemData();
|
|
if (data == null)
|
|
return;
|
|
|
|
if (data.IsExpanded)
|
|
data.IsExpanded = false;
|
|
else if (data.Parent != null)
|
|
SetSelection(data.Parent);
|
|
}
|
|
|
|
protected void ExpandItem()
|
|
{
|
|
ITreeViewItemData data = GetLastSelectedItemData();
|
|
if (data == null)
|
|
return;
|
|
|
|
if (data.IsExpanded)
|
|
{
|
|
if (data.Children.Count > 0)
|
|
SetSelection(data.Children[0]);
|
|
}
|
|
else
|
|
{
|
|
data.IsExpanded = true;
|
|
}
|
|
}
|
|
|
|
private void SetSelection(IEnumerable<ITreeViewItemData> newSelection, bool selectFirst = true)
|
|
{
|
|
ClearSelection();
|
|
foreach(ITreeViewItemData item in newSelection)
|
|
item.IsSelected = true;
|
|
if (selectFirst)
|
|
m_lastSelection = newSelection.First();
|
|
else
|
|
m_lastSelection = newSelection.Last();
|
|
}
|
|
|
|
private void SetSelection(ITreeViewItemData newSelection)
|
|
{
|
|
ClearSelection();
|
|
newSelection.IsSelected = true;
|
|
m_lastSelection = newSelection;
|
|
}
|
|
|
|
private ITreeViewItemData GetLastSelectedItemData()
|
|
{
|
|
ITreeViewItemData data = null;
|
|
if (m_lastSelection != null)
|
|
data = m_lastSelection;
|
|
if (data == null && SelectedItems.Count > 0)
|
|
data = SelectedItems[0] as ITreeViewItemData;
|
|
|
|
return data;
|
|
}
|
|
|
|
private bool IsMouseOverScrollbar(object sender, Point mousePosition)
|
|
{
|
|
if (sender is Visual)
|
|
{
|
|
HitTestResult hit = VisualTreeHelper.HitTest(sender as Visual, mousePosition);
|
|
|
|
if (hit == null) return false;
|
|
|
|
DependencyObject dObj = hit.VisualHit;
|
|
while (dObj != null)
|
|
{
|
|
if (dObj is ScrollBar) return true;
|
|
|
|
if ((dObj is Visual) || (dObj is Visual3D)) dObj = VisualTreeHelper.GetParent(dObj);
|
|
else dObj = LogicalTreeHelper.GetParent(dObj);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool HasMouseMovedEnough(Point mousePosition)
|
|
{
|
|
if (Math.Abs(m_mouseDownPosition.X - mousePosition.X) > 10)
|
|
return true;
|
|
if (Math.Abs(m_mouseDownPosition.Y - mousePosition.Y) > 10)
|
|
return true;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public class TreeListItem : ListViewItem
|
|
{
|
|
public static readonly DependencyProperty IsExpandedProperty = DependencyProperty.Register("IsExpanded", typeof(bool), typeof(TreeListItem));
|
|
public static readonly DependencyProperty IsFilteredProperty = DependencyProperty.Register("IsFiltered", typeof(bool), typeof(TreeListItem));
|
|
public static readonly DependencyProperty LevelProperty = DependencyProperty.Register("Level", typeof(int), typeof(TreeListItem));
|
|
public static readonly DependencyProperty HasChildrenProperty = DependencyProperty.Register("HasChildren", typeof(bool), typeof(TreeListItem), new UIPropertyMetadata());
|
|
public static readonly DependencyProperty IconSourceProperty = DependencyProperty.Register("IconSource", typeof(BitmapSource), typeof(TreeListItem));
|
|
public static readonly DependencyProperty IconSourceTemplateProperty = DependencyProperty.Register("IconSourceTemplate", typeof(DataTemplate), typeof(TreeListItem));
|
|
public static readonly DependencyProperty ReserveIconSpaceProperty = DependencyProperty.Register("ReserveIconSpace", typeof(bool), typeof(TreeListItem));
|
|
public static readonly DependencyProperty PerforceStatusProperty = DependencyProperty.Register("PerforceStatus", typeof(string), typeof(TreeListItem));
|
|
public static readonly DependencyProperty PerforceUpToDateProperty = DependencyProperty.Register("PerforceUpToDate", typeof(bool), typeof(TreeListItem));
|
|
|
|
public bool IsExpanded
|
|
{
|
|
get { return (bool) GetValue(IsExpandedProperty); }
|
|
set { SetValue(IsExpandedProperty, value); }
|
|
}
|
|
|
|
public bool IsFiltered
|
|
{
|
|
get { return (bool) GetValue(IsFilteredProperty); }
|
|
set { SetValue(IsFilteredProperty, value); }
|
|
}
|
|
|
|
public int Level
|
|
{
|
|
get { return (int) GetValue(LevelProperty); }
|
|
set { SetValue(LevelProperty, value); }
|
|
}
|
|
|
|
public bool HasChildren
|
|
{
|
|
get { return (bool) GetValue(HasChildrenProperty); }
|
|
set { SetValue(HasChildrenProperty, value); }
|
|
}
|
|
|
|
public BitmapSource IconSource
|
|
{
|
|
get { return (BitmapSource) GetValue(IconSourceProperty); }
|
|
set { SetValue(IconSourceProperty, value); }
|
|
}
|
|
|
|
public DataTemplate IconSourceTemplate
|
|
{
|
|
get { return (DataTemplate) GetValue(IconSourceTemplateProperty); }
|
|
set { SetValue(IconSourceTemplateProperty, value); }
|
|
}
|
|
|
|
public bool ReserveIconSpace
|
|
{
|
|
get { return (bool) GetValue(ReserveIconSpaceProperty); }
|
|
set { SetValue(ReserveIconSpaceProperty, value); }
|
|
}
|
|
|
|
public string PerforceStatus
|
|
{
|
|
get { return (string) GetValue(PerforceStatusProperty); }
|
|
set { SetValue(PerforceStatusProperty, value); }
|
|
}
|
|
|
|
public bool PerforceUpToDate
|
|
{
|
|
get { return (bool) GetValue(PerforceUpToDateProperty); }
|
|
set { SetValue(PerforceUpToDateProperty, value); }
|
|
}
|
|
}
|
|
}
|