From 15e758c9eef0c841250cde0deddb61b94db28dc0 Mon Sep 17 00:00:00 2001 From: David Hall Date: Fri, 11 Jan 2019 18:07:23 -0700 Subject: [PATCH] Added ExplorerBrowser implementation (not currently visible or functional) --- UnitTests/UI/ExplorerBrowserTests.cs | 29 + UnitTests/UnitTests.csproj | 3 +- WIndows.Forms/Controls/ExplorerBrowser.bmp | Bin 0 -> 822 bytes WIndows.Forms/Controls/ExplorerBrowser.cs | 1545 +++++++++++++++++++++++ WIndows.Forms/Design/ExplorerBrowserDesigner.cs | 30 + WIndows.Forms/Vanara.Windows.Forms.csproj | 14 +- 6 files changed, 1617 insertions(+), 4 deletions(-) create mode 100644 UnitTests/UI/ExplorerBrowserTests.cs create mode 100644 WIndows.Forms/Controls/ExplorerBrowser.bmp create mode 100644 WIndows.Forms/Controls/ExplorerBrowser.cs create mode 100644 WIndows.Forms/Design/ExplorerBrowserDesigner.cs diff --git a/UnitTests/UI/ExplorerBrowserTests.cs b/UnitTests/UI/ExplorerBrowserTests.cs new file mode 100644 index 00000000..2a2faa00 --- /dev/null +++ b/UnitTests/UI/ExplorerBrowserTests.cs @@ -0,0 +1,29 @@ +using NUnit.Framework; +using System.Windows.Forms; + +namespace Vanara.Windows.Forms.Tests +{ + [TestFixture()] + public class ExplorerBrowserTests + { + [OneTimeSetUp] + public void SetupFixture() => Application.EnableVisualStyles(); + + [Test()] + public void ExplorerBrowserTest() + { + var frm = MakeTestForm(out var eb); + eb.ContentFlags = ExplorerBrowserContentSectionOptions.NoSubfolders | ExplorerBrowserContentSectionOptions.NoWebView | ExplorerBrowserContentSectionOptions.HideFileNames + | ExplorerBrowserContentSectionOptions.NoColumnHeader | ExplorerBrowserContentSectionOptions.UseSearchFolder; + eb.ViewMode = ExplorerBrowserViewMode.Icon; + frm.ShowDialog(); + } + + private static Form MakeTestForm(out ExplorerBrowser eb) + { + var frm = new Form { Size = new System.Drawing.Size(300, 300) }; + frm.Controls.Add(eb = new ExplorerBrowser { Name = "eb", Dock = DockStyle.Fill }); + return frm; + } + } +} \ No newline at end of file diff --git a/UnitTests/UnitTests.csproj b/UnitTests/UnitTests.csproj index 5f223b2e..2499b973 100644 --- a/UnitTests/UnitTests.csproj +++ b/UnitTests/UnitTests.csproj @@ -43,6 +43,7 @@ + @@ -134,6 +135,7 @@ + @@ -264,7 +266,6 @@ Vanara.Windows.Shell - diff --git a/WIndows.Forms/Controls/ExplorerBrowser.bmp b/WIndows.Forms/Controls/ExplorerBrowser.bmp new file mode 100644 index 0000000000000000000000000000000000000000..0dca03d9593c681de4b28eac7ea9a67ac540c25c GIT binary patch literal 822 zcmZ?rHDhJ~12Z700mK4O%*Y@C76%bR+z<>C_)jAEMmlgJ8bHhjD*yQY13?5-hNd2u zJrJX?s;}C12Mxh(9}xBV3-$w%o2NIjUvRmtT49LI?E=>1hszDY(R*!5u9yUDu T@u)$TB2PU;7m*~1$@xD3Ni+#K literal 0 HcmV?d00001 diff --git a/WIndows.Forms/Controls/ExplorerBrowser.cs b/WIndows.Forms/Controls/ExplorerBrowser.cs new file mode 100644 index 00000000..2d2b40e7 --- /dev/null +++ b/WIndows.Forms/Controls/ExplorerBrowser.cs @@ -0,0 +1,1545 @@ +// Heavily leverages the work done by Microsoft on the control by the same name in WindowsVistaApiPack. Work was done to improve the designer +// experience, remove nested properties, add missing capabilities, simplify COM calls, align names to those in other mainstream controls, and +// use the Vanara libraries. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Windows.Forms; +using System.Windows.Forms.VisualStyles; +using Vanara.Extensions; +using Vanara.PInvoke; +using Vanara.Windows.Shell; +using static Vanara.PInvoke.Shell32; +using static Vanara.PInvoke.ShlwApi; +using static Vanara.PInvoke.User32_Gdi; +using IServiceProvider = Vanara.PInvoke.Shell32.IServiceProvider; + +namespace Vanara.Windows.Forms +{ + /// + /// Indicates the content options of the explorer browser. Typically use one, or a bitwise combination of these flags to specify how + /// content should appear in the explorer browser control + /// + [Flags] + public enum ExplorerBrowserContentSectionOptions : uint + { + /// No options. + None = FOLDERFLAGS.FWF_NONE, + + /// The view should be left-aligned. + AlignLeft = FOLDERFLAGS.FWF_ALIGNLEFT, + + /// Automatically arrange the elements in the view. + AutoArrange = FOLDERFLAGS.FWF_AUTOARRANGE, + + /// Turns on check mode for the view + CheckSelect = FOLDERFLAGS.FWF_CHECKSELECT, + + /// When the view is set to "Tile" the layout of a single item should be extended to the width of the view. + ExtendedTiles = FOLDERFLAGS.FWF_EXTENDEDTILES, + + /// When an item is selected, the item and all its sub-items are highlighted. + FullRowSelect = FOLDERFLAGS.FWF_FULLROWSELECT, + + /// The view should not display file names + HideFileNames = FOLDERFLAGS.FWF_HIDEFILENAMES, + + /// The view should not save view state in the browser. + NoBrowserViewState = FOLDERFLAGS.FWF_NOBROWSERVIEWSTATE, + + /// Do not display a column header in the view in any view mode. + NoColumnHeader = FOLDERFLAGS.FWF_NOCOLUMNHEADER, + + /// Only show the column header in details view mode. + NoHeaderInAllViews = FOLDERFLAGS.FWF_NOHEADERINALLVIEWS, + + /// The view should not display icons. + NoIcons = FOLDERFLAGS.FWF_NOICONS, + + /// Do not show subfolders. + NoSubfolders = FOLDERFLAGS.FWF_NOSUBFOLDERS, + + /// Navigate with a single click + SingleClickActivate = FOLDERFLAGS.FWF_SINGLECLICKACTIVATE, + + /// Do not allow more than a single item to be selected. + SingleSelection = FOLDERFLAGS.FWF_SINGLESEL, + + /// + /// Make the folder behave like the desktop. This value applies only to the desktop and is not used for typical Shell folders. + /// + Desktop = FOLDERFLAGS.FWF_DESKTOP, + + /// Draw transparently. This is used only for the desktop. + Transparent = FOLDERFLAGS.FWF_TRANSPARENT, + + /// Do not add scroll bars. This is used only for the desktop. + NoScrollBars = FOLDERFLAGS.FWF_NOSCROLL, + + /// The view should not be shown as a web view. + NoWebView = FOLDERFLAGS.FWF_NOWEBVIEW, + + /// + /// Windows Vista and later. Do not re-enumerate the view (or drop the current contents of the view) when the view is refreshed. + /// + NoEnumOnRefresh = FOLDERFLAGS.FWF_NOENUMREFRESH, + + /// Windows Vista and later. Do not allow grouping in the view + NoGrouping = FOLDERFLAGS.FWF_NOGROUPING, + + /// Windows Vista and later. Do not display filters in the view. + NoFilters = FOLDERFLAGS.FWF_NOFILTERS, + + /// Windows Vista and later. Items can be selected using check-boxes. + AutoCheckSelect = FOLDERFLAGS.FWF_AUTOCHECKSELECT, + + /// Windows Vista and later. The view should list the number of items displayed in each group. To be used with IFolderView2::SetGroupSubsetCount. + SubsetGroup = FOLDERFLAGS.FWF_SUBSETGROUPS, + + /// Windows Vista and later. Use the search folder for stacking and searching. + UseSearchFolder = FOLDERFLAGS.FWF_USESEARCHFOLDER, + + /// + /// Windows Vista and later. Ensure right-to-left reading layout in a right-to-left system. Without this flag, the view displays + /// strings from left-to-right both on systems set to left-to-right and right-to-left reading layout, which ensures that file names + /// display correctly. + /// + AllowRtlReading = FOLDERFLAGS.FWF_ALLOWRTLREADING, + } + + /// These flags are used with . + [Flags] + public enum ExplorerBrowserLoadFlags + { + /// No flags. + None = EXPLORER_BROWSER_FILL_FLAGS.EBF_NONE, + + /// + /// Causes to first populate the results folder with the contents of the parent folders + /// of the items in the data object, and then select only the items that are in the data object. + /// + SelectFromDataObject = EXPLORER_BROWSER_FILL_FLAGS.EBF_SELECTFROMDATAOBJECT, + + /// + /// Do not allow dropping on the folder. In other words, do not register a drop target for the view. Applications can then register + /// their own drop targets. + /// + NoDropTarget = EXPLORER_BROWSER_FILL_FLAGS.EBF_NODROPTARGET, + } + + /// + /// Specifies the options that control subsequent navigation. Typically use one, or a bitwise combination of these flags to specify how + /// the explorer browser navigates. + /// + [Flags] + public enum ExplorerBrowserNavigateOptions + { + /// No options. + None = EXPLORER_BROWSER_OPTIONS.EBO_NONE, + + /// Always navigate, even if you are attempting to navigate to the current folder. + AlwaysNavigate = EXPLORER_BROWSER_OPTIONS.EBO_ALWAYSNAVIGATE, + + /// Do not navigate further than the initial navigation. + NavigateOnce = EXPLORER_BROWSER_OPTIONS.EBO_NAVIGATEONCE, + + /// + /// Use the following standard panes: Commands Module pane, Navigation pane, Details pane, and Preview pane. An implementer of + /// IExplorerPaneVisibility can modify the components of the Commands Module that are shown. For more information see, + /// IExplorerPaneVisibility::GetPaneState. If EBO_SHOWFRAMES is not set, Explorer browser uses a single view object. + /// + ShowFrames = EXPLORER_BROWSER_OPTIONS.EBO_SHOWFRAMES, + + /// Do not update the travel log. + NoTravelLog = EXPLORER_BROWSER_OPTIONS.EBO_NOTRAVELLOG, + + /// Do not use a wrapper window. This flag is used with legacy clients that need the browser parented directly on themselves. + NoWrapperWindow = EXPLORER_BROWSER_OPTIONS.EBO_NOWRAPPERWINDOW, + + /// Show WebView for SharePoint sites. + HtmlSharePointView = EXPLORER_BROWSER_OPTIONS.EBO_HTMLSHAREPOINTVIEW, + + /// Introduced in Windows Vista. Do not draw a border around the browser window. + NoBorder = EXPLORER_BROWSER_OPTIONS.EBO_NOBORDER, + + /// Introduced in Windows Vista. Do not persist the view state. + NoPersistViewState = EXPLORER_BROWSER_OPTIONS.EBO_NOPERSISTVIEWSTATE, + } + + /// Flags specifying the folder to be browsed. + [Flags] + public enum ExplorerBrowserNavigationItemCategory : uint + { + /// An absolute PIDL, relative to the desktop. + Absolute = SBSP.SBSP_ABSOLUTE, + + /// Windows Vista and later. Navigate without the default behavior of setting focus into the new view. + ActivateNoFocus = SBSP.SBSP_ACTIVATE_NOFOCUS, + + /// Enable auto-navigation. + AllowAutoNavigate = SBSP.SBSP_ALLOW_AUTONAVIGATE, + + /// + /// Microsoft Internet Explorer 6 Service Pack 2 (SP2) and later. The navigation was possibly initiated by a web page with scripting + /// code already present on the local system. + /// + CallerUntrusted = SBSP.SBSP_CALLERUNTRUSTED, + + /// + /// Windows 7 and later. Do not add a new entry to the travel log. When the user enters a search term in the search box and + /// subsequently refines the query, the browser navigates forward but does not add an additional travel log entry. + /// + CreateNoHistory = SBSP.SBSP_CREATENOHISTORY, + + /// + /// Use default behavior, which respects the view option (the user setting to create new windows or to browse in place). In most + /// cases, calling applications should use this flag. + /// + Default = SBSP.SBSP_DEFBROWSER, + + /// Use the current window. + UseCurrentWindow = SBSP.SBSP_DEFMODE, + + /// + /// Specifies a folder tree for the new browse window. If the current browser does not match the SBSP.SBSP_EXPLOREMODE of the browse + /// object call, a new window is opened. + /// + ExploreMode = SBSP.SBSP_EXPLOREMODE, + + /// + /// Windows Internet Explorer 7 and later. If allowed by current registry settings, give the browser a destination to navigate to. + /// + FeedNavigation = SBSP.SBSP_FEEDNAVIGATION, + + /// Windows Vista and later. Navigate without clearing the search entry field. + KeepSearchText = SBSP.SBSP_KEEPWORDWHEELTEXT, + + /// Navigate back, ignore the PIDL. + NavigateBack = SBSP.SBSP_NAVIGATEBACK, + + /// Navigate forward, ignore the PIDL. + NavigateForward = SBSP.SBSP_NAVIGATEFORWARD, + + /// Creates another window for the specified folder. + NewWindow = SBSP.SBSP_NEWBROWSER, + + /// Suppress selection in the history pane. + NoHistorySelect = SBSP.SBSP_NOAUTOSELECT, + + /// Do not transfer the browsing history to the new window. + NoTransferHistory = SBSP.SBSP_NOTRANSFERHIST, + + /// + /// Specifies no folder tree for the new browse window. If the current browser does not match the SBSP.SBSP_OPENMODE of the browse + /// object call, a new window is opened. + /// + NoFolderTree = SBSP.SBSP_OPENMODE, + + /// Browse the parent folder, ignore the PIDL. + ParentFolder = SBSP.SBSP_PARENT, + + /// Windows 7 and later. Do not make the navigation complete sound for each keystroke in the search box. + PlayNoSound = SBSP.SBSP_PLAYNOSOUND, + + /// Enables redirection to another URL. + Redirect = SBSP.SBSP_REDIRECT, + + /// A relative PIDL, relative to the current folder. + Relative = SBSP.SBSP_RELATIVE, + + /// Browse to another folder with the same Windows Explorer window. + SameWindow = SBSP.SBSP_SAMEBROWSER, + + /// Microsoft Internet Explorer 6 Service Pack 2 (SP2) and later. The navigate should allow ActiveX prompts. + TrustedForActiveX = SBSP.SBSP_TRUSTEDFORACTIVEX, + + /// + /// Microsoft Internet Explorer 6 Service Pack 2 (SP2) and later. The new window is the result of a user initiated action. Trust the + /// new window if it immediately attempts to download content. + /// + TrustFirstDownload = SBSP.SBSP_TRUSTFIRSTDOWNLOAD, + + /// + /// Microsoft Internet Explorer 6 Service Pack 2 (SP2) and later. The window is navigating to an untrusted, non-HTML file. If the + /// user attempts to download the file, do not allow the download. + /// + UntrustedForDownload = SBSP.SBSP_UNTRUSTEDFORDOWNLOAD, + + /// Write no history of this navigation in the history Shell folder. + WriteNoHistory = SBSP.SBSP_WRITENOHISTORY + } + + /// Indicates the viewing mode of the explorer browser + public enum ExplorerBrowserViewMode + { + /// Choose the best view mode for the folder + Auto = FOLDERVIEWMODE.FVM_AUTO, + + /// (New for Windows7) + Content = FOLDERVIEWMODE.FVM_CONTENT, + + /// Object names and other selected information, such as the size or date last updated, are shown. + Details = FOLDERVIEWMODE.FVM_DETAILS, + + /// The view should display medium-size icons. + Icon = FOLDERVIEWMODE.FVM_ICON, + + /// Object names are displayed in a list view. + List = FOLDERVIEWMODE.FVM_LIST, + + /// The view should display small icons. + SmallIcon = FOLDERVIEWMODE.FVM_SMALLICON, + + /// The view should display thumbnail icons. + Thumbnail = FOLDERVIEWMODE.FVM_THUMBNAIL, + + /// The view should display icons in a filmstrip format. + ThumbStrip = FOLDERVIEWMODE.FVM_THUMBSTRIP, + + /// The view should display large icons. + Tile = FOLDERVIEWMODE.FVM_TILE + } + + /// Indicates the visibility state of an ExplorerBrowser pane. + public enum PaneVisibilityState + { + /// Allow the explorer browser to determine if this pane is displayed. + Default = EXPLORERPANESTATE.EPS_DONTCARE, + + /// Hide the pane + Hide = EXPLORERPANESTATE.EPS_DEFAULT_OFF | EXPLORERPANESTATE.EPS_FORCE, + + /// Show the pane + Show = EXPLORERPANESTATE.EPS_DEFAULT_ON | EXPLORERPANESTATE.EPS_FORCE + } + + /// The direction argument for Navigate + internal enum NavigationLogDirection + { + /// Navigates forward through the navigation log + Forward, + + /// Navigates backward through the travel log + Backward + } + + /// + /// ExplorerBrowser is a browser object that can be either navigated or that can host a view of a data object. As a + /// full-featured browser object, it also supports an automatic travel log. + /// + /// + /// + /// + /// + /// + /// + [Designer(typeof(Design.ExplorerBrowserDesigner)), DefaultProperty(nameof(Name)), DefaultEvent(nameof(SelectionChanged))] + [ToolboxItem(true), ToolboxBitmap(typeof(ExplorerBrowser), "ExplorerBrowser.bmp")] + [Description("A Shell browser object that can be either navigated or that can host a view of a data object.")] + public class ExplorerBrowser : UserControl, IServiceProvider, IExplorerPaneVisibility, IExplorerBrowserEvents, ICommDlgBrowser3, IMessageFilter + { + internal uint eventsCookie; + internal IExplorerBrowser explorerBrowserControl; + internal FOLDERSETTINGS folderSettings = new FOLDERSETTINGS(FOLDERVIEWMODE.FVM_AUTO, defaultFolderFlags); + + private const FOLDERFLAGS defaultFolderFlags = FOLDERFLAGS.FWF_USESEARCHFOLDER | FOLDERFLAGS.FWF_NOWEBVIEW; + private const int defaultThumbnailSize = 32; + private const int HRESULT_CANCELLED = unchecked((int)0x800704C7); + private const int HRESULT_RESOURCE_IN_USE = unchecked((int)0x800700AA); + + private static readonly string defaultPropBagName = typeof(ExplorerBrowser).FullName; + private static readonly Guid IID_ICommDlgBrowser = new Guid("000214F1-0000-0000-C000-000000000046"); + + private Tuple antecreationNavigationTarget; + private EXPLORER_BROWSER_OPTIONS options = EXPLORER_BROWSER_OPTIONS.EBO_SHOWFRAMES; + private string propertyBagName = defaultPropBagName; + private int thumbnailSize = defaultThumbnailSize; + private ExplorerBrowserViewEvents viewEvents; + + /// Initializes a new instance of the class. + public ExplorerBrowser() + { + History = new ExplorerBrowserNavigationLog(this); + Items = new ShellItemCollection(this, SVGIO.SVGIO_ALLVIEW); + SelectedItems = new ShellItemCollection(this, SVGIO.SVGIO_SELECTION); + } + + /// Fires when the Items collection changes. + [Category("Action"), Description("Items changed.")] + public event EventHandler ItemsChanged; + + /// Fires when the ExplorerBorwser view has finished enumerating files. + [Category("Behavior"), Description("View is done enumerating files.")] + public event EventHandler ItemsEnumerated; + + /// + /// Fires when a navigation has been 'completed': no Navigating listener has canceled, and the ExplorerBorwser has created a new + /// view. The view will be populated with new items asynchronously, and ItemsChanged will be fired to reflect this some time later. + /// + [Category("Action"), Description("Navigation complete.")] + public event EventHandler Navigated; + + /// Fires when a navigation has been initiated, but is not yet complete. + [Category("Action"), Description("Navigation initiated, but not complete.")] + public event EventHandler Navigating; + + /// + /// Fires when either a Navigating listener cancels the navigation, or if the operating system determines that navigation is not possible. + /// + [Category("Action"), Description("Navigation failed.")] + public event EventHandler NavigationFailed; + + /// Fires when the item selected in the view has changed (i.e., a rename ). This is not the same as SelectionChanged. + [Category("Action"), Description("Selected item has changed.")] + public event EventHandler SelectedItemModified; + + /// Fires when the SelectedItems collection changes. + [Category("Behavior"), Description("Selection changed.")] + public event EventHandler SelectionChanged; + + /// The view should be left-aligned. + [Browsable(false), DefaultValue(false), Category("Appearance"), Description("The view should be left-aligned.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool AlignLeft + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.AlignLeft); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.AlignLeft, value); + } + + /// + /// Ensure right-to-left reading layout in a right-to-left system. Without this flag, the view displays strings from left-to-right + /// both on systems set to left-to-right and right-to-left reading layout, which ensures that file names display correctly. + /// + [Browsable(false), DefaultValue(false), Category("Appearance"), Description("Ensure right-to-left reading layout in a right-to-left system.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool AllowRtlReading + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.AllowRtlReading); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.AllowRtlReading, value); + } + + /// Always navigate, even if you are attempting to navigate to the current folder. + [DefaultValue(false), Category("Behavior"), Description("Always navigate, even if you are attempting to navigate to the current folder.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool AlwaysNavigate + { + get => IsNavFlagSet(ExplorerBrowserNavigateOptions.AlwaysNavigate); + set => SetNavFlag(ExplorerBrowserNavigateOptions.AlwaysNavigate, value); + } + + /// Automatically arrange the elements in the view. + [DefaultValue(false), Category("Behavior"), Description("Automatically arrange the elements in the view.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool AutoArrange + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.AutoArrange); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.AutoArrange, value); + } + + /// Items can be selected using check-boxes. + [DefaultValue(false), Category("Behavior"), Description("Items can be selected using check-boxes.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool AutoCheckSelect + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.AutoCheckSelect); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.AutoCheckSelect, value); + } + + /// Turns on check mode for the view + [DefaultValue(false), Category("Behavior"), Description("Turns on check mode for the view")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool CheckSelect + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.CheckSelect); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.CheckSelect, value); + } + + /// The binary representation of the ExplorerBrowser content flags + [Browsable(false), DefaultValue(0), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + public ExplorerBrowserContentSectionOptions ContentFlags + { + get => (ExplorerBrowserContentSectionOptions)folderSettings.fFlags; + set + { + folderSettings.fFlags = (FOLDERFLAGS)value | defaultFolderFlags; + explorerBrowserControl?.SetFolderSettings(folderSettings); + } + } + + /// Make the folder behave like the desktop. This applies only to the desktop and is not used for typical Shell folders. + [Browsable(false), DefaultValue(false), Category("Appearance"), Description("Make the folder behave like the desktop.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool Desktop + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.Desktop); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.Desktop, value); + } + + /// When the view is in "tile view mode" the layout of a single item should be extended to the width of the view. + [DefaultValue(false), Category("Appearance"), Description("The layout of a single item should be extended to the width of the view.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool ExtendedTiles + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.ExtendedTiles); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.ExtendedTiles, value); + } + + /// When an item is selected, the item and all its sub-items are highlighted. + [DefaultValue(false), Category("Behavior"), Description("When an item is selected, the item and all its sub-items are highlighted.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool FullRowSelect + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.FullRowSelect); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.FullRowSelect, value); + } + + /// The view should not display file names + [DefaultValue(false), Category("Appearance"), Description("The view should not display file names")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool HideFileNames + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.HideFileNames); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.HideFileNames, value); + } + + /// Contains the navigation history of the ExplorerBrowser + [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public ExplorerBrowserNavigationLog History { get; } + + /// The set of ShellItems in the Explorer Browser + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IReadOnlyList Items { get; } + + /// Do not navigate further than the initial navigation. + [DefaultValue(false), Category("Behavior"), Description("Do not navigate further than the initial navigation.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NavigateOnce + { + get => IsNavFlagSet(ExplorerBrowserNavigateOptions.NavigateOnce); + set => SetNavFlag(ExplorerBrowserNavigateOptions.NavigateOnce, value); + } + + /// The binary flags that are passed to the explorer browser control's GetOptions/SetOptions methods + [Browsable(false), DefaultValue(0), DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + public ExplorerBrowserNavigateOptions NavigationFlags + { + get => (ExplorerBrowserNavigateOptions)(explorerBrowserControl?.GetOptions() ?? options); + set + { + // Always forcing SHOWFRAMES because we handle IExplorerPaneVisibility + options = (EXPLORER_BROWSER_OPTIONS)value | EXPLORER_BROWSER_OPTIONS.EBO_SHOWFRAMES; + explorerBrowserControl?.SetOptions(options); + } + } + + /// The view should not save view state in the browser. + [DefaultValue(false), Category("Behavior"), Description("The view should not save view state in the browser.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoBrowserViewState + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.NoBrowserViewState); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.NoBrowserViewState, value); + } + + /// Do not display a column header in the view in any view mode. + [DefaultValue(false), Category("Appearance"), Description("Do not display a column header in the view in any view mode.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoColumnHeader + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.NoColumnHeader); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.NoColumnHeader, value); + } + + /// Do not re-enumerate the view (or drop the current contents of the view) when the view is refreshed. + [DefaultValue(false), Category("Behavior"), Description("Do not re-enumerate the view (or drop the current contents of the view) when the view is refreshed.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoEnumOnRefresh + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.NoEnumOnRefresh); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.NoEnumOnRefresh, value); + } + + /// Do not display filters in the view. + [DefaultValue(false), Category("Appearance"), Description("Do not display filters in the view.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoFilters + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.NoFilters); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.NoFilters, value); + } + + /// Do not allow grouping in the view. + [DefaultValue(false), Category("Appearance"), Description("Do not allow grouping in the view.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoGrouping + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.NoGrouping); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.NoGrouping, value); + } + + /// Only show the column header in details view mode. + [DefaultValue(false), Category("Appearance"), Description("Only show the column header in details view mode.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoHeaderInAllViews + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.NoHeaderInAllViews); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.NoHeaderInAllViews, value); + } + + /// The view should not display icons. + [DefaultValue(false), Category("Appearance"), Description("The view should not display icons.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoIcons + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.NoIcons); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.NoIcons, value); + } + + /// Introduced in Windows Vista. Do not persist the view state. + [DefaultValue(false), Category("Behavior"), Description("Do not persist the view state.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoPersistViewState + { + get => IsNavFlagSet(ExplorerBrowserNavigateOptions.NoPersistViewState); + set => SetNavFlag(ExplorerBrowserNavigateOptions.NoPersistViewState, value); + } + + /// Do not add scroll bars. This is used only for the desktop. + [Browsable(false), DefaultValue(false), Category("Appearance"), Description("Do not add scroll bars.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoScrollBars + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.NoScrollBars); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.NoScrollBars, value); + } + + /// Do not show subfolders. + [DefaultValue(false), Category("Appearance"), Description("Do not show subfolders.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoSubfolders + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.NoSubfolders); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.NoSubfolders, value); + } + + /// Do not update the travel log. + [DefaultValue(false), Category("Behavior"), Description("Do not update the travel log.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoTravelLog + { + get => IsNavFlagSet(ExplorerBrowserNavigateOptions.NoTravelLog); + set => SetNavFlag(ExplorerBrowserNavigateOptions.NoTravelLog, value); + } + + /// Do not use a wrapper window. This flag is used with legacy clients that need the browser parented directly on themselves. + [Browsable(false), DefaultValue(false), Category("Behavior"), Description("Do not use a wrapper window.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool NoWrapperWindow + { + get => IsNavFlagSet(ExplorerBrowserNavigateOptions.NoWrapperWindow); + set => SetNavFlag(ExplorerBrowserNavigateOptions.NoWrapperWindow, value); + } + + /// Controls the visibility of the various ExplorerBrowser panes on subsequent navigation + [Category("Appearance"), Description("Set visibility of child panes.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Content)] + public ExplorerBrowserPaneVisibility PaneVisibility { get; } = new ExplorerBrowserPaneVisibility(); + + /// The name of the property bag used to persist changes to the ExplorerBrowser's view state. + [Browsable(false), Category("Data"), Description("Name of the property bag used to persist changes to the ExplorerBrowser's view state")] + public string PropertyBagName + { + get => propertyBagName; + set + { + propertyBagName = value; + explorerBrowserControl?.SetPropertyBag(propertyBagName); + } + } + + /// The set of selected ShellItems in the Explorer Browser + [Browsable(false)] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public IReadOnlyList SelectedItems { get; } + + /// Navigate with a single click. + [DefaultValue(false), Category("Behavior"), Description("Navigate with a single click.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool SingleClickActivate + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.SingleClickActivate); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.SingleClickActivate, value); + } + + /// Do not allow more than a single item to be selected. + [DefaultValue(false), Category("Behavior"), Description("Do not allow more than a single item to be selected.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool SingleSelection + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.SingleSelection); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.SingleSelection, value); + } + + /// The view should list the number of items displayed in each group. + [DefaultValue(false), Category("Appearance"), Description("The view should list the number of items displayed in each group.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool SubsetGroup + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.SubsetGroup); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.SubsetGroup, value); + } + + /// The size of the thumbnails in pixels. + [Category("Appearance"), DefaultValue(defaultThumbnailSize), Description("The size of the thumbnails in pixels.")] + public int ThumbnailSize + { + get + { + var iFV2 = GetFolderView2(); + if (iFV2 is null) return thumbnailSize; + try + { + iFV2.GetViewModeAndIconSize(out var fvm, out var iconSize); + return thumbnailSize = iconSize; + } + finally + { + iFV2 = null; + } + } + set + { + var iFV2 = GetFolderView2(); + if (iFV2 is null) return; + try + { + iFV2.GetViewModeAndIconSize(out var fvm, out var iconSize); + iFV2.SetViewModeAndIconSize(fvm, thumbnailSize = value); + } + finally + { + iFV2 = null; + } + } + } + + /// Draw transparently. This is used only for the desktop. + [Browsable(false), DefaultValue(false), Category("Appearance"), Description("Draw transparently.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool Transparent + { + get => IsContentFlagSet(ExplorerBrowserContentSectionOptions.Transparent); + set => SetContentFlag(ExplorerBrowserContentSectionOptions.Transparent, value); + } + + /// Show WebView for SharePoint sites. + [Browsable(false), DefaultValue(false), Category("Behavior"), Description("Show WebView for SharePoint sites.")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] + public bool UseHtmlSharePointView + { + get => IsNavFlagSet(ExplorerBrowserNavigateOptions.HtmlSharePointView); + set => SetNavFlag(ExplorerBrowserNavigateOptions.HtmlSharePointView, value); + } + + /// The viewing mode of the Explorer Browser + [DefaultValue(typeof(ExplorerBrowserViewMode), "Auto"), Category("Appearance"), Description("The viewing mode of the Explorer Browser.")] + public ExplorerBrowserViewMode ViewMode + { + get => (ExplorerBrowserViewMode)folderSettings.ViewMode; + set + { + folderSettings.ViewMode = (FOLDERVIEWMODE)value; + explorerBrowserControl?.SetFolderSettings(folderSettings); + } + } + + /// + protected override Size DefaultSize => new Size(200, 150); + + /// Removes all items from the results folder. + public void ClearCustomItems() => explorerBrowserControl?.RemoveAll(); + + /// Navigates to the last item in the navigation history list. This does not change the set of locations in the navigation log. + /// True if the navigation succeeded, false if it failed for any reason. + public bool GoBack() => History.NavigateLog(NavigationLogDirection.Backward); + + /// Navigates to the next item in the navigation history list. This does not change the set of locations in the navigation log. + /// True if the navigation succeeded, false if it failed for any reason. + public bool GoForward() => History.NavigateLog(NavigationLogDirection.Forward); + + /// Creates a custom folder and fills it with items. + /// + /// An interface pointer on the source object that will fill the control. This can be an or any object that + /// can be used with . + /// + /// One of the values. + public void LoadCustomItems(object obj, ExplorerBrowserLoadFlags flags = ExplorerBrowserLoadFlags.None) => explorerBrowserControl?.FillFromObject(obj, (EXPLORER_BROWSER_FILL_FLAGS)flags); + + /// + /// Clears the Explorer Browser of existing content, fills it with content from the specified container, and adds a new point to the + /// Travel Log. + /// + /// The shell container to navigate to. + /// The category of the . + public void Navigate(ShellItem shellItem, ExplorerBrowserNavigationItemCategory category = ExplorerBrowserNavigationItemCategory.Absolute) + { + if (shellItem == null) + throw new ArgumentNullException(nameof(shellItem)); + + if (explorerBrowserControl == null) + { + antecreationNavigationTarget = new Tuple(shellItem, category); + } + else + { + try + { + explorerBrowserControl.BrowseToObject(shellItem.IShellItem, (SBSP)category); + } + catch (COMException e) + { + if (e.ErrorCode == HRESULT_RESOURCE_IN_USE || e.ErrorCode == HRESULT_CANCELLED) + { + OnNavigationFailed(new NavigationFailedEventArgs { FailedLocation = shellItem }); + } + else + { + throw new ArgumentException("Unable to browse to this shell item.", nameof(shellItem), e); + } + } + catch (Exception e) + { + throw new ArgumentException("Unable to browse to this shell item.", nameof(shellItem), e); + } + } + } + /// Navigate within the navigation log. This does not change the set of locations in the navigation log. + /// An index into the navigation logs Locations collection. + /// True if the navigation succeeded, false if it failed for any reason. + public bool NavigateToHistoryIndex(int historyIndex) => History.NavigateLog(historyIndex); + + HRESULT ICommDlgBrowser3.GetCurrentFilter(StringBuilder pszFileSpec, int cchFileSpec) => HRESULT.S_OK; + + HRESULT ICommDlgBrowser3.GetDefaultMenuText(IShellView ppshv, StringBuilder pszText, int cchMax) => HRESULT.S_FALSE; + + HRESULT IExplorerPaneVisibility.GetPaneState(in Guid ep, out EXPLORERPANESTATE peps) + { + switch (ep) + { + case var a when a.Equals(IExplorerPaneVisibilityConstants.EP_AdvQueryPane): + peps = (EXPLORERPANESTATE)PaneVisibility.AdvancedQuery; + break; + + case var a when a.Equals(IExplorerPaneVisibilityConstants.EP_Commands): + peps = (EXPLORERPANESTATE)PaneVisibility.Commands; + break; + + case var a when a.Equals(IExplorerPaneVisibilityConstants.EP_Commands_Organize): + peps = (EXPLORERPANESTATE)PaneVisibility.CommandsOrganize; + break; + + case var a when a.Equals(IExplorerPaneVisibilityConstants.EP_Commands_View): + peps = (EXPLORERPANESTATE)PaneVisibility.CommandsView; + break; + + case var a when a.Equals(IExplorerPaneVisibilityConstants.EP_DetailsPane): + peps = (EXPLORERPANESTATE)PaneVisibility.Details; + break; + + case var a when a.Equals(IExplorerPaneVisibilityConstants.EP_NavPane): + peps = (EXPLORERPANESTATE)PaneVisibility.Navigation; + break; + + case var a when a.Equals(IExplorerPaneVisibilityConstants.EP_PreviewPane): + peps = (EXPLORERPANESTATE)PaneVisibility.Preview; + break; + + case var a when a.Equals(IExplorerPaneVisibilityConstants.EP_QueryPane): + peps = (EXPLORERPANESTATE)PaneVisibility.Query; + break; + + case var a when a.Equals(IExplorerPaneVisibilityConstants.EP_Ribbon): + peps = (EXPLORERPANESTATE)PaneVisibility.Ribbon; + break; + + case var a when a.Equals(IExplorerPaneVisibilityConstants.EP_StatusBar): + peps = (EXPLORERPANESTATE)PaneVisibility.StatusBar; + break; + + default: + peps = EXPLORERPANESTATE.EPS_DONTCARE; + break; + } + return HRESULT.S_OK; + } + + HRESULT ICommDlgBrowser3.GetViewFlags(out CDB2GVF pdwFlags) + { + pdwFlags = CDB2GVF.CDB2GVF_SHOWALLFILES; + return HRESULT.S_OK; + } + + HRESULT ICommDlgBrowser3.IncludeObject(IShellView ppshv, IntPtr pidl) + { + OnItemsChanged(); + return HRESULT.S_OK; + } + + //HRESULT ICommDlgBrowser.IncludeObject(IShellView ppshv, PIDL pidl) => ((ICommDlgBrowser3)this).IncludeObject(ppshv, pidl); + + HRESULT ICommDlgBrowser3.Notify(IShellView ppshv, CDB2N dwNotifyType) => HRESULT.S_OK; + + HRESULT ICommDlgBrowser3.OnColumnClicked(IShellView ppshv, int iColumn) => HRESULT.S_OK; + + HRESULT ICommDlgBrowser3.OnDefaultCommand(IShellView ppshv) => HRESULT.S_FALSE; + + //HRESULT ICommDlgBrowser.OnDefaultCommand(IShellView ppshv) => ((ICommDlgBrowser3)this).OnDefaultCommand(ppshv); + + HRESULT IExplorerBrowserEvents.OnNavigationComplete(IntPtr pidlFolder) + { + folderSettings.ViewMode = GetCurrentViewMode(); + OnNavigated(new NavigatedEventArgs { NewLocation = new ShellItem(pidlFolder) }); + return HRESULT.S_OK; + } + + HRESULT IExplorerBrowserEvents.OnNavigationFailed(IntPtr pidlFolder) + { + OnNavigationFailed(new NavigationFailedEventArgs { FailedLocation = new ShellItem(pidlFolder) }); + return HRESULT.S_OK; + } + + HRESULT IExplorerBrowserEvents.OnNavigationPending(IntPtr pidlFolder) + { + OnNavigating(new NavigatingEventArgs { PendingLocation = new ShellItem(pidlFolder) }, out var cancelled); + return cancelled ? (HRESULT)HRESULT_CANCELLED : HRESULT.S_OK; + } + + HRESULT ICommDlgBrowser3.OnPreViewCreated(IShellView ppshv) => HRESULT.S_OK; + + HRESULT ICommDlgBrowser3.OnStateChange(IShellView ppshv, CDBOSC uChange) + { + if (uChange == CDBOSC.CDBOSC_SELCHANGE) + OnSelectionChanged(); + return HRESULT.S_OK; + } + + //HRESULT ICommDlgBrowser.OnStateChange(IShellView ppshv, CDBOSC uChange) => ((ICommDlgBrowser3)this).OnStateChange(ppshv, uChange); + + HRESULT IExplorerBrowserEvents.OnViewCreated(IShellView psv) + { + viewEvents.ConnectToView((IShellView)psv); + return HRESULT.S_OK; + } + + bool IMessageFilter.PreFilterMessage(ref Message m) => (explorerBrowserControl as IInputObject)?.TranslateAcceleratorIO(m.ToMSG()).Succeeded ?? false; + HRESULT IServiceProvider.QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject) + { + HRESULT hr = HRESULT.E_NOINTERFACE; + ppvObject = default; + if (guidService.Equals(typeof(IExplorerPaneVisibility).GUID)) + { + //hr = InteropExtensions.QueryInterface(this, guidService, out ppvObject); + ppvObject = Marshal.GetComInterfaceForObject(this, typeof(IExplorerPaneVisibility)); + hr = HRESULT.S_OK; + } + else if (guidService.Equals(IID_ICommDlgBrowser) && (riid.Equals(IID_ICommDlgBrowser) || riid.Equals(typeof(ICommDlgBrowser3).GUID))) + { + //hr = InteropExtensions.QueryInterface(this, riid, out ppvObject); + ppvObject = Marshal.GetComInterfaceForObject(this, typeof(ICommDlgBrowser3)); + hr = HRESULT.S_OK; + } + return hr; + } + + /// Gets the IFolderView2 interface from the explorer browser. + /// An instance. + internal IFolderView2 GetFolderView2() => explorerBrowserControl?.GetCurrentView(); + + /// Gets the items in the ExplorerBrowser as an IShellItemArray + /// + internal IShellItemArray GetItemsArray(SVGIO opt) + { + var iFV2 = GetFolderView2(); + if (iFV2 is null) return null; + try + { + return iFV2.Items(opt); + } + finally + { + iFV2 = null; + } + } + + /// Raises the event. + protected internal virtual void OnItemsChanged() => ItemsChanged?.Invoke(this, EventArgs.Empty); + + /// Raises the event. + protected internal virtual void OnItemsEnumerated() => ItemsEnumerated?.Invoke(this, EventArgs.Empty); + + /// Raises the event. + protected internal virtual void OnNavigated(NavigatedEventArgs ncevent) + { + if (ncevent?.NewLocation == null) return; + Navigated?.Invoke(this, ncevent); + } + + /// Raises the event. + protected internal virtual void OnNavigating(NavigatingEventArgs npevent, out bool cancelled) + { + cancelled = false; + if (Navigating == null || npevent?.PendingLocation == null) return; + foreach (var del in Navigating.GetInvocationList()) + { + del.DynamicInvoke(new object[] { this, npevent }); + if (npevent.Cancel) + cancelled = true; + } + } + + /// Raises the event. + protected internal virtual void OnNavigationFailed(NavigationFailedEventArgs nfevent) + { + if (nfevent?.FailedLocation == null) return; + NavigationFailed?.Invoke(this, nfevent); + } + + /// Raises the event. + protected internal virtual void OnSelectedItemModified() => SelectedItemModified?.Invoke(this, EventArgs.Empty); + + /// Raises the event. + protected internal virtual void OnSelectionChanged() => SelectionChanged?.Invoke(this, EventArgs.Empty); + + /// Called when [create control]. + protected override void OnCreateControl() + { + base.OnCreateControl(); + + if (!DesignMode) + { + explorerBrowserControl = new IExplorerBrowser(); + + // hooks up IExplorerPaneVisibility and ICommDlgBrowser event notifications + SetSite(this); + + // hooks up IExplorerBrowserEvents event notification + explorerBrowserControl.Advise(this, out eventsCookie); + + // sets up ExplorerBrowser view connection point events + viewEvents = new ExplorerBrowserViewEvents(this); + + explorerBrowserControl.Initialize(Handle, ClientRectangle, folderSettings); + + // Force an initial show frames so that IExplorerPaneVisibility works the first time it is set. This also enables the control + // panel to be browsed to. If it is not set, then navigating to the control panel succeeds, but no items are visible in the view. + explorerBrowserControl.SetOptions(options); + + // ExplorerBrowserOptions.NoBorder does not work, so we do it manually... + RemoveWindowBorder(); + + explorerBrowserControl.SetPropertyBag(propertyBagName); + + if (antecreationNavigationTarget != null) + { + BeginInvoke(new MethodInvoker(delegate + { + Navigate(antecreationNavigationTarget.Item1, antecreationNavigationTarget.Item2); + antecreationNavigationTarget = null; + })); + } + } + + Application.AddMessageFilter(this); + } + + /// Cleans up the explorer browser events+object when the window is being taken down. + /// An EventArgs that contains event data. + protected override void OnHandleDestroyed(EventArgs e) + { + if (explorerBrowserControl != null) + { + // unhook events + viewEvents.DisconnectFromView(); + explorerBrowserControl.Unadvise(eventsCookie); + SetSite(null); + + // destroy the explorer browser control + explorerBrowserControl.Destroy(); + + // release com reference to it + explorerBrowserControl = null; + } + + base.OnHandleDestroyed(e); + } + + /// Raises the event. + /// The instance containing the event data. + protected override void OnPaint(PaintEventArgs pe) + { + if (DesignMode && pe != null) + { + var cr = ClientRectangle; + pe.Graphics.FillRectangle(SystemBrushes.Window, cr); + if (VisualStyleRenderer.IsSupported) + { + var btn = new VisualStyleRenderer(VisualStyleElement.ScrollBar.ArrowButton.UpDisabled); + var sz = btn.GetPartSize(pe.Graphics, ThemeSizeType.True); + var rsb = new Rectangle(cr.X + cr.Width - sz.Width, cr.Y, sz.Width, cr.Height); + new VisualStyleRenderer(VisualStyleElement.ScrollBar.LowerTrackVertical.Disabled).DrawBackground(pe.Graphics, rsb); + rsb.Height = sz.Height; + btn.DrawBackground(pe.Graphics, rsb); + rsb.Offset(0, cr.Height - sz.Height); + new VisualStyleRenderer(VisualStyleElement.ScrollBar.ArrowButton.DownDisabled).DrawBackground(pe.Graphics, rsb); + } + ControlPaint.DrawBorder(pe.Graphics, cr, SystemColors.WindowFrame, ButtonBorderStyle.Solid); + + using (var font = new Font("Segoe UI", 9)) + using (var sf = new StringFormat { Alignment = StringAlignment.Near, LineAlignment = StringAlignment.Near }) + { + pe.Graphics.DrawString(nameof(ExplorerBrowser), font, SystemBrushes.GrayText, cr, sf); + } + } + + base.OnPaint(pe); + } + + /// Sizes the native control to match the WinForms control wrapper. + /// Contains information about the size changed event. + protected override void OnSizeChanged(EventArgs e) + { + explorerBrowserControl?.SetRect(default, ClientRectangle); + base.OnSizeChanged(e); + } + + private FOLDERVIEWMODE GetCurrentViewMode() + { + var ifv2 = GetFolderView2(); + if (ifv2 is null) return 0; + try + { + return ifv2.GetCurrentViewMode(); + } + finally + { + ifv2 = null; + } + } + private bool IsContentFlagSet(ExplorerBrowserContentSectionOptions flag) => folderSettings.fFlags.IsFlagSet((FOLDERFLAGS)flag); + + private bool IsNavFlagSet(ExplorerBrowserNavigateOptions flag) => NavigationFlags.IsFlagSet(flag); + + /// Find the native control handle, remove its border style, then ask for a redraw. + private void RemoveWindowBorder() + { + // There is an option (EBO_NOBORDER) to avoid showing a border on the native ExplorerBrowser control so we wouldn't have to + // remove it afterwards, but: + // 1. It's not implemented by the Windows API Code Pack + // 2. The flag doesn't seem to work anyway (tested on 7 and 8.1) For reference: EXPLORER_BROWSER_OPTIONS https://msdn.microsoft.com/en-us/library/windows/desktop/bb762501(v=vs.85).aspx + var hwnd = FindWindowEx(Handle, default, "ExplorerBrowserControl", default); + var explorerBrowserStyle = (WindowStyles)GetWindowLongAuto(hwnd, WindowLongFlags.GWL_STYLE).ToInt32(); + SetWindowLong(hwnd, WindowLongFlags.GWL_STYLE, (int)explorerBrowserStyle.ClearFlags(WindowStyles.WS_CAPTION | WindowStyles.WS_BORDER)); + SetWindowPos(hwnd, default, 0, 0, 0, 0, SetWindowPosFlags.SWP_FRAMECHANGED | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOSIZE); + } + private void ResetPropertyBagName() => PropertyBagName = defaultPropBagName; + + private void SetContentFlag(ExplorerBrowserContentSectionOptions flag, bool value) + { + folderSettings.fFlags = folderSettings.fFlags.SetFlags((FOLDERFLAGS)flag, value); + explorerBrowserControl?.SetFolderSettings(folderSettings); + } + + private void SetNavFlag(ExplorerBrowserNavigateOptions flag, bool value) => NavigationFlags = NavigationFlags.SetFlags(flag, value); + + private void SetSite(IServiceProvider sp) => (explorerBrowserControl as IObjectWithSite)?.SetSite(sp); + + private bool ShouldSerializePropertyBagName() => propertyBagName != defaultPropBagName; + + /// The navigation log is a history of the locations visited by the explorer browser. + public class ExplorerBrowserNavigationLog + { + private ExplorerBrowser parent = null; + + /// The pending navigation log action. null if the user is not navigating via the navigation log. + private PendingNavigation pendingNavigation; + + internal ExplorerBrowserNavigationLog(ExplorerBrowser parent) + { + // Hook navigation events from the parent to distinguish between navigation log induced navigation, and other navigations. + this.parent = parent ?? throw new ArgumentNullException(nameof(parent)); + this.parent.Navigated += OnNavigated; + this.parent.NavigationFailed += OnNavigationFailed; + } + + /// Fires when the navigation log changes or the current navigation position changes + public event EventHandler NavigationLogChanged; + + /// Indicates the presence of locations in the log that can be reached by calling Navigate(Backward) + public bool CanNavigateBackward => CurrentLocationIndex > 0; + + /// Indicates the presence of locations in the log that can be reached by calling Navigate(Forward) + public bool CanNavigateForward => CurrentLocationIndex < Locations.Count - 1; + + /// Gets the shell object in the Locations collection pointed to by CurrentLocationIndex. + public ShellItem CurrentLocation => CurrentLocationIndex < 0 ? null : Locations[CurrentLocationIndex]; + + /// + /// An index into the Locations collection. The ShellItem pointed to by this index is the current location of the ExplorerBrowser. + /// + public int CurrentLocationIndex { get; set; } = -1; + + /// The navigation log + public List Locations { get; } = new List(); + + /// Clears the contents of the navigation log. + public void Clear() + { + if (Locations.Count == 0) return; + + var oldCanNavigateBackward = CanNavigateBackward; + var oldCanNavigateForward = CanNavigateForward; + + Locations.Clear(); + CurrentLocationIndex = -1; + + var args = new NavigationLogEventArgs + { + LocationsChanged = true, + CanNavigateBackwardChanged = oldCanNavigateBackward != CanNavigateBackward, + CanNavigateForwardChanged = oldCanNavigateForward != CanNavigateForward + }; + NavigationLogChanged?.Invoke(this, args); + } + + internal bool NavigateLog(NavigationLogDirection direction) + { + // determine proper index to navigate to + var locationIndex = 0; + if (direction == NavigationLogDirection.Backward && CanNavigateBackward) + { + locationIndex = CurrentLocationIndex - 1; + } + else if (direction == NavigationLogDirection.Forward && CanNavigateForward) + { + locationIndex = CurrentLocationIndex + 1; + } + else + { + return false; + } + + // initiate traversal request + var location = Locations[locationIndex]; + pendingNavigation = new PendingNavigation(location, locationIndex); + parent.Navigate(location); + return true; + } + + internal bool NavigateLog(int index) + { + // can't go anywhere + if (index >= Locations.Count || index < 0) return false; + + // no need to re navigate to the same location + if (index == CurrentLocationIndex) return false; + + // initiate traversal request + var location = Locations[index]; + pendingNavigation = new PendingNavigation(location, index); + parent.Navigate(location); + return true; + } + + private void OnNavigated(object sender, NavigatedEventArgs args) + { + var eventArgs = new NavigationLogEventArgs(); + var oldCanNavigateBackward = CanNavigateBackward; + var oldCanNavigateForward = CanNavigateForward; + + if (pendingNavigation != null) + { + // navigation log traversal in progress + + // determine if new location is the same as the traversal request + var shellItemsEqual = pendingNavigation.Location.IShellItem.Compare(args.NewLocation.IShellItem, SICHINTF.SICHINT_ALLFIELDS) == 0; + if (shellItemsEqual == false) + { + // new location is different than traversal request, behave is if it never happened! remove history following + // currentLocationIndex, append new item + if (CurrentLocationIndex < Locations.Count - 1) + { + Locations.RemoveRange(CurrentLocationIndex + 1, Locations.Count - (CurrentLocationIndex + 1)); + } + Locations.Add(args.NewLocation); + CurrentLocationIndex = Locations.Count - 1; + eventArgs.LocationsChanged = true; + } + else + { + // log traversal successful, update index + CurrentLocationIndex = pendingNavigation.Index; + eventArgs.LocationsChanged = false; + } + pendingNavigation = null; + } + else + { + // remove history following currentLocationIndex, append new item + if (CurrentLocationIndex < Locations.Count - 1) + { + Locations.RemoveRange(CurrentLocationIndex + 1, Locations.Count - (CurrentLocationIndex + 1)); + } + Locations.Add(args.NewLocation); + CurrentLocationIndex = Locations.Count - 1; + eventArgs.LocationsChanged = true; + } + + // update event args + eventArgs.CanNavigateBackwardChanged = oldCanNavigateBackward != CanNavigateBackward; + eventArgs.CanNavigateForwardChanged = oldCanNavigateForward != CanNavigateForward; + + NavigationLogChanged?.Invoke(this, eventArgs); + } + + private void OnNavigationFailed(object sender, NavigationFailedEventArgs args) => pendingNavigation = null; + + /// A navigation traversal request + private class PendingNavigation + { + internal PendingNavigation(ShellItem location, int index) + { + Location = location; + Index = index; + } + + internal int Index { get; set; } + + internal ShellItem Location { get; set; } + } + } + + /// Controls the visibility of the various ExplorerBrowser panes on subsequent navigation + [TypeConverter(typeof(BetterExpandableObjectConverter)), Serializable] + public class ExplorerBrowserPaneVisibility + { + /// Additional fields and options to aid in a search. + [DefaultValue(PaneVisibilityState.Default), Category("Appearance"), Description("Additional fields and options to aid in a search.")] + public PaneVisibilityState AdvancedQuery { get; set; } = PaneVisibilityState.Default; + + /// Commands module along the top of the Windows Explorer window. + [DefaultValue(PaneVisibilityState.Default), Category("Appearance"), Description("Commands module along the top of the Windows Explorer window.")] + public PaneVisibilityState Commands { get; set; } = PaneVisibilityState.Default; + + /// Organize menu within the commands module. + [DefaultValue(PaneVisibilityState.Default), Category("Appearance"), Description("Organize menu within the commands module.")] + public PaneVisibilityState CommandsOrganize { get; set; } = PaneVisibilityState.Default; + + /// View menu within the commands module. + [DefaultValue(PaneVisibilityState.Default), Category("Appearance"), Description("View menu within the commands module.")] + public PaneVisibilityState CommandsView { get; set; } = PaneVisibilityState.Default; + + /// Pane showing metadata along the bottom of the Windows Explorer window. + [DefaultValue(PaneVisibilityState.Default), Category("Appearance"), Description("Pane showing metadata along the bottom of the Windows Explorer window.")] + public PaneVisibilityState Details { get; set; } = PaneVisibilityState.Default; + + /// The pane on the left side of the Windows Explorer window that hosts the folders tree and Favorites. + [DefaultValue(PaneVisibilityState.Default), Category("Appearance"), Description("The pane on the left side of the Windows Explorer window that hosts the folders tree and Favorites.")] + public PaneVisibilityState Navigation { get; set; } = PaneVisibilityState.Default; + + /// Pane on the right of the Windows Explorer window that shows a large reading preview of the file. + [DefaultValue(PaneVisibilityState.Default), Category("Appearance"), Description("Pane on the right of the Windows Explorer window that shows a large reading preview of the file.")] + public PaneVisibilityState Preview { get; set; } = PaneVisibilityState.Default; + + /// Quick filter buttons to aid in a search. + [DefaultValue(PaneVisibilityState.Default), Category("Appearance"), Description("Quick filter buttons to aid in a search.")] + public PaneVisibilityState Query { get; set; } = PaneVisibilityState.Default; + + /// + /// Introduced in Windows 8: The ribbon, which is the control that replaced menus and toolbars at the top of many Microsoft applications. + /// + [DefaultValue(PaneVisibilityState.Default), Category("Appearance"), Description("The ribbon, which is the control that replaced menus and toolbars at the top of many Microsoft applications.")] + public PaneVisibilityState Ribbon { get; set; } = PaneVisibilityState.Default; + + /// Introduced in Windows 8: A status bar that indicates the progress of some process, such as copying or downloading. + [DefaultValue(PaneVisibilityState.Default), Category("Appearance"), Description("A status bar that indicates the progress of some process, such as copying or downloading.")] + public PaneVisibilityState StatusBar { get; set; } = PaneVisibilityState.Default; + } + + /// This provides a connection point container compatible dispatch interface for hooking into the ExplorerBrowser view. + [ComVisible(true)] + [ClassInterface(ClassInterfaceType.AutoDual)] + private class ExplorerBrowserViewEvents : IDisposable + { + private const int ContentsChanged = 207; + private const int FileListEnumDone = 201; + private const int SelectedItemModified = 220; + private const int SelectionChanged = 200; + private static readonly Guid IID_DShellFolderViewEvents = new Guid("62112AA2-EBE4-11cf-A5FB-0020AFE7292D"); + private static readonly Guid IID_IDispatch = new Guid("00020400-0000-0000-C000-000000000046"); + private ExplorerBrowser parent; + private uint viewConnectionPointCookie; + private object viewDispatch; + + /// Default constructor for ExplorerBrowserViewEvents + public ExplorerBrowserViewEvents() : this(null) { } + + internal ExplorerBrowserViewEvents(ExplorerBrowser parent) => this.parent = parent; + + /// Finalizes ExplorerBrowserViewEvents + ~ExplorerBrowserViewEvents() + { + Dispose(false); + } + + /// Disconnects and disposes object. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// The contents of the view have changed + [DispId(ContentsChanged)] + public void ViewContentsChanged() => parent.OnItemsChanged(); + + /// The enumeration of files in the view is complete + [DispId(FileListEnumDone)] + public void ViewFileListEnumDone() => parent.OnItemsEnumerated(); + + /// The selected item in the view has changed (not the same as the selection has changed) + [DispId(SelectedItemModified)] + public void ViewSelectedItemModified() => parent.OnSelectedItemModified(); + + /// The view selection has changed + [DispId(SelectionChanged)] + public void ViewSelectionChanged() => parent.OnSelectionChanged(); + + internal void ConnectToView(IShellView psv) + { + DisconnectFromView(); + viewDispatch = psv.GetItemObject(SVGIO.SVGIO_BACKGROUND, IID_IDispatch); + var hr = ConnectToConnectionPoint(this, IID_DShellFolderViewEvents, true, viewDispatch, ref viewConnectionPointCookie, out var _); + if (hr != HRESULT.S_OK) + viewDispatch = null; + } + + internal void DisconnectFromView() + { + if (viewDispatch is null) return; + ConnectToConnectionPoint(null, IID_DShellFolderViewEvents, false, viewDispatch, ref viewConnectionPointCookie, out var _); + viewDispatch = null; + viewConnectionPointCookie = 0; + } + + // These need to be public to be accessible via AutoDual reflection + /// Disconnects and disposes object. + /// + protected virtual void Dispose(bool disposed) + { + if (disposed) + { + DisconnectFromView(); + } + } + } + + /// Represents a collection of attached to an . + private class ShellItemCollection : IReadOnlyList + { + private readonly ExplorerBrowser eb; + private readonly SVGIO option; + + internal ShellItemCollection(ExplorerBrowser eb, SVGIO opt) + { + this.eb = eb; + option = opt; + } + + /// Gets the number of elements in the collection. + /// Returns a value. + public int Count => (int)Array.GetCount(); + + private IShellItemArray Array => eb.GetItemsArray(option); + + private IEnumerable Items + { + get + { + var array = Array; + for (uint i = 0; i < array.GetCount(); i++) + yield return array.GetItemAt(i); + } + } + + /// Gets the at the specified index. + /// The . + /// The zero-based index of the element to get. + public ShellItem this[int index] + { + get + { + try + { + return ShellItem.Open(Array.GetItemAt((uint)index)); + } + catch + { + return null; + } + } + } + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + public IEnumerator GetEnumerator() => Items.Select(ShellItem.Open).GetEnumerator(); + + /// Returns an enumerator that iterates through the collection. + /// An enumerator that can be used to iterate through the collection. + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + } + + /// Event argument for The Navigated event + public class NavigatedEventArgs : EventArgs + { + /// The new location of the explorer browser + public ShellItem NewLocation { get; set; } + } + + /// Event argument for The Navigating event + public class NavigatingEventArgs : EventArgs + { + /// Set to 'True' to cancel the navigation. + public bool Cancel { get; set; } + + /// The location being navigated to + public ShellItem PendingLocation { get; set; } + } + + /// Event argument for the NavigatinoFailed event + public class NavigationFailedEventArgs : EventArgs + { + /// The location the browser would have navigated to. + public ShellItem FailedLocation { get; set; } + } + + /// The event argument for NavigationLogChangedEvent + public class NavigationLogEventArgs : EventArgs + { + /// Indicates CanNavigateBackward has changed + public bool CanNavigateBackwardChanged { get; set; } + + /// Indicates CanNavigateForward has changed + public bool CanNavigateForwardChanged { get; set; } + + /// Indicates the Locations collection has changed + public bool LocationsChanged { get; set; } + } +} \ No newline at end of file diff --git a/WIndows.Forms/Design/ExplorerBrowserDesigner.cs b/WIndows.Forms/Design/ExplorerBrowserDesigner.cs new file mode 100644 index 00000000..e42e10c2 --- /dev/null +++ b/WIndows.Forms/Design/ExplorerBrowserDesigner.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing.Design; +using System.Windows.Forms.Design; + +namespace Vanara.Windows.Forms.Design +{ + [global::System.Security.Permissions.PermissionSet(global::System.Security.Permissions.SecurityAction.Demand, Name = "FullTrust")] + [EditorBrowsable(EditorBrowsableState.Never)] + internal class ExplorerBrowserDesigner : AttributedControlDesigner, IToolboxUser + { + public ExplorerBrowserDesigner() { } + + public override SelectionRules SelectionRules => (SelectionRules.Visible | SelectionRules.AllSizeable | SelectionRules.Moveable); + + protected override IEnumerable PropertiesToRemove { get; } = new string[] { "AutoEllipsis", "BackColor", + "BackgroundImage", "BackgroundImageLayout", "CausesValidation", "ContextMenuStrip", "Cursor", "Font", + "ForeColor", "IMEMode", "Padding", "Text" }; + + public override void Initialize(IComponent component) + { + base.Initialize(component); + AutoResizeHandles = true; + } + + bool IToolboxUser.GetToolSupported(ToolboxItem tool) => true; + + void IToolboxUser.ToolPicked(ToolboxItem tool) { } + } +} \ No newline at end of file diff --git a/WIndows.Forms/Vanara.Windows.Forms.csproj b/WIndows.Forms/Vanara.Windows.Forms.csproj index 6351df1c..c02acef6 100644 --- a/WIndows.Forms/Vanara.Windows.Forms.csproj +++ b/WIndows.Forms/Vanara.Windows.Forms.csproj @@ -43,6 +43,12 @@ BitmapProperty, BoolProperty, CloakingSource, CollapsiblePanelBorderCondition, C + + + + + + @@ -51,8 +57,12 @@ BitmapProperty, BoolProperty, CloakingSource, CollapsiblePanelBorderCondition, C + + + + @@ -66,6 +76,7 @@ BitmapProperty, BoolProperty, CloakingSource, CollapsiblePanelBorderCondition, C + @@ -98,9 +109,6 @@ BitmapProperty, BoolProperty, CloakingSource, CollapsiblePanelBorderCondition, C Component - - Component - Component