How to do the processing after showing the Windows Dialog? - c#

I have a tabpage user control and 3 property pages for the tabpage user control which are assigned dynamically. This tabpage control is being shown inside the dialog.
The processing and filtering of data for the tabpage control is taking more time and this is resulting in a busy icon shown for more than 10 seconds before opening the Dialog.
I would like to show an empty Dialog opened and show the busy icon while the processing and filtering of data is done and finally shows inside the Dialog.
This is basically changing the order of processing.
However, I am not able to achieve this, and once the dialog is opened it waits for the user input and after giving the input only it goes to the next line. (as observed during debugging).
In the code below, the line MnemonicSelector.InitializeMnemonicSelectorParameters(parameters);
is responsible for the processing and moving that after the showdialog is resulting in object not found action when the user clicks on any item inside the property page under the dialog.
public override MnemonicSelectorResult ShowMnemonicSelector(MnemonicSelectorSearchParameters parameters)
{
MnemonicSelector.InitializeMnemonicSelectorParameters(parameters);
ResizeMnemonicSelectorIfNeeded();
SetupMnemonicDialog(m_PropertyDialog, MnemonicSelector, MnemonicSelector.Title);
DialogResult dResult = ShowFakeDialog(m_PropertyDialog, MnemonicSelector.Title);
return MnemonicSelector.Result;
}
private void ResizeMnemonicSelectorIfNeeded()
{
if ((MnemonicSelector.ClientSize.Width < 909) || (MnemonicSelector.ClientSize.Height < 620))
m_PropertyDialog.ClientSize = new System.Drawing.Size(939, 697);
}
protected void SetupMnemonicDialog(PropertiesDialogControl propertydialog, PropertyPage page, string title)
{
List<PropertyPage> pages = new List<PropertyPage>();
pages.Insert(0, page);
PropertyPage[] propertyPages = pages.ToArray();
if (title != null)
propertydialog.Text = title;
propertydialog.SetPropertyPages(new List<PropertyPage>(propertyPages));
}
public virtual DialogResult ShowFakeDialog(Control contents, string title)
{
return ShowFakeDialog(contents, title, false, "");
}
public DialogResult ShowFakeDialog(Control contents, string title, bool isCancelButtonVisible, string cancelButtonText)
{
FakeDialog fakeDialog = new FakeDialog(this, contents, title, isCancelButtonVisible, cancelButtonText);
using (fakeDialog)
{
lock (this)
{
FakeDialog previousFakeDialog = _activeFakeDialog;
_activeFakeDialog = fakeDialog;
try
{
return fakeDialog.ShowDialog();
}
finally
{
_activeFakeDialog = previousFakeDialog;
}
}
}
}
Please advice on on I can achieve the desired functionality wherein, I can show the dialog and load the property pages (processing) later.

You should use a background worker thread for this. Check this. It should help you to use background worker to separate your progress animation from your processing logic.

Related

Implement loader in windows form like web application

I'm trying displaying Loader while any long running process being executing in windows forms. I have implemented code for that, but loader being displayed but not in CenterParent location, it will be displayed on Center of the screen.
Code:
CPLoader is form that I want to display while any process executing.
public class CommonLoader
{
CPLoader cploader = new CPLoader();
readonly Form form = null;
public CommonLoader(Form frm)
{
form = frm;
}
public void ShowLoader()
{
try
{
if (form.InvokeRequired)
{
try
{
cploader = new CPLoader();
cploader.ShowDialog();
}
catch
{
Console.WriteLine("Cp loader exception");
}
}
else
{
Thread th = new Thread(ShowLoader);
th.IsBackground = false;
th.Start();
}
}
catch
{
Console.WriteLine("Cp loader exception");
}
}
/// <summary>
/// this method will used for hide loader while process stop
/// </summary>
public void HideLoader()
{
try
{
if (cploader != null)
{
Thread.Sleep(200);
cploader.Invoke(new Action(cploader.Close));
}
}
catch
{
Console.WriteLine("Cp loader exception");
}
}
}
I have also try cploader.ShowDialog() with frm.BeginInvoke(new MethodInvoker(delegate(){cploader.ShowDialog(form); })).
If I use BeginInvoke() then I'm unable to close the loader.
Splash screens, progress screens etc appeared in Visual Basic or Delphi desktop applications long before web applications. They are just modeless forms/windows displayed on top of their application. They don't need threads either - back then applications were mostly single threaded.
Background threads can't modify the UI anyway, which means that the entire ShowLoader method does nothing more than try to call :
cploader = new CPLoader();
cploader.ShowDialog();
All of this can be replaced with
public void ShowLoader()
{
cploader.ShowDialog();
}
public void HideLoader()
{
cploader.Hide();
//or Close if we don't intend to reuse the loader
}
Specifying the parent
Calling ShowDialog without any parameters creates a window whose parent is the desktop. That's why the window appears centered on the screen, not the application.
To specify an owner/parent, just pass it as the owner parameter to ShowDialog or Show.
The following code can be used to display a dialog box centered on the current form :
var myDialog=new MyDialogForm();
myDialog.ShowDialog(this);
This means that ShowLoader probably has to accept the owner as a parameter :
public void ShowLoader(Form frm)
{
cploader.ShowDialog(frm);
}
Modeless windows
ShowDialog() is used to display a modal form - a form that retains the focus until it's closed, just like a dialog box. That's why the method is called ShowDialog() instead of ShowModal().
A loader needs to be modeless, so Show should be used instead :
public void ShowLoader(Form frm)
{
cploader.Show(frm);
}
Another difference is that ShowDialog returns a result with the user's choice (OK, Cancel etc) while Show returns nothing.
Modal Loader with notification
If you want to create a modal loader with ShowDialog but still perform some work in the background, you need a way to notify that loader from the background thread. You can do that using the Progress class.
The loader can expose IProgress<T> as a property. The T parameter can be a simple string or integer showing progress, or a complex entity with progress, a string message and a status indicator. For laziness' sake, let's use string and close the dialog if the value is empty :
public IProgress<string> Progress{get;private set;}
public CPLoader()
{
this.Progress=new Progress<string>(UpdateUI);
}
private void UpdateUI(string msg)
{
if(String.IsNullOrWhitespace(msg))
{
this.DialogResult=DialogResult.Cancel;
this.Close();
}
else
{
this.SomeLabel.Text=msg;
}
}
The code that works in the background needs access to that IProgress<string> property. Let's say the code that needs to work in the background is :
void Work(IProgress<string> progress)
{
for(int i=0;i<1000000;i++)
{
//Do something CPU intensive
//Report every 1000 items
if(i%1000==0)
{
progress.Report($"{i} out of 1000000");
}
}
//This tells the loader to close.
progress.Report("");
}
This code can run in the background and use the loader this way :
var loader=new CPLoader();
var task=Task.Run(()=>DoWork(loader.Progress));
loader.ShowDialog();
await task;
The loader is initialized first, giving us access to the IProgress<T> instance. The job gets started in the background after that with Task.Run. When it finishes, it sends an empty progress string and the loader's UpdateUI method closed the dialog in response
The code that needs to perform work while loading can access that IProgress<string> interface and use it to signal pro

"Only a single ContentDialog can be open at any time." error while opening another contentdialog

I am using Windows.UI.Xaml.Controls.ContentDialog to show a confirmation. And based on the response from the first dialog I would (or would not) show another dialog. But, when I am trying to open the second content dialog it throws : "Only a single ContentDialog can be open at any time." error. Even though in the UI, first dialog would be closed but somehow I am still not able to open the second dialog. Any idea?
I have created some code to handle this type of conundrum in my Apps:
public static class ContentDialogMaker
{
public static async void CreateContentDialog(ContentDialog Dialog, bool awaitPreviousDialog) { await CreateDialog(Dialog, awaitPreviousDialog); }
public static async Task CreateContentDialogAsync(ContentDialog Dialog, bool awaitPreviousDialog) { await CreateDialog(Dialog, awaitPreviousDialog); }
static async Task CreateDialog(ContentDialog Dialog, bool awaitPreviousDialog)
{
if (ActiveDialog != null)
{
if (awaitPreviousDialog)
{
await DialogAwaiter.Task;
DialogAwaiter = new TaskCompletionSource<bool>();
}
else ActiveDialog.Hide();
}
ActiveDialog = Dialog;
ActiveDialog.Closed += ActiveDialog_Closed;
await ActiveDialog.ShowAsync();
ActiveDialog.Closed -= ActiveDialog_Closed;
}
public static ContentDialog ActiveDialog;
static TaskCompletionSource<bool> DialogAwaiter = new TaskCompletionSource<bool>();
private static void ActiveDialog_Closed(ContentDialog sender, ContentDialogClosedEventArgs args) { DialogAwaiter.SetResult(true); }
}
To use these Methods, you need to create the ContentDialog and its content in a variable, then pass the variable, and bool into the Method.
Use CreateContentDialogAsync(), if you require a callback in your app code, say if you have a button in your Dialog, and you want wait for a button press, and then get the value from the form in code after the dialog.
Use CreateContentDialog(), if you don't need to wait for the Dialog to complete in your UI Code.
Use awaitPreviousDialog to wait for the previous dialog to complete before showing the next Dialog, or set false, to remove the previous Dialog, then show the next Dialog, say, if you want to show an Error Box, or the next Dialog is more important.
Example:
await ContentDialogMaker.CreateContentDialogAsync(new ContentDialog
{
Title = "Warning",
Content = new TextBlock
{
Text = "Roaming Appdata Quota has been reached, if you are seeing this please let me know via feedback and bug reporting, this means that any further changes to data will not be synced across devices.",
TextWrapping = TextWrapping.Wrap
},
PrimaryButtonText = "OK"
}, awaitPreviousDialog: true);
William Bradley's approach above is good. Just to polish it up a bit, here is an extension method to submit and await the showing of a content dialog; the dialog will be shown after all the other content dialogs that have already been submitted. Note: by the time the user clicks through earlier backlogged dialogs you may no longer want to show the dialog that you have submitted; to indicate this you may pass a predicate that will be tested after the other dialogs have been dismissed.
static public class ContentDialogExtensions
{
static public async Task<ContentDialogResult> EnqueueAndShowIfAsync( this ContentDialog contentDialog, Func<bool> predicate = null)
{
TaskCompletionSource<Null> currentDialogCompletion = new TaskCompletionSource<Null>();
TaskCompletionSource<Null> previousDialogCompletion = null;
// No locking needed since we are always on the UI thread.
if (!CoreApplication.MainView.CoreWindow.Dispatcher.HasThreadAccess) { throw new NotSupportedException("Can only show dialog from UI thread."); }
previousDialogCompletion = ContentDialogExtensions.PreviousDialogCompletion;
ContentDialogExtensions.PreviousDialogCompletion = currentDialogCompletion;
if (previousDialogCompletion != null) {
await previousDialogCompletion.Task;
}
var whichButtonWasPressed = ContentDialogResult.None;
if (predicate == null || predicate()) {
whichButtonWasPressed = await contentDialog.ShowAsync();
}
currentDialogCompletion.SetResult(null);
return whichButtonWasPressed;
}
static private TaskCompletionSource<Null> PreviousDialogCompletion = null;
}
Another way might be to use a SemaphoreSlim(1,1).
"Only a single ContentDialog can be open at a time"
This statement is not entirely true. You can only ShowAsync one ContentDialog at a time. All you need to do is hide the current ContentDialog before opening another one. Then, after the "await ShowAsync" of the second ContentDailog, your simply call "var T = this.ShowAync()" to unhide it. Example:
public sealed partial class MyDialog2 : ContentDialog
{
...
}
public sealed partial class MyDialog1 : ContentDialog
{
...
private async void Button1_Click(object sender, RoutedEventArgs e)
{
// Hide MyDialog1
this.Hide();
// Show MyDialog2 from MyDialog1
var C = new MyDialog2();
await C.ShowAsync();
// Unhide MyDialog1
var T = ShowAsync();
}
}
I know this is slightly old, but one simpler solution instead of going through all this pain is to just register a callback for the ContentDialog_Closed event. By this point you can be sure the previous dialog has been closed, and can open your next dialog. :)
Only a single ContentDialog can be open at any time.
That is a fact. (I was really surprised, but just for a moment)
You can't have more than one at any time and it is more like guideline from Microsoft, because it's really messy to have multiple dialogs on top of each other filled with content.
Try to change your UX to display only one sophisticated ContentDialog and for all other messages use MessageDialog - it supports multiple buttons(only two for phones, but more on desktop) for user response but without Checkboxes or similar "smart"-content stuff.
In my case MessageDialogs were really helpful, but in some areas I used chained ContentDialogs but for that you must await the first one, and open second right after without any exceptions. In your case it seems like ContentDialog was not fully closed when you tried to open next one.
Hope it helps!
I like this answer https://stackoverflow.com/a/47986634/942855, this will allow us ot handle binding all events.
So extended it a little to check the multiple calls to show dialog.
private int _dialogDisplayCount;
private async void Logout_OnClick(object sender, RoutedEventArgs e)
{
try
{
_dialogDisplayCount++;
ContentDialog noWifiDialog = new ContentDialog
{
Title = "Logout",
Content = "Are you sure, you want to Logout?",
PrimaryButtonText = "Yes",
CloseButtonText = "No"
};
noWifiDialog.PrimaryButtonClick += ContentDialog_PrimaryButtonClick;
//await noWifiDialog.ShowAsync();
await noWifiDialog.EnqueueAndShowIfAsync(() => _dialogDisplayCount);
}
catch (Exception exception)
{
_rootPage.NotifyUser(exception.ToString(), NotifyType.DebugErrorMessage);
}
finally
{
_dialogDisplayCount = 0;
}
}
modified predicate
public class Null { private Null() { } }
public static class ContentDialogExtensions
{
public static async Task<ContentDialogResult> EnqueueAndShowIfAsync(this ContentDialog contentDialog, Func<int> predicate = null)
{
TaskCompletionSource<Null> currentDialogCompletion = new TaskCompletionSource<Null>();
// No locking needed since we are always on the UI thread.
if (!CoreApplication.MainView.CoreWindow.Dispatcher.HasThreadAccess) { throw new NotSupportedException("Can only show dialog from UI thread."); }
var previousDialogCompletion = _previousDialogCompletion;
_previousDialogCompletion = currentDialogCompletion;
if (previousDialogCompletion != null)
{
await previousDialogCompletion.Task;
}
var whichButtonWasPressed = ContentDialogResult.None;
if (predicate == null || predicate() <=1)
{
whichButtonWasPressed = await contentDialog.ShowAsync();
}
currentDialogCompletion.SetResult(null);
return whichButtonWasPressed;
}
private static TaskCompletionSource<Null> _previousDialogCompletion;
}

How to close all open instances of a modeless dialog box?

I have a C# winform project that displays a list of results based on a user's search criteria. For each item on the list, the user can open a modeless dialog box showing more details about the selected item.
Every time the user opens an instance of my details window, this code runs:
public void showDetails()
{
GetDetails route = new GetDetails();
route.myParent = this;
route.Show();
}
In order to compare details between two or more items, the user is allowed to open as many instances of this dialog box as it likes. I'd like to be able to close any and all open instances of this window when the user conducts a new search from the main form window? I've tried Googling, but no luck ... does anyone know how to do this?
Application.OpenForms is a collection of open forms owned by the application
try find all details dialogs and close them like this:
foreach(var f in Application.OpenForms.OfType<GetDetails>().ToList())
{
f.Close();
}
You don't really tell, but I assume your GetDetails is a System.Windows.Forms.Control (probably a form, a dialog box, a message box, etc).
If you look closely to your Form.InitializeComponent, you'll see that Form has a property Controls. All child controls are added to the control collection.
If you add each created route to your control collection you can ask this collection for all objects of type GetDetails and order them to close:
public void ShowDetails()
{
var route = new GetDetails();
route.myParent = this;
this.Controls.Add(route);
route.Show();
}
public void CloseAllRoutes()
{
foreach (var route in this.Controls.Where( control => control is GetDetails))
{
route.Close();
}
}
You need to be certain that when a rout is closed, or disposed or something the following code is called:
private void OnRouteClosed (object sender, ...)
{
if (sender is GetDetails)
{
this.Controls.Remove(sender);
}
}

C# ActiveX--how to keep the pops-up dialog(windows.Forms.Form) always on top of browser(IE8)

I have write the ActiveX using C# to communicate with the other browser-based system b, it need pops up an dialog in an other thread because of the existing architecture. the current behavior is that the dialog can be hidden behind if i click the browser title. Is it possible to keep the pops-up dialog always on the top of browser(IE8)? Thanks in advance.
public int operation()
{
....
MyMsgBox myMsgBox = new MyMsgBox(message,title);
evt = System.Threading.AutoResetEvent(false);
Thread showDialogThread = new Thread(ShowMsgDialog);
ShowDislogThread.SetApartmentState(System.Threading.ApartmentState.STA);
showDialogThread.Start(myMsgBox);
System.Threading.WaitHanle.WaitAll(new System.Threading.WaitHandle[] {evt});
....
}
public void ShowMsgDialog(object requestObj)
{
MyMsgBox msgBox = (MyMsgbox)requestObj;
msgBox.showDialog();
evt.Set();
}
Class MyMsgBox:Form
{
public MyMsgBox(string message, string title)
{
//do initialization....
}
}
I have tried to set the TopMost of Form to 'true', then it will be always on the top of all applications. it's not meet the requirement as the pops-up dialog need be only always on the top of browser. Thanks.
I don't think that what you want will be possible.
However, you can make a div stretched across all page and set and event on mouse move to call BringToFront on your ActiveX object. That should do the trick.

Displaying a "User control is loading" message while loading a User Control

I have a Winforms Application with a TabStrip Control. During runtime, UserControls are to be loaded into different tabs dynamically.
I want to present a "User Control xyz is loading" message to the user (setting an existing label to visible and changing its text) before the UserControl is loaded and until the loading is completely finished.
My approaches so far:
Trying to load the User Control in a BackgroundWorker thread. This fails, because I have to access Gui-Controls during the load of the UserControl
Trying to show the message in a BackgroundWorker thread. This obviously fails because the BackgroundWorker thread is not the UI thread ;-)
Show the Message, call DoEvents(), load the UserControl. This leads to different behaviour (flickering, ...) everytime I load a UserControl, and I can not control when and how to set it to invisible again.
To sum it up, I have two questions:
How to ensure the message is visible directly, before loading the User control
How to ensure the message is set to invisible again, just in the moment the UserControl is completely loaded (including all DataBindings, grid formattings, etc.)
what we use is similar to this:
create a new form that has whatever you want to show the user,
implement a static method where you can call this form to be created inside itself, to prevent memory leaks
create a new thread within this form so that form is running in a seperated thread and stays responsive; we use an ajax control that shows a progress bar filling up.
within the method you use to start the thread set its properties to topmost true to ensure it stays on top.
for instance do this in your main form:
loadingForm.ShowLoadingScreen("usercontrollname");
//do something
loadingform.CloseLoadingScreen();
in the loading form class;
public LoadingScreen()
{
InitializeComponent();
}
public static void ShowLoadingScreen(string usercontrollname)
{
// do something with the usercontroll name if desired
if (_LoadingScreenThread == null)
{
_LoadingScreenThread = new Thread(new ThreadStart(DoShowLoadingScreen));
_LoadingScreenThread.IsBackground = true;
_LoadingScreenThread.Start();
}
}
public static void CloseLoadingScreen()
{
if (_ls.InvokeRequired)
{
_ls.Invoke(new MethodInvoker(CloseLoadingScreen));
}
else
{
Application.ExitThread();
_ls.Dispose();
_LoadingScreenThread = null;
}
}
private static void DoShowLoadingScreen()
{
_ls = new LoadingScreen();
_ls.FormBorderStyle = FormBorderStyle.None;
_ls.MinimizeBox = false;
_ls.ControlBox = false;
_ls.MaximizeBox = false;
_ls.TopMost = true;
_ls.StartPosition = FormStartPosition.CenterScreen;
Application.Run(_ls);
}
Try again your second approach:
Trying to show the message in a BackgroundWorker thread. This obviously fails because the BackgroundWorker thread is not the UI thread ;-)
But this time, use the following code in your background thread in order to update your label:
label.Invoke((MethodInvoker) delegate {
label.Text = "User Control xyz is loading";
label.Visible = true;
});
// Load your user control
// ...
label.Invoke((MethodInvoker) delegate {
label.Visible = false;
});
Invoke allows you to update your UI in another thread.
Working from #wterbeek's example, I modified the class for my own purposes:
center it over the loading form
modification of its opacity
sizing it to the parent size
show it as a dialog and block all user interaction
I was required to show a throbber
I received a null error on line:
if (_ls.InvokeRequired)
so I added a _shown condition (if the action completes so fast that the _LoadingScreenThread thread is not even run) to check if the form exists or not.
Also, if the _LoadingScreenThread is not started, Application.Exit will close the main thread.
I thought to post it for it may help someone else. Comments in the code will explain more.
public partial class LoadingScreen : Form {
private static Thread _LoadingScreenThread;
private static LoadingScreen _ls;
//condition required to check if the form has been loaded
private static bool _shown = false;
private static Form _parent;
public LoadingScreen() {
InitializeComponent();
}
//added the parent to the initializer
//CHECKS FOR NULL HAVE NOT BEEN IMPLEMENTED
public static void ShowLoadingScreen(string usercontrollname, Form parent) {
// do something with the usercontroll name if desired
_parent = parent;
if (_LoadingScreenThread == null) {
_LoadingScreenThread = new Thread(new ThreadStart(DoShowLoadingScreen));
_LoadingScreenThread.SetApartmentState(ApartmentState.STA);
_LoadingScreenThread.IsBackground = true;
_LoadingScreenThread.Start();
}
}
public static void CloseLoadingScreen() {
//if the operation is too short, the _ls is not correctly initialized and it throws
//a null error
if (_ls!=null && _ls.InvokeRequired) {
_ls.Invoke(new MethodInvoker(CloseLoadingScreen));
} else {
if (_shown)
{
//if the operation is too short and the thread is not started
//this would close the main thread
_shown = false;
Application.ExitThread();
}
if (_LoadingScreenThread != null)
_LoadingScreenThread.Interrupt();
//this check prevents the appearance of the loader
//or its closing/disposing if shown
//have not found the answer
//if (_ls !=null)
//{
_ls.Close();
_ls.Dispose();
//}
_LoadingScreenThread = null;
}
}
private static void DoShowLoadingScreen() {
_ls = new LoadingScreen();
_ls.FormBorderStyle = FormBorderStyle.None;
_ls.MinimizeBox = false;
_ls.ControlBox = false;
_ls.MaximizeBox = false;
_ls.TopMost = true;
//get the parent size
_ls.Size = _parent.Size;
//get the location of the parent in order to show the form over the
//target form
_ls.Location = _parent.Location;
//in order to use the size and the location specified above
//we need to set the start position to "Manual"
_ls.StartPosition =FormStartPosition.Manual;
//set the opacity
_ls.Opacity = 0.5;
_shown = true;
//Replaced Application.Run with ShowDialog to show as dialog
//Application.Run(_ls);
_ls.ShowDialog();
}
}

Categories

Resources