sources/Google.Solutions.Mvvm/Controls/OperationProgressDialog.cs (214 lines of code) (raw):

// // Copyright 2022 Google LLC // // Licensed to the Apache Software Foundation (ASF) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The ASF licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, // software distributed under the License is distributed on an // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. // using Google.Solutions.Common.Diagnostics; using System; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; namespace Google.Solutions.Mvvm.Controls { /// <summary> /// Explorer-style progress dialog for coping items. /// </summary> public interface IOperationProgressDialog { /// <summary> /// Show a Shell progress dialog for a file copy operation. /// </summary> IOperation StartCopyOperation( IWin32Window owner, ulong totalItems, ulong totalSize); } /// <summary> /// Represents an active operation. Dispose to end the /// operation. /// </summary> public interface IOperation : IDisposable { /// <summary> /// Token indicating whether the user has cancelled /// the operation. /// </summary> CancellationToken CancellationToken { get; } /// <summary> /// Report that an item has been completed. /// </summary> void OnItemCompleted(); /// <summary> /// Report that a chunk of bytes has been completed. /// </summary> void OnBytesCompleted(ulong delta); /// <summary> /// Indicate that the operation is temporarily blocked /// by an error. /// </summary> bool IsBlockedByError { get; set; } } [SkipCodeCoverage("UI code")] public class OperationProgressDialog : IOperationProgressDialog { public IOperation StartCopyOperation( IWin32Window owner, ulong totalItems, ulong totalSize) { var flags = PROGDLG.OPDONTDISPLAYSOURCEPATH | PROGDLG.OPDONTDISPLAYDESTPATH | PROGDLG.OPDONTDISPLAYLOCATIONS | PROGDLG.MODAL; return new Operation( owner, totalItems, totalSize, SPACTION.COPYING, PDMODE.RUN, flags); } //--------------------------------------------------------------------- // Inner classes. //--------------------------------------------------------------------- private sealed class Operation : IOperation { private const string CLSID_ProgressDialog = "{F8383852-FCD3-11d1-A6B9-006097DF5BD4}"; private readonly CancellationTokenSource cancellationTokenSource; private readonly IOperationsProgressDialog dialog; private readonly ulong totalItems; private readonly ulong totalSize; private ulong itemsCompleted = 0; private ulong sizeCompleted = 0; private bool blockedByError = false; public CancellationToken CancellationToken => this.cancellationTokenSource.Token; private void UpdateProgress() { if (this.dialog.GetOperationStatus() == PDOPSTATUS.CANCELLED) { this.cancellationTokenSource.Cancel(); } this.dialog.UpdateProgress( this.sizeCompleted, this.totalSize, this.sizeCompleted, this.totalSize, this.itemsCompleted, this.totalItems); } public Operation( IWin32Window owner, ulong totalItems, ulong totalSize, SPACTION operation, PDMODE mode, PROGDLG flags) { this.totalItems = totalItems; this.totalSize = totalSize; this.dialog = (IOperationsProgressDialog) Activator.CreateInstance( Type.GetTypeFromCLSID(new Guid(CLSID_ProgressDialog))); this.dialog.StartProgressDialog( owner.Handle, flags); this.dialog.SetOperation(operation); this.dialog.SetMode(mode); this.cancellationTokenSource = new CancellationTokenSource(); } public void OnItemCompleted() { this.itemsCompleted++; UpdateProgress(); } public void OnBytesCompleted(ulong delta) { this.sizeCompleted += delta; UpdateProgress(); } public bool IsBlockedByError { get => this.blockedByError; set { if (value != this.blockedByError) { this.blockedByError = value; this.dialog.SetMode(this.blockedByError ? PDMODE.ERRORSBLOCKING : PDMODE.RUN); } } } public void Dispose() { this.dialog.StopProgressDialog(); Marshal.ReleaseComObject(this.dialog); } } //--------------------------------------------------------------------- // Interop declarations. //--------------------------------------------------------------------- [ComImport] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [Guid("43826d1e-e718-42ee-bc55-a1e261c37bfe")] internal interface IShellItem { void BindToHandler(IntPtr pbc, [MarshalAs(UnmanagedType.LPStruct)] Guid bhid, [MarshalAs(UnmanagedType.LPStruct)] Guid riid, out IntPtr ppv); void GetParent(out IShellItem ppsi); void GetDisplayName(SIGDN sigdnName, out IntPtr ppszName); void GetAttributes(uint sfgaoMask, out uint psfgaoAttribs); void Compare(IShellItem psi, uint hint, out int piOrder); }; [ComImport] [Guid("0C9FB851-E5C9-43EB-A370-F0677B13874C")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] internal interface IOperationsProgressDialog { void StartProgressDialog(IntPtr hwndOwner, PROGDLG flags); void StopProgressDialog(); void SetOperation(SPACTION action); void SetMode(PDMODE mode); void UpdateProgress(ulong ullPointsCurrent, ulong ullPointsTotal, ulong ullSizeCurrent, ulong ullSizeTotal, ulong ullItemsCurrent, ulong ullItemsTotal); void UpdateLocations(IShellItem psiSource, IShellItem psiTarget, IShellItem psiItem); void ResetTimer(); void PauseTimer(); void ResumeTimer(); void GetMilliseconds(ulong pullElapsed, ulong pullRemaining); PDOPSTATUS GetOperationStatus(); } internal enum SIGDN : uint { NORMALDISPLAY = 0, PARENTRELATIVEPARSING = 0x80018001, PARENTRELATIVEFORADDRESSBAR = 0x8001c001, DESKTOPABSOLUTEPARSING = 0x80028000, PARENTRELATIVEEDITING = 0x80031001, DESKTOPABSOLUTEEDITING = 0x8004c000, FILESYSPATH = 0x80058000, URL = 0x80068000 } internal enum PDMODE : uint { DEFAULT = 0x00000000, RUN = 0x00000001, PREFLIGHT = 0x00000002, UNDOING = 0x00000004, ERRORSBLOCKING = 0x00000008, INDETERMINATE = 0x00000010 } internal enum SPACTION : uint { NONE = 0, MOVING, COPYING, RECYCLING, APPLYINGATTRIBS, DOWNLOADING, SEARCHING_INTERNET, CALCULATING, UPLOADING, SEARCHING_FILES, DELETING, RENAMING, FORMATTING, COPY_MOVING } internal enum PDOPSTATUS : uint { RUNNING = 1, PAUSED = 2, CANCELLED = 3, STOPPED = 4, ERRORS = 5 } internal enum PROGDLG : uint { NORMAL = 0x00000000, MODAL = 0x00000001, AUTOTIME = 0x00000002, NOTIME = 0x00000004, NOMINIMIZE = 0x00000008, NOPROGRESSBAR = 0x00000010, MARQUEEPROGRESS = 0x00000020, NOCANCEL = 0x00000040, OPDEFAULT = 0x00000000, OPENABLEPAUSE = 0x00000080, OPALLOWUNDO = 0x00000100, OPDONTDISPLAYSOURCEPATH = 0x00000200, OPDONTDISPLAYDESTPATH = 0x00000400, OPNOMULTIDAYESTIMATES = 0x00000800, OPDONTDISPLAYLOCATIONS = 0x00001000 } } }