CustomTaskPane in Excel doesn't appear in new Workbooks - c#

I've added a CustomTaskPane to Excel 2013 that lets users quickly search for photos. It works well and fine if the user only opens/creates one workbook. Problem is if they open another workbook or create a new one, the task pane doesn't appear in the new window that appears. It simply stays put in the original window. I know this behavior is caused by the fact I was only initializing the panel upon opening Excel. I added an event handler to the ActiveWindow event to initialize a new panel when they open another workbook.
Problem is I cannot figure out how to tell if the CustomTaskPane is already present in a window already. If it is, it simple creates another CustomTaskPane, so there are now two within that window. I wrote the following code to dispose the original and create a new one, but it introduces some lag (1-5 seconds) that would drive users crazy every time they change workbook windows. Is there a way to see if a CustomTaskPane already exists in a window to avoid disposing and recreating a new one to avoid stacking duplicate task panes?
Microsoft.Office.Tools.CustomTaskPane PartPhotoTaskPane;
Globals.ThisAddIn.Application.WindowActivate += Application_WindowActivate;
void Application_WindowActivate(Excel.Workbook Wb, Excel.Window Wn)
{
if (PartPhotoTaskPane != null)
{
PartPhotoTaskPane.Dispose();
InitalizePartPhotoViewerTaskPane(EPPF);
}
else
{
InitalizePartPhotoViewerTaskPane(EPPF);
}
}
/// <summary>
/// Start up the part photo viewer task pane
/// </summary>
private void InitalizePartPhotoViewerTaskPane(ExcelPartPhotoFunctions _EPPF)
{
//intialize the part search
try
{
PartPhotoTaskPane = Globals.ThisAddIn.CustomTaskPanes.Add(new PartPhotoSearchPane(_EPPF), "Part Information", Globals.ThisAddIn.Application.ActiveWindow);
PartPhotoTaskPane.Visible = Properties.Settings.Default.InfoPaneOpenStatus;
PartPhotoTaskPane.Width = 260;
}
catch (Exception e)
{
MessageBox.Show("Error starting Part Info Toolbar:" + Environment.NewLine +
e.Message + Environment.NewLine + e.StackTrace, "Error!", MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}

Use the hwnd (Globals.ThisAddIn.Application.Hwnd) to identify the Excel window. This will work well both for Office2013 (which uses an SDI approach) and older versions of Office that use MDI windows. Here is a class you can use for this:
public class TaskPaneManager
{
static Dictionary<string, CustomTaskPane> _createdPanes = new Dictionary<string, CustomTaskPane>();
/// <summary>
/// Gets the taskpane by name (if exists for current excel window then returns existing instance, otherwise uses taskPaneCreatorFunc to create one).
/// </summary>
/// <param name="taskPaneId">Some string to identify the taskpane</param>
/// <param name="taskPaneTitle">Display title of the taskpane</param>
/// <param name="taskPaneCreatorFunc">The function that will construct the taskpane if one does not already exist in the current Excel window.</param>
public static CustomTaskPane GetTaskPane(string taskPaneId, string taskPaneTitle, Func<UserControl> taskPaneCreatorFunc)
{
string key = string.Format("{0}({1})", taskPaneId, Globals.ThisAddIn.Application.Hwnd);
if (!_createdPanes.ContainsKey(key))
{
var pane = Globals.ThisAddIn.CustomTaskPanes.Add(taskPaneCreatorFunc(), taskPaneTitle);
_createdPanes[key] = pane;
}
return _createdPanes[key];
}
}
Here I'm actually combining the Excel window hwnd and some arbitrary string identifier to identify the taskpane. The idea is to support multiple taskpanes in the same addin.
Here is an example for how to use it from the ribbon:
private void button1_Click(object sender, RibbonControlEventArgs e)
{
var taskpane = TaskPaneManager.GetTaskPane("A", "Task pane type A", () => new UserControl1());
taskpane.Visible = !taskpane.Visible;
}
private void button2_Click(object sender, RibbonControlEventArgs e)
{
var taskpane = TaskPaneManager.GetTaskPane("B", "Task pane type B", () => new UserControl2());
taskpane.Visible = !taskpane.Visible;
}
If you open multiple workbooks in Excel, both Excel window will have their own taspaneA and taskpaneB.

Related

Save PowerPoint slide as image from VSTO application

I am developing a VSTO application for PowerPoint using C#. The goal is to export the selected slide of the opened PowerPoint presentation to PNG file on user's computer every 5 seconds.
PowerPoint API provides the following way to export the slide:
(Slide)Application.ActiveWindow.View.Slide.Export("D:/path", "png")
However, every time this method is called, PowerPoint window freezes (maybe deactivates?) for a split second, and because of this any expanded menus get closed (for example, the menu opened by right-clicking on a slide, menu for inserting shapes, etc.)
I am looking for a way to avoid this. Is there a way to fix this issue when using Slide.Export method? Or maybe there are some alternatives to using it?
I tried using custom libraries like Aspose.Slides, and they can fix this issue, but cause an even worse one: they can't access the Presentation object presented by PowerPoint assembly, so in order to use them on your assembly, you would have to save a copy to the computer and open it, which is a bad solution in my case.
Any ideas on how to fix my issue will be very helpful.
Edit: to reproduce the issue, create a VSTO add-in project for PowerPoint and replace ThisAddIn with the following code:
public partial class ThisAddIn
{
public Form form = new Form
{
Opacity = 0.01,
Visible = false,
};
delegate void InvokeEventHandler();
private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
form.Show();
var del = new InvokeEventHandler(() => Timer_Tick());
form.Invoke(del);
var timer = new System.Timers.Timer();
timer.Interval = 5000;
timer.Elapsed += (s, ea) => form.Invoke(del);
timer.Start();
}
private void Timer_Tick()
{
try
{
var slide = (Slide)Application.ActiveWindow.View.Slide;
slide.Export(Path.Combine(Path.GetTempPath(), "test"), "png");
}
catch
{
return;
}
}
private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
{
}
#region VSTO generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InternalStartup()
{
this.Startup += new System.EventHandler(ThisAddIn_Startup);
this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
}
#endregion
}
When PowerPoint opens, right-click on a slide and wait for a couple of seconds. When timer ticks, the menu will be closed.
The specified graphics format must have an export filter registered in the Windows registry. You can specify either the registered extension or the registered filter name. Microsoft PowerPoint will first search for a matching extension in the registry. If no extension that matches the specified string is found, PowerPoint will look for a filter name that matches.
Try using the JPG file format instead:
With Application.ActivePresentation.Slides(3)
.Export "c:\my documents\Graphic Format\" & _
"Slide 3 of Annual Sales", "JPG"
End With

Display MS Word document in window for editing

 Hello, I am building windows C# WPF application which communicates with Microsoft Office word document (.docx). The app should update .docx template file with user input and this step is achived successfuly using OpenXML. The other part of the app is to show the edited word document to the user inside of the applicaion window or using MS Word and allow him to add some more information if he wants to do it. The problem I am facing is:
I should disable my application controls while word document is opened and I should enable them once the word is closed, also I want to know if the word app was saved (if a user made changes).  The next code is button click event for openning word document:
using System.Windows;
using Microsoft.Office.Interop.Word;
using Application = Microsoft.Office.Interop.Word.Application;
public class MainWindowViewModel : BaseViewModel
{
...
... some view model initialization
...
public bool AreControlsEnabled { get; set; } = true;
private void OpenWord ()
{
AreControlsEnabled = false;
var app = new Application()
{
Visible = true
};
var doc = app.Documents.Open("pathtofile.docx");
var docClass = app.ActiveDocument as DocumentClass;
docClass.DocumentEvents2_Event_Close += DocClass_DocumentEvents2_Event_Close;
docClass.DocumentEvents_Event_Close += DocClass_DocumentEvents_Event_Close;
app.DocumentBeforeClose += new ApplicationEvents4_DocumentBeforeCloseEventHandler(DocBeforeClose);
app.DocumentBeforeSave += new ApplicationEvents4_DocumentBeforeSaveEventHandler(DocBeforeSave);
}
private void DocClass_DocumentEvents2_Event_Close ()
{
MessageBox.Show("DocClass_DocumentEvents2_Event_Close");
AreControlsEnabled = true;
}
private void DocClass_DocumentEvents_Event_Close ()
{
MessageBox.Show("DocClass_DocumentEvents_Event_Close");
AreControlsEnabled = true;
}
private void DocBeforeClose (Document doc, ref bool cancel)
{
MessageBox.Show("DocBeforeClose");
AreControlsEnabled = true;
}
private void DocBeforeSave (Document doc, ref bool SaveAsUI, ref bool cancel)
{
MessageBox.Show("DocBeforeSave");
AreControlsEnabled = true;
}
}
 When I run the code - I see opened MS Word document as expected, but when I close it or save - no one of events fired and I can't understand why. Also, I can use System.Diagnostics.Process to launch the Word and add exit event to it, but in this way I can't know if the user applied some changes. So, if someone solved this problem, help me please.   Thank you for reading and answers
You can:
Get the Current Changed Date of the File
Use System.Diagnostics.Process to start Word.
After The process ends you check the Changed Date again
If the user saved the file the Changed Date is updated
I don't know if the process is still running if the user just closes the document but not word. For this you could observe the Folder of the Dokument for thos ~... Temp Files Word creates while a document is open...

VSTO Word post save event

Sorry for the length of this, In the past I've been asked before to included everything I've tried when asking these sort of questions.
I'm writing a Word Add-in and need to make changes to the document that I cannot achieve using the Word object model. Therefore, after the document is save to disk, I need to capture that event, close the file, do what I need to do and reopen it. (I know, not elegant, but that's what I have to work with.)
Word has Before Save and Before Close, but no after save event. I found tricks online to simulate an after save event by creating another thread and using COM's IMessageFilter (not from System.Windows.Forms) to handle COM Retry calls, or posting a message back on the main thread so I can execute code after it's saved. But this doesn't work because if the file is saved as a result of the user attempting to close the document I can't get the file name in my "callback" method because Word.Document object has already been deleted.
So I tried explicitly call Save myself in my BeforeSave event handler and return Cancel = true. That works great when user selects save, or they had once saved to disk. But if the user closes a new document without saving and then selects 'yes' to whether or not they want to save, Word displays another "SaveAs" dialog after I've already handled the save after I return from the BeforeSave event, even though I set Cancel = true in my BeforeSave event handler.
So then I tried doing the something similar with the BeforeClose event. I handle the close and save myself and then returning Cancel = true from my event handler. But doing so stops word from trying to close multiple documents when the user is attempting to shut down the application.
I even tried handling WM_CLOSE, but that lead to similar problems as above.
Can anyone offer a solution?
I came across this a while back, I think it may do what you want. Here's a copy of what's there in case it ever disappears.
When I wrote my first Word AfterSave Event entry, it was designed for Word 2007, and was – as it turns out – not a catch all. So I have updated it here (thanks for the catch go to Pat Lemm).
When the document was closed, you never got access to the Saved filename. So, I have updated the code here and it now works in all conditions and has been tested in Word 2013.
Here is how it works:
Upon initialization you pass it your Word object.
It attaches to the Before Save Event.
When any save event occurs, it kicks off a thread that loops until the Background Save is complete.
Once the background save is done, it checks to see if the document Saved == true:
If Saved == true: then a regular save did occur.
If Saved == false: then it had to be an AutoSave
In each case it will fire a unique event:
AfterSaveUiEvent
AfterSaveEvent
AfterAutoSaveEvent
Additionally, if the document being saved is also being closed, we catch the filename on the WindowDeactivate event on the way out. This can now be accessed by the caller (as you can see in the example below), to get the full filename of the closed document.
Here is the code to the class:
public class WordSaveHandler
{
public delegate void AfterSaveDelegate(Word.Document doc, bool isClosed);
// public events
public event AfterSaveDelegate AfterUiSaveEvent;
public event AfterSaveDelegate AfterAutoSaveEvent;
public event AfterSaveDelegate AfterSaveEvent;
// module level
private bool preserveBackgroundSave;
private Word.Application oWord;
string closedFilename = string.Empty;
/// <summary>
/// CONSTRUCTOR takes the Word application object to link to.
/// </summary>
/// <param name="oApp"></param>
public WordSaveHandler(Word.Application oApp)
{
oWord = oApp;
// hook to before save
oWord.DocumentBeforeSave += oWord_DocumentBeforeSave;
oWord.WindowDeactivate += oWord_WindowDeactivate;
}
/// <summary>
/// Public property to get the name of the file
/// that was closed and saved
/// </summary>
public string ClosedFilename
{
get
{
return closedFilename;
}
}
/// <summary>
/// WORD EVENT fires before a save event.
/// </summary>
/// <param name="Doc"></param>
/// <param name="SaveAsUI"></param>
/// <param name="Cancel"></param>
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
preserveBackgroundSave = oWord.Options.BackgroundSave;
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(() =>
{
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 compelte and we finish up by
/// determineing which type of save it was:
/// 1) UI
/// 2) Regular
/// 3) AutoSave
/// </summary>
/// <param name="Doc"></param>
/// <param name="UiSave"></param>
private void Handle_WaitForAfterSave(Word.Document Doc, bool UiSave)
{
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 (isBusy())
Thread.Sleep(1);
}
// check to see if still saving in the background
// we will hang here until this changes.
while (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)
{
AfterUiSaveEvent(null, true);
}
else
{
AfterSaveEvent(null, true);
}
}
catch
{
oWord.Options.BackgroundSave = 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)
{
AfterUiSaveEvent(Doc, false);
}
}
catch
{
// DOC is null or invalid. This occurs because the doc
// was closed. So we return doc closed and null as the
// document
AfterUiSaveEvent(null, true);
}
}
else
{
// if the document is still dirty
// then we know an AutoSave happened
try
{
if (Doc.Saved == false)
AfterAutoSaveEvent(Doc, false); // fire autosave event
else
AfterSaveEvent(Doc, false); // fire regular save event
}
catch
{
// DOC is closed
AfterSaveEvent(null, true);
}
}
}
catch { }
finally
{
// reset and exit thread
oWord.Options.BackgroundSave = 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>
void oWord_WindowDeactivate(Word.Document Doc, Word.Window Wn)
{
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 = oWord.ActiveDocument.Application;
return false; // not busy
}
catch
{
// so, Word is busy and we return true
return true;
}
}
}
And here is how you set it up and attach to it’s events:
public partial class ThisAddIn
{
WordSaveHandler wsh = null;
private void ThisAddIn_Startup(object sender,
System.EventArgs e)
{
// attach the save handler
wsh = new WordSaveHandler(Application);
wsh.AfterAutoSaveEvent += new WordSaveHandler.AfterSaveDelegate(wsh_AfterAutoSaveEvent);
wsh.AfterSaveEvent += new WordSaveHandler.AfterSaveDelegate(wsh_AfterSaveEvent);
wsh.AfterUiSaveEvent += new WordSaveHandler.AfterSaveDelegate(wsh_AfterUiSaveEvent);
}
void wsh_AfterUiSaveEvent(Word.Document doc, bool isClosed)
{
if (!isClosed)
MessageBox.Show("After SaveAs Event");
else
MessageBox.Show("After Close and SaveAs Event. The filname was: " + wsh.ClosedFilename);
}
void wsh_AfterSaveEvent(Word.Document doc, bool isClosed)
{
if (!isClosed)
MessageBox.Show("After Save Event");
else
MessageBox.Show("After Close and Save Event. The filname was: " + wsh.ClosedFilename);
}
void wsh_AfterAutoSaveEvent(Word.Document doc, bool isClosed)
{
MessageBox.Show("After AutoSave Event");
}
// etc.
}

In a VSTO MS Word add-in, a Ribbon Toggle button clicked in one document causes it to be clicked in other running instances of MS Word

I have developed a MS Word add-in and it has several buttons in the ribbon menu. The first button is a Toggle button and clicking it causes a task pane to load. The problem I have been experiencing is that if I have multiple instances of MS Word open and I click this toggle button in one of the instances, it automatically appears clicked in the other running instances of MS Word as well but the task pane appears to be loaded only in the instance where I had clicked the toggle button. I would like the toggle button to work independently in every instance of MS Word. I have tried several ways of doing this but haven't found a solution yet. I have experienced the same behavior for another add-in that I developed for MS PowerPoint.
Any help in this matter will be appreciated.
I have a ribbon designer (named rbcOfficeAddin) added to my add-in project and it has a toggle button named btnTaskPane. Below is the code:
public partial class rbcOfficeAddin
{
private void btnTaskPane_Click(object sender, RibbonControlEventArgs e)
{
if (this.btnTaskPane.Checked)
{ this.btnTaskPane.Label = "Hide Task Pane"; }
else { this.btnTaskPane.Label = "Show Hide Pane"; }
Globals.ThisAddIn.ShowHideActionPane(this.btnTaskPane.Checked);
}
}
The click handler of toggle button calls a method of ThisAddin as shown below:
public partial class ThisAddIn
{
private bool operationsPaneCreated = false;
public void ShowHideActionPane(bool flag)
{
try
{
if (!this.operationsPaneCreated)
{
this.CreateTaskPane();
this.operationsPaneCreated = true;
}
myCustomTaskPane.Visible = flag;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void CreateTaskPane()
{
//OperationsPane is a user control
oOperationsPane = new OperationsPane();
myCustomTaskPane = this.CustomTaskPanes.Add(oOperationsPane,
"Operations Pane");
myCustomTaskPane.DockPosition =
Office.MsoCTPDockPosition.msoCTPDockPositionFloating;
myCustomTaskPane.Height = 500;
myCustomTaskPane.Width = oOperationsPane.Width;
myCustomTaskPane.DockPosition =
Office.MsoCTPDockPosition.msoCTPDockPositionRight;
myCustomTaskPane.Width = 420;
myCustomTaskPane.Control.AutoScroll = true;
myCustomTaskPane.Visible = false;
myCustomTaskPane.VisibleChanged += myCustomTaskPane_VisibleChanged;
}
void myCustomTaskPane_VisibleChanged(object sender, EventArgs e)
{
try
{
if (!myCustomTaskPane.Visible && Globals.Ribbons.rbcOfficeAddin.btnTaskPane.Checked)
{
Globals.Ribbons.rbcOfficeAddin.btnTaskPane.Checked = false;
Globals.Ribbons.rbcOfficeAddin.btnTaskPane.Label = "Show Task Pane";
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
This same question was asked and answered on the MSDN forums. I'm copying/pasting the relevant information here, with attributions, from
https://social.msdn.microsoft.com/Forums/vstudio/en-US/1c3cab02-b231-4453-ae09-325e025606bf/in-a-vsto-ms-word-addin-a-ribbon-toggle-button-clicked-in-one-document-causes-it-to-be-clicked-in?forum=vsto
Answer from Starain chen Microsoft contingent staff, Moderator:
As far as I know, this is by design, for an instance with multiple
word documents, the same add-in will be in a domain, for example, a
variable is changed will be affect other word documents if they are in
the same instance. So, you need to deal with this scenario. (e.g. base
on document name)
Answer from Cindy Meister, MVP, Moderator:
The following article explains the approach. It's valid for Ribbon
customizations as well as just Custom Task Panes
https://msdn.microsoft.com/en-us/library/vstudio/bb608620

How to allow for multiple popups at once in WinRT?

If you call the ShowAsync command on a MessageDialog object when another MessageDialog object has already been displayed to the user but not dismissed (i.e. you show a popup when another one is already up), an UnauthorizedAccessException is thrown. This can make things difficult when you have multiple threads attempting to alert the user at the same time.
My current (stopgap) solution is merely to surround the ShowAsync call with a try/catch block and swallow the exception. This undesirably leads to the user missing out on subsequent notifications. The only other way around this that I can think of is to manually implement some sort of popup queue. This seems like an inordinate amount of work, however, considering other frameworks (like Windows Phone) do not have this issue and will merely display the popups one after another as the user dismisses them.
Is there another way to solve this problem?
You can easy do it with this extension method:
public static class MessageDialogShower
{
private static SemaphoreSlim _semaphore;
static MessageDialogShower()
{
_semaphore = new SemaphoreSlim(1);
}
public static async Task<IUICommand> ShowDialogSafely(this MessageDialog dialog)
{
await _semaphore.WaitAsync();
var result = await dialog.ShowAsync();
_semaphore.Release();
return result;
}
}
There are many ways to approach it and the choice might depend on your skills, requirements and preferences.
My personal choice is to avoid using dialog boxes altogether since they are bad for user experience (evil). There are then alternative solutions like displaying a separate screen/page with the UI requiring user to provide some input when it really is required or displaying a non-modal popup somewhere on the side/edge/corner if the user input is optional and hiding it after a moment or some other sort of notification that doesn't break user flow.
If you disagree or don't have the time, resources or skills to implement an alternative - you can create some sort of a wrapper around MessageDialog.ShowAsync() call to either queue or ignore new requests while a dialog is already shown.
This class has extension methods to allow to either ignore a new show request when another dialog is already displayed or queue up the requests:
/// <summary>
/// MessageDialog extension methods
/// </summary>
public static class MessageDialogExtensions
{
private static TaskCompletionSource<MessageDialog> _currentDialogShowRequest;
/// <summary>
/// Begins an asynchronous operation showing a dialog.
/// If another dialog is already shown using
/// ShowAsyncQueue or ShowAsyncIfPossible method - it will wait
/// for that previous dialog to be dismissed before showing the new one.
/// </summary>
/// <param name="dialog">The dialog.</param>
/// <returns></returns>
/// <exception cref="System.InvalidOperationException">This method can only be invoked from UI thread.</exception>
public static async Task ShowAsyncQueue(this MessageDialog dialog)
{
if (!Window.Current.Dispatcher.HasThreadAccess)
{
throw new InvalidOperationException("This method can only be invoked from UI thread.");
}
while (_currentDialogShowRequest != null)
{
await _currentDialogShowRequest.Task;
}
var request = _currentDialogShowRequest = new TaskCompletionSource<MessageDialog>();
await dialog.ShowAsync();
_currentDialogShowRequest = null;
request.SetResult(dialog);
}
/// <summary>
/// Begins an asynchronous operation showing a dialog.
/// If another dialog is already shown using
/// ShowAsyncQueue or ShowAsyncIfPossible method - it will wait
/// return immediately and the new dialog won't be displayed.
/// </summary>
/// <param name="dialog">The dialog.</param>
/// <returns></returns>
/// <exception cref="System.InvalidOperationException">This method can only be invoked from UI thread.</exception>
public static async Task ShowAsyncIfPossible(this MessageDialog dialog)
{
if (!Window.Current.Dispatcher.HasThreadAccess)
{
throw new InvalidOperationException("This method can only be invoked from UI thread.");
}
while (_currentDialogShowRequest != null)
{
return;
}
var request = _currentDialogShowRequest = new TaskCompletionSource<MessageDialog>();
await dialog.ShowAsync();
_currentDialogShowRequest = null;
request.SetResult(dialog);
}
}
Test
// This should obviously be displayed
var dialog = new MessageDialog("await ShowAsync", "Dialog 1");
await dialog.ShowAsync();
// This should be displayed because we awaited the previous request to return
dialog = new MessageDialog("await ShowAsync", "Dialog 2");
await dialog.ShowAsync();
// All other requests below are invoked without awaiting
// the preceding ones to complete (dialogs being closed)
// This will show because there is no dialog shown at this time
dialog = new MessageDialog("ShowAsyncIfPossible", "Dialog 3");
dialog.ShowAsyncIfPossible();
// This will not show because there is a dialog shown at this time
dialog = new MessageDialog("ShowAsyncIfPossible", "Dialog 4");
dialog.ShowAsyncIfPossible();
// This will show after Dialog 3 is dismissed
dialog = new MessageDialog("ShowAsyncQueue", "Dialog 5");
dialog.ShowAsyncQueue();
// This will not show because there is a dialog shown at this time (Dialog 3)
dialog = new MessageDialog("ShowAsyncIfPossible", "Dialog 6");
dialog.ShowAsyncIfPossible();
// This will show after Dialog 5 is dismissed
dialog = new MessageDialog("ShowAsyncQueue", "Dialog 7");
dialog.ShowAsyncQueue();
// This will show after Dialog 7 is dismissed
dialog = new MessageDialog("ShowAsyncQueue", "Dialog 8");
dialog.ShowAsyncQueue();

Categories

Resources