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();
Related
Team!!
I have a website that let's people download data into an excel or pdf file. The download takes long enough that I want to display a "Please Wait" modal while the processing is happening. I found some code that does this, but the problem is it just counts from 1 to 100 and then quits. I'd like to alter that so that it displays until the download is complete.
When the use clicks the button to begin the export, the expot_click function is called. The WorkerMethod function below it gets passed into the class and is where the count from 1 to 100 is occurring. I'm not sure what to change, or where to change it.
protected void export_click(object sender, EventArgs e)
{
string exportType = hidExportType.Value;
string exportSections = hidSections.Value;
string exportStudents = hidStudents.Value;
//Display a "Please Wait" message while the download is happening
object result = WaitWindow.Show(this.WorkerMethod);
switch (exportType)
{
case "csv":
export_csv(exportSections, exportStudents);
break;
case "excel":
export_excel(exportSections, exportStudents);
break;
case "pdf":
export_pdf(exportSections, exportStudents);
break;
default:
break;
}
//I'm guessing here is where I'd want to do something to make the wait display quit?
}
private void WorkerMethod(object sender, Jacksonsoft.WaitWindowEventArgs e)
{
// Do something
for (int progress = 1; progress <= 100; progress++)
{
System.Threading.Thread.Sleep(20);
// Update the wait window message
e.Window.Message = string.Format
("Please wait ... {0}%", progress.ToString().PadLeft(3));
}
// Use the arguments sent in
if (e.Arguments.Count > 0)
{
// Set the result to return
e.Result = e.Arguments[0].ToString();
}
else
{
// Set the result to return
e.Result = "Hello World";
}
}
Below is the class that gets called:
/*
* Created by SharpDevelop.
* User: mjackson
* Date: 05/03/2010
* Time: 09:36
*
* To change this template use Tools | Options | Coding | Edit Standard Headers.
*/
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace Jacksonsoft
{
/// <summary>
/// Displays a window telling the user to wait while a process is executing.
/// </summary>
public class WaitWindow
{
/// <summary>
/// Shows a wait window with the text 'Please wait...' while executing the passed method.
/// </summary>
/// <param name="workerMethod">Pointer to the method to execute while displaying the wait window.</param>
/// <returns>The result argument from the worker method.</returns>
public static object Show(EventHandler<WaitWindowEventArgs> workerMethod){
return WaitWindow.Show(workerMethod, null);
}
/// <summary>
/// Shows a wait window with the specified text while executing the passed method.
/// </summary>
/// <param name="workerMethod">Pointer to the method to execute while displaying the wait window.</param>
/// <param name="message">The text to display.</param>
/// <returns>The result argument from the worker method.</returns>
public static object Show(EventHandler<WaitWindowEventArgs> workerMethod, string message){
WaitWindow instance = new WaitWindow();
return instance.Show(workerMethod, message, new List<object>());
}
/// <summary>
/// Shows a wait window with the specified text while executing the passed method.
/// </summary>
/// <param name="workerMethod">Pointer to the method to execute while displaying the wait window.</param>
/// <param name="message">The text to display.</param>
/// <param name="args">Arguments to pass to the worker method.</param>
/// <returns>The result argument from the worker method.</returns>
public static object Show(EventHandler<WaitWindowEventArgs> workerMethod, string message, params object[] args){
List<object> arguments = new List<object>();
arguments.AddRange(args);
WaitWindow instance = new WaitWindow();
return instance.Show(workerMethod, message, arguments);
}
#region Instance implementation
private WaitWindow(){}
private WaitWindowGUI _GUI;
internal delegate void MethodInvoker<T>(T parameter1);
internal EventHandler<WaitWindowEventArgs> _WorkerMethod;
internal List<object> _Args;
/// <summary>
/// Updates the message displayed in the wait window.
/// </summary>
public string Message{
set{
this._GUI.Invoke(new MethodInvoker<string>(this._GUI.SetMessage), value);
}
}
/// <summary>
/// Cancels the work and exits the wait windows immediately
/// </summary>
public void Cancel(){
this._GUI.Invoke(new MethodInvoker(this._GUI.Cancel), null);
}
private object Show(EventHandler<WaitWindowEventArgs> workerMethod, string message, List<object> args){
// Validate Parameters
if (workerMethod == null){
throw new ArgumentException("No worker method has been specified.", "workerMethod");
} else {
this._WorkerMethod = workerMethod;
}
this._Args = args;
if (string.IsNullOrEmpty(message)){
message = "Please wait...";
}
// Set up the window
this._GUI = new WaitWindowGUI(this);
this._GUI.MessageLabel.Text = message;
// Call it
this._GUI.ShowDialog();
object result = this._GUI._Result;
// clean up
Exception _Error = this._GUI._Error;
this._GUI.Dispose();
// Return result or throw and exception
if (_Error != null){
throw _Error;
} else {
return result;
}
}
#endregion Instance implementation
}
}
Any suggestions on what to do to get this working would be greatly appreciated!!
You can't use a server side window.
but, what you can do is pop or display a dialog WHEN the user clicks on the button. That pop dialog will stay open while the web page is up on the server being processed. When the server page is done, then that new fresh server page is then send back to the client side browser and thus the display message will go away on its own.
So, say your button to start process is a simple button. When clicked on that "longer" server side code runs.
So, you keep the buttion, but add a client side script to pop up a dialog message.
In this case we will use a jQuery.UI dialog.
So, you will need in the web page jQquery, and jQuery.UI.
So, now the simple button code becomes this:
<asp:Button ID="cmdBigProcess" runat="server" Text="Start the reactor!"
OnClientClick="showait();return true;"
OnClick="cmdBigProcess_Click" />
<div id="mywait" style="display:none;border:solid">
<h2>Big process running - please wait</h2>
</div>
<script>
function showait() {
// lets pop jquery.UI dialog
var mydiv = $("#mywait")
mydiv.dialog({
modal: true, appendTo: "form",
title: "Please wait", closeText: "",
width: "400px"
});
}
</script>
So, we added a simple "div" that will hold the message.
Now, when you click on the button, the "on client click" code runs FIRST, and displays the dialog, then your server side code runs. As noted, we don't have to dismiss or do anything to the dialog, since when the web page is posted, it travels up to the server. The code behind runs (may or may not change thing on the web page), and THEN when the page is done, the whole web page now travels back down to the web side, browser re-plots and re-loads the web page (this is the so called round trip, or often called the page life-cycle. it is BEYOND critical that you grasp and understand this round trip concept. Your code behind NEVER directly interacts with the user. But upon hitting a buttion, the WHOLE web page travels up to server, code runs. And EVEN when the code modifies the controls and things in the web page? They will NOT show up, NOT update, and user will not see any changes to that web page until ALL YOUR code behind is done. Once code behind is done, then the WHOLE page makes the trip back to browser and is re-loaded.
So, the re-load of the page is what in fact will case the popped up dialog to be dismissed. Note that jQuery.UI dialogs (and most web dialogs) do NOT halt code when called - code continues.
The result looks like this:
I have a helper method that shows an ContentDialog with an input box that accepts string data. My issue is that it takes about 1 second after OK is clicked before the caller gets the string back (and it's very noticeable). I theorized that maybe it was the dialog fading out default animation/transition to end before the dialog returned.
In the code below, there's about a 1 second delay between when OK is clicked on the dialog and when the "return textBox.Text" fires.
/// <summary>
/// Shows an simple input box to get a string value in return.
/// </summary>
/// <param name="title">The title to show at the top of the dialog.</param>
/// <param name="message">The message shown directly above the input box.</param>
/// <param name="defaultValue">A value to prepopulate in the input box if any.</param>
/// <returns></returns>
public static async Task<string> ShowInput(string message, string defaultValue, string title)
{
var dialog = new ContentDialog
{
Title = title
};
var panel = new StackPanel();
panel.Children.Add(new TextBlock { Text = message, TextWrapping = Windows.UI.Xaml.TextWrapping.Wrap });
var textBox = new TextBox();
textBox.Text = defaultValue;
textBox.SelectAll();
textBox.KeyUp += (o, e) =>
{
if (e.Key == Windows.System.VirtualKey.Enter)
{
dialog.Hide();
}
e.Handled = true;
};
panel.Children.Add(textBox);
dialog.Content = panel;
dialog.PrimaryButtonText = "OK";
await dialog.ShowAsync();
return textBox.Text;
}
My questions are:
Am I missing something I should be setting or is this delay after clicking OK on the ContentDialog out the out of the box behavior?
If it is caused by a transition can I disable it?
Is there anyway I can speed up the time between when OK is clicked and the await returns?
I am running against 1809 Build 17763.379.
Thank you in advance.
The following solution is not really a performance fix, since there is nothing wrong with the code you have posted.
The basic idea is - since this process takes a while based on the device you can just show a spinner / loading screen just before calling your ShowInput() method and hiding it after you are done using the value/text that is returned by the ShowInput().
For example you can try something like this :
showLoader();
string x = await ShowInput("message","","title");
...
//some more code
...
hideLoader();
Where showLoader/hideLoader will show or hide a lightweight loader screen something like this :
This will make sure user waits until the code has completed execution. (This might be overkill if you are doing something very trivial with the text input)
As Pratyay mentioned in his comment, the amount of time will vary by device.
But as far as the await keyword goes, I believe what you are experiencing is intended behavior.
The await operator is applied to a task in an asynchronous method to insert a suspension point in the execution of the method until the awaited task completes. The task represents ongoing work.
Source # Microsoft Doc
This means that your ShowInput function will return its Task<string> object as soon as it reaches the await keyword. Then after dialog.ShowAsync(); returns it will continue the ShowInput function asynchronously and place the results into the Task object for you to retrieve.
So although your ShowInput function should return almost immediately. You will notice a delay between dialog.ShowAsync(); and return textBox.Text;.
The other thing to keep in mind is that when a window closes, there is usually a bit of processing (disposing of resources, etc.) before the window loop finishes. The way your code is written, you will have to wait until all of that finishes before you get your result.
Is there anyway I can speed up the time between when OK is clicked and the await returns?
I figure the quickest way to get your response back is to not await the ContentDialog, but rather await a signal that occurs right when the content is available.
public static Task<string> ShowInput(string message, string defaultValue, string title)
{
var dialog = new ContentDialog { Title = title };
var panel = new StackPanel();
panel.Children.Add(new TextBlock { Text = message, TextWrapping = Windows.UI.Xaml.TextWrapping.Wrap });
var textBox = new TextBox() { Text = defaultValue };
textBox.SelectAll();
var signal = new TaskCompletionSource<string>();
textBox.KeyUp += (o, e) =>
{
if (e.Key == Windows.System.VirtualKey.Enter)
{
dialog.Hide();
signal.SetResult(textBox.Text);
}
e.Handled = true;
};
dialog.PrimaryButtonClick += (o, e) =>
{
dialog.Hide();
signal.SetResult(textBox.Text);
};
panel.Children.Add(textBox);
dialog.Content = panel;
dialog.PrimaryButtonText = "OK";
dialog.ShowAsync();
return signal.Task;
}
Edit: Doing it this way no longer needs the extra await, as the Task being created has the final result in it.
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.
}
I have a parent form open a modal form which basically allows a user to change the database settings of the application.
When the user clicks the save button on the modal (child) form it saves the Settings object with the new settings but I need to have the Main form check that the database settings are correct.
I currently do this through a function which tries to simply connect to the database and if successful return true, false if this failed. That function I execute within the applications constructor so it runs fine whenever the application is closed and restarted.
I tried the following within the modal form after saving the settings but get a NullReference exception for object myManager.
This is the function which gets the new settings and saves them and then attempts to call the parent forms CheckDatabaseIsSetup() public function to test the db connection.
/// <summary>
/// Save the settings and then hide the Settings window
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btn_Save_Click(object sender, EventArgs e)
{
// TRUE: User indicates that we are to connect using a trusted connection
// FALSE: User wants to use Integrated security to connect.
if (rb_UseTrustedConnection.Checked)
{
AppSettings.DatabaseName = tb_Trusted_DbName.Text;
AppSettings.Server = tb_Trusted_Server.Text;
AppSettings.UseIntergratedSecurity = false;
}
else
{
AppSettings.DatabaseName = tb_Secure_DbName.Text;
AppSettings.Server = tb_Secure_Server.Text;
AppSettings.Username = tb_Secure_Username.Text;
AppSettings.Password = tb_Secure_Password.Text;
AppSettings.UseIntergratedSecurity = true;
}
try
{
AppSettings.SaveSettings();
BushBreaksLodgeManagerMain myManager = (BushBreaksLodgeManagerMain)this.ParentForm;
myManager.CheckDatabaseIsSetup();
}
catch (Exception ex)
{
log.LogAppendWithException(ex);
}
this.Hide();
}
You should instead use the Owner property in the modal form rather than the ParentForm property as follows:
BushBreaksLodgeManagerMain myManager = (BushBreaksLodgeManagerMain)this.Owner;
The Owner property defines the actual relationship between the owned(modal) form and the parent(owner) form.
Its better to define an event in child form and handle this event in main form
and when ever you raise this event in child form mainform can does its own job
BushBreaksLodgeManagerMain myManager = (BushBreaksLodgeManagerMain)this.ParentForm;
You can check the above line whether ParentForm is of Type/can cast to BushBreaksLodgeManagerMain. I presume the case wasn't successful hence return null
I'd usually do this by using object intercommunication such as provided with my Emesary library; The design is to use notifications in such a way that the request is sent and handled by anything that knows it needs to process these notifications, so for example it's easy to add in extra event handlers that are disconnected.
In which case the code for checking the database settings would become:
if (ReceiptStatus.OK ==
GlobalNotifier.NotifyAll(new CheckDatabaseIsSetupNotification(tb_Secure_DbName.Text,
tb_Secure_Server.Text,
tb_Secure_Username.Text,
tb_Secure_Password.Text,
true))
{
// do something.
}
To make this work you'd need to implement IReceiver in BushBreaksLodgeManagerMain and in the constructor call
GlobalTransmitter.Register(this);
then implement the interface receive:
public ReceiptStatus Receive(INotification _message)
{
if (_message is CheckDatabaseIsSetupNotification)
{
var message = _message as CheckDatabaseIsSetupNotification;
if (connect_to(message.DatabaseName, message.Server, Message.Username, message.Password, message.UseIntergratedSecurity))
return ReceiptStatus.OK;
else
return ReceiptStatus.Fail;
}
return ReceiptStatus.NotProcessed;
}
You can do this using Windows events - but this way is clearer and allows inter operation with objects that don't necessarily have windows.
I'm using Control.Invoke() to show a dialog. The code is a handler to get credentials from the user and it can be execute in a thread, this is the reason I perform the call into an InvokeRequired/Invoke snippet.
Sometimes, and only in some machines, when I close the dialog, the application becomes unresposive (it does not manage some mouse clicks, but manage others). If I execute some "allowed" actions, the application start to be responsive again. It seems that processing any event, the application fixes itself.
Do you know any known bug in the .NET framework, or something that could cause this issue?
Thanks in advance.
EDIT: This is the code I'm using:
public class GuiCredentialsHandler
{
// control used to invoke if needed
private static Control mInvokeControl;
// control used as parent for showDialog (could be null)
private static Control mParentControl;
/// <summary>
/// Initialize a GetCredentials handler for current process.
/// This method should be always called from the UI thread, for
/// a correctly handling for invokes (when the handler is called
/// from a thread).
/// </summary>
/// <param name="parentControl">Application top form.
/// Can be null if unknown</param>
public static void Initialize(Control parentControl)
{
if (parentControl != null)
{
mInvokeControl = parentControl;
}
else
{
mInvokeControl = new Control();
// force to create window handle
// otherwise, invoke required always
// return false
mInvokeControl.CreateControl();
}
mParentControl = parentControl;
}
public static Credentials GetCredentials(
string servername, SEIDWorkingMode serverWorkingMode)
{
if (mInvokeControl.InvokeRequired)
{
return mInvokeControl.Invoke(
new GetCredentialsDelegate(DoGetCredentials),
new object[] { servername, serverWorkingMode })
as Credentials;
}
else
{
return DoGetCredentials(servername, serverWorkingMode);
}
}
private static Credentials DoGetCredentials(
string servername, SEIDWorkingMode serverWorkingMode)
{
GetCredentialsDialog dialog = new GetCredentialsDialog();
dialog.Server = servername;
dialog.WorkingMode = serverWorkingMode;
DialogResult result = dialog.ShowDialog(mParentControl);
if (result == DialogResult.Cancel) return null;
UserInfoRetriever retriever = new UserInfoRetriever(
servername, serverWorkingMode,
dialog.UserName, dialog.Password);
SEID seid = retriever.GetCurrentUser();
return new Credentials(seid, serverWorkingMode);
}
public delegate Credentials GetCredentialsDelegate(
string serverName,
SEIDWorkingMode mode);
Is Control.Invoke actually needed in this case?
I was always under the impression that invoke was used to ensure that UI elements are accessed by the thread that creates the control which is usually the UI thread but does not have to be.
In this case it looks like you are trying to create a dialog from a thread and hence you should be able to update it from the thread. (Obviously you can't access it from outside your thread, which will include the main UI thread).
If I'm wrong no doubt this will get downvoted very quickly.
mParentControl will always be set equal to parentControl even if its NULL which does not seem right.
The reason your program becomes unresposive is because your mParentControl is NULL:
DialogResult result = dialog.ShowDialog(mParentControl);
One solution around this problem is to only show the dialog if the parent is known.
if ( mParentControl != NULL )
DialogResult result = dialog.ShowDialog(mParentControl);
else
DialogResult result = dialog.ShowDialog(mInvokeControl);
I based my answer on the following code:
if (parentControl != null)
{
mInvokeControl = parentControl;
}
I think you mean my answer makes no sense. What makes more sense that Hans Passant comment doesn't hold some truth or your code is actually correct and you discovered a bug. Since you are being rude I will just take my experience and help somebody else. Humor yourself and add code to avoid the mParentControl is Null situation because IT CAN HAPPEN. mParentControl is ALWAYS set to parentcontrol even when its NULL.
Application top form. /// Can be null if
unknown