using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using Vanara.Collections; using Vanara.InteropServices; using Vanara.PInvoke; using static Vanara.PInvoke.Ole32; using static Vanara.PInvoke.PropSys; using static Vanara.PInvoke.Shell32; namespace Vanara.Windows.Shell { /// Queued and static file operations using the Shell. /// public class ShellFileOperations : IDisposable { private const OperationFlags defaultOptions = OperationFlags.AllowUndo | OperationFlags.NoConfirmMkDir; private bool disposedValue = false; private IFileOperation op; private OperationFlags opFlags = defaultOptions; private IFileOperationProgressSink sink; private uint sinkCookie; /// Initializes a new instance of the class. /// The window that owns the modal dialog. This value can be . public ShellFileOperations(System.Windows.Forms.IWin32Window owner = null) { op = new IFileOperation(); if (owner != null) op.SetOwnerWindow(owner.Handle); sink = new OpSink(this); sinkCookie = op.Advise(sink); } /// Finalizes an instance of the class. ~ShellFileOperations() { Dispose(false); } /// Performs caller-implemented actions after the last operation performed by the call to IFileOperation is complete. public event EventHandler FinishOperations; /// Performs caller-implemented actions after the copy process for each item is complete. public event EventHandler PostCopyItem; /// Performs caller-implemented actions after the delete process for each item is complete. public event EventHandler PostDeleteItem; /// Performs caller-implemented actions after the move process for each item is complete. public event EventHandler PostMoveItem; /// Performs caller-implemented actions after the new item is created. public event EventHandler PostNewItem; /// Performs caller-implemented actions after the rename process for each item is complete. public event EventHandler PostRenameItem; /// Performs caller-implemented actions before the copy process for each item begins. public event EventHandler PreCopyItem; /// Performs caller-implemented actions before the delete process for each item begins. public event EventHandler PreDeleteItem; /// Performs caller-implemented actions before the move process for each item begins. public event EventHandler PreMoveItem; /// Performs caller-implemented actions before the process to create a new item begins. public event EventHandler PreNewItem; /// Performs caller-implemented actions before the rename process for each item begins. public event EventHandler PreRenameItem; /// Performs caller-implemented actions before any specific file operations are performed. public event EventHandler StartOperations; /// Updates the progress. public event System.ComponentModel.ProgressChangedEventHandler UpdateProgress; /// Flags that control the file operation. [Flags] public enum OperationFlags : uint { /// /// The pTo member specifies multiple destination files (one for each source file in pFrom) rather than one directory where all source files are to /// be deposited. /// MultiDestFiles = FILEOP_FLAGS.FOF_MULTIDESTFILES, /// Do not display a progress dialog box. Silent = FILEOP_FLAGS.FOF_SILENT, /// Give the item being operated on a new name in a move, copy, or rename operation if an item with the target name already exists. RenameOnCollision = FILEOP_FLAGS.FOF_RENAMEONCOLLISION, /// Respond with Yes to All for any dialog box that is displayed. NoConfirmation = FILEOP_FLAGS.FOF_NOCONFIRMATION, /// /// If FOF_RENAMEONCOLLISION is specified and any files were renamed, assign a name mapping object that contains their old and new names to the /// hNameMappings member. This object must be freed using SHFreeNameMappings when it is no longer needed. /// WantMappingHandle = FILEOP_FLAGS.FOF_WANTMAPPINGHANDLE, /// /// Preserve undo information, if possible. /// Prior to Windows Vista, operations could be undone only from the same process that performed the original operation. /// /// In Windows Vista and later systems, the scope of the undo is a user session. Any process running in the user session can undo another operation. /// The undo state is held in the Explorer.exe process, and as long as that process is running, it can coordinate the undo functions. /// /// If the source file parameter does not contain fully qualified path and file names, this flag is ignored. /// AllowUndo = FILEOP_FLAGS.FOF_ALLOWUNDO, /// Perform the operation only on files (not on folders) if a wildcard file name (*.*) is specified. FilesOnly = FILEOP_FLAGS.FOF_FILESONLY, /// Display a progress dialog box but do not show individual file names as they are operated on. SimpleProgress = FILEOP_FLAGS.FOF_SIMPLEPROGRESS, /// Do not confirm the creation of a new folder if the operation requires one to be created. NoConfirmMkDir = FILEOP_FLAGS.FOF_NOCONFIRMMKDIR, /// /// Do not display a message to the user if an error occurs. If this flag is set without FOFX_EARLYFAILURE, any error is treated as if the user had /// chosen Ignore or Continue in a dialog box. It halts the current action, sets a flag to indicate that an action was aborted, and proceeds with the /// rest of the operation. /// NoErrorUI = FILEOP_FLAGS.FOF_NOERRORUI, /// Do not copy the security attributes of the item. NoCopySecurityAttribs = FILEOP_FLAGS.FOF_NOCOPYSECURITYATTRIBS, /// Only operate in the local folder. Do not operate recursively into subdirectories. NoRecursion = FILEOP_FLAGS.FOF_NORECURSION, /// Do not move connected items as a group. Only move the specified files. NoConnectedElements = FILEOP_FLAGS.FOF_NO_CONNECTED_ELEMENTS, /// /// Send a warning if a file or folder is being destroyed during a delete operation rather than recycled. This flag partially overrides FOF_NOCONFIRMATION. /// WantNukeWarning = FILEOP_FLAGS.FOF_WANTNUKEWARNING, /// /// Walk into Shell namespace junctions. By default, junctions are not entered. For more information on junctions, see Specifying a Namespace /// Extension's Location. /// NoSkipJunctions = FILEOP_FLAGS.FOFX_NOSKIPJUNCTIONS, /// If possible, create a hard link rather than a new instance of the file in the destination. PreferHardLink = FILEOP_FLAGS.FOFX_PREFERHARDLINK, /// If an operation requires elevated rights and the FOF_NOERRORUI flag is set to disable error UI, display a UAC UI prompt nonetheless. ShowElevationPrompt = FILEOP_FLAGS.FOFX_SHOWELEVATIONPROMPT, /// /// If FOFX_EARLYFAILURE is set together with FOF_NOERRORUI, the entire set of operations is stopped upon encountering any error in any operation. /// This flag is valid only when FOF_NOERRORUI is set. /// EarlyFailure = FILEOP_FLAGS.FOFX_EARLYFAILURE, /// /// Rename collisions in such a way as to preserve file name extensions. This flag is valid only when FOF_RENAMEONCOLLISION is also set. /// PreserveFileExtensions = FILEOP_FLAGS.FOFX_PRESERVEFILEEXTENSIONS, /// /// Keep the newer file or folder, based on the Date Modified property, if a collision occurs. This is done automatically with no prompt UI presented /// to the user. /// KeepNewerFile = FILEOP_FLAGS.FOFX_KEEPNEWERFILE, /// Do not use copy hooks. NoCopyHooks = FILEOP_FLAGS.FOFX_NOCOPYHOOKS, /// Do not allow the progress dialog to be minimized. NoMinimizeBox = FILEOP_FLAGS.FOFX_NOMINIMIZEBOX, /// /// Copy the security attributes of the source item to the destination item when performing a cross-volume move operation. Without this flag, the /// destination item receives the security attributes of its new folder. /// MoveACLsAcrossVolumes = FILEOP_FLAGS.FOFX_MOVEACLSACROSSVOLUMES, /// Do not display the path of the source item in the progress dialog. DontDisplaySourcePath = FILEOP_FLAGS.FOFX_DONTDISPLAYSOURCEPATH, /// Do not display the path of the destination item in the progress dialog. DontDisplayDestPath = FILEOP_FLAGS.FOFX_DONTDISPLAYDESTPATH, /// /// Introduced in Windows Vista SP1. The user expects a requirement for rights elevation, so do not display a dialog box asking for a confirmation of /// the elevation. /// RequireElevation = FILEOP_FLAGS.FOFX_REQUIREELEVATION, /// Introduced in Windows 8. The file operation was user-invoked and should be placed on the undo stack. This flag is preferred to FOF_ALLOWUNDO. AddUndoRecord = FILEOP_FLAGS.FOFX_ADDUNDORECORD, /// Introduced in Windows 7. Display a Downloading instead of Copying message in the progress dialog. CopyAsDownload = FILEOP_FLAGS.FOFX_COPYASDOWNLOAD, /// Introduced in Windows 7. Do not display the location line in the progress dialog. DontDisplayLocations = FILEOP_FLAGS.FOFX_DONTDISPLAYLOCATIONS, } /// Used by methods of the ITransferSource and ITransferDestination interfaces to control their file operations. [Flags] public enum TransferFlags { /// Fail if the destination already exists, unless TSF_OVERWRITE_EXIST is specified. This is a default behavior. Normal = TRANSFER_SOURCE_FLAGS.TSF_NORMAL, /// Fail if the destination already exists, unless TSF_OVERWRITE_EXIST is specified. This is a default behavior FailExist = TRANSFER_SOURCE_FLAGS.TSF_FAIL_EXIST, /// Rename with auto-name generation if the destination already exists. RenameExist = TRANSFER_SOURCE_FLAGS.TSF_RENAME_EXIST, /// Overwrite or merge with the destination. OverwriteExist = TRANSFER_SOURCE_FLAGS.TSF_OVERWRITE_EXIST, /// Allow creation of a decrypted destination. AllowDecryption = TRANSFER_SOURCE_FLAGS.TSF_ALLOW_DECRYPTION, /// No discretionary access control list (DACL), system access control list (SACL), or owner. NoSecurity = TRANSFER_SOURCE_FLAGS.TSF_NO_SECURITY, /// /// Copy the creation time as part of the copy. This can be useful for a move operation that is being used as a copy and delete operation (TSF_MOVE_AS_COPY_DELETE). /// CopyCreationTime = TRANSFER_SOURCE_FLAGS.TSF_COPY_CREATION_TIME, /// Copy the last write time as part of the copy. CopyWriteTime = TRANSFER_SOURCE_FLAGS.TSF_COPY_WRITE_TIME, /// Assign write, read, and delete permissions as share mode. UseFullAccess = TRANSFER_SOURCE_FLAGS.TSF_USE_FULL_ACCESS, /// Recycle on file delete, if possible. DeleteRecycleIfPossible = TRANSFER_SOURCE_FLAGS.TSF_DELETE_RECYCLE_IF_POSSIBLE, /// Hard link to the desired source (not required). This avoids a normal copy operation. CopyHardLink = TRANSFER_SOURCE_FLAGS.TSF_COPY_HARD_LINK, /// Copy the localized name. CopyLocalizedName = TRANSFER_SOURCE_FLAGS.TSF_COPY_LOCALIZED_NAME, /// Move as a copy and delete operation. MoveAsCopyDelete = TRANSFER_SOURCE_FLAGS.TSF_MOVE_AS_COPY_DELETE, /// Suspend Shell events. SuspendShellEvents = TRANSFER_SOURCE_FLAGS.TSF_SUSPEND_SHELLEVENTS, } /// Gets or sets options that control file operations. public OperationFlags Options { get => opFlags; set { if (value == opFlags) return; op.SetOperationFlags((FILEOP_FLAGS)value); opFlags = value; } } public int QueuedOperations { get; protected set; } // TODO: public Form CustomProgressDialog { get; set; } /// Copies a single item to a specified destination using the Shell to provide progress and error dialogs. /// A that specifies the source item. /// A that specifies the destination folder to contain the copy of the item. /// /// An optional new name for the item after it has been copied. This can be . If , the name of the /// destination item is the same as the source. /// /// Options that control file operations. public static void Copy(ShellItem source, ShellFolder dest, string newName = null, OperationFlags options = defaultOptions) { using (var sop = new ShellFileOperations()) { sop.Options = options; HRESULT hr = HRESULT.S_OK; sop.PostCopyItem += OnPost; try { sop.QueueCopyOperation(source, dest, newName); sop.PerformOperations(); hr.ThrowIfFailed(); } finally { sop.PostCopyItem -= OnPost; } void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; } } /// Copies a set of items to a specified destination using the Shell to provide progress and error dialogs. /// An of instances that represent the group of items to be copied. /// A that specifies the destination folder to contain the copy of the items. /// Options that control file operations. public static void Copy(IEnumerable sourceItems, ShellFolder dest, OperationFlags options = defaultOptions) { using (var sop = new ShellFileOperations()) { sop.Options = options; HRESULT hr = HRESULT.S_OK; sop.PostCopyItem += OnPost; try { sop.QueueCopyOperation(sourceItems, dest); sop.PerformOperations(); hr.ThrowIfFailed(); } finally { sop.PostCopyItem -= OnPost; } void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; } } /// Deletes a single item using the Shell to provide progress and error dialogs. /// A that specifies the item to be deleted. /// Options that control file operations. public static void Delete(ShellItem source, OperationFlags options = defaultOptions) { using (var sop = new ShellFileOperations()) { sop.Options = options; HRESULT hr = HRESULT.S_OK; sop.PostDeleteItem += OnPost; try { sop.QueueDeleteOperation(source); sop.PerformOperations(); hr.ThrowIfFailed(); } finally { sop.PostDeleteItem -= OnPost; } void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; } } /// Deletes a set of items using the Shell to provide progress and error dialogs. /// An of instances which represents the group of items to be deleted. /// Options that control file operations. public static void Delete(IEnumerable sourceItems, OperationFlags options = defaultOptions) { using (var sop = new ShellFileOperations()) { sop.Options = options; HRESULT hr = HRESULT.S_OK; sop.PostDeleteItem += OnPost; try { sop.QueueDeleteOperation(sourceItems); sop.PerformOperations(); hr.ThrowIfFailed(); } finally { sop.PostDeleteItem -= OnPost; } void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; } } /// Moves a single item to a specified destination using the Shell to provide progress and error dialogs. /// A that specifies the source item. /// A that specifies the destination folder to contain the moved item. /// /// An optional new name for the item in its new location. This can be . If , the name of the destination /// item is the same as the source. /// /// Options that control file operations. public static void Move(ShellItem source, ShellFolder dest, string newName = null, OperationFlags options = defaultOptions) { using (var sop = new ShellFileOperations()) { sop.Options = options; HRESULT hr = HRESULT.S_OK; sop.PostMoveItem += OnPost; try { sop.QueueMoveOperation(source, dest, newName); sop.PerformOperations(); hr.ThrowIfFailed(); } finally { sop.PostMoveItem -= OnPost; } void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; } } /// Moves a set of items to a specified destination using the Shell to provide progress and error dialogs. /// An of instances which represents the group of items to be moved. /// A that specifies the destination folder to contain the moved items. /// Options that control file operations. public static void Move(IEnumerable sourceItems, ShellFolder dest, OperationFlags options = defaultOptions) { using (var sop = new ShellFileOperations()) { sop.Options = options; HRESULT hr = HRESULT.S_OK; sop.PostMoveItem += OnPost; try { sop.QueueMoveOperation(sourceItems, dest); sop.PerformOperations(); hr.ThrowIfFailed(); } finally { sop.PostMoveItem -= OnPost; } void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; } } /// Creates a new item in a specified location using the Shell to provide progress and error dialogs. /// A that specifies the destination folder that will contain the new item. /// The file name of the new item, for instance Newfile.txt. /// A value that specifies the file system attributes for the file or folder. /// /// The name of the template file (for example Excel9.xls) that the new item is based on, stored in one of the following locations: /// /// CSIDL_COMMON_TEMPLATES. The default path for this folder is %ALLUSERSPROFILE%\Templates. /// CSIDL_TEMPLATES. The default path for this folder is %USERPROFILE%\Templates. /// %SystemRoot%\shellnew /// /// /// This is a string used to specify an existing file of the same type as the new file, containing the minimal content that an application wants to /// include in any new file. /// /// This parameter is normally to specify a new, blank file. /// /// Options that control file operations. public static void NewItem(ShellFolder dest, string name, System.IO.FileAttributes attr = System.IO.FileAttributes.Normal, string template = null, OperationFlags options = defaultOptions) { using (var sop = new ShellFileOperations()) { sop.Options = options; HRESULT hr = HRESULT.S_OK; sop.PostNewItem += OnPost; try { sop.QueueNewItemOperation(dest, name, attr, template); sop.PerformOperations(); hr.ThrowIfFailed(); } finally { sop.PostRenameItem -= OnPost; } void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; } } /// Renames a single item to a new display name using the Shell to provide progress and error dialogs. /// A that specifies the source item. /// The new display name of the item. /// Options that control file operations. public static void Rename(ShellItem source, string newName = null, OperationFlags options = defaultOptions) { using (var sop = new ShellFileOperations()) { sop.Options = options; HRESULT hr = HRESULT.S_OK; sop.PostRenameItem += OnPost; try { sop.QueueRenameOperation(source, newName); sop.PerformOperations(); hr.ThrowIfFailed(); } finally { sop.PostRenameItem -= OnPost; } void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; } } /// /// Renames a set of items that are to be given a new display name using the Shell to provide progress and error dialogs. All items are given the same name. /// /// An of instances which represents the group of items to be renamed. /// The new display name of the items. /// Options that control file operations. public static void Rename(IEnumerable sourceItems, string newName, OperationFlags options = defaultOptions) { using (var sop = new ShellFileOperations()) { sop.Options = options; HRESULT hr = HRESULT.S_OK; sop.PostRenameItem += OnPost; try { sop.QueueRenameOperation(sourceItems, newName); sop.PerformOperations(); hr.ThrowIfFailed(); } finally { sop.PostRenameItem -= OnPost; } void OnPost(object sender, ShellFileOpEventArgs e) => hr = e.Result; } } /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// Executes all selected operations. /// /// This method is called last to execute those actions that have been specified earlier by calling their individual methods. For instance, /// does not rename the item, it simply sets the parameters. The actual renaming is done when you /// call PerformOperations. /// public void PerformOperations() { op.PerformOperations(); QueuedOperations = 0; } /// Declares a set of properties and values to be set on an item. /// The item to receive the new property values. /// /// An , which contains a dictionary of objects that specify the properties to be set and their new values. /// public void QueueApplyPropertiesOperation(ShellItem item, ShellItemPropertyUpdates props) { op.SetProperties(props.IPropertyChangeArray); op.ApplyPropertiesToItem(item.IShellItem); QueuedOperations++; } /// Declares a set of properties and values to be set on a set of items. /// /// An of instances that represent the group of items to which to apply the properties. /// /// /// An , which contains a dictionary of objects that specify the properties to be set and their new values. /// public void QueueApplyPropertiesOperation(IEnumerable items, ShellItemPropertyUpdates props) { op.SetProperties(props.IPropertyChangeArray); op.ApplyPropertiesToItems(new ShellItemArray(items).IShellItemArray); QueuedOperations++; } /// Declares a single item that is to be copied to a specified destination. /// A that specifies the source item. /// A that specifies the destination folder to contain the copy of the item. /// /// An optional new name for the item after it has been copied. This can be . If , the name of the /// destination item is the same as the source. /// public void QueueCopyOperation(ShellItem source, ShellFolder dest, string newName = null) { op.CopyItem(source.IShellItem, dest.IShellItem, newName, null); QueuedOperations++; } /// Declares a set of items that are to be copied to a specified destination. /// An of instances that represent the group of items to be copied. /// A that specifies the destination folder to contain the copy of the items. public void QueueCopyOperation(IEnumerable sourceItems, ShellFolder dest) { op.CopyItems(new ShellItemArray(sourceItems).IShellItemArray, dest.IShellItem); QueuedOperations++; } /// Declares a single item that is to be deleted. /// >A that specifies the item to be deleted. public void QueueDeleteOperation(ShellItem item) { op.DeleteItem(item.IShellItem, null); QueuedOperations++; } /// Declares a set of items that are to be deleted. /// An of instances which represents the group of items to be deleted. public void QueueDeleteOperation(IEnumerable items) { op.DeleteItems(new ShellItemArray(items).IShellItemArray); QueuedOperations++; } /// Declares a single item that is to be moved to a specified destination. /// A that specifies the source item. /// A that specifies the destination folder to contain the moved item. /// /// An optional new name for the item in its new location. This can be . If , the name of the destination /// item is the same as the source. /// public void QueueMoveOperation(ShellItem source, ShellFolder dest, string newName = null) { op.MoveItem(source.IShellItem, dest.IShellItem, newName, null); QueuedOperations++; } /// Declares a set of items that are to be moved to a specified destination. /// An of instances which represents the group of items to be moved. /// A that specifies the destination folder to contain the moved items. public void QueueMoveOperation(IEnumerable sourceItems, ShellFolder dest) { op.MoveItems(new ShellItemArray(sourceItems).IShellItemArray, dest.IShellItem); QueuedOperations++; } /// Declares a new item that is to be created in a specified location. /// A that specifies the destination folder that will contain the new item. /// The file name of the new item, for instance Newfile.txt. /// A value that specifies the file system attributes for the file or folder. /// /// The name of the template file (for example Excel9.xls) that the new item is based on, stored in one of the following locations: /// /// CSIDL_COMMON_TEMPLATES. The default path for this folder is %ALLUSERSPROFILE%\Templates. /// CSIDL_TEMPLATES. The default path for this folder is %USERPROFILE%\Templates. /// %SystemRoot%\shellnew /// /// /// This is a string used to specify an existing file of the same type as the new file, containing the minimal content that an application wants to /// include in any new file. /// /// This parameter is normally to specify a new, blank file. /// public void QueueNewItemOperation(ShellFolder dest, string name, System.IO.FileAttributes attr = System.IO.FileAttributes.Normal, string template = null) { op.NewItem(dest.IShellItem, attr, name, template, null); QueuedOperations++; } /// Declares a single item that is to be given a new display name. /// A that specifies the source item. /// The new display name of the item. public void QueueRenameOperation(ShellItem source, string newName) { op.RenameItem(source.IShellItem, newName, null); QueuedOperations++; } /// Declares a set of items that are to be given a new display name. All items are given the same name. /// An of instances which represents the group of items to be renamed. /// The new display name of the items. public void QueueRenameOperation(IEnumerable sourceItems, string newName) { op.RenameItems(new ShellItemArray(sourceItems).IShellItemArray, newName); QueuedOperations++; } /// Releases unmanaged and - optionally - managed resources. /// true to release both managed and unmanaged resources; false to release only unmanaged resources. protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { // TODO: dispose managed state (managed objects). } if (sink != null) op.Unadvise(sinkCookie); if (op != null) { Marshal.FinalReleaseComObject(op); op = null; } disposedValue = true; } } /// Arguments supplied to the event. /// public class ShellFileNewOpEventArgs : ShellFileOpEventArgs { internal ShellFileNewOpEventArgs(TRANSFER_SOURCE_FLAGS flags, IShellItem source, IShellItem folder, IShellItem dest, string name, HRESULT hr, string templ, uint attr) : base(flags, source, folder, dest, name, hr) { TemplateName = templ; FileAttributes = (System.IO.FileAttributes)attr; } /// Gets the name of the template. /// The name of the template. public string TemplateName { get; protected set; } /// Gets the file attributes. /// The file attributes. public System.IO.FileAttributes FileAttributes { get; protected set; } } /// Arguments supplied to events from . Depending on the event, some properties may not be set. /// public class ShellFileOpEventArgs : EventArgs { internal ShellFileOpEventArgs(TRANSFER_SOURCE_FLAGS flags, IShellItem source, IShellItem folder = null, IShellItem dest = null, string name = null, HRESULT hr = default) { Flags = (TransferFlags)flags; if (source != null) SourceItem = ShellItem.Open(source); if (folder != null) DestFolder = ShellItem.Open(folder); if (dest != null) DestItem = ShellItem.Open(dest); Name = name; Result = hr; } /// Gets the destination folder. /// The destination folder. public ShellItem DestFolder { get; protected set; } /// Gets the destination item. /// The destination item. public ShellItem DestItem { get; protected set; } /// Gets the tranfer flag values. /// The flags. public TransferFlags Flags { get; protected set; } /// Gets the name of the item. /// The item name. public string Name { get; protected set; } /// Gets the result of the operation. /// The result. public HRESULT Result { get; protected set; } /// Gets the source item. /// The source item. public ShellItem SourceItem { get; protected set; } /// Returns a that represents this instance. /// A that represents this instance. public override string ToString() => $"HR:{Result};Src:{SourceItem};DFld:{DestFolder};Dst:{DestItem};Name:{Name}"; } private class OpSink : IFileOperationProgressSink { private readonly ShellFileOperations parent; public OpSink(ShellFileOperations ops) { parent = ops; } public void FinishOperations(HRESULT hrResult) => parent.FinishOperations?.Invoke(parent, new ShellFileOpEventArgs(0, null, null, null, null, hrResult)); public void PauseTimer() { } public void PostCopyItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, HRESULT hrCopy, IShellItem psiNewlyCreated) => parent.PostCopyItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, psiDestinationFolder, psiNewlyCreated, pszNewName, hrCopy)); public void PostDeleteItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, HRESULT hrDelete, IShellItem psiNewlyCreated) => parent.PostDeleteItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, null, psiNewlyCreated, null, hrDelete)); public void PostMoveItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, HRESULT hrMove, IShellItem psiNewlyCreated) => parent.PostMoveItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, psiDestinationFolder, psiNewlyCreated, pszNewName, hrMove)); public void PostNewItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, [MarshalAs(UnmanagedType.LPWStr)] string pszTemplateName, uint dwFileAttributes, HRESULT hrNew, IShellItem psiNewItem) => parent.PostNewItem?.Invoke(parent, new ShellFileNewOpEventArgs(dwFlags, null, psiDestinationFolder, psiNewItem, pszNewName, hrNew, pszTemplateName, dwFileAttributes)); public void PostRenameItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName, HRESULT hrRename, IShellItem psiNewlyCreated) => parent.PostRenameItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, null, psiNewlyCreated, pszNewName, hrRename)); public void PreCopyItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName) => parent.PreCopyItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, psiDestinationFolder, null, pszNewName)); public void PreDeleteItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem) => parent.PreDeleteItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem)); public void PreMoveItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName) => parent.PreMoveItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, psiDestinationFolder, null, pszNewName)); public void PreNewItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiDestinationFolder, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName) => parent.PreNewItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, null, psiDestinationFolder, null, pszNewName)); public void PreRenameItem(TRANSFER_SOURCE_FLAGS dwFlags, IShellItem psiItem, [MarshalAs(UnmanagedType.LPWStr)] string pszNewName) => parent.PreRenameItem?.Invoke(parent, new ShellFileOpEventArgs(dwFlags, psiItem, null, null, pszNewName)); public void ResetTimer() { } public void ResumeTimer() { } public void StartOperations() => parent.StartOperations?.Invoke(parent, EventArgs.Empty); public void UpdateProgress(uint iWorkTotal, uint iWorkSoFar) => parent.UpdateProgress?.Invoke(parent, new System.ComponentModel.ProgressChangedEventArgs((int)(iWorkSoFar * 100 / iWorkTotal), null)); } } /// /// A dictionary of properties that can be used to set or update property values on Shell items via the /// method. This class wraps the /// COM interface. /// /// /// public class ShellItemPropertyUpdates : IDictionary, IDisposable { private IPropertyChangeArray changes; /// Initializes a new instance of the class. public ShellItemPropertyUpdates() { PSCreatePropertyChangeArray(null, null, null, 0, typeof(IPropertyChangeArray).GUID, out changes).ThrowIfFailed(); } /// Gets the number of elements contained in the . public int Count => (int)changes.GetCount(); /// Gets the COM interface for . /// The value. public IPropertyChangeArray IPropertyChangeArray => changes; /// Gets an containing the keys of the . public ICollection Keys { get { var l = new List(Count); for (uint i = 0; i < Count; i++) { using (var p = new ComReleaser(changes.GetAt(i, typeof(IPropertyChange).GUID))) l.Add(p.Item.GetPropertyKey()); } return l; } } /// Gets an containing the values in the . public ICollection Values { get { var l = new List(Count); for (int i = 0; i < Count; i++) l.Add(this[i].Value); return l; } } /// Gets a value indicating whether the is read-only. bool ICollection>.IsReadOnly => false; /// Gets or sets the with the specified key. /// The . /// The key. /// The object associated with . /// key public object this[PROPERTYKEY key] { get => TryGetValue(key, out var value) ? value : throw new ArgumentOutOfRangeException(nameof(key)); set => changes.AppendOrReplace(ToPC(key, value)); } internal KeyValuePair this[int index] { get { using (var p = new ComReleaser(changes.GetAt((uint)index, typeof(IPropertyChange).GUID))) { p.Item.ApplyToPropVariant(new PROPVARIANT(), out var pv); return new KeyValuePair(p.Item.GetPropertyKey(), pv.Value); } } } /// Adds an element with the provided key and value to the . /// The object to use as the key of the element to add. /// The object to use as the value of the element to add. public void Add(PROPERTYKEY key, object value) { changes.Append(ToPC(key, value)); } /// Removes all items from the . public void Clear() { for (uint i = (uint)Count - 1; i >= 0; i--) changes.RemoveAt(i); } /// Determines whether the contains an element with the specified key. /// The key to locate in the . /// true if the contains an element with the key; otherwise, false. public bool ContainsKey(PROPERTYKEY key) => changes.IsKeyInArray(key).Succeeded; /// Returns an enumerator that iterates through the collection. /// A that can be used to iterate through the collection. public IEnumerator> GetEnumerator() => new IEnumFromIndexer>(changes.GetCount, i => this[(int)i]).GetEnumerator(); /// Removes the element with the specified key from the . /// The key of the element to remove. /// /// true if the element is successfully removed; otherwise, false. This method also returns false if was not found in the original . /// public bool Remove(PROPERTYKEY key) { var idx = IndexOf(key); if (idx == -1) return false; try { changes.RemoveAt((uint)idx); return true; } catch { return false; } } /// Gets the value associated with the specified key. /// The key whose value to get. /// /// When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the /// parameter. This parameter is passed uninitialized. /// /// /// true if the object that implements contains an element with the specified key; otherwise, false. /// public bool TryGetValue(PROPERTYKEY key, out object value) { value = null; var idx = IndexOf(key); if (idx == -1) return false; try { value = this[idx]; return true; } catch { return false; } } void ICollection>.Add(KeyValuePair item) => Add(item.Key, item.Value); bool ICollection>.Contains(KeyValuePair item) => ContainsKey(item.Key) && this[item.Key] == item.Value; void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { if (array == null) throw new ArgumentNullException(nameof(array)); if (arrayIndex + Count > array.Length) throw new ArgumentOutOfRangeException(nameof(arrayIndex)); for (int i = 0; i < Count; i++) array[i + arrayIndex] = this[i]; } void IDisposable.Dispose() { if (changes == null) return; Marshal.FinalReleaseComObject(changes); changes = null; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); bool ICollection>.Remove(KeyValuePair item) { var idx = IndexOf(item.Key); if (idx == -1) return false; if (this[idx].Value != item.Value) return false; try { changes.RemoveAt((uint)idx); return true; } catch { return false; } } private int IndexOf(PROPERTYKEY key) { for (uint i = 0; i < Count; i++) { using (var p = new ComReleaser(changes.GetAt(i, typeof(IPropertyChange).GUID))) if (key == p.Item.GetPropertyKey()) return (int)i; } return -1; } private IPropertyChange ToPC(PROPERTYKEY key, object value, PKA_FLAGS flags = PKA_FLAGS.PKA_SET) { PSCreateSimplePropertyChange(PKA_FLAGS.PKA_SET, key, new PROPVARIANT(value), typeof(IPropertyChange).GUID, out var pc).ThrowIfFailed(); return pc; } } }