Detect "Don't Save" in BeforeSave event in Word VSTO - c#

I'm developing a VSTO add-in for Microsoft Word and I'm having difficulty with handling the BeforeSave event. I wish to edit the document each time the user saves but the BeforeSave event appears to be triggering in a certain scenarios when the user is not saving the document.
Assuming a user opens a blank Word document, enters some text and then tries to close the document. The dialog
displays. If the user clicks "Don't Save", the BeforeSave event still triggers (the event only triggers after the Save Changes dialog closes). Is there anyway to detect the difference between the use clicking Don't Save and clicking save or else to prevent the Before Save event from triggering in this scenario?
The event handler is being assigned using Word.ApplicationEvents4_DocumentBeforeSaveEventHandler
and the event handler signature is
Application_DocumentBeforeSave(Word.Document doc, ref bool saveAsUI, ref bool cancel)
Any help would be great thanks

The Word object model doesn't provide any event property for that. The best what you can do in that case are listed below:
If the document has never been saved before, you may check out the Document.Saved property.
Cancel the default action in the Application.DocumentBeforeSave event handler by setting the Cancel parameter to true. So, you can show your own dialog which mimics the default built-in one where you may handle every action made by the user.

The solution that worked for me in the end is based on the article at https://theofficecontext.com/2013/04/26/updated-word-after-save-event/
For reference, I'm working with Office 2019 and below is the code that worked for me - a slightly simplified version to filter out the user clicking Don't Save and trigger a Post Save event.
using System.Threading;
using Word = Microsoft.Office.Interop.Word;
/// <summary>
/// The word save handler.
/// </summary>
public class WordSaveHandler
{
public delegate void AfterSaveDelegate(Word.Document doc, bool isClosed);
// public events
public event AfterSaveDelegate AfterSaveEvent;
// module level
private bool preserveBackgroundSave;
private Word.Application oWord;
private string closedFilename = string.Empty;
/// <summary>
/// Initializes a new instance of the <see cref="WordSaveHandler"/> class.
/// CONSTRUCTOR takes the Word application object to link to.
/// </summary>
/// <param name="oApp">Word Application.</param>
public WordSaveHandler(Word.Application oApp)
{
this.oWord = oApp;
// hook to before save
this.oWord.DocumentBeforeSave += this.OWord_DocumentBeforeSave;
this.oWord.WindowDeactivate += this.OWord_WindowDeactivate;
}
/// <summary>
/// Gets public property to get the name of the file
/// that was closed and saved.
/// </summary>
public string ClosedFilename
{
get
{
return this.closedFilename;
}
}
/// <summary>
/// WORD EVENT fires before a save event.
/// </summary>
/// <param name="doc"></param>
/// <param name="saveAsUI"></param>
/// <param name="cancel"></param>
private void OWord_DocumentBeforeSave(Word.Document doc, ref bool saveAsUI, ref bool cancel)
{
// This could mean one of four things:
// 1) we have the user clicking the save button
// 2) Another add-in or process firing a resular Document.Save()
// 3) A Save As from the user so the dialog came up
// 4) Or an Auto-Save event
// so, we will start off by first:
// 1) Grabbing the current background save flag. We want to force
// the save into the background so that Word will behave
// asyncronously. Typically, this feature is on by default,
// but we do not want to make any assumptions or this code
// will fail.
// 2) Next, we fire off a thread that will keep checking the
// BackgroundSaveStatus of Word. And when that flag is OFF
// no know we are AFTER the save event
this.preserveBackgroundSave = this.oWord.Options.BackgroundSave;
this.oWord.Options.BackgroundSave = true;
// kick off a thread and pass in the document object
bool uiSave = saveAsUI; // have to do this because the bool from Word is passed to us as ByRef
new Thread(() =>
{
this.Handle_WaitForAfterSave(doc, uiSave);
}).Start();
}
/// <summary>
/// This method is the thread call that waits for the same to compelte.
/// The way we detect the After Save event is to essentially enter into
/// a loop where we keep checking the background save status. If the
/// status changes we know the save is complete and we finish up by
/// determineing which type of save it was:
/// 1) UI
/// 2) Regular
/// 3) AutoSave.
/// </summary>
/// <param name="doc">Document being saved.</param>
/// <param name="uiSave">Whether a SaveAs UI is displayed.</param>
private void Handle_WaitForAfterSave(Word.Document doc, bool uiSave)
{
bool docSaved = false;
try
{
// we have a UI save, so we need to get stuck
// here until the user gets rid of the SaveAs dialog
if (uiSave)
{
while (this.IsBusy())
{
Thread.Sleep(1);
}
}
docSaved = doc.Saved;
// check to see if still saving in the background
// we will hang here until this changes.
while (this.oWord.BackgroundSavingStatus > 0)
{
Thread.Sleep(1);
}
}
catch (ThreadAbortException)
{
// we will get a thread abort exception when Word
// is in the process of closing, so we will
// check to see if we were in a UI situation
// or not
if (uiSave)
{
this.AfterSaveEvent(null, true);
return;
}
else
{
// new, close, don't save - docSaved = FALSE
// open close don't save - docSaved = FALSE
// open close save - docSaved = TRUE
if (docSaved)
{
this.AfterSaveEvent(null, true);
}
return;
}
}
catch
{
this.oWord.Options.BackgroundSave = this.preserveBackgroundSave;
return; // swallow the exception
}
try
{
// if it is a UI save, the Save As dialog was shown
// so we fire the after ui save event
if (uiSave)
{
// we need to check to see if the document is
// saved, because of the user clicked cancel
// we do not want to fire this event
try
{
if (doc.Saved == true)
{
this.AfterSaveEvent(doc, false);
// new, save
// new, save as
// open save as
// open, turn on autosave
// new, turn on autosave
}
}
catch
{
// DOC is null or invalid. This occurs because the doc
// was closed. So we return doc closed and null as the
// document
this.AfterSaveEvent(null, true);
// -- new, close, save
}
}
else
{
// if the document is still dirty
// then we know an AutoSave happened
try
{
this.AfterSaveEvent(doc, false); // fire regular save event
// open, save
// open, autosave
}
catch
{
// DOC is closed
this.AfterSaveEvent(null, true);
}
}
}
catch { }
finally
{
// reset and exit thread
this.oWord.Options.BackgroundSave = this.preserveBackgroundSave;
}
}
/// <summary>
/// WORD EVENT – Window Deactivate
/// Fires just before we close the document and it
/// is the last moment to get the filename.
/// </summary>
/// <param name="doc"></param>
/// <param name="wn"></param>
private void OWord_WindowDeactivate(Word.Document doc, Word.Window wn)
{
this.closedFilename = doc.FullName;
}
/// <summary>
/// Determines if Word is busy essentially that the File Save
/// dialog is currently open
/// </summary>
/// <param name="oApp"></param>
/// <returns></returns>
private bool IsBusy()
{
try
{
// if we try to access the application property while
// Word has a dialog open, we will fail
object o = this.oWord.ActiveDocument.Application;
return false; // not busy
}
catch
{
// so, Word is busy and we return true
return true;
}
}
}

Related

Why async await operation is still time consuming

I have a Winforms application where I am trying to print a pdf document which has multiple layers on it.
But the problem is, This all operation are running on UI thread and it is hanging the UI(not responding) for long time.
I know, this is happening because of UI thread is blocked so, I have tried to make this operation asynchronous by the help of powerful async/await keyword but still my long running method is not being asynchronous. It is not coming forward from the await tasks and still opearation is taking the same time as like synchronous operation.
What I tried:
Please see below:
/// <summary>
/// Show Print Dialog
/// </summary>
private void ShowPrintDialog()
{
// Initialize print dialog
System.Windows.Controls.PrintDialog prtDialog = new System.Windows.Controls.PrintDialog();
prtDialog.PageRangeSelection = PageRangeSelection.AllPages;
prtDialog.UserPageRangeEnabled = false;
_printOptions.PrintQueue = null;
_printOptions.PrintTicket = null;
Enabled = false;
// if there is a default printer then set it
string defaulPrinter = prtDialog.PrintQueue == null ? string.Empty : prtDialog.PrintQueue.FullName;
// Display the dialog. This returns true if the user selects the Print button.
if (prtDialog.ShowDialog() == true)
{
_printOptions.PrintQueue = prtDialog.PrintQueue;
_printOptions.PrintTicket = prtDialog.PrintTicket;
_printOptions.UseDefaultPrinter = (defaulPrinter == prtDialog.PrintQueue.FullName);
}
// Re-enable the form
Enabled = true;
}
/// <summary>
/// Event raised when user clicks Print
/// </summary>
/// <param name="sender">Source of the event</param>
/// <param name="e">Event specific arguments</param>
private void cmdOk_Click(object sender, EventArgs e)
{
ShowPrintDialog();
if (_printOptions.PrintTicket != null)
{
//Set search Options
_print.ExportDataItem = true;
_print.FileName = SearchTemplateName;
//shows progress bar form.
using (frmPrintSearchResultsProgress frmProgress =
new frmPrintSearchResultsProgress(_print, this, _printOptions))
{
frmProgress.ShowDialog(this);
}
if (_print.ExportDataItem && !_print.DataItemExported && !_print.CancelExport)
{
MessageBox.Show("No Document printed.");
}
}
//Store selected options for current user
SaveOptions();
if (!SkipExport)
Close();
}
/// <summary>
/// Event raised when progress form is shown.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void frmExportSearchResultsProgress_Shown(object sender, EventArgs e)
{
try
{
Application.DoEvents();
dispatcher = Dispatcher.CurrentDispatcher;
// record export/print job start time
_startedUtc = DateTime.UtcNow;
_print.WritingToPdfIndicator = lblWritingPdfFile;
lblProgress.Text = Properties.Resources.PrintSearchResults;
await dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(DoDataItemPrint));
}
}
/// <summary>
/// Prints the selected data items.
/// </summary>
private void DoDataItemPrint()
{
// LONG RUNNING OPERATIONS..
// THIS OPERATION IS BLOCKING THE UI.
}
So, as per mentioned in above code when I opened the PringDialogForm then it is opening a Progress Bar form to see the progress of printing the document and from here frmExportSearchResultsProgress_Shown() event is fired and inside it, I am calling the DoDataItemPrint() method which is time consuming.
So, I tried to make frmExportSearchResultsProgress_Shown event as async/await but still operation is taking the same time as previous.
Can anyone please suggest me where I am doing wrong?
your frmExportSearchResultsProgress_Shown method starts on the UI thread
it then dispatches DoDataItemPrint to the ... same UI thread
it schedules a continuation (via await) so that when that incomplete thing happens, we get back into frmExportSearchResultsProgress_Shown, and since there's probably a sync-context in play here, the sync-context capture (implicit in await) would push us to ... the UI thread
As you can see: everything is happening on the UI thread.
If you want to not block the UI, you need to get off the UI thread. That could be as simple as using Task.Run to invoke DoDataItemPrint, but without knowing what that code contains, it is impossible to know whether you're using thread-bound controls to do the printing. If you are... it will be hard to get away from that.

C# how to send scanner string to form 2

I am creating an app with two forms.
On my main form1 I have a scanner that gets connected through a button.
I have a text box on my form2 and in this text box is where I want the information that gets scanned to go here.
there is no textbox on my form1.
I cant seem to figure out how to do this I was wondering if any one had any tips.
Thank you
This is the code in form 1 that allows me to set up the scanner
/// Event Handler for Scanner Setup Button
/// <param name="sender"></param>
/// <param name="e"></param>
private void scannerFormBTN_Click(object sender, EventArgs e)
{
try
{
//If the Button is yellow , disconnect the hand scanner
if (scannerBTN.BackColor == Color.LightGreen)
{
scanner.ReadStringArrived -= new ReadStringArrivedHandler(OnReadStringArrived);
scanner.Disconnect();
scanner = null;
scannerFormBTN.BackColor = Color.IndianRed;
this.scannerFormBTN.Text = "Setup Hand Scanner";
MessageBox.Show("Hand Scanner Disconnected.", "Alert");
}
//If scanner is not connected
else
{
Setupscanner scannerForm = new Setupscanner(); //Instantiate the Scanner Form
//Show the form. DialogResult = yes if Scanner is connected successfully.
DialogResult connection_successfull = scannerForm.ShowDialog();
if (connection_successfull == DialogResult.Yes)
{
this.scanner = scannerForm.sys; //Set the Local Hand Scanner variable to the successfully connected scanner
if (scanner.IsConnected)
{
scanner.ReadStringArrived += new ReadStringArrivedHandler(OnReadStringArrived); //Register Read String event.
scannerFormBTN.Text = "Hand Scanner Connected. Click to Disconnect";
scannerBTN.BackColor = Color.LightGreen; //Change the color of the Hand Scanner Button
}
}
}
}
catch (Exception ex) { MessageBox.Show("There was an error. Exception reads : \n\n " + ex.Message, "Error"); }
}
/// <summary>
/// Event that gets fired when string arrives from a connected hand scanner
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void OnReadStringArrived(Object sender, ReadStringArrivedEventArgs args)
{
SetSerNumber(args.ReadString);
}
/// <summary>
/// Used to set the Serial Number to the incoming Scanner String
/// </summary>
/// <param name="msg"></param>
private void SetSerNumber(string msg)
{
//If the event is fired on a different thread than the control was created
if (Part_Code_Text_Box.InvokeRequired)
{
SetSerialNumber S = new SetSerialNumber(SetSerNumber);
this.Invoke(S, new object[] { msg }); //Invoke the delegate S on the current thread
}
else
{
Part_Code_Text_Box.Text = msg.Trim();
}
}
Create a Property Or Field in Form 2 ..let's Call It StringFromScanner For Now .
When You Want To Show another From u will most likely Create an Object From it ...Set This Object's StringFromScanner To The String You Have ..and When You Open The Second Form Will Have The value of the string in this property and u can assign it to any control
In Form 1
Form2 objectfromform2 = new Form2();
objectfromform2.StringFromScanner = MyString;
objectfromform2.Show();
in Form 2
MytextBox.Text = StringFromScanner
You Can Also Forget About The Property and Use The Constructor To Achive The Same Result

UI event logging using Listbox

I am having a hard time creating a class where I can use ListBox to log events.
I know i have tonnes of articles on what I am asking on SO and google but that i didn't make out of it. So i am asking a little help here:
I have three classes:
1: WinForm - Where my list box is placed. From here I pass my listbox in the FileEnumeratorClass() constructor.
2: FileEnumeratorClass - Recieving listbox here and then passing it to logger class.
class FileEnumeratorClass {
UILogger logger;
/// <summary>
/// ctor
/// </summary>
public FileEnumeratorClass (ListBox lbLog)
{
logger = new UILogger(lbLog);
}
/// <summary>
/// Figures out what has changed between the src and destination
/// What is currently implemented does not work......the solution is to compare SRC and DESTINATION for the first time.
/// And verify with end user.
/// </summary>
public List<FileDetails> IdentifyChanges()
{
logger.AddToLog("Change detection initiated...");
//Deletes any existing cached package. Assuming it is in incosistent form
logger.AddToLog( "Cleaning up any cached local package.");
DeleteLocalPackage();
}
}
3: UILogger
public class UILogger
{
public UILogger(ListBox lb)
{
lbLog = lb;
}
// This delegate enables asynchronous calls for setting
// the text property on a control.
delegate void AddToLogCallback(string text);
public void AddToLog( string message)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (lbLog.InvokeRequired)
{
var d = new AddToLogCallback(AddToLog);
lbLog.Invoke(d, new object[] { message });
}
else
{
// add this line at the top of the log
lbLog.Items.Insert(0, message);
}
// keep only a few lines in the log
while (lbLog.Items.Count > 1000)
{
lbLog.Items.RemoveAt(lbLog.Items.Count - 1);
}
}
}
But the above code does not work as expected. All showing up when thread is done. What I need is to call the methods AddToLog() in the same sequence as they are written/called in FileEnumeratorClass -> IdentifyChanges().
You need to call Control.Invalidate() method whenever you want the listbox to update its contents to force listbox to redraw itself.
Please note that these control are thread safe which means any async or child thread must not update any UI control. Use proper callbacks to UI thread and let the UI thread update the control.
public List<FileDetails> IdentifyChanges()
{
logger.AddToLog("Change detection initiated...");
//Deletes any existing cached package. Assuming it is in incosistent form
logger.AddToLog( "Cleaning up any cached local package.");
//Invalidate the control to redraw.
logger.Invalidate();
DeleteLocalPackage();
}

Why would commenting out a line of code change whether a *previous* line of code is executed?

With this code:
using (pbDialog = new pbDialogs())
{
ProgressBar = new frmProgress(this, false);
ProgressBar.SetProgressLabelText("Inventory Data");
MessageBox.Show("Set progress label text to Inventory data");
typeProgress = (int) ProgressStates.ProgressQRY;
ProgressBar.label1.Text += " (Receiving)";
if (pbDialog != null)
{
MessageBox.Show("pbDialog is not null");
//pbDialog.ShowDialog(ProgressBar, this);
}
else
{
MessageBox.Show("pbDialog IS null");
ProgressBar.ShowDialog();
}
ProgressBar = null;
MessageBox.Show("Made it to compressDB()");
compressDB();
. . .
}
I see "Set progress label text to Inventory data"
then "pbDialog is not null"
then "Made it to compressDB()"
Nothing too odd there; but if I uncomment the line that is commented above, I see only "pbDialog is not null"
It is hanging for some reason as a result to the call to ShowDialog(); what is really strange, though, is that this prevents "Set progress label text to Inventory data" from displaying. Why would that be the case?
Note: I think the "pb" in the code stands for "peanut brittle" or some such; I'm pretty sure about the "brittle" part, anyway.
UPDATE
Yeah, the use of ShowDialog() with pbDialog is one of scads of examples that the original coder was practicing job security by obscurity - but then he [un]fortunately skedaddled, leaving in his wake a cesspool of spaghetti/eggshell code with no comments, misleading names and every sort of bizarre and convoluted, counterintuitive practice imaginable in the witches brew he purportedly considered a masterpiece of elegant design and clever-clever tricks.
pbDialog is an instance of a class (pbDialogs). Just to give you a taste of how macabre and convoluted and tangled it all is, here is that class:
public class pbDialogs : IDisposable
{
private static Form m_top;
public pbDialogs()
{
} // pbDialogs Constructor
public static void Activate( Form form )
{
form.Capture = true;
IntPtr hwnd = OpenNETCF.Win32.Win32Window.GetCapture();
form.Capture = false;
OpenNETCF.Win32.Win32Window.SetForegroundWindow( hwnd );
} // Activate
/// <summary>
/// This method makes ShowDialog work the way I want, I think.
/// </summary>
/// <remarks>
/// Here is what it does:
/// 1. Sets the caption of the new window to the same as the caller's.
/// 2. Clears the caption of the parent so it won't show up in the task list.
/// 3. When the ShowDialog call returns, brings the previous window
/// back to the foreground.
/// </remarks>
/// <param name="dialog"></param>
/// <param name="parent"></param>
public void ShowDialog( Form dialog, Control parent )
{
Control top = parent.TopLevelControl;
string caption = top.Text;
dialog.Text = caption;
top.Text = "--pending--"; // Don't show parent in task list
dialog.Activated += new EventHandler( form_Activated );
dialog.Closed += new EventHandler( form_Closed );
m_top = dialog; // New top-most form
dialog.ShowDialog();
m_top = (Form)top; // The top dialog just changed
dialog.Activated -= new EventHandler( form_Activated );
dialog.Closed -= new EventHandler( form_Closed );
top.Text = caption; // Make visible in task list again
Activate( (Form)top ); // And make it the active window
} // ShowDialog
/// <summary>
/// If one of our other windows, such as the main window,
/// receives an activate event, it will activate the current
/// top-most window instead.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void form_Activated( object sender, EventArgs e )
{
if( (m_top != null) && !(sender == m_top) ) // Is this the top-most window?
Activate( m_top ); // No, activate the top dialog
} // form_Activated
private static void form_Closed( object sender, EventArgs e )
{
m_top = null; // When you close the top dialog, it's not top anymore
} // form_Closed
#region IDisposable Members
public void Dispose()
{
// TODO: Add pbDialogs.Dispose implementation
}
#endregion
} // class pbDialogs
There is also a "related" ProgressBar -- a form which shares a file with pbDialogs, and whose instance variable is defined in the file that contains the code above thusly:
public static frmProgress ProgressBar;
This is definitely "whack-a-mole" code - if I make one small, seemingly innocuous change, all Dallas breaks loose in what even a semi-sane person would consider a completely unrelated part of the code.
This may be an indication of just how squirrelly this code/project is: I will make a new build after commenting out a couple of lines, and the size of the file will change from 400KB to 408, or from 412 to 408, etc. It's not normal behavior for an .exe to change that much in size (in a relative sense) with such a small change, is it?
UPATE 2
This, in frmProgress (which has both "public class frmProgress : System.Windows.Forms.Form" and "public class pbDialogs : IDisposable") scares me:
using System.Windows.Forms;
using OpenNETCF.Windows.Forms;
The second (OpenNETCF) is grayed out, indicating it's not really used, but it may be that it was previously used, and somehow that "Windows.Forms" code inadvertently got switched to "System" code, and that may be contributing to its current groundsquirellyness.
ShowDialog is generally a blocking call. The code will not continue past this until the dialog is closed.

How to bring up the built-in File Copy dialog?

I'll be copying a large file over the network using my winforms app and I need to show some kind of progress bar. Rather than cook up my own copy routine, I was thinking that it might be better to simply show the built-in file copy dialog.
I would also need a "Copy complete" and "Copy failed" notification.
I need this to work on Windows XP, Vista and 7. Is there a way to call to engage this functionality from my c# code?
Answer taken from: here
Windows Vista does indeed include a new copy engine that supports exactly what you're looking to do. However, it's possible that previously existing functionality may meet your needs. For example, if you want to copy, move, rename, or delete an individual file or directory, you can take advantage of SHFileOperation (exposed from shell32.dll), which is already wrapped by the Visual Basic® runtime. If you're using Visual Basic 2005, you can simply use functionality from the My namespace, for example:
My.Computer.FileSystem.CopyDirectory(
sourcePath, destinationPath, UIOption.AllDialogs)
Accomplishing the same thing in C# involves only a little more work, adding a reference to Microsoft.VisualBasic.dll (from the Microsoft® .NET Framework installation directory) and using code such as the following:
using Microsoft.VisualBasic.FileIO;
...
FileSystem.CopyDirectory(
sourcePath, destinationPath, UIOption.AllDialogs);
When run, this will result in the same progress UI you'd see if you were doing the same file operations from Windows Explorer. In fact, when running on Windows Vista, you automatically get the new Window Vista progress UI, as shown in Figure 1.
I know it's a old thread but I faced to similar requirement and finally this is the code I used,
maybe it helps someone else.
the credit belongs to here
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Pietschsoft
{
public class NativeProgressDialog
{
private IntPtr parentHandle;
private Win32IProgressDialog pd = null;
private string title = string.Empty;
private string cancelMessage = string.Empty;
private string line1 = string.Empty;
private string line2 = string.Empty;
private string line3 = string.Empty;
private uint value = 0;
private uint maximum = 100;
public string Title
{
get
{
return this.title;
}
set
{
this.title = value;
if (pd != null)
{
pd.SetTitle(this.title);
}
}
}
public string CancelMessage
{
get
{
return this.cancelMessage;
}
set
{
this.cancelMessage = value;
if (pd != null)
{
pd.SetCancelMsg(this.cancelMessage, null);
}
}
}
public string Line1
{
get
{
return this.line1;
}
set
{
this.line1 = value;
if (pd != null)
{
pd.SetLine(1, this.line1, false, IntPtr.Zero);
}
}
}
public string Line2
{
get
{
return this.line2;
}
set
{
this.line2 = value;
if (pd != null)
{
pd.SetLine(2, this.line2, false, IntPtr.Zero);
}
}
}
public string Line3
{
get
{
return this.line3;
}
set
{
this.line3 = value;
if (pd != null)
{
pd.SetLine(3, this.line3, false, IntPtr.Zero);
}
}
}
public uint Value
{
get
{
return this.value;
}
set
{
this.value = value;
if (pd != null)
{
pd.SetProgress(this.value, this.maximum);
}
}
}
public uint Maximum
{
get
{
return this.maximum;
}
set
{
this.maximum = value;
if (pd != null)
{
pd.SetProgress(this.value, this.maximum);
}
}
}
public bool HasUserCancelled
{
get
{
if (pd != null)
{
return pd.HasUserCancelled();
}
else
return false;
}
}
public NativeProgressDialog(IntPtr parentHandle)
{
this.parentHandle = parentHandle;
}
public void ShowDialog(params PROGDLG[] flags)
{
if (pd == null)
{
pd = (Win32IProgressDialog)new Win32ProgressDialog();
pd.SetTitle(this.title);
pd.SetCancelMsg(this.cancelMessage, null);
pd.SetLine(1, this.line1, false, IntPtr.Zero);
pd.SetLine(2, this.line2, false, IntPtr.Zero);
pd.SetLine(3, this.line3, false, IntPtr.Zero);
PROGDLG dialogFlags = PROGDLG.Normal;
if (flags.Length != 0)
{
dialogFlags = flags[0];
for (var i = 1; i < flags.Length; i++)
{
dialogFlags = dialogFlags | flags[i];
}
}
pd.StartProgressDialog(this.parentHandle, null, dialogFlags, IntPtr.Zero);
}
}
public void CloseDialog()
{
if (pd != null)
{
pd.StopProgressDialog();
//Marshal.ReleaseComObject(pd);
pd = null;
}
}
#region "Win32 Stuff"
// The below was copied from: http://pinvoke.net/default.aspx/Interfaces/IProgressDialog.html
public static class shlwapi
{
[DllImport("shlwapi.dll", CharSet = CharSet.Auto)]
static extern bool PathCompactPath(IntPtr hDC, [In, Out] StringBuilder pszPath, int dx);
}
[ComImport]
[Guid("EBBC7C04-315E-11d2-B62F-006097DF5BD4")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface Win32IProgressDialog
{
/// <summary>
/// Starts the progress dialog box.
/// </summary>
/// <param name="hwndParent">A handle to the dialog box's parent window.</param>
/// <param name="punkEnableModless">Reserved. Set to null.</param>
/// <param name="dwFlags">Flags that control the operation of the progress dialog box. </param>
/// <param name="pvResevered">Reserved. Set to IntPtr.Zero</param>
void StartProgressDialog(
IntPtr hwndParent, //HWND
[MarshalAs(UnmanagedType.IUnknown)] object punkEnableModless, //IUnknown
PROGDLG dwFlags, //DWORD
IntPtr pvResevered //LPCVOID
);
/// <summary>
/// Stops the progress dialog box and removes it from the screen.
/// </summary>
void StopProgressDialog();
/// <summary>
/// Sets the title of the progress dialog box.
/// </summary>
/// <param name="pwzTitle">A pointer to a null-terminated Unicode string that contains the dialog box title.</param>
void SetTitle(
[MarshalAs(UnmanagedType.LPWStr)] string pwzTitle //LPCWSTR
);
/// <summary>
/// Specifies an Audio-Video Interleaved (AVI) clip that runs in the dialog box. Note: Note This method is not supported in Windows Vista or later versions.
/// </summary>
/// <param name="hInstAnimation">An instance handle to the module from which the AVI resource should be loaded.</param>
/// <param name="idAnimation">An AVI resource identifier. To create this value, use the MAKEINTRESOURCE macro. The control loads the AVI resource from the module specified by hInstAnimation.</param>
void SetAnimation(
IntPtr hInstAnimation, //HINSTANCE
ushort idAnimation //UINT
);
/// <summary>
/// Checks whether the user has canceled the operation.
/// </summary>
/// <returns>TRUE if the user has cancelled the operation; otherwise, FALSE.</returns>
/// <remarks>
/// The system does not send a message to the application when the user clicks the Cancel button.
/// You must periodically use this function to poll the progress dialog box object to determine
/// whether the operation has been canceled.
/// </remarks>
[PreserveSig]
[return: MarshalAs(UnmanagedType.Bool)]
bool HasUserCancelled();
/// <summary>
/// Updates the progress dialog box with the current state of the operation.
/// </summary>
/// <param name="dwCompleted">An application-defined value that indicates what proportion of the operation has been completed at the time the method was called.</param>
/// <param name="dwTotal">An application-defined value that specifies what value dwCompleted will have when the operation is complete.</param>
void SetProgress(
uint dwCompleted, //DWORD
uint dwTotal //DWORD
);
/// <summary>
/// Updates the progress dialog box with the current state of the operation.
/// </summary>
/// <param name="ullCompleted">An application-defined value that indicates what proportion of the operation has been completed at the time the method was called.</param>
/// <param name="ullTotal">An application-defined value that specifies what value ullCompleted will have when the operation is complete.</param>
void SetProgress64(
ulong ullCompleted, //ULONGLONG
ulong ullTotal //ULONGLONG
);
/// <summary>
/// Displays a message in the progress dialog.
/// </summary>
/// <param name="dwLineNum">The line number on which the text is to be displayed. Currently there are three lines—1, 2, and 3. If the PROGDLG_AUTOTIME flag was included in the dwFlags parameter when IProgressDialog::StartProgressDialog was called, only lines 1 and 2 can be used. The estimated time will be displayed on line 3.</param>
/// <param name="pwzString">A null-terminated Unicode string that contains the text.</param>
/// <param name="fCompactPath">TRUE to have path strings compacted if they are too large to fit on a line. The paths are compacted with PathCompactPath.</param>
/// <param name="pvResevered"> Reserved. Set to IntPtr.Zero.</param>
/// <remarks>This function is typically used to display a message such as "Item XXX is now being processed." typically, messages are displayed on lines 1 and 2, with line 3 reserved for the estimated time.</remarks>
void SetLine(
uint dwLineNum, //DWORD
[MarshalAs(UnmanagedType.LPWStr)] string pwzString, //LPCWSTR
[MarshalAs(UnmanagedType.VariantBool)] bool fCompactPath, //BOOL
IntPtr pvResevered //LPCVOID
);
/// <summary>
/// Sets a message to be displayed if the user cancels the operation.
/// </summary>
/// <param name="pwzCancelMsg">A pointer to a null-terminated Unicode string that contains the message to be displayed.</param>
/// <param name="pvResevered">Reserved. Set to NULL.</param>
/// <remarks>Even though the user clicks Cancel, the application cannot immediately call
/// IProgressDialog::StopProgressDialog to close the dialog box. The application must wait until the
/// next time it calls IProgressDialog::HasUserCancelled to discover that the user has canceled the
/// operation. Since this delay might be significant, the progress dialog box provides the user with
/// immediate feedback by clearing text lines 1 and 2 and displaying the cancel message on line 3.
/// The message is intended to let the user know that the delay is normal and that the progress dialog
/// box will be closed shortly.
/// It is typically is set to something like "Please wait while ...". </remarks>
void SetCancelMsg(
[MarshalAs(UnmanagedType.LPWStr)] string pwzCancelMsg, //LPCWSTR
object pvResevered //LPCVOID
);
/// <summary>
/// Resets the progress dialog box timer to zero.
/// </summary>
/// <param name="dwTimerAction">Flags that indicate the action to be taken by the timer.</param>
/// <param name="pvResevered">Reserved. Set to NULL.</param>
/// <remarks>
/// The timer is used to estimate the remaining time. It is started when your application
/// calls IProgressDialog::StartProgressDialog. Unless your application will start immediately,
/// it should call Timer just before starting the operation.
/// This practice ensures that the time estimates will be as accurate as possible. This method
/// should not be called after the first call to IProgressDialog::SetProgress.</remarks>
void Timer(
PDTIMER dwTimerAction, //DWORD
object pvResevered //LPCVOID
);
}
[ComImport]
[Guid("F8383852-FCD3-11d1-A6B9-006097DF5BD4")]
public class Win32ProgressDialog
{
}
/// <summary>
/// Flags that indicate the action to be taken by the ProgressDialog.SetTime() method.
/// </summary>
public enum PDTIMER : uint //DWORD
{
/// <summary>Resets the timer to zero. Progress will be calculated from the time this method is called.</summary>
Reset = (0x01),
/// <summary>Progress has been suspended.</summary>
Pause = (0x02),
/// <summary>Progress has been resumed.</summary>
Resume = (0x03)
}
[Flags]
public enum PROGDLG : uint //DWORD
{
/// <summary>Normal progress dialog box behavior.</summary>
Normal = 0x00000000,
/// <summary>The progress dialog box will be modal to the window specified by hwndParent. By default, a progress dialog box is modeless.</summary>
Modal = 0x00000001,
/// <summary>Automatically estimate the remaining time and display the estimate on line 3. </summary>
/// <remarks>If this flag is set, IProgressDialog::SetLine can be used only to display text on lines 1 and 2.</remarks>
AutoTime = 0x00000002,
/// <summary>Do not show the "time remaining" text.</summary>
NoTime = 0x00000004,
/// <summary>Do not display a minimize button on the dialog box's caption bar.</summary>
NoMinimize = 0x00000008,
/// <summary>Do not display a progress bar.</summary>
/// <remarks>Typically, an application can quantitatively determine how much of the operation remains and periodically pass that value to IProgressDialog::SetProgress. The progress dialog box uses this information to update its progress bar. This flag is typically set when the calling application must wait for an operation to finish, but does not have any quantitative information it can use to update the dialog box.</remarks>
NoProgressBar = 0x00000010
}
#endregion
}
}
How you can use it:
private Pietschsoft.NativeProgressDialog pd;
private uint progressPercent;
Timer timer1 = new Timer();
private void button1_Click_1(object sender, EventArgs e)
{
timer1.Interval = 300;
timer1.Tick += (s,ev)=>
{
progressPercent++;
if (pd.HasUserCancelled)
{
timer1.Stop();
pd.CloseDialog();
}
else
{
// Update the progress value
pd.Value = progressPercent;
pd.Line2 = "Percent " + progressPercent.ToString() + "%";
if (progressPercent >= 100)
{
timer1.Stop();
pd.CloseDialog();
}
}
};
pd = new Pietschsoft.NativeProgressDialog(this.Handle);
pd.Title = "Performing Operation";
pd.CancelMessage = "Please wait while the operation is cancelled";
pd.Maximum = 100;
pd.Value = 0;
pd.Line1 = "Line One";
pd.Line3 = "Calculating Time Remaining...";
//pd.ShowDialog(); // Defaults to PROGDLG.Normal
pd.ShowDialog(Pietschsoft.NativeProgressDialog.PROGDLG.Modal, Pietschsoft.NativeProgressDialog.PROGDLG.AutoTime, Pietschsoft.NativeProgressDialog.PROGDLG.NoMinimize);
progressPercent = 0;
timer1.Start();
}

Categories