mirror of
https://github.com/AtomUI/AtomUI.git
synced 2026-10-22 17:35:30 +08:00
712 lines
24 KiB
C#
712 lines
24 KiB
C#
using System.Collections.Specialized;
|
|
using System.Reactive.Disposables;
|
|
using AtomUI.Controls;
|
|
using AtomUI.Controls.Utils;
|
|
using AtomUI.Data;
|
|
using AtomUI.Desktop.Controls.Themes;
|
|
using AtomUI.MotionScene;
|
|
using Avalonia;
|
|
using Avalonia.Animation.Easings;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Controls.Metadata;
|
|
using Avalonia.Controls.Primitives;
|
|
using Avalonia.Input;
|
|
using Avalonia.Interactivity;
|
|
using Avalonia.LogicalTree;
|
|
using Avalonia.Media;
|
|
using Avalonia.VisualTree;
|
|
|
|
namespace AtomUI.Desktop.Controls;
|
|
|
|
using AvaloniaTreeItem = Avalonia.Controls.TreeViewItem;
|
|
|
|
[PseudoClasses(TreeViewPseudoClass.NodeToggleTypeCheckBox, TreeViewPseudoClass.NodeToggleTypeRadio)]
|
|
public class TreeViewItem : AvaloniaTreeItem, IRadioButton, ITreeViewItemData
|
|
{
|
|
#region 公共属性定义
|
|
public static readonly StyledProperty<PathIcon?> IconProperty =
|
|
AvaloniaProperty.Register<TreeViewItem, PathIcon?>(nameof(Icon));
|
|
|
|
public static readonly StyledProperty<bool?> IsCheckedProperty =
|
|
AvaloniaProperty.Register<TreeViewItem, bool?>(nameof(IsChecked), false);
|
|
|
|
public static readonly StyledProperty<PathIcon?> SwitcherExpandIconProperty =
|
|
AvaloniaProperty.Register<TreeViewItem, PathIcon?>(nameof(SwitcherExpandIcon));
|
|
|
|
public static readonly StyledProperty<PathIcon?> SwitcherCollapseIconProperty =
|
|
AvaloniaProperty.Register<TreeViewItem, PathIcon?>(nameof(SwitcherCollapseIcon));
|
|
|
|
public static readonly StyledProperty<PathIcon?> SwitcherRotationIconProperty =
|
|
AvaloniaProperty.Register<TreeViewItem, PathIcon?>(nameof(SwitcherRotationIcon));
|
|
|
|
public static readonly StyledProperty<PathIcon?> SwitcherLoadingIconProperty =
|
|
AvaloniaProperty.Register<TreeViewItem, PathIcon?>(nameof(SwitcherRotationIcon));
|
|
|
|
public static readonly StyledProperty<PathIcon?> SwitcherLeafIconProperty =
|
|
AvaloniaProperty.Register<TreeViewItem, PathIcon?>(nameof(SwitcherLeafIcon));
|
|
|
|
public static readonly DirectProperty<TreeViewItem, bool> IsLeafProperty =
|
|
AvaloniaProperty.RegisterDirect<TreeViewItem, bool>(nameof(IsLeaf),
|
|
o => o.IsLeaf);
|
|
|
|
public static readonly StyledProperty<bool> IsLoadingProperty =
|
|
AvaloniaProperty.Register<TreeViewItem, bool>(nameof(IsLoading), false);
|
|
|
|
public static readonly StyledProperty<string?> GroupNameProperty =
|
|
RadioButton.GroupNameProperty.AddOwner<TreeViewItem>();
|
|
|
|
public static readonly StyledProperty<bool> IsIndicatorEnabledProperty =
|
|
AvaloniaProperty.Register<TreeViewItem, bool>(nameof(IsIndicatorEnabled), true);
|
|
|
|
public PathIcon? Icon
|
|
{
|
|
get => GetValue(IconProperty);
|
|
set => SetValue(IconProperty, value);
|
|
}
|
|
|
|
public PathIcon? SwitcherExpandIcon
|
|
{
|
|
get => GetValue(SwitcherExpandIconProperty);
|
|
set => SetValue(SwitcherExpandIconProperty, value);
|
|
}
|
|
|
|
public PathIcon? SwitcherCollapseIcon
|
|
{
|
|
get => GetValue(SwitcherCollapseIconProperty);
|
|
set => SetValue(SwitcherCollapseIconProperty, value);
|
|
}
|
|
|
|
public PathIcon? SwitcherRotationIcon
|
|
{
|
|
get => GetValue(SwitcherRotationIconProperty);
|
|
set => SetValue(SwitcherRotationIconProperty, value);
|
|
}
|
|
|
|
public PathIcon? SwitcherLoadingIcon
|
|
{
|
|
get => GetValue(SwitcherLoadingIconProperty);
|
|
set => SetValue(SwitcherLoadingIconProperty, value);
|
|
}
|
|
|
|
public PathIcon? SwitcherLeafIcon
|
|
{
|
|
get => GetValue(SwitcherLeafIconProperty);
|
|
set => SetValue(SwitcherLeafIconProperty, value);
|
|
}
|
|
|
|
public bool? IsChecked
|
|
{
|
|
get => GetValue(IsCheckedProperty);
|
|
set => SetValue(IsCheckedProperty, value);
|
|
}
|
|
|
|
private bool _isLeaf;
|
|
|
|
public bool IsLeaf
|
|
{
|
|
get => _isLeaf;
|
|
internal set => SetAndRaise(IsLeafProperty, ref _isLeaf, value);
|
|
}
|
|
|
|
public bool IsLoading
|
|
{
|
|
get => GetValue(IsLoadingProperty);
|
|
set => SetValue(IsLoadingProperty, value);
|
|
}
|
|
|
|
public string? GroupName
|
|
{
|
|
get => GetValue(GroupNameProperty);
|
|
set => SetValue(GroupNameProperty, value);
|
|
}
|
|
|
|
public bool IsIndicatorEnabled
|
|
{
|
|
get => GetValue(IsIndicatorEnabledProperty);
|
|
set => SetValue(IsIndicatorEnabledProperty, value);
|
|
}
|
|
|
|
IList<ITreeViewItemData> ITreeNode<ITreeViewItemData>.Children => Items.OfType<ITreeViewItemData>().ToList();
|
|
ITreeNode<ITreeViewItemData>? ITreeNode<ITreeViewItemData>.ParentNode => Parent as ITreeNode<ITreeViewItemData>;
|
|
|
|
public TreeNodeKey? ItemKey { get; set; }
|
|
|
|
#endregion
|
|
|
|
#region 公共事件定义
|
|
|
|
public static readonly RoutedEvent<RoutedEventArgs> ClickEvent =
|
|
RoutedEvent.Register<MenuItem, RoutedEventArgs>(
|
|
nameof(Click),
|
|
RoutingStrategies.Bubble);
|
|
|
|
public event EventHandler<RoutedEventArgs>? Click
|
|
{
|
|
add => AddHandler(ClickEvent, value);
|
|
remove => RemoveHandler(ClickEvent, value);
|
|
}
|
|
#endregion
|
|
|
|
#region 内部属性定义
|
|
|
|
internal static readonly DirectProperty<TreeViewItem, TreeItemHoverMode> NodeHoverModeProperty =
|
|
AvaloniaProperty.RegisterDirect<TreeViewItem, TreeItemHoverMode>(nameof(NodeHoverMode),
|
|
o => o.NodeHoverMode,
|
|
(o, v) => o.NodeHoverMode = v);
|
|
|
|
internal static readonly DirectProperty<TreeViewItem, bool> IsShowLineProperty =
|
|
AvaloniaProperty.RegisterDirect<TreeViewItem, bool>(nameof(IsShowLine),
|
|
o => o.IsShowLine,
|
|
(o, v) => o.IsShowLine = v);
|
|
|
|
internal static readonly DirectProperty<TreeViewItem, bool> IsShowIconProperty =
|
|
AvaloniaProperty.RegisterDirect<TreeViewItem, bool>(nameof(IsShowIcon),
|
|
o => o.IsShowIcon,
|
|
(o, v) => o.IsShowIcon = v);
|
|
|
|
internal static readonly DirectProperty<TreeViewItem, bool> IsDraggingProperty =
|
|
AvaloniaProperty.RegisterDirect<TreeViewItem, bool>(nameof(IsDragging),
|
|
o => o.IsDragging,
|
|
(o, v) => o.IsDragging = v);
|
|
|
|
internal static readonly DirectProperty<TreeViewItem, bool> IsDragOverProperty =
|
|
AvaloniaProperty.RegisterDirect<TreeViewItem, bool>(nameof(IsDragOver),
|
|
o => o.IsDragOver,
|
|
(o, v) => o.IsDragOver = v);
|
|
|
|
internal static readonly StyledProperty<bool> IsMotionEnabledProperty =
|
|
MotionAwareControlProperty.IsMotionEnabledProperty.AddOwner<TreeViewItem>();
|
|
|
|
internal static readonly DirectProperty<TreeViewItem, bool> IsSwitcherRotationProperty =
|
|
AvaloniaProperty.RegisterDirect<TreeViewItem, bool>(
|
|
nameof(IsSwitcherRotation),
|
|
o => o.IsSwitcherRotation,
|
|
(o, v) => o.IsSwitcherRotation = v);
|
|
|
|
internal static readonly StyledProperty<ItemToggleType> ToggleTypeProperty =
|
|
TreeView.ToggleTypeProperty.AddOwner<TreeViewItem>();
|
|
|
|
internal static readonly StyledProperty<bool> IsShowLeafIconProperty =
|
|
AvaloniaProperty.Register<TreeViewItem, bool>(nameof(IsShowLeafIcon));
|
|
|
|
private TreeItemHoverMode _nodeHoverMode;
|
|
|
|
internal TreeItemHoverMode NodeHoverMode
|
|
{
|
|
get => _nodeHoverMode;
|
|
set => SetAndRaise(NodeHoverModeProperty, ref _nodeHoverMode, value);
|
|
}
|
|
|
|
private bool _isShowLine;
|
|
|
|
internal bool IsShowLine
|
|
{
|
|
get => _isShowLine;
|
|
set => SetAndRaise(IsShowLineProperty, ref _isShowLine, value);
|
|
}
|
|
|
|
private bool _isShowIcon = true;
|
|
|
|
internal bool IsShowIcon
|
|
{
|
|
get => _isShowIcon;
|
|
set => SetAndRaise(IsShowIconProperty, ref _isShowIcon, value);
|
|
}
|
|
|
|
private bool _isDragging;
|
|
|
|
internal bool IsDragging
|
|
{
|
|
get => _isDragging;
|
|
set => SetAndRaise(IsDraggingProperty, ref _isDragging, value);
|
|
}
|
|
|
|
private bool _isDragOver;
|
|
|
|
internal bool IsDragOver
|
|
{
|
|
get => _isDragOver;
|
|
set => SetAndRaise(IsDragOverProperty, ref _isDragOver, value);
|
|
}
|
|
|
|
internal bool IsMotionEnabled
|
|
{
|
|
get => GetValue(IsMotionEnabledProperty);
|
|
set => SetValue(IsMotionEnabledProperty, value);
|
|
}
|
|
|
|
private bool _isSwitcherRotation;
|
|
|
|
internal bool IsSwitcherRotation
|
|
{
|
|
get => _isSwitcherRotation;
|
|
set => SetAndRaise(IsSwitcherRotationProperty, ref _isSwitcherRotation, value);
|
|
}
|
|
|
|
public ItemToggleType ToggleType
|
|
{
|
|
get => GetValue(ToggleTypeProperty);
|
|
set => SetValue(ToggleTypeProperty, value);
|
|
}
|
|
|
|
internal bool IsShowLeafIcon
|
|
{
|
|
get => GetValue(IsShowLeafIconProperty);
|
|
set => SetValue(IsShowLeafIconProperty, value);
|
|
}
|
|
|
|
internal TreeView? OwnerTreeView { get; set; }
|
|
|
|
private ITreeViewInteractionHandler? TreeViewInteractionHandler => this.FindLogicalAncestorOfType<TreeView>()?.InteractionHandler;
|
|
|
|
#endregion
|
|
|
|
private bool _animating;
|
|
private BaseMotionActor? _itemsPresenterMotionActor;
|
|
private readonly BorderRenderHelper _borderRenderHelper;
|
|
private TreeViewItemHeader? _header;
|
|
private readonly Dictionary<TreeViewItem, CompositeDisposable> _itemsBindingDisposables = new();
|
|
|
|
static TreeViewItem()
|
|
{
|
|
AffectsRender<TreeViewItem>(
|
|
IsShowLineProperty,
|
|
IsShowLeafIconProperty,
|
|
IsDraggingProperty,
|
|
IsDragOverProperty,
|
|
BorderBrushProperty,
|
|
BorderThicknessProperty,
|
|
NodeHoverModeProperty,
|
|
BackgroundProperty);
|
|
}
|
|
|
|
public TreeViewItem()
|
|
{
|
|
_borderRenderHelper = new BorderRenderHelper();
|
|
LogicalChildren.CollectionChanged += HandleItemsCollectionChanged;
|
|
}
|
|
|
|
private void HandleItemsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (e.Action == NotifyCollectionChangedAction.Remove)
|
|
{
|
|
if (e.OldItems != null)
|
|
{
|
|
foreach (var item in e.OldItems)
|
|
{
|
|
if (item is TreeViewItem treeViewItem)
|
|
{
|
|
if (_itemsBindingDisposables.TryGetValue(treeViewItem, out var disposable))
|
|
{
|
|
disposable.Dispose();
|
|
}
|
|
_itemsBindingDisposables.Remove(treeViewItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
|
{
|
|
base.OnAttachedToVisualTree(e);
|
|
OwnerTreeView = this.GetLogicalAncestors().OfType<TreeView>().FirstOrDefault();
|
|
}
|
|
|
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
|
|
{
|
|
base.OnPropertyChanged(change);
|
|
|
|
if (change.Property == ItemCountProperty)
|
|
{
|
|
IsLeaf = ItemCount == 0;
|
|
}
|
|
|
|
else if (change.Property == GroupNameProperty)
|
|
{
|
|
HandleGroupNameChanged(change);
|
|
}
|
|
else if (change.Property == IsCheckedProperty)
|
|
{
|
|
HandleIsCheckedChanged(change);
|
|
}
|
|
else if (change.Property == ToggleTypeProperty)
|
|
{
|
|
HandleToggleTypeChanged(change);
|
|
}
|
|
|
|
if (this.IsAttachedToVisualTree())
|
|
{
|
|
if (change.Property == IsExpandedProperty)
|
|
{
|
|
HandleExpandedChanged();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void HandleExpandedChanged()
|
|
{
|
|
if (IsExpanded)
|
|
{
|
|
ExpandChildren();
|
|
}
|
|
else
|
|
{
|
|
CollapseChildren();
|
|
}
|
|
}
|
|
|
|
private void HandleGroupNameChanged(AvaloniaPropertyChangedEventArgs change)
|
|
{
|
|
(TreeViewInteractionHandler as DefaultTreeViewInteractionHandler)?.OnGroupOrTypeChanged(this, change.GetOldValue<string>());
|
|
}
|
|
|
|
private void HandleToggleTypeChanged(AvaloniaPropertyChangedEventArgs change)
|
|
{
|
|
var newValue = change.GetNewValue<ItemToggleType>();
|
|
PseudoClasses.Set(TreeViewPseudoClass.NodeToggleTypeRadio, newValue == ItemToggleType.Radio);
|
|
PseudoClasses.Set(TreeViewPseudoClass.NodeToggleTypeCheckBox, newValue == ItemToggleType.CheckBox);
|
|
|
|
(TreeViewInteractionHandler as DefaultTreeViewInteractionHandler)?.OnGroupOrTypeChanged(this, GroupName);
|
|
}
|
|
|
|
private void HandleIsCheckedChanged(AvaloniaPropertyChangedEventArgs change)
|
|
{
|
|
var newValue = change.GetNewValue<bool?>();
|
|
PseudoClasses.Set(StdPseudoClass.Checked, newValue == true);
|
|
|
|
(TreeViewInteractionHandler as DefaultTreeViewInteractionHandler)?.OnCheckedChanged(this);
|
|
}
|
|
|
|
private void ExpandChildren()
|
|
{
|
|
if (_itemsPresenterMotionActor is null ||
|
|
_animating ||
|
|
OwnerTreeView is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!IsMotionEnabled || OwnerTreeView.IsExpandAllProcess)
|
|
{
|
|
_itemsPresenterMotionActor.Opacity = 1.0;
|
|
_itemsPresenterMotionActor.IsVisible = true;
|
|
return;
|
|
}
|
|
|
|
_animating = true;
|
|
_header?.NotifyAnimating(true);
|
|
|
|
var motion = OwnerTreeView.OpenMotion ?? new ExpandMotion(Direction.Top, null, new CubicEaseOut());
|
|
motion.Duration = OwnerTreeView.MotionDuration;
|
|
|
|
motion.Run(_itemsPresenterMotionActor, () => { _itemsPresenterMotionActor.IsVisible = true; },
|
|
() =>
|
|
{
|
|
_animating = false;
|
|
_header?.NotifyAnimating(false);
|
|
});
|
|
}
|
|
|
|
private void CollapseChildren()
|
|
{
|
|
if (_itemsPresenterMotionActor is null ||
|
|
_animating ||
|
|
OwnerTreeView is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!IsMotionEnabled || OwnerTreeView.IsExpandAllProcess)
|
|
{
|
|
_itemsPresenterMotionActor.Opacity = 0.0;
|
|
_itemsPresenterMotionActor.IsVisible = false;
|
|
return;
|
|
}
|
|
|
|
_animating = true;
|
|
_header?.NotifyAnimating(true);
|
|
|
|
var motion = OwnerTreeView.CloseMotion ?? new CollapseMotion(Direction.Top, null, new CubicEaseIn());
|
|
motion.Duration = OwnerTreeView.MotionDuration;
|
|
|
|
motion.Run(_itemsPresenterMotionActor, null, () =>
|
|
{
|
|
_itemsPresenterMotionActor.IsVisible = false;
|
|
_animating = false;
|
|
_header?.NotifyAnimating(false);
|
|
});
|
|
}
|
|
|
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
|
{
|
|
base.OnApplyTemplate(e);
|
|
_header = e.NameScope.Find<TreeViewItemHeader>(TreeViewItemThemeConstants.HeaderPart);
|
|
_itemsPresenterMotionActor =
|
|
e.NameScope.Find<BaseMotionActor>(TreeViewItemThemeConstants.ItemsPresenterMotionActorPart);
|
|
|
|
IsLeaf = ItemCount == 0;
|
|
|
|
var originIsMotionEnabled = IsMotionEnabled;
|
|
try
|
|
{
|
|
SetCurrentValue(IsMotionEnabledProperty, false);
|
|
HandleExpandedChanged();
|
|
}
|
|
finally
|
|
{
|
|
SetCurrentValue(IsMotionEnabledProperty, originIsMotionEnabled);
|
|
}
|
|
|
|
}
|
|
|
|
internal bool IsEffectiveCheckable()
|
|
{
|
|
if (!IsEnabled || !IsIndicatorEnabled || ToggleType == ItemToggleType.None)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public override void Render(DrawingContext context)
|
|
{
|
|
if (NodeHoverMode == TreeItemHoverMode.WholeLine && _header != null)
|
|
{
|
|
using var state = context.PushTransform(Matrix.CreateTranslation(_header.Bounds.X, 0));
|
|
_borderRenderHelper.Render(context,
|
|
_header.Bounds.Size,
|
|
new Thickness(),
|
|
CornerRadius,
|
|
BackgroundSizing.InnerBorderEdge,
|
|
Background,
|
|
null);
|
|
}
|
|
|
|
if (IsShowLine)
|
|
{
|
|
RenderTreeNodeLine(context);
|
|
}
|
|
}
|
|
|
|
private void RenderTreeNodeLine(DrawingContext context)
|
|
{
|
|
if (_header is null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var penWidth = BorderThickness.Top;
|
|
using var state = context.PushRenderOptions(new RenderOptions
|
|
{
|
|
EdgeMode = EdgeMode.Aliased
|
|
});
|
|
|
|
var isLastChild = false;
|
|
if (Parent is ItemsControl parentTreeItem)
|
|
{
|
|
if (parentTreeItem.ContainerFromIndex(parentTreeItem.ItemCount - 1) == this)
|
|
{
|
|
isLastChild = true;
|
|
}
|
|
}
|
|
|
|
var switcherButtonRect = _header.SwitcherButtonRect(this);
|
|
|
|
if (!IsLeaf && !isLastChild && _itemsPresenterMotionActor?.IsVisible == true)
|
|
{
|
|
var switcherMiddleBottom = new Point(switcherButtonRect.X + switcherButtonRect.Width / 2, switcherButtonRect.Bottom);
|
|
var blockStartPoint = new Point(switcherMiddleBottom.X, switcherMiddleBottom.Y);
|
|
var blockEndPoint = new Point(blockStartPoint.X, DesiredSize.Height);
|
|
context.DrawLine(new Pen(BorderBrush, penWidth), blockStartPoint, blockEndPoint);
|
|
}
|
|
|
|
// 画孩子线条
|
|
if (!IsShowLeafIcon && IsLeaf)
|
|
{
|
|
{
|
|
// 纵向
|
|
var childStartPoint = new Point(switcherButtonRect.X + switcherButtonRect.Width / 2, switcherButtonRect.Top);
|
|
var childEndPoint = new Point(switcherButtonRect.X + switcherButtonRect.Width / 2, isLastChild ? switcherButtonRect.Bottom : switcherButtonRect.Top + DesiredSize.Height);
|
|
if (isLastChild)
|
|
{
|
|
childEndPoint = childEndPoint.WithY(childEndPoint.Y / 2);
|
|
}
|
|
|
|
context.DrawLine(new Pen(BorderBrush, penWidth), childStartPoint.WithY(0), childEndPoint);
|
|
}
|
|
|
|
{
|
|
// 横向
|
|
var childStartPoint = new Point(switcherButtonRect.X + switcherButtonRect.Width / 2 - penWidth / 2, switcherButtonRect.Top + switcherButtonRect.Height / 2 - penWidth / 2);
|
|
var childEndPoint = new Point(switcherButtonRect.Right, switcherButtonRect.Top + switcherButtonRect.Height / 2 - penWidth / 2);
|
|
|
|
context.DrawLine(new Pen(BorderBrush, penWidth), childStartPoint, childEndPoint);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal Rect GetDragBounds(bool includeChildren = false)
|
|
{
|
|
var dragOffset = _header?.GetDragIndicatorOffset(this) ?? default;
|
|
var offsetX = dragOffset.X;
|
|
return new Rect(new Point(offsetX, 0),
|
|
new Size(Bounds.Width - offsetX,
|
|
includeChildren ? Bounds.Height : _header?.Bounds.Height ?? default));
|
|
}
|
|
|
|
internal bool IsInDragBounds(Point point)
|
|
{
|
|
return GetDragBounds(true).Contains(point);
|
|
}
|
|
|
|
internal bool IsInDragHeaderBounds(Point point)
|
|
{
|
|
return GetDragBounds().Contains(point);
|
|
}
|
|
|
|
internal bool IsDragOverForOffsetY(Point point)
|
|
{
|
|
if (OwnerTreeView is null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return new Rect(Bounds.Size).Contains(point);
|
|
}
|
|
|
|
internal DragPreviewAdorner BuildPreviewAdorner()
|
|
{
|
|
return new DragPreviewAdorner(_header!);
|
|
}
|
|
|
|
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
|
{
|
|
base.OnPointerReleased(e);
|
|
if (PointInHeaderBounds(e))
|
|
{
|
|
NotifyHeaderClick();
|
|
}
|
|
}
|
|
|
|
internal bool PointInHeaderBounds(PointerReleasedEventArgs e)
|
|
{
|
|
var bounds = new Rect(new Point(0, 0), _header?.DesiredSize ?? default);
|
|
var point = e.GetPosition(_header);
|
|
return bounds.Contains(point);
|
|
}
|
|
|
|
protected virtual void NotifyHeaderClick()
|
|
{
|
|
}
|
|
|
|
internal void RaiseClick() => RaiseEvent(new RoutedEventArgs(ClickEvent));
|
|
|
|
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
|
|
{
|
|
return new TreeViewItem();
|
|
}
|
|
|
|
protected override bool NeedsContainerOverride(
|
|
object? item,
|
|
int index,
|
|
out object? recycleKey)
|
|
{
|
|
return NeedsContainer<TreeViewItem>(item, out recycleKey);
|
|
}
|
|
|
|
protected override void ContainerForItemPreparedOverride(
|
|
Control container,
|
|
object? item,
|
|
int index)
|
|
{
|
|
base.ContainerForItemPreparedOverride(container, item, index);
|
|
if (container is TreeViewItem treeViewItem)
|
|
{
|
|
treeViewItem.OwnerTreeView = OwnerTreeView;
|
|
var disposables = new CompositeDisposable(8);
|
|
|
|
if (item != null && item is not Visual && item is ITreeViewItemData treeViewItemData)
|
|
{
|
|
ApplyNodeData(treeViewItem, treeViewItemData);
|
|
}
|
|
|
|
if (ItemTemplate != null)
|
|
{
|
|
disposables.Add(BindUtils.RelayBind(this, ItemTemplateProperty, treeViewItem, TreeViewItem.HeaderTemplateProperty));
|
|
}
|
|
|
|
disposables.Add(BindUtils.RelayBind(this, IsMotionEnabledProperty, treeViewItem, TreeViewItem.IsMotionEnabledProperty));
|
|
disposables.Add(BindUtils.RelayBind(this, NodeHoverModeProperty, treeViewItem, TreeViewItem.NodeHoverModeProperty));
|
|
disposables.Add(BindUtils.RelayBind(this, IsShowLineProperty, treeViewItem, TreeViewItem.IsShowLineProperty));
|
|
disposables.Add(BindUtils.RelayBind(this, IsShowIconProperty, treeViewItem, TreeViewItem.IsShowIconProperty));
|
|
disposables.Add(BindUtils.RelayBind(this, IsShowLeafIconProperty, treeViewItem,
|
|
TreeViewItem.IsShowLeafIconProperty));
|
|
disposables.Add(BindUtils.RelayBind(this, IsSwitcherRotationProperty, treeViewItem, TreeViewItem.IsSwitcherRotationProperty));
|
|
disposables.Add(BindUtils.RelayBind(this, ToggleTypeProperty, treeViewItem, TreeViewItem.ToggleTypeProperty));
|
|
|
|
PrepareTreeViewItem(treeViewItem, item, index, disposables);
|
|
|
|
if (_itemsBindingDisposables.TryGetValue(treeViewItem, out var oldDisposables))
|
|
{
|
|
oldDisposables.Dispose();
|
|
_itemsBindingDisposables.Remove(treeViewItem);
|
|
}
|
|
_itemsBindingDisposables.Add(treeViewItem, disposables);
|
|
}
|
|
else
|
|
{
|
|
throw new ArgumentOutOfRangeException(nameof(container), "The container type is incorrect, it must be type TreeViewItem.");
|
|
}
|
|
}
|
|
|
|
protected virtual void PrepareTreeViewItem(TreeViewItem treeViewItem, object? item, int index, CompositeDisposable compositeDisposable)
|
|
{
|
|
}
|
|
|
|
internal static void ApplyNodeData(TreeViewItem treeViewItem, ITreeViewItemData treeViewItemData)
|
|
{
|
|
if (treeViewItemData is not Visual)
|
|
{
|
|
if (!treeViewItem.IsSet(TreeViewItem.HeaderProperty))
|
|
{
|
|
treeViewItem.SetCurrentValue(TreeViewItem.HeaderProperty, treeViewItem);
|
|
}
|
|
|
|
if (!treeViewItem.IsSet(TreeViewItem.IconProperty))
|
|
{
|
|
treeViewItem.SetCurrentValue(TreeViewItem.IconProperty, treeViewItemData.Icon);
|
|
}
|
|
|
|
if (treeViewItem.ItemKey == null)
|
|
{
|
|
treeViewItem.ItemKey = treeViewItemData.ItemKey;
|
|
}
|
|
|
|
if (!treeViewItem.IsSet(TreeViewItem.IsCheckedProperty))
|
|
{
|
|
treeViewItem.SetCurrentValue(TreeViewItem.IsCheckedProperty, treeViewItemData.IsChecked);
|
|
}
|
|
|
|
if (!treeViewItem.IsSet(TreeViewItem.IsSelectedProperty))
|
|
{
|
|
treeViewItem.SetCurrentValue(TreeViewItem.IsSelectedProperty, treeViewItemData.IsSelected);
|
|
}
|
|
|
|
if (!treeViewItem.IsSet(TreeViewItem.IsEnabledProperty))
|
|
{
|
|
treeViewItem.SetCurrentValue(TreeViewItem.IsEnabledProperty, treeViewItemData.IsEnabled);
|
|
}
|
|
|
|
if (!treeViewItem.IsSet(TreeViewItem.IsExpandedProperty))
|
|
{
|
|
treeViewItem.SetCurrentValue(TreeViewItem.IsExpandedProperty, treeViewItemData.IsExpanded);
|
|
}
|
|
if (!treeViewItem.IsSet(TreeViewItem.IsIndicatorEnabledProperty))
|
|
{
|
|
treeViewItem.SetCurrentValue(TreeViewItem.IsIndicatorEnabledProperty, treeViewItemData.IsIndicatorEnabled);
|
|
}
|
|
}
|
|
}
|
|
} |