JD2022-TU1/main/tools/framework/JD.WPF/Controls/TreeList.cs

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); }
}
}
}