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
}
}
}