123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- using System;
- using System.Collections.Generic;
- using System.ComponentModel;
- using System.Drawing;
- using System.Windows.Forms;
- namespace FastReport.Controls
- {
- /// <summary>
- /// TreeView control with multiselect support.
- /// </summary>
- /// <remarks>
- /// This control is for internal use only.
- /// </remarks>
- [ToolboxItem(false)]
- public class TreeViewMultiSelect : TreeView
- {
- private bool trackSelection;
- private Timer labelEditTimer;
- private List<TreeNode> selectedNodes;
- internal List<TreeNode> SelectedNodes
- {
- get => selectedNodes;
- set
- {
- UnpaintSelectedNodes();
- selectedNodes.Clear();
- selectedNodes.AddRange(value);
- PaintSelectedNodes();
- }
- }
- internal new TreeNode SelectedNode
- {
- get => SelectedNodes.Count == 0 ? null : SelectedNodes[0];
- set
- {
- // do not use TreeView.SelectedNode. Simulate selection using TreeNode.BackColor.
- // This way we have full control over selection and eliminate treeview's unexpected behaviour.
- // However we also need to implement such things as invoke label edit, keyboard navigation
- base.SelectedNode = null;
- UnpaintSelectedNodes();
- SelectedNodes.Clear();
- if (value != null)
- SelectedNodes.Add(value);
- PaintSelectedNodes();
- OnSelectionChanged();
- }
- }
- internal void TrackSelection()
- {
- trackSelection = true;
- base.SelectedNode = SelectedNode;
- base.SelectedNode = null;
- trackSelection = false;
- }
- internal event EventHandler SelectionChanged;
- internal event MouseEventHandler RightMouseButtonClicked;
- private void OnSelectionChanged() => SelectionChanged?.Invoke(this, EventArgs.Empty);
- private void OnRightMouseButtonClicked(MouseEventArgs e) => RightMouseButtonClicked?.Invoke(this, e);
- private List<TreeNode> GetAllNodes(bool visibleOnly = false)
- {
- var allNodes = new List<TreeNode>();
- GetAllNodes(Nodes, allNodes, visibleOnly);
- return allNodes;
- }
- private void GetAllNodes(TreeNodeCollection nodes, List<TreeNode> allNodes, bool visibleOnly)
- {
- foreach (TreeNode node in nodes)
- {
- allNodes.Add(node);
- if (node.IsExpanded || !visibleOnly)
- GetAllNodes(node.Nodes, allNodes, visibleOnly);
- }
- }
- private void PaintSelectedNodes()
- {
- Color backColor = SystemColors.Highlight;
- Color foreColor = SystemColors.HighlightText;
- foreach (TreeNode node in SelectedNodes)
- {
- node.BackColor = backColor;
- node.ForeColor = foreColor;
- }
- }
- private void UnpaintSelectedNodes()
- {
- foreach (TreeNode node in SelectedNodes)
- {
- node.BackColor = BackColor;
- node.ForeColor = ForeColor;
- }
- }
- private void EnsureItemSelected(TreeNode node)
- {
- if (node != null && !SelectedNodes.Contains(node))
- {
- SelectedNode = node;
- }
- }
- private void StartLabelEditTimer()
- {
- if (labelEditTimer == null)
- labelEditTimer = new Timer();
- labelEditTimer.Interval = SystemInformation.DoubleClickTime;
- labelEditTimer.Start();
- labelEditTimer.Tick += (s, e) =>
- {
- StopLabelEditTimer();
- SelectedNode?.BeginEdit();
- };
- }
- private void StopLabelEditTimer()
- {
- if (labelEditTimer != null)
- {
- labelEditTimer.Stop();
- labelEditTimer.Dispose();
- labelEditTimer = null;
- }
- }
- private void DoNodeClick(TreeNode node)
- {
- UnpaintSelectedNodes();
- if (ModifierKeys == Keys.None)
- {
- // regular click (w/o Ctrl or Shift): select clicked node
- SelectedNodes.Clear();
- SelectedNodes.Add(node);
- }
- else if (ModifierKeys == Keys.Control)
- {
- // click with Ctrl: toggle node selection
- if (SelectedNodes.Contains(node))
- SelectedNodes.Remove(node);
- else
- SelectedNodes.Add(node);
- // keep one node selected
- if (SelectedNodes.Count == 0)
- SelectedNodes.Add(node);
- }
- else if (ModifierKeys == Keys.Shift)
- {
- // click with Shift: select all nodes between the "start" node and clicked node
- if (SelectedNodes.Count == 0)
- {
- // nothing selected yet?
- SelectedNodes.Add(node);
- }
- else
- {
- // find the start and the end indexes
- var allNodes = GetAllNodes();
- // the start index is the index of the first selected node
- int startIndex = allNodes.IndexOf(SelectedNodes[0]);
- // the end index is the index of clicked node
- int endIndex = allNodes.IndexOf(node);
- SelectedNodes.Clear();
- if (startIndex <= endIndex)
- {
- // direct selection
- for (int i = startIndex; i <= endIndex; i++)
- SelectedNodes.Add(allNodes[i]);
- }
- else if (endIndex < startIndex)
- {
- // reverse selection
- for (int i = startIndex; i >= endIndex; i--)
- SelectedNodes.Add(allNodes[i]);
- }
- }
- }
- PaintSelectedNodes();
- }
- /// <inheritdoc/>
- protected override void OnBeforeSelect(TreeViewCancelEventArgs e)
- {
- if (trackSelection)
- return;
- // we don't use TreeView.SelectedNode. Prevent node selection
- e.Cancel = true;
- }
- /// <inheritdoc/>
- protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e)
- {
- if (e.Button == MouseButtons.Left)
- {
- if (ModifierKeys == Keys.None && SelectedNodes.Count == 1 && SelectedNode == e.Node)
- {
- // clicked on a single, already selected node: doubleclick or label edit
- if (e.X >= e.Node.Bounds.X) // do we clicked on a node label?
- StartLabelEditTimer();
- }
- else
- {
- // handle multiselection
- DoNodeClick(e.Node);
- OnSelectionChanged();
- }
- }
- else if (e.Button == MouseButtons.Right)
- {
- // clicked not-yet-selected item: select it
- EnsureItemSelected(e.Node);
- OnRightMouseButtonClicked(e);
- }
- }
- /// <inheritdoc/>
- protected override void OnNodeMouseDoubleClick(TreeNodeMouseClickEventArgs e)
- {
- base.OnNodeMouseDoubleClick(e);
- StopLabelEditTimer();
- }
- /// <inheritdoc/>
- protected override void OnItemDrag(ItemDragEventArgs e)
- {
- // dragging not-yet-selected item: select it
- EnsureItemSelected(e.Item as TreeNode);
- base.OnItemDrag(e);
- }
- /// <inheritdoc/>
- protected override void OnKeyDown(KeyEventArgs e)
- {
- base.OnKeyDown(e);
- if (SelectedNode == null)
- return;
- var allNodes = GetAllNodes(true);
- int index = allNodes.IndexOf(SelectedNode);
- switch (e.KeyCode)
- {
- case Keys.Up:
- SelectedNode = index > 0 ? allNodes[index - 1] : allNodes[0];
- e.Handled = true;
- break;
- case Keys.Down:
- SelectedNode = index < allNodes.Count - 1 ? allNodes[index + 1] : SelectedNode;
- e.Handled = true;
- break;
- case Keys.Left:
- if (SelectedNode.Nodes.Count > 0 && SelectedNode.IsExpanded)
- SelectedNode.Collapse();
- else
- SelectedNode = SelectedNode.Parent ?? SelectedNode;
- e.Handled = true;
- break;
- case Keys.Right:
- if (SelectedNode.Nodes.Count > 0 && !SelectedNode.IsExpanded)
- SelectedNode.Expand();
- else if (SelectedNode.Nodes.Count > 0)
- SelectedNode = SelectedNode.Nodes[0];
- e.Handled = true;
- break;
- }
- }
- /// <inheritdoc/>
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (disposing)
- StopLabelEditTimer();
- }
- /// <summary>
- /// Creates a new instance of the TreeViewMultiSelect control.
- /// </summary>
- public TreeViewMultiSelect()
- {
- selectedNodes = new List<TreeNode>();
- }
- }
- }
|