using CustomControls; using System.Windows.Controls; namespace System.Windows.Forms { public partial class TreeView : Control { private bool mouseMoved; private Point mouseDownPoint; private TreeNode selectedNodeAtMouseDown; private TreeNode nodeAtMouseDown; private TreeNode editedNode; private Timer labelEditTimer; private bool nodeDoubleClicked; protected new CustomControls.TreeView control { get; } public TreeNodeCollection Nodes { get; } public bool CheckBoxes { get => control.CheckBoxes == Visibility.Visible; set => control.CheckBoxes = value ? Visibility.Visible : Visibility.Collapsed; } private ImageList imageList; public ImageList ImageList { get => imageList; set { imageList = value; control.Images = value != null ? Visibility.Visible : Visibility.Collapsed; ResetImageList(); } } public bool LabelEdit { get; set; } private TreeNode selectedNode; public TreeNode SelectedNode { get => selectedNode; set { if (value != selectedNode) { if (value != null) { value.IsSelected = true; } else if (SelectedNode != null) { SelectedNode.IsSelected = false; } selectedNode = value; SelectItemHelper.Select(control, value); } } } public bool HideSelection { get => control.HideSelection; set => control.HideSelection = value; } public string PathSeparator { get; set; } public bool Sorted { get; set; } // TODO? // not applicable / ignored public int ImageIndex { get; set; } public int SelectedImageIndex { get; set; } public bool ShowRootLines { get; set; } public bool ShowPlusMinus { get; set; } public bool ShowLines { get; set; } public int Indent { get; set; } public bool RightToLeftLayout { get; set; } public bool HotTracking { get; set; } public int ItemHeight { get; set; } public event TreeViewEventHandler AfterCheck; public event TreeViewEventHandler AfterCollapse; public event TreeViewEventHandler AfterExpand; public event TreeViewEventHandler AfterSelect; public event TreeViewCancelEventHandler BeforeCheck; public event TreeViewCancelEventHandler BeforeCollapse; public event TreeViewCancelEventHandler BeforeExpand; public event TreeViewCancelEventHandler BeforeSelect; public event ItemDragEventHandler ItemDrag; public event TreeNodeMouseClickEventHandler NodeMouseClick; public event TreeNodeMouseClickEventHandler NodeMouseDoubleClick; public event NodeLabelEditEventHandler AfterLabelEdit; public event NodeLabelEditEventHandler BeforeLabelEdit; public new event MouseEventHandler MouseUp; public new event KeyEventHandler KeyDown; private void WireEvents() { control.AcceptEdit += (sender, e) => editedNode?.EndEdit(false); control.CancelEdit += (sender, e) => editedNode?.EndEdit(true); control.PreviewMouseDown += (sender, e) => { mouseMoved = false; selectedNodeAtMouseDown = SelectedNode; mouseDownPoint = e.GetPosition(control); // do not allow change selection in MouseDown. Do it in MouseUp to be consistent with WinForms nodeAtMouseDown = NodeHitTest(mouseDownPoint); if (nodeAtMouseDown != null) { e.Handled = true; if (e.ClickCount == 2 && e.LeftButton == Input.MouseButtonState.Pressed) nodeDoubleClicked = true; } }; control.MouseMove += (sender, e) => { if (e.LeftButton == Input.MouseButtonState.Pressed) { var pos = e.GetPosition(control); if (!mouseMoved && Math.Sqrt(Math.Pow(mouseDownPoint.X - pos.X, 2) + Math.Pow(mouseDownPoint.Y - pos.Y, 2)) > 5) { if (nodeAtMouseDown != null) { control.Focus(); OnItemDrag(new ItemDragEventArgs(MouseButtons.Left, nodeAtMouseDown)); } mouseMoved = true; } } }; control.MouseUp += (sender, e) => { if (!mouseMoved) { var pos = e.GetPosition(control); var node = NodeHitTest(pos); if (node != null) { // without this, editing label won't work correctly control.Focus(); mouseMoved = true; // workaround: do not allow MouseMove if context menu was shown in the OnNodeMouseClick if (nodeDoubleClicked) { StopLabelEditTimer(); OnNodeMouseDoubleClick(new TreeNodeMouseClickEventArgs(nodeAtMouseDown, MouseButtons.Left, 0, (int)(mouseDownPoint.X * DpiScale), (int)(mouseDownPoint.Y * DpiScale))); } else { OnNodeMouseClick(new TreeNodeMouseClickEventArgs(node, Helper.GetMouseButton(e.ChangedButton), 0, (int)(pos.X * DpiScale), (int)(pos.Y * DpiScale))); } OnMouseUp(Helper.GetMouseEventArgs(control, e)); if (e.ChangedButton == Input.MouseButton.Left) { node.IsSelected = true; if (LabelEdit && !nodeDoubleClicked && selectedNodeAtMouseDown == node && NodeHitTest(pos, true) != null) StartLabelEditTimer(); } nodeDoubleClicked = false; } } }; control.PreviewKeyDown += (sender, e) => { if (editedNode == null) { var args = Helper.GetKeyEventArgs(e); OnKeyDown(args); if (args.Handled) e.Handled = true; } }; } private TreeNode NodeHitTest(Point point, bool textOnly = false) { var item = control.InputHitTest(point); if (item is TextBlock txt && txt.Name == "PART_TextBlock") { var node = txt.DataContext as TreeNode; if (node != null) { var rect = txt.BoundsRelativeTo(control); node.SetBounds(new Drawing.Rectangle((int)(rect.Left * DpiScale), (int)(rect.Top * DpiScale), (int)(rect.Width * DpiScale), (int)(rect.Height * DpiScale))); } return node; } if (!textOnly && item is Image img && img.Name == "PART_Image") return img.DataContext as TreeNode; return null; } private void ResetImageList() { foreach (var node in Nodes) node.ResetImageSourceAll(); } private void StartLabelEditTimer() { if (labelEditTimer == null) labelEditTimer = new Timer(); labelEditTimer.Interval = SystemInformation.DoubleClickTime; labelEditTimer.Start(); labelEditTimer.Tick += (s, e) => { labelEditTimer.Stop(); selectedNodeAtMouseDown?.BeginEdit(); }; } private void StopLabelEditTimer() { if (labelEditTimer != null) { labelEditTimer.Stop(); labelEditTimer = null; } } protected new virtual void OnMouseUp(MouseEventArgs e) => MouseUp?.Invoke(this, e); protected new virtual void OnKeyDown(KeyEventArgs e) => KeyDown?.Invoke(this, e); protected virtual void OnAfterCheck(TreeViewEventArgs e) => AfterCheck?.Invoke(this, e); protected virtual void OnAfterCollapse(TreeViewEventArgs e) => AfterCollapse?.Invoke(this, e); protected virtual void OnAfterExpand(TreeViewEventArgs e) => AfterExpand?.Invoke(this, e); protected virtual void OnAfterSelect(TreeViewEventArgs e) => AfterSelect?.Invoke(this, e); protected virtual void OnBeforeCheck(TreeViewCancelEventArgs e) => BeforeCheck?.Invoke(this, e); protected virtual void OnBeforeCollapse(TreeViewCancelEventArgs e) => BeforeCollapse?.Invoke(this, e); protected virtual void OnBeforeExpand(TreeViewCancelEventArgs e) => BeforeExpand?.Invoke(this, e); protected virtual void OnBeforeSelect(TreeViewCancelEventArgs e) => BeforeSelect?.Invoke(this, e); protected virtual void OnItemDrag(ItemDragEventArgs e) => ItemDrag?.Invoke(this, e); protected virtual void OnNodeMouseDoubleClick(TreeNodeMouseClickEventArgs e) => NodeMouseDoubleClick?.Invoke(this, e); protected virtual void OnNodeMouseClick(TreeNodeMouseClickEventArgs e) => NodeMouseClick?.Invoke(this, e); protected virtual void OnAfterLabelEdit(NodeLabelEditEventArgs e) => AfterLabelEdit?.Invoke(this, e); protected virtual void OnBeforeLabelEdit(NodeLabelEditEventArgs e) => BeforeLabelEdit?.Invoke(this, e); internal bool DoNodeSelect(TreeNode node) { var args = new TreeViewCancelEventArgs(node, false, TreeViewAction.Unknown); OnBeforeSelect(args); if (args.Cancel) return false; selectedNode = node; OnAfterSelect(new TreeViewEventArgs(node)); return true; } internal void DoAfterCheck(TreeViewEventArgs e) => OnAfterCheck(e); internal void DoAfterCollapse(TreeViewEventArgs e) => OnAfterCollapse(e); internal void DoAfterExpand(TreeViewEventArgs e) => OnAfterExpand(e); internal void DoBeforeCheck(TreeViewCancelEventArgs e) => OnBeforeCheck(e); internal void DoBeforeExpand(TreeViewCancelEventArgs e) => OnBeforeExpand(e); internal void DoBeforeCollapse(TreeViewCancelEventArgs e) => OnBeforeCollapse(e); internal void DoOnAfterLabelEdit(NodeLabelEditEventArgs e) => OnAfterLabelEdit(e); internal void DoOnBeforeLabelEdit(NodeLabelEditEventArgs e) => OnBeforeLabelEdit(e); internal void SetEditedNode(TreeNode node) => editedNode = node; public void ExpandAll() { foreach (var node in Nodes) node.ExpandAll(); } public void CollapseAll() { foreach (var node in Nodes) node.CollapseAll(); } public TreeNode GetNodeAt(Drawing.Point point) => GetNodeAt(point.X, point.Y); public TreeNode GetNodeAt(int x, int y) => NodeHitTest(new Point(x / DpiScale, y / DpiScale)); public void BeginUpdate() { Nodes.RaiseListChangedEvents = false; } public void EndUpdate() { Nodes.RaiseListChangedEvents = true; Nodes.ResetBindings(); } public TreeView() { Nodes = new(null); Nodes.SetOwner(this); control = new(); control.ItemsSource = Nodes; VirtualizingPanel.SetIsVirtualizing(control, true); VirtualizingPanel.SetVirtualizationMode(control, VirtualizationMode.Recycling); WireEvents(); // do this after wiring events. Some event handlers inside SetControl may set the Handled flag to true which prevents additional event handlers to fire up SetControl(control); PathSeparator = "\\"; } } }