using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Drawing; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; using Vanara.Extensions; using Vanara.PInvoke; using Vanara.Windows.Shell; using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.Shell32; using static Vanara.PInvoke.User32; using DragEventArgs = System.Windows.Forms.DragEventArgs; using IMessageFilter = System.Windows.Forms.IMessageFilter; namespace Vanara.Windows.Forms { /// The location on the IShellItem that was clicked. public enum ItemHitLocation { /// The click missed the IShellItem. NoWhere = NSTCEHITTEST.NSTCEHT_NOWHERE, /// The click was on the icon of the IShellItem. OnIcon = NSTCEHITTEST.NSTCEHT_ONITEMICON, /// The click was on the label text of the IShellItem. OnLabel = NSTCEHITTEST.NSTCEHT_ONITEMLABEL, /// The click was on the indented space on the leftmost side of the IShellItem. OnIndent = NSTCEHITTEST.NSTCEHT_ONITEMINDENT, /// The click was on the expando button of the IShellItem. OnButton = NSTCEHITTEST.NSTCEHT_ONITEMBUTTON, /// The click was on the rightmost side of the text of the IShellItem. OnRight = NSTCEHITTEST.NSTCEHT_ONITEMRIGHT, /// The click was on the state icon of the IShellItem. OnStateIcon = NSTCEHITTEST.NSTCEHT_ONITEMSTATEICON, /// The click was on the item icon or the item label or the state icon of the IShellItem. OnItem = NSTCEHITTEST.NSTCEHT_ONITEM, /// The click was on the tab button of the IShellItem. OnTabButton = NSTCEHITTEST.NSTCEHT_ONITEMTABBUTTON, } /// Actions on a exposed through . public enum ShellNamespaceTreeControlAction { /// Unknown action. Unknown, /// A keystroke caused the action. ByKeyboard, /// A mouse click caused the action. ByMouse, /// An item has been added. AfterAdd, /// An item has been deleted. AfterDelete, /// An item is about to be deleted. BeforeDelete, /// An item is being collapsed. Collapse, /// An item is being expanded. Expand } /// Determines the image displayed to the right of an item in . public enum ShellTreeItemButton { /// No button is displayed to the right of an item. None, /// /// Displays an arrow on the right side of an item if the item is a folder. The action associated with the arrow is implementation specific. /// Arrow, /// Displays a red X on the right side of an item. The action associated with the X is implementation specific. Delete, /// /// Displays a refresh button on the right side of an item. The action associated with the button is implementation specific. /// Refresh } /// The style of check box to display. public enum ShellTreeItemCheckBoxStyle { /// Display no check box. This is the default. None, /// Adds a standard, two-state, checkbox icon on the leftmost side of a given item. Normal, /// /// Adds a checkbox icon on the leftmost side of a given item with a square in the center, that indicates that the node is partially selected. /// Partial, /// /// Adds a checkbox icon on the leftmost side of a given item that contains a red X, which indicates that the item is excluded from /// the current selection. Without this exclusion icon, selection of a parent item includes selection of its child items. /// Exclusion, /// /// Adds a checkbox on the leftmost side of a given item that contains an icon of a dimmed check mark, that indicates that a node is /// selected because its parent is selected. /// Dimmed } /// Specifies the state of a tree item. [Flags] public enum ShellTreeItemState : uint { /// The item has default state; it is not selected, expanded, bolded or disabled. None = NSTCITEMSTATE.NSTCIS_NONE, /// The item is selected. Selected = NSTCITEMSTATE.NSTCIS_SELECTED, /// The item is expanded. Expanded = NSTCITEMSTATE.NSTCIS_EXPANDED, /// The item is bold. Bold = NSTCITEMSTATE.NSTCIS_BOLD, /// The item is disabled. Disabled = NSTCITEMSTATE.NSTCIS_DISABLED, /// Windows 7 and later. The item is selected, but not expanded. SelectedNotExpanded = NSTCITEMSTATE.NSTCIS_SELECTEDNOEXPAND, } /// A control used to view and manipulate nodes in a tree of Shell items. [Designer(typeof(Design.ShellNamespaceTreeControlDesigner)), DefaultProperty(nameof(ShowPlusMinus)), DefaultEvent(nameof(AfterSelect))] [ToolboxItem(true), ToolboxBitmap(typeof(ShellNamespaceTreeControl), "ShellNamespaceTreeControl.bmp")] [Description("A Shell object that displays a tree of shell items.")] [ComVisible(true), Guid("0639efb8-7701-472e-863d-d6fbc543d736")] public class ShellNamespaceTreeControl : Control, Shell32.IServiceProvider, INameSpaceTreeControlEvents, INameSpaceTreeControlDropHandler, IMessageFilter //, INameSpaceTreeAccessible { internal INameSpaceTreeControl pCtrl; private const NSTCSTYLE defaultStyle = NSTCSTYLE.NSTCS_HASEXPANDOS | NSTCSTYLE.NSTCS_ROOTHASEXPANDO | NSTCSTYLE.NSTCS_FADEINOUTEXPANDOS | NSTCSTYLE.NSTCS_NOINFOTIP | NSTCSTYLE.NSTCS_ALLOWJUNCTIONS | NSTCSTYLE.NSTCS_SHOWSELECTIONALWAYS | NSTCSTYLE.NSTCS_FULLROWSELECT | NSTCSTYLE.NSTCS_TABSTOP; private const NSTCSTYLE2 defaultStyle2 = NSTCSTYLE2.NTSCS2_NOSINGLETONAUTOEXPAND | NSTCSTYLE2.NSTCS2_INTERRUPTNOTIFICATIONS | NSTCSTYLE2.NSTCS2_DISPLAYPINNEDONLY | NSTCSTYLE2.NTSCS2_NEVERINSERTNONENUMERATED; private const NSTCITEMSTATE NSTCITEMSTATE_ALL = NSTCITEMSTATE.NSTCIS_SELECTED | NSTCITEMSTATE.NSTCIS_EXPANDED | NSTCITEMSTATE.NSTCIS_BOLD | NSTCITEMSTATE.NSTCIS_DISABLED | NSTCITEMSTATE.NSTCIS_SELECTEDNOEXPAND; private const NSTCSTYLE NSTCSTYLE_ALL = NSTCSTYLE.NSTCS_HASEXPANDOS | NSTCSTYLE.NSTCS_HASLINES | NSTCSTYLE.NSTCS_SINGLECLICKEXPAND | NSTCSTYLE.NSTCS_FULLROWSELECT | NSTCSTYLE.NSTCS_SPRINGEXPAND | NSTCSTYLE.NSTCS_HORIZONTALSCROLL | NSTCSTYLE.NSTCS_ROOTHASEXPANDO | NSTCSTYLE.NSTCS_SHOWSELECTIONALWAYS | NSTCSTYLE.NSTCS_NOINFOTIP | NSTCSTYLE.NSTCS_EVENHEIGHT | NSTCSTYLE.NSTCS_NOREPLACEOPEN | NSTCSTYLE.NSTCS_DISABLEDRAGDROP | NSTCSTYLE.NSTCS_NOORDERSTREAM | NSTCSTYLE.NSTCS_RICHTOOLTIP | NSTCSTYLE.NSTCS_BORDER | NSTCSTYLE.NSTCS_NOEDITLABELS | NSTCSTYLE.NSTCS_TABSTOP | NSTCSTYLE.NSTCS_FAVORITESMODE | NSTCSTYLE.NSTCS_AUTOHSCROLL | NSTCSTYLE.NSTCS_FADEINOUTEXPANDOS | NSTCSTYLE.NSTCS_EMPTYTEXT | NSTCSTYLE.NSTCS_CHECKBOXES | NSTCSTYLE.NSTCS_PARTIALCHECKBOXES | NSTCSTYLE.NSTCS_EXCLUSIONCHECKBOXES | NSTCSTYLE.NSTCS_DIMMEDCHECKBOXES | NSTCSTYLE.NSTCS_NOINDENTCHECKS | NSTCSTYLE.NSTCS_ALLOWJUNCTIONS | NSTCSTYLE.NSTCS_SHOWTABSBUTTON | NSTCSTYLE.NSTCS_SHOWDELETEBUTTON | NSTCSTYLE.NSTCS_SHOWREFRESHBUTTON; private const NSTCSTYLE2 NSTCSTYLE2_ALL = NSTCSTYLE2.NSTCS2_INTERRUPTNOTIFICATIONS | NSTCSTYLE2.NSTCS2_SHOWNULLSPACEMENU | NSTCSTYLE2.NSTCS2_DISPLAYPADDING | NSTCSTYLE2.NSTCS2_DISPLAYPINNEDONLY | NSTCSTYLE2.NTSCS2_NOSINGLETONAUTOEXPAND | NSTCSTYLE2.NTSCS2_NEVERINSERTNONENUMERATED; private uint adviseCookie = uint.MaxValue; private BorderStyle borderStyle = BorderStyle.None; private HWND hWndNsTreeCtrl, hWndTreeView; private bool oleUninit = false; private INameSpaceTreeControl2 pCtrl2; private EnumFlagIndexer style = defaultStyle; private EnumFlagIndexer style2 = defaultStyle2; private string theme = null; /// Initializes a new instance of the class. public ShellNamespaceTreeControl() { RootItems = new ShellNamespaceTreeRootList(this); BackColor = SystemColors.Window; oleUninit = Ole32.OleInitialize().Succeeded; } /// Called after an item is expanded. [Category("Behavior"), Description("Occurs when an item has been expanded.")] public event EventHandler AfterExpand; /// Called after an item has been added. [Category("Behavior"), Description("Occurs when an item has been added.")] public event EventHandler AfterItemAdd; /// Called after an item and all of its children are deleted. [Category("Behavior"), Description("Occurs when an item has been deleted.")] public event EventHandler AfterItemDelete; /// Called after the item leaves edit mode. [Category("Behavior"), Description("Occurs when the text of an item has been edited by the user.")] public event EventHandler AfterLabelEdit; /// Occurs when the selection changes. [Category("Behavior"), Description("Occurs when an item has been selected.")] public event EventHandler AfterSelect; /// Called before an item is expanded. [Category("Behavior"), Description("Occurs when an item it about to be expanded.")] public event EventHandler BeforeExpand; /// Called before an item and all of its children are deleted. [Category("Behavior"), Description("Occurs when an item is about to be deleted.")] public event EventHandler BeforeItemDelete; /// Called before the item goes into edit mode. [Category("Behavior"), Description("Occurs when the text of an item is about to be edited by the user.")] public event EventHandler BeforeLabelEdit; /// Called when the user clicks a button on the mouse. [Category("Behavior"), Description("Occurs when an item is clicked by the user.")] public event EventHandler ItemMouseClick; /// Called when the user double-clicks a button on the mouse. [Category("Behavior"), Description("Occurs when an item is double-clicked with the mouse.")] public event EventHandler ItemMouseDoubleClick; /// Indicates whether to insert spacing (padding) between top-level nodes. [DefaultValue(false), Category("Appearance"), Description("Indicates whether to insert spacing (padding) between top-level nodes.")] public bool AddTopLevelNodePadding { get => HasTreeStyle(NSTCSTYLE2.NSTCS2_DISPLAYPADDING); set => SetTreeStyle(NSTCSTYLE2.NSTCS2_DISPLAYPADDING, value); } /// /// Indicates whether to allow junctions. A junction point, or just junction, is a root of a namespace extension that is normally /// displayed by Windows Explorer as a folder in both tree and folder views. For Windows Explorer to display your extension's files /// and subfolders, you must specify where the root folder is located in the Shell namespace hierarchy. Junctions exist in the file /// system as files, but are not treated as files. An example is a compressed file with a .zip file name extension, which to the /// file system is just a file. However, if this file is treated as a junction, it can represent an entire namespace. This allows /// the namespace tree control to treat compressed files and similar junctions as folders rather than as files. /// [DefaultValue(true), Category("Behavior"), Description("Indicates whether to allow junctions.")] public bool AllowJunctions { get => HasTreeStyle(NSTCSTYLE.NSTCS_ALLOWJUNCTIONS); set => SetTreeStyle(NSTCSTYLE.NSTCS_ALLOWJUNCTIONS, value); } /// Gets or sets the border style. /// The border style. [DefaultValue(BorderStyle.None), Category("Appearance"), Description("The border style of the control.")] public BorderStyle BorderStyle { get => borderStyle; set { if (borderStyle != value) { borderStyle = value; /*InitializeControl();*/ } } } /// /// Indicates whether to display check boxes on the leftmost side of the items. These check boxes can be of types partial, exclusion /// or dimmed. /// [DefaultValue(ShellTreeItemCheckBoxStyle.None), Category("Appearance"), Description("Indicates the type of check boxes to display besides nodes.")] public ShellTreeItemCheckBoxStyle CheckBoxStyle { get => style switch { var s when s[NSTCSTYLE.NSTCS_PARTIALCHECKBOXES] => ShellTreeItemCheckBoxStyle.Partial, var s when s[NSTCSTYLE.NSTCS_EXCLUSIONCHECKBOXES] => ShellTreeItemCheckBoxStyle.Exclusion, var s when s[NSTCSTYLE.NSTCS_DIMMEDCHECKBOXES] => ShellTreeItemCheckBoxStyle.Dimmed, var s when s[NSTCSTYLE.NSTCS_CHECKBOXES] => ShellTreeItemCheckBoxStyle.Normal, _ => ShellTreeItemCheckBoxStyle.None }; set { var lstyle = ((NSTCSTYLE)style).SetFlags(NSTCSTYLE.NSTCS_CHECKBOXES | NSTCSTYLE.NSTCS_PARTIALCHECKBOXES | NSTCSTYLE.NSTCS_EXCLUSIONCHECKBOXES | NSTCSTYLE.NSTCS_DIMMEDCHECKBOXES, false) | value switch { ShellTreeItemCheckBoxStyle.Normal => NSTCSTYLE.NSTCS_CHECKBOXES, ShellTreeItemCheckBoxStyle.Partial => NSTCSTYLE.NSTCS_CHECKBOXES | NSTCSTYLE.NSTCS_PARTIALCHECKBOXES, ShellTreeItemCheckBoxStyle.Exclusion => NSTCSTYLE.NSTCS_CHECKBOXES | NSTCSTYLE.NSTCS_EXCLUSIONCHECKBOXES, ShellTreeItemCheckBoxStyle.Dimmed => NSTCSTYLE.NSTCS_CHECKBOXES | NSTCSTYLE.NSTCS_DIMMEDCHECKBOXES, _ => 0 }; if (style != lstyle) { style = lstyle; UpdateStyle(); } } } /// /// Indicates whether to allow drag-and-drop operations within the control. Note that you can still drag an item from outside of the /// control and drop it onto the control. /// [DefaultValue(false), Category("Behavior"), Description("Indicates whether to allow drag-and-drop operations within the control.")] public bool DisableDragDrop { get => HasTreeStyle(NSTCSTYLE.NSTCS_DISABLEDRAGDROP); set => SetTreeStyle(NSTCSTYLE.NSTCS_DISABLEDRAGDROP, value); } /// /// Indicates whether to filter items based on the System.IsPinnedToNameSpaceTree value when INameSpaceTreeControlFolderCapabilities /// is implemented. /// [DefaultValue(true), Category("Behavior"), Description("Indicates whether to filter items based on their pinned value.")] public bool DisplayPinnedItemsOnly { get => HasTreeStyle(NSTCSTYLE2.NSTCS2_DISPLAYPINNEDONLY); set => SetTreeStyle(NSTCSTYLE2.NSTCS2_DISPLAYPINNEDONLY, value); } /// /// Indicates whether to set the height of the items to an even height. By default, the height of items can be even or odd. /// [DefaultValue(false), Category("Appearance"), Description("Indicates whether to set the height of the items to an even height.")] public bool ForceEvenHeight { get => HasTreeStyle(NSTCSTYLE.NSTCS_EVENHEIGHT); set => SetTreeStyle(NSTCSTYLE.NSTCS_EVENHEIGHT, value); } /// /// Indicates whether the selection of an item fills the row with inverse text to the end of the window area, regardless of the /// length of the text. When this option is not declared, only the area behind text is inverted. /// [DefaultValue(true), Category("Appearance"), Description("Indicates whether the highlight spans the width of the control.")] public bool FullRowSelect { get => HasTreeStyle(NSTCSTYLE.NSTCS_FULLROWSELECT); set => SetTreeStyle(NSTCSTYLE.NSTCS_FULLROWSELECT, value); } /// Indicates whether the node of an item is not outlined when the control does not have the focus. [DefaultValue(false), Category("Appearance"), Description("Removes highlight from the selected item when control does not have the focus.")] public bool HideSelection { get => !HasTreeStyle(NSTCSTYLE.NSTCS_SHOWSELECTIONALWAYS); set => SetTreeStyle(NSTCSTYLE.NSTCS_SHOWSELECTIONALWAYS, !value); } /// /// If the control does not have the focus and there are items that are preceded by expandos, then these expandos are visible only /// when the mouse pointer is near to the control. /// [DefaultValue(true), Category("Appearance"), Description("Expandos are only visible when mouse hovers near the control.")] public bool HotExpandos { get => HasTreeStyle(NSTCSTYLE.NSTCS_FADEINOUTEXPANDOS); set => SetTreeStyle(NSTCSTYLE.NSTCS_FADEINOUTEXPANDOS, value); } /// Indicates whether to allow creation of an in-place edit box, which would allow the user to rename the given item. [DefaultValue(true), Category("Behavior"), Description("Indicates whether the user can edit the label text of items.")] public bool LabelEdit { get => !HasTreeStyle(NSTCSTYLE.NSTCS_NOEDITLABELS); set => SetTreeStyle(NSTCSTYLE.NSTCS_NOEDITLABELS, !value); } /// Indicates whether check boxes are located at the far left edge of the window area instead of being indented. [DefaultValue(false), Category("Appearance"), Description("Removes indent before item check boxes.")] public bool NoIndentCheckBoxes { get => HasTreeStyle(NSTCSTYLE.NSTCS_NOINDENTCHECKS); set => SetTreeStyle(value ? NSTCSTYLE.NSTCS_NOINDENTCHECKS | NSTCSTYLE.NSTCS_CHECKBOXES : NSTCSTYLE.NSTCS_NOINDENTCHECKS, value); } /// Gets the root items. /// The root items. [Browsable(false)] public ShellNamespaceTreeRootList RootItems { get; } /// Gets the currently selected item, or if nothing is selected. /// The selected item, or if nothing is selected. [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ShellItem SelectedItem { get { if (pCtrl != null && pCtrl.GetSelectedItems(out var items).Succeeded) { try { return ShellItem.Open(items.GetItemAt(0)); } finally { Marshal.ReleaseComObject(items); } } return null; } set { var state = GetItemState(value); SetItemState(value, (ShellTreeItemState)NSTCITEMSTATE_ALL, state | ShellTreeItemState.Selected); } } /// /// Indicates which button to display on the right side of an item. The action associated with the button is implementation specific. /// [DefaultValue(ShellTreeItemButton.None), Category("Appearance"), Description("Indicates which button to display on the right side of an item.")] public ShellTreeItemButton ShowFolderButton { get => style switch { var s when s[NSTCSTYLE.NSTCS_SHOWTABSBUTTON] => ShellTreeItemButton.Arrow, var s when s[NSTCSTYLE.NSTCS_SHOWDELETEBUTTON] => ShellTreeItemButton.Delete, var s when s[NSTCSTYLE.NSTCS_SHOWREFRESHBUTTON] => ShellTreeItemButton.Refresh, _ => ShellTreeItemButton.None }; set { var lstyle = ((NSTCSTYLE)style).SetFlags(NSTCSTYLE.NSTCS_SHOWTABSBUTTON | NSTCSTYLE.NSTCS_SHOWDELETEBUTTON | NSTCSTYLE.NSTCS_SHOWREFRESHBUTTON, false) | value switch { ShellTreeItemButton.Arrow => NSTCSTYLE.NSTCS_SHOWTABSBUTTON, ShellTreeItemButton.Delete => NSTCSTYLE.NSTCS_SHOWDELETEBUTTON, ShellTreeItemButton.Refresh => NSTCSTYLE.NSTCS_SHOWREFRESHBUTTON, _ => 0 }; if (style != lstyle) { style = lstyle; UpdateStyle(); } } } /// Indicates whether to display infotips when the mouse cursor is over an item. [DefaultValue(false), Category("Behavior"), Description("Indicates whether ToolTips will be displayed on the items.")] public bool ShowItemToolTips { get => !HasTreeStyle(NSTCSTYLE.NSTCS_NOINFOTIP); set { SetTreeStyle(NSTCSTYLE.NSTCS_NOINFOTIP, !value); if (value) SetTreeStyle(NSTCSTYLE.NSTCS_RICHTOOLTIP, false); } } /// Indicates whether the control draws lines to the left of the tree items that lead to their individual parent items. [DefaultValue(false), Category("Appearance"), Description("Indicates whether lines are displayed between sibling items and between parent and child items.")] public bool ShowLines { get => HasTreeStyle(NSTCSTYLE.NSTCS_HASLINES); set => SetTreeStyle(NSTCSTYLE.NSTCS_HASLINES, value); } /// /// If , the control displays an indicator on the leftmost edge of those items that have child items. Clicking /// on the indicator expands the item to reveal the children of the item. /// [DefaultValue(true), Category("Appearance"), Description("Indicates if plus/minus buttons will be shown next to parent nodes.")] public bool ShowPlusMinus { get => HasTreeStyle(NSTCSTYLE.NSTCS_HASEXPANDOS); set => SetTreeStyle(NSTCSTYLE.NSTCS_HASEXPANDOS, value); } /// Indicates whether the root item is preceded by an expando that allows expansion of the root item. [DefaultValue(true), Category("Appearance"), Description("Indicates whether lines are display between root items.")] public bool ShowRootLines { get => HasTreeStyle(NSTCSTYLE.NSTCS_ROOTHASEXPANDO); set => SetTreeStyle(NSTCSTYLE.NSTCS_ROOTHASEXPANDO, value); } /// Indicates whether an item expands to show its child items in response to a single mouse click. [DefaultValue(false), Category("Behavior"), Description("Indicates whether an item expands to show its child items in response to a single mouse click.")] public bool SingleClickExpand { get => HasTreeStyle(NSTCSTYLE.NSTCS_SINGLECLICKEXPAND); set => SetTreeStyle(NSTCSTYLE.NSTCS_SINGLECLICKEXPAND, value); } /// /// Indicates whether when one item is selected and expanded and you select a second item, the first selection automatically collapses. /// [DefaultValue(false), Category("Behavior"), Description("Indicates whether when one item is selected and expanded and you select a second item, the first selection automatically collapses.")] public bool SpringExpand { get => HasTreeStyle(NSTCSTYLE.NSTCS_SPRINGEXPAND); set => SetTreeStyle(NSTCSTYLE.NSTCS_SPRINGEXPAND, value); } /// If the control is hosted, you can tabstop into the control. [DefaultValue(true), Category("Behavior"), Description("Indicates whether the user can use the TAB key to gain access to the control.")] public new bool TabStop { get => HasTreeStyle(NSTCSTYLE.NSTCS_TABSTOP); set { if (SetTreeStyle(NSTCSTYLE.NSTCS_TABSTOP, value)) OnTabStopChanged(EventArgs.Empty); } } /// Gets or sets the desktop theme for the current control. /// The name of the desktop theme to which the current window is being set. [DefaultValue(null), Category("Appearance"), Description("Gets or sets the desktop theme for the current control.")] public string Theme { get => theme; set { theme = value; pCtrl?.SetTheme(value); } } // TODO: Figure how to do this best //[DefaultValue(false)] //public bool Scrollable //{ // get => HasTreeStyle(NSTCSTYLE.NSTCS_HORIZONTALSCROLL); // set => SetTreeStyle(NSTCSTYLE.NSTCS_HORIZONTALSCROLL, value); //} //[DefaultValue(false)] //public bool NoReplaceOpen //{ // get => HasTreeStyle(NSTCSTYLE.NSTCS_NOREPLACEOPEN); // set => SetTreeStyle(NSTCSTYLE.NSTCS_NOREPLACEOPEN, value); //} // TODO: Figure how to do this best //[DefaultValue(false)] //public bool AutoHScroll //{ // get => HasTreeStyle(NSTCSTYLE.NSTCS_AUTOHSCROLL); // set => SetTreeStyle(NSTCSTYLE.NSTCS_AUTOHSCROLL, value); //} /// /// Indicates whether to use a rich tooltip. Rich tooltips display the item's icon in addition to the item's text. A standard /// tooltip displays only the item's text. The tree view displays tooltips only for items in the tree that are partially visible. /// [DefaultValue(false), Category("Behavior"), Description("Indicates whether to use a rich tooltip.")] public bool UseRichToolTips { get => HasTreeStyle(NSTCSTYLE.NSTCS_RICHTOOLTIP); set => SetTreeStyle(value ? NSTCSTYLE.NSTCS_RICHTOOLTIP | NSTCSTYLE.NSTCS_NOINFOTIP : NSTCSTYLE.NSTCS_RICHTOOLTIP, value); } /// /// Gets the required creation parameters when the control handle is created. /// protected override CreateParams CreateParams { get { var cp = base.CreateParams; if (!this.IsDesignMode()) { cp.Style &= ~(int)WindowStyles.WS_VISIBLE; } return cp; } } /// Collapses all of the items in the tree. public void CollapseAll() => pCtrl.CollapseAll(); /// Ensures that the given item is visible. /// The Shell item for which the visibility is being ensured. public void EnsureVisible(ShellItem item) => pCtrl.EnsureItemVisible(item.IShellItem); /// Gets state information about a Shell item. /// A pointer to the Shell item from which to retrieve the state. /// The state of the specified item. public ShellTreeItemState GetItemState(ShellItem item) => pCtrl.GetItemState(item?.IShellItem, NSTCITEMSTATE_ALL, out var state).Succeeded ? (ShellTreeItemState)state : 0; /// Retrieves the item that a given point is in, if any. /// The point to be tested. /// The item in which the point exists, or if the point does not exist in an item. public ShellItem HitTest(Point pt) { pCtrl.HitTest(pt, out var psi); return psi is null ? null : ShellItem.Open(psi); } /// Sets state information for a Shell item. /// /// The Shell item for which to set the state. /// /// /// Specifies which information is being set, in the form of a bitmap. /// /// /// A bitmap that contains the values to set for the flags specified in . /// /// /// The value specifies which bits in the value pointed to by are to be set. /// Other bits are ignored. As a simple example, if =Selected, then the first bit in the value determines whether that flag is set (1) or removed (0). /// public void SetItemState(ShellItem item, ShellTreeItemState stateMask, ShellTreeItemState state) => pCtrl.SetItemState(item?.IShellItem, (NSTCITEMSTATE)stateMask, (NSTCITEMSTATE)state); bool IMessageFilter.PreFilterMessage(ref Message m) { if (m.Msg == (int)WindowMessage.WM_KEYDOWN || m.Msg == (int)WindowMessage.WM_KEYUP) Debug.WriteLine($"PreFileter msg: {(WindowMessage)m.Msg}, {(Keys)unchecked((int)(long)m.WParam)}"); return (pCtrl as ExplorerBrowser.IInputObject_WinForms)?.TranslateAcceleratorIO(m) == HRESULT.S_OK; } HRESULT INameSpaceTreeControlEvents.OnAfterContextMenu(IShellItem psi, IContextMenu pcmIn, in Guid riid, out object ppv) { if (riid == typeof(IContextMenu).GUID) { // TODO: Figure out how to host ContextMenuStrip //if (ContextMenuStrip != null) //{ // var ictxmenu = new IContextMenu(); // ppv = ContextMenuStrip.; /* get IContextMenu from ContextMenuStrip */ // return HRESULT.S_OK; //} ppv = pcmIn; } else { ppv = default; } return HRESULT.E_NOTIMPL; } HRESULT INameSpaceTreeControlEvents.OnAfterExpand(IShellItem psi) { AfterExpand?.Invoke(this, new ShellNamespaceTreeControlEventArgs(psi, ShellNamespaceTreeControlAction.Expand)); return HRESULT.S_OK; } HRESULT INameSpaceTreeControlEvents.OnBeforeContextMenu(IShellItem psi, in Guid riid, out object ppv) { if (riid == typeof(IContextMenu).GUID) { ppv = default; // TODO: replace with result from event return HRESULT.E_NOTIMPL; // Change if menu provided by event } else { ppv = default; return HRESULT.E_NOTIMPL; } } HRESULT INameSpaceTreeControlEvents.OnBeforeExpand(IShellItem psi) { var args = new ShellNamespaceTreeControlCancelEventArgs(psi, false, ShellNamespaceTreeControlAction.Expand); try { BeforeExpand?.Invoke(this, args); } catch (Exception ex) { return HRESULT.FromException(ex); } return args.Cancel ? HRESULT.S_FALSE : HRESULT.S_OK; } HRESULT INameSpaceTreeControlEvents.OnBeforeItemDelete(IShellItem psi) { var args = new ShellNamespaceTreeControlCancelEventArgs(psi, false, ShellNamespaceTreeControlAction.BeforeDelete); BeforeItemDelete?.Invoke(this, args); return args.Cancel ? HRESULT.S_FALSE : HRESULT.S_OK; } HRESULT INameSpaceTreeControlEvents.OnBeforeStateImageChange(IShellItem psi) => HRESULT.S_OK; HRESULT INameSpaceTreeControlEvents.OnBeginLabelEdit(IShellItem psi) { BeforeLabelEdit?.Invoke(this, new ShellNamespaceTreeControlItemLabelEditEventArgs(psi)); return HRESULT.S_OK; } HRESULT INameSpaceTreeControlDropHandler.OnDragEnter(IShellItem psiOver, IShellItemArray psiaData, bool fOutsideSource, uint grfKeyState, ref uint pdwEffect) { var ido = new DataObject(); ido.SetFileDropList(GetStringCollection(psiaData)); var dragEvent = new DragEventArgs(ido, (int)grfKeyState, MousePosition.X, MousePosition.Y, DragDropEffects.All, (DragDropEffects)pdwEffect); base.OnDragEnter(dragEvent); pdwEffect = (uint)dragEvent.Effect; return HRESULT.S_OK; } HRESULT INameSpaceTreeControlDropHandler.OnDragLeave(IShellItem psiOver) { base.OnDragLeave(EventArgs.Empty); return HRESULT.S_OK; } HRESULT INameSpaceTreeControlDropHandler.OnDragOver(IShellItem psiOver, IShellItemArray psiaData, uint grfKeyState, ref uint pdwEffect) { var ido = new DataObject(); ido.SetFileDropList(GetStringCollection(psiaData)); var dragEvent = new DragEventArgs(ido, (int)grfKeyState, MousePosition.X, MousePosition.Y, DragDropEffects.All, (DragDropEffects)pdwEffect); base.OnDragOver(dragEvent); pdwEffect = (uint)dragEvent.Effect; return HRESULT.S_OK; } HRESULT INameSpaceTreeControlDropHandler.OnDragPosition(IShellItem psiOver, IShellItemArray psiaData, int iNewPosition, int iOldPosition) => HRESULT.E_FAIL; HRESULT INameSpaceTreeControlDropHandler.OnDrop(IShellItem psiOver, IShellItemArray psiaData, int iPosition, uint grfKeyState, ref uint pdwEffect) { var ido = new DataObject(); ido.SetFileDropList(GetStringCollection(psiaData)); var dragEvent = new DragEventArgs(ido, (int)grfKeyState, MousePosition.X, MousePosition.Y, DragDropEffects.All, (DragDropEffects)pdwEffect); base.OnDragDrop(dragEvent); pdwEffect = (uint)dragEvent.Effect; return HRESULT.S_OK; } HRESULT INameSpaceTreeControlDropHandler.OnDropPosition(IShellItem psiOver, IShellItemArray psiaData, int iNewPosition, int iOldPosition) => HRESULT.E_FAIL; HRESULT INameSpaceTreeControlEvents.OnEndLabelEdit(IShellItem psi) { AfterLabelEdit?.Invoke(this, new ShellNamespaceTreeControlItemLabelEditEventArgs(psi)); return HRESULT.S_OK; } HRESULT INameSpaceTreeControlEvents.OnGetDefaultIconIndex(IShellItem psi, out int piDefaultIcon, out int piOpenIcon) { piDefaultIcon = piOpenIcon = default; return HRESULT.E_NOTIMPL; } HRESULT INameSpaceTreeControlEvents.OnGetToolTip(IShellItem psi, StringBuilder pszTip, int cchTip) => HRESULT.E_NOTIMPL; HRESULT INameSpaceTreeControlEvents.OnItemAdded(IShellItem psi, bool fIsRoot) { AfterItemAdd?.Invoke(this, new ShellNamespaceTreeControlEventArgs(psi, ShellNamespaceTreeControlAction.AfterAdd)); return HRESULT.E_NOTIMPL; //return HRESULT.S_OK; } HRESULT INameSpaceTreeControlEvents.OnItemClick(IShellItem psi, NSTCEHITTEST nstceHitTest, NSTCECLICKTYPE nstceClickType) { var mb = MouseButtons.None; switch (nstceClickType) { case NSTCECLICKTYPE.NSTCECT_LBUTTON: mb = MouseButtons.Left; break; case NSTCECLICKTYPE.NSTCECT_MBUTTON: mb = MouseButtons.Middle; break; case NSTCECLICKTYPE.NSTCECT_RBUTTON: mb = MouseButtons.Right; break; } var args = new ShellNamespaceTreeControlItemMouseClickEventArgs(psi, mb, nstceHitTest); if (nstceClickType > NSTCECLICKTYPE.NSTCECT_BUTTON) ItemMouseDoubleClick?.Invoke(this, args); else ItemMouseClick?.Invoke(this, args); return args.Handled ? HRESULT.S_OK : HRESULT.S_FALSE; } HRESULT INameSpaceTreeControlEvents.OnItemDeleted(IShellItem psi, bool fIsRoot) { AfterItemDelete?.Invoke(this, new ShellNamespaceTreeControlEventArgs(psi, ShellNamespaceTreeControlAction.AfterDelete)); return HRESULT.E_NOTIMPL; //return HRESULT.S_OK; } HRESULT INameSpaceTreeControlEvents.OnItemStateChanged(IShellItem psi, NSTCITEMSTATE nstcisMask, NSTCITEMSTATE nstcisState) => HRESULT.S_OK; HRESULT INameSpaceTreeControlEvents.OnItemStateChanging(IShellItem psi, NSTCITEMSTATE nstcisMask, NSTCITEMSTATE nstcisState) => HRESULT.S_OK; HRESULT INameSpaceTreeControlEvents.OnKeyboardInput(uint uMsg, IntPtr wParam, IntPtr lParam) { System.Diagnostics.Debug.WriteLine($"Kbd msg: {(WindowMessage)uMsg}, {(Keys)unchecked((int)(long)wParam)}"); //var args = new KeyEventArgs((Keys)unchecked((int)(long)wParam) | ModifierKeys); var hSel = SendMessage(hWndTreeView, ComCtl32.TreeViewMessage.TVM_GETNEXTITEM, ComCtl32.TreeViewActionFlag.TVGN_CARET); if (hSel != default && uMsg == (uint)WindowMessage.WM_KEYUP) { switch ((Keys)unchecked((int)(long)wParam)) { case Keys.Down: //Move(IsExpanded(hSel) ? ComCtl32.TreeViewActionFlag.TVGN_CHILD : ComCtl32.TreeViewActionFlag.TVGN_NEXT); Move(ComCtl32.TreeViewActionFlag.TVGN_NEXTVISIBLE); break; case Keys.Up: //var hPrev = SendMessage(hWndTreeView, ComCtl32.TreeViewMessage.TVM_GETNEXTITEM, ComCtl32.TreeViewActionFlag.TVGN_PREVIOUS, hSel); //if (hPrev != default) //{ // if (IsExpanded(hPrev)) // Move(ComCtl32.TreeViewActionFlag.TVGN_PREVIOUSVISIBLE); // else // SelItem(hPrev); //} Move(ComCtl32.TreeViewActionFlag.TVGN_PREVIOUSVISIBLE); break; case Keys.Right: if (IsExpanded(hSel)) Move(ComCtl32.TreeViewActionFlag.TVGN_CHILD); else SendMessage(hWndTreeView, ComCtl32.TreeViewMessage.TVM_EXPAND, ComCtl32.TreeViewExpandFlags.TVE_EXPAND, hSel); break; case Keys.Left: if (IsExpanded(hSel)) SendMessage(hWndTreeView, ComCtl32.TreeViewMessage.TVM_EXPAND, ComCtl32.TreeViewExpandFlags.TVE_COLLAPSE, hSel); else Move(ComCtl32.TreeViewActionFlag.TVGN_PARENT); break; case Keys.Home: Move(ComCtl32.TreeViewActionFlag.TVGN_ROOT); break; case Keys.End: Move(ComCtl32.TreeViewActionFlag.TVGN_LASTVISIBLE); break; case Keys.Enter: break; case Keys.Space: break; } } //if (uMsg == (uint)WindowMessage.WM_KEYUP) // OnKeyUp(args); //return args.Handled ? HRESULT.S_FALSE : HRESULT.S_OK; return HRESULT.S_FALSE; bool IsExpanded(IntPtr item) => SendMessage(hWndTreeView, (uint)ComCtl32.TreeViewMessage.TVM_GETITEMSTATE, item, (IntPtr)(int)ComCtl32.TreeViewItemStates.TVIS_EXPANDED) == (IntPtr)(int)ComCtl32.TreeViewItemStates.TVIS_EXPANDED; void Move(ComCtl32.TreeViewActionFlag dir) { SelItem(SendMessage(hWndTreeView, ComCtl32.TreeViewMessage.TVM_GETNEXTITEM, dir, hSel)); } void SelItem(IntPtr hNext) { if (hNext != default) SendMessage(hWndTreeView, ComCtl32.TreeViewMessage.TVM_SELECTITEM, ComCtl32.TreeViewActionFlag.TVGN_CARET, hNext); } } HRESULT INameSpaceTreeControlEvents.OnPropertyItemCommit(IShellItem psi) => HRESULT.S_FALSE; HRESULT INameSpaceTreeControlEvents.OnSelectionChanged(IShellItemArray psiaSelection) { OnAfterSelect(); return HRESULT.S_OK; } HRESULT Shell32.IServiceProvider.QueryService(in Guid guidService, in Guid riid, out IntPtr ppvObject) { if (riid == typeof(INameSpaceTreeControlDropHandler).GUID) { ppvObject = Marshal.GetComInterfaceForObject(this, typeof(INameSpaceTreeControlDropHandler)); ; return HRESULT.S_OK; } else if (riid == typeof(INameSpaceTreeControlEvents).GUID) { ppvObject = Marshal.GetComInterfaceForObject(this, typeof(INameSpaceTreeControlEvents)); ; return HRESULT.S_OK; } ppvObject = default; return HRESULT.E_NOINTERFACE; } /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. /// protected override void Dispose(bool disposing) { base.Dispose(disposing); if (oleUninit) Ole32.OleUninitialize(); } /// Raises the event. protected virtual void OnAfterSelect() => AfterSelect?.Invoke(this, EventArgs.Empty); /// Raises the method. protected override void OnCreateControl() { base.OnCreateControl(); if (this.IsDesignMode()) return; // Grab interfaces pCtrl = new INameSpaceTreeControl(); pCtrl2 = pCtrl as INameSpaceTreeControl2; // Initialize and capture child window handles var rect = BorderStyle == BorderStyle.None ? Bounds : Rectangle.Inflate(Bounds, -1, -1); pCtrl.Initialize(Parent.Handle, rect, style).ThrowIfFailed(); var sb = new StringBuilder(512); var srect = (RECT)RectangleToScreen(rect); hWndNsTreeCtrl = User32.EnumChildWindows(Parent.Handle).First(h => IsClass(h, "NamespaceTreeControl")); hWndTreeView = hWndNsTreeCtrl.EnumChildWindows().First(h => IsClass(h, "SysTreeView32")); // Remove default roots and update style pCtrl.RemoveAllRoots(); UpdateStyle(); // Setup event handler and sink pCtrl.TreeAdvise(this, out adviseCookie).ThrowIfFailed(); SetSite(this); Application.AddMessageFilter(this); bool IsClass(HWND hWnd, string className) => GetClassName(hWnd, sb, sb.Capacity) > 0 && sb.ToString() == className && GetWindowRect(hWnd, out var r) && r == srect; } /// Raises the event. /// An that contains the event data. protected override void OnGotFocus(EventArgs e) { base.OnGotFocus(e); if (!hWndTreeView.IsNull) SetFocus(hWndTreeView); } /// Raises the event. /// A that contains the event data. protected override void OnKeyDown(KeyEventArgs e) { System.Diagnostics.Debug.WriteLine($"Base KeyDown: {e.KeyCode}"); base.OnKeyDown(e); } /// Raises the event. /// A that contains the event data. protected override void OnKeyUp(KeyEventArgs e) { System.Diagnostics.Debug.WriteLine($"Base KeyUp: {e.KeyCode}"); base.OnKeyUp(e); } /// Raises the event. /// The instance containing the event data. protected override void OnHandleDestroyed(EventArgs e) { if (pCtrl != null) { if (adviseCookie > 0) pCtrl.TreeUnadvise(adviseCookie); SetSite(null); pCtrl = null; } pCtrl2 = null; base.OnHandleDestroyed(e); } /// Raises the event. /// The instance containing the event data. protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (borderStyle != BorderStyle.None) ControlPaint.DrawBorder3D(e.Graphics, Bounds, Border3DStyle.Flat); } /// Raises the event. /// The instance containing the event data. protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); SetWindowPos(hWndNsTreeCtrl, HWND.NULL, Left, Top, Width, Height, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOOWNERZORDER | SetWindowPosFlags.SWP_NOZORDER); } /// Enables a container to pass an object a pointer to the interface for its site. /// /// A pointer to the IServiceProvider interface pointer of the site managing this object. If , the /// object should call Release on any existing site at which point the object no longer knows its site. /// protected virtual void SetSite(Shell32.IServiceProvider sp) => (pCtrl as IObjectWithSite)?.SetSite(sp); private static System.Collections.Specialized.StringCollection GetStringCollection(IShellItemArray psiaData) { using var shiArray = new ShellItemArray(psiaData); var fileList = new System.Collections.Specialized.StringCollection(); fileList.AddRange(shiArray.Select(shi => shi.ParsingName).ToArray()); return fileList; } private IEnumerable EnumChildren(IShellItem psi) { if (psi is null) yield break; foreach (var i in GetChildren(psi)) { yield return i; GetChildren(i); } IEnumerable GetChildren(IShellItem shi) { var hr = pCtrl.GetNextItem(shi, NSTCGNI.NSTCGNI_CHILD, out var nxt); while (hr.Succeeded) { yield return nxt; hr = pCtrl.GetNextItem(nxt, NSTCGNI.NSTCGNI_NEXT, out nxt); } } } private IEnumerable EnumVisibleItems() { var hr = pCtrl.GetNextItem(null, NSTCGNI.NSTCGNI_FIRSTVISIBLE, out var psi); while (hr.Succeeded) { yield return psi; hr = pCtrl.GetNextItem(psi, NSTCGNI.NSTCGNI_NEXTVISIBLE, out psi); } } private bool HasTreeStyle(NSTCSTYLE style) => this.style[style]; private bool HasTreeStyle(NSTCSTYLE2 style) => style2[style]; private bool SetTreeStyle(NSTCSTYLE style, bool value) { if (HasTreeStyle(style) == value) return false; this.style[style] = value; UpdateStyle(); return true; } private void SetTreeStyle(NSTCSTYLE2 style, bool value) { if (HasTreeStyle(style) == value) return; style2[style] = value; UpdateStyle(); } private void UpdateStyle() { if (!IsHandleCreated) return; // Set styles pCtrl2?.SetControlStyle(NSTCSTYLE_ALL, style); pCtrl2?.SetControlStyle2(NSTCSTYLE2_ALL, style2); // Get the current root items and states of visible items var states = EnumVisibleItems().Select(i => (i, GetState(i))).Where(t => t.Item2 != NSTCITEMSTATE.NSTCIS_NONE).ToList(); // Reset the list (required to refresh) RootItems.SyncWithParent(); // Add back all roots and their states foreach (var (i, state, num, childOnly) in RootItems.Select(i => (i, GetState(i.IShellItem), pCtrl.GetItemCustomState(i.IShellItem, out var num).Succeeded ? num : 0, RootItems.onlyShowChildren[i]))) { //RootItems.Add(i, childOnly, state.IsFlagSet(NSTCITEMSTATE.NSTCIS_EXPANDED)); if (state != NSTCITEMSTATE.NSTCIS_NONE) pCtrl.SetItemState(i.IShellItem, NSTCITEMSTATE_ALL, state); if (num != 0) pCtrl.SetItemCustomState(i.IShellItem, num); } // Add back all states for visible items foreach (var (i, state) in states) pCtrl.SetItemState(i, NSTCITEMSTATE_ALL, state); NSTCITEMSTATE GetState(IShellItem shi) => pCtrl.GetItemState(shi, NSTCITEMSTATE_ALL, out var state).Succeeded ? state : 0; } //HRESULT INameSpaceTreeAccessible.OnGetDefaultAccessibilityAction(IShellItem psi, out string pbstrDefaultAction) => throw new NotImplementedException(); //HRESULT INameSpaceTreeAccessible.OnDoDefaultAccessibilityAction(IShellItem psi) => throw new NotImplementedException(); //HRESULT INameSpaceTreeAccessible.OnGetAccessibilityRole(IShellItem psi, out object pvarRole) => throw new NotImplementedException(); } /// Provides data for the BeforeExpand, and BeforeSelect events of a control. /// public class ShellNamespaceTreeControlCancelEventArgs : CancelEventArgs { /// Initializes a new instance of the class. /// The shell item instance. /// to cancel the event; otherwise, . /// The action performed. public ShellNamespaceTreeControlCancelEventArgs(ShellItem shellItem, bool cancel, ShellNamespaceTreeControlAction action) : base(cancel) { Item = shellItem; Action = action; } internal ShellNamespaceTreeControlCancelEventArgs(IShellItem psi, bool cancel, ShellNamespaceTreeControlAction action) : this(psi is null ? null : ShellItem.Open(psi), cancel, action) { } /// The action associated with this event. public ShellNamespaceTreeControlAction Action { get; } /// The shell item associated with this event. public ShellItem Item { get; } } /// Event arguments for actions against . /// public class ShellNamespaceTreeControlEventArgs : EventArgs { /// Initializes a new instance of the class. /// The shell item instance. /// The action performed. public ShellNamespaceTreeControlEventArgs(ShellItem shellItem, ShellNamespaceTreeControlAction action) { Item = shellItem; Action = action; } internal ShellNamespaceTreeControlEventArgs(IShellItem psi, ShellNamespaceTreeControlAction action) { Item = psi is null ? null : ShellItem.Open(psi); Action = action; } /// The action associated with this event. public ShellNamespaceTreeControlAction Action { get; } /// The shell item associated with this event. public ShellItem Item { get; } } /// Arguments for item label edit events in a . /// public class ShellNamespaceTreeControlItemLabelEditEventArgs : EventArgs { /// Initializes a new instance of the class. /// The shell item. public ShellNamespaceTreeControlItemLabelEditEventArgs(ShellItem shellItem) => Item = shellItem; internal ShellNamespaceTreeControlItemLabelEditEventArgs(IShellItem psi) => Item = psi is null ? null : ShellItem.Open(psi); /// On return, set to to cancel the edit. public bool CancelEdit { get; set; } /// The shell item associated with this event. public ShellItem Item { get; } /// The label associated with the selected item. public string Label => Item?.Name; } /// Arguments for mouse click events in a . public class ShellNamespaceTreeControlItemMouseClickEventArgs : HandledMouseEventArgs { internal ShellNamespaceTreeControlItemMouseClickEventArgs(IShellItem item, MouseButtons button, NSTCEHITTEST ht) : base(button, 1, 0, 0, 0) { Item = ShellItem.Open(item); HitLocation = (ItemHitLocation)ht; } /// The location of the item that has been clicked. public ItemHitLocation HitLocation { get; } /// The shell item associated with this event. public ShellItem Item { get; } } /// Encapsulates the list of root items in a . public class ShellNamespaceTreeRootList : IList { internal Dictionary onlyShowChildren = new Dictionary(); internal ShellNamespaceTreeRootList(ShellNamespaceTreeControl parent) => Parent = parent; /// Gets the number of elements contained in the . public int Count => onlyShowChildren.Count; /// Gets a value indicating whether this instance is read only. /// if this instance is read only; otherwise, . bool ICollection.IsReadOnly => false; private ShellNamespaceTreeControl Parent { get; } /// Gets or sets the at the specified index. /// The . /// The index. /// A instance. public ShellItem this[int index] { get => GetItemArray()[index]; set { if (index == Count) Add(value, false, false); else { ((IList)this).RemoveAt(index); Insert(index, value, false, false); } } } /// Appends a Shell item to the list of roots in a tree. /// The Shell item to append. /// The root is hidden so that the children only are visible. Mutually exclusive with NSTCRS_VISIBLE. /// The root is expanded upon initialization. public void Add(ShellItem item, bool showChildrenOnly, bool expanded) { Parent.pCtrl.AppendRoot(item.IShellItem, item.IsFolder ? SHCONTF.SHCONTF_FOLDERS : SHCONTF.SHCONTF_NONFOLDERS, (expanded ? NSTCROOTSTYLE.NSTCRS_EXPANDED : 0) | (showChildrenOnly ? NSTCROOTSTYLE.NSTCRS_HIDDEN : NSTCROOTSTYLE.NSTCRS_VISIBLE)); onlyShowChildren[item] = showChildrenOnly; } /// Removes all items from this list. public void Clear() { Parent.pCtrl.RemoveAllRoots(); onlyShowChildren.Clear(); } /// /// Copies the elements of the to an , starting at a particular /// index. /// /// /// The one-dimensional that is the destination of the elements copied from . The must have zero-based indexing. /// /// The zero-based index in at which copying begins. public void CopyTo(ShellItem[] array, int arrayIndex) => onlyShowChildren.Keys.CopyTo(array, arrayIndex); /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public IEnumerator GetEnumerator() => onlyShowChildren.Keys.GetEnumerator(); /// Searches for the specified object and returns the zero-based index of the first occurrence within the entire list. /// The object to locate in the list. The value can be for reference types. /// The zero-based index of the first occurrence of item within the entire list, if found; otherwise, -1. public int IndexOf(ShellItem item) => onlyShowChildren.Keys.ToList().FindIndex(i => i.Equals(item)); /// Inserts a Shell item to the list of roots in a tree. /// The index at which to insert the root. /// The Shell item to append. /// The root is hidden so that the children only are visible. Mutually exclusive with NSTCRS_VISIBLE. /// The root is expanded upon initialization. public void Insert(int index, ShellItem item, bool showChildrenOnly, bool expanded) { Parent.pCtrl.InsertRoot(index, item.IShellItem, item.IsFolder ? SHCONTF.SHCONTF_FOLDERS : SHCONTF.SHCONTF_NONFOLDERS, (expanded ? NSTCROOTSTYLE.NSTCRS_EXPANDED : 0) | (showChildrenOnly ? NSTCROOTSTYLE.NSTCRS_HIDDEN : NSTCROOTSTYLE.NSTCRS_VISIBLE)); onlyShowChildren[item] = showChildrenOnly; } /// Removes the first occurrence of a specific object from the list. /// The object to remove from the list. The value can be for reference types. /// /// if item is successfully removed; otherwise, . This method also returns if item was not found in the list. /// public bool Remove(ShellItem item) { onlyShowChildren.Remove(item); return Parent.pCtrl.RemoveRoot(item.IShellItem).Succeeded; } internal void SyncWithParent() { if (Parent.pCtrl is null) return; Parent.pCtrl.RemoveAllRoots(); foreach (var kv in onlyShowChildren) { Parent.pCtrl.AppendRoot(kv.Key.IShellItem, kv.Key.IsFolder ? SHCONTF.SHCONTF_FOLDERS : SHCONTF.SHCONTF_NONFOLDERS, kv.Value ? NSTCROOTSTYLE.NSTCRS_HIDDEN : NSTCROOTSTYLE.NSTCRS_VISIBLE); } } /// Adds an object to the end of the list. /// The object to be added to the end of the list. The value can be for reference types. void ICollection.Add(ShellItem item) => Add(item, false, false); /// Determines whether an element is in the list. /// The object to locate in the list. The value can be for reference types. /// if item is found in the list; otherwise, . bool ICollection.Contains(ShellItem item) => onlyShowChildren.ContainsKey(item); /// Returns an enumerator that iterates through the list. /// An for the list. IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// Inserts an element into the lsit at the specified index. /// The zero-based index at which item should be inserted. /// The object to insert. The value can be null for reference types. void IList.Insert(int index, ShellItem item) => Insert(index, item, false, false); /// Removes the element at the specified index of the list. /// The zero-based index of the element to remove. void IList.RemoveAt(int index) { Remove(this[index]); } internal ShellItemArray GetItemArray() => Parent.pCtrl.GetRootItems(out var pItems).Succeeded ? new ShellItemArray(pItems) : new ShellItemArray(); } }