show dialog then call method in it's OnLoad method - winforms

I use C#, Winforms.
From MainForm, I instantiate MailSendForm dialog form which sends an email, the send method is in it's OnLoad method, there is progress bar on this form that shows the sending progress.
The problem is that the form doesn't show up, untill the send finishes and the message box that shows "Success", is closed.
Is there a way to show the sending form before the send begins?
//--- MainForm
private void SendOrder(...)
{
var sm = new MailSendForm(...);
sm.ShowDialog();
}
//--- MailSendForm
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
SendEmail();
}
private void SendEmail()
{
....
// Send mail.
var success = mailman.SendEmail(email);
if (success)
{
MessageBox.Show("email sent successfully");
}
else
{
MessageBox.Show(mailman.LastErrorText);
}
}
public void mailman_OnPercentDone(object source, Chilkat.PercentDoneEventArgs args)
{
progressBar.EditValue = args.PercentDone;
if (_cancelled)
{
args.Abort = true;
}
Application.DoEvents();
}

the Shown event solved my problem.
Thank you EirĂ­kur Fannar Torfason.

Related

Due to the use of Dispatcher,t he window still locks

I have a program similar to chatbot in Wpf.
I have a stack where I create the user controls I have and enter them.
I have to use Net3.5 .
The response from the server is delayed.
The problem I have is when I type and send the textbox the server does not answer,
I can not type another question and the window is locked.
Did I use Dispatcher correctly?
private void send_Click(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
DispatchFit();
}
private void DispatchFit()
{
Dispatcher.BeginInvoke(new Action(ResponsServer), DispatcherPriority.Background);
}
public void ResponsServer()
{
Thread.Sleep(3000);
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}
When that ResponsServer() callback is being processed on your UI thread, then that Sleep is elongating the amount of time that callback is taking to process (Sleep does not pump the UI's dispatcher message queue).
If you want your callback to be done after 3 seconds, then you need to use a timer, or you can use "async" to cause a delayed processing of your callback.
Look at this question: Delayed Dispatch Invoke?
Or use this to have a BackgroundWorker do the delay and then call your ResponsServer on the UI thread (not the best code as it creates a new BackgroundWorker each time).
https://www.codeproject.com/Tips/240274/Execute-later-for-delayed-action
You are a little confused about the methods.
If I understand correctly, then Sleep is an emulation of the delay in the execution of sending a message to the server.
Then you need something like:
private async void send_ClickAsync(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
await ResponsServerAsync();
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}
public async Task ResponsServerAsync()
{
Thread.Sleep(3000);
}
For .Net Framework 3.5
private void send_Click(object sender, RoutedEventArgs e)
{
stack.Children.Add(new UserControl_Send()
{
DataSend = txt_input.Text,
DateTimeBot = DateTime.Now.ToString("HH:mm")
});
Thread thread = new Thread(ResponsServer);
thread.Start();
}
public void ResponsServer()
{
Thread.Sleep(3000);
if (Dispatcher.CheckAccess())
{
StackChildrenAdd();
}
else
{
Dispatcher.InvokeAsync(StackChildrenAdd);
}
}
private void StackChildrenAdd()
{
stack.Children.Add(new UserControl_Receive()
{
DataRecive = get(txt_input.Text),
DateTimeBot = DateTime.Now.ToString("HH:mm"),
});
}

Wpf child form, OnClosing event and await

I have a child form launched form a parent form with:
ConfigForm cfg = new ConfigForm();
cfg.ShowDialog();
This child form is used to configure some application parameters.
I want to check if there are some changes not saved, and if so, warn the user.
So my On OnClosing event is declared this way:
private async void ChildFormClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
// Here i call a function that compare the current config with the saved config
bool isUptated = CheckUnsavedChanges();
// If updated is false, it means that there are unsaved changes...
if (!isUpdated)
{
e.Cancel = true;
// At this point i create a MessageDialog (Mahapps) to warn the user about unsaved changes...
MessageDialogStyle style = MessageDialogStyle.AffirmativeAndNegative;
var metroDialogSettings = new MetroDialogSettings()
{
AffirmativeButtonText = "Close",
NegativeButtonText = "Cancel"
};
var result = await this.ShowMessageAsync("Config", "There are unsaved changes, do you want to exit?", style, metroDialogSettings);
// If we press Close, we want to close child form and go back to parent...
if (result == MessageDialogResult.Affirmative)
{
e.Cancel = false;
}
}
}
My logic says that if i declare e.cancel to false it will continue closing the form, but it doesn't happen, the child form remains open.
My guess is that the async call is doing something i don't understand, because if i declare ChildFormClosing in this way:
private async void ChildFormClosing(object sender, System.ComponentModel.CancelEventArgs e)
{
bool isUptated = CheckUnsavedChanges();
e.Cancel = true;
if (!isUpdated)
{
MessageDialogStyle style = MessageDialogStyle.AffirmativeAndNegative;
var metroDialogSettings = new MetroDialogSettings()
{
AffirmativeButtonText = "Close",
NegativeButtonText = "Cancel"
};
var result = await this.ShowMessageAsync("Config", "There are unsaved changes, do you want to exit?", style, metroDialogSettings);
if (result == MessageDialogResult.Affirmative)
{
e.Cancel = false;
}
}
else
{
e.Cancel = false;
}
}
The final else e.Cancel = false works and the child form is closed...
Any clue?
Thanks!
Since this method is an event handler for a window, it will be called on the UI thread already, so there is no need to show the message box asynchronously.
As for the strange behavior that you are seeing, this is related to the await in the event handler. When you await a method call, what is actually happening is that everything up until the await is executed as normal, but once the await statement is reach control returns to the caller. Once the method that is awaited upon returns, then the rest of the original method executes.
The code that fires the OnClosing event is probably not designed with asynchronous event handlers in mind, so it assumes that if an event handler returns, it has finished whatever work it needs to do. Since your event handler sets CancelEventArgs.Cancel to true before it awaits on a method call, the caller to your event handler sees that it is set to true, so it doesn't close the form.
This is why showing the message box synchronously works: the entire method is executed before control returns to the caller, so CancelEventArgs.Cancel is always set to its expected value.
Raymond Chen recently posted two articles about async that might be interesting reading: Crash course in async and await and The perils of async void. The second article describes why async event handlers tend to not work how you expect them to.
The main problem with using async/await in OnClosing is, as Andy explained, that as soon as the await statement is executed, control is returned to the caller and the closing process continues.
We can work around this by making another round trip back to OnClosing after awaiting, this time with a flag to indicate whether to actually close or not, but the problem is that calling Close while the Window is already closing, is not allowed and throws an exception.
The way to solve this issue is to simply defer the execution of Close to after the current closing process, at which point it becomes valid again to close the window.
I wanted to do something like this to allow the user to handle async closing logic in the ViewModel.
I don't know if there are other edge cases that I haven't covered, but this code so far works for me:
CoreWindow.cs
public class CoreWindow : Window
{
private bool _isClosing;
private bool _canClose;
private BaseDialogViewModel ViewModel => (BaseDialogViewModel) DataContext;
public CoreWindow()
{
DataContextChanged += OnDataContextChanged;
}
private void OnDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.OldValue is BaseDialogViewModel oldDataContext)
{
oldDataContext.Closed -= OnViewModelClosed;
}
if (e.NewValue is BaseDialogViewModel newDataContext)
{
newDataContext.Closed += OnViewModelClosed;
}
}
private void OnViewModelClosed(object sender, EventArgs e)
{
if (!_isClosing)
{
_isClosing = true;
Close();
}
}
protected override async void OnClosing(CancelEventArgs e)
{
if (ViewModel == null)
{
base.OnClosing(e);
return;
}
if (!_canClose)
{
// Immediately cancel closing, because the decision
// to cancel is made in the ViewModel and not here
e.Cancel = true;
base.OnClosing(e);
try
{
// Ask ViewModel if allowed to close
bool closed = await ViewModel.OnClosing();
if (closed)
{
// Set _canClose to true, so that when we call Close again
// and return to this method, we proceed to close as usual
_canClose = true;
// Close cannot be called while Window is in closing state, so use
// InvokeAsync to defer execution of Close after OnClosing returns
_ = Dispatcher.InvokeAsync(Close, DispatcherPriority.Normal);
}
}
catch (Exception ex)
{
// TODO: Log exception
}
finally
{
_isClosing = false;
}
}
base.OnClosing(e);
}
}
BaseDialogViewModel.cs
public class BaseDialogViewModel : BaseViewModel
{
public event EventHandler Closed;
public bool? DialogResult { get; set; }
public void Close()
{
Closed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Override to add custom logic while dialog is closing
/// </summary>
/// <returns>True if should close dialog, otherwise false</returns>
public virtual Task<bool> OnClosing()
{
return Task.FromResult(true);
}
}
BaseViewModel just contains some validation and property notification stuff, not really relevant to show here.
Big thanks to Rick Strahl for the Dispatcher solution!
UPDATE:
It's possible to use await Task.Yield(); instead of Dispatcher.InvokeAsync.

Timer not getting called when backgroundworker running

I have a WPF window with a button that spawns a BackgroundWorker thread to create and send an email. While this BackgroundWorker is running, I want to display a user control that displays some message followed by an animated "...". That animation is run by a timer inside the user control.
Even though my mail sending code is on a BackgroundWorker, the timer in the user control never gets called (well, it does but only when the Backgroundworker is finished, which kinda defeats the purpose...).
Relevant code in the WPF window:
private void button_Send_Click(object sender, RoutedEventArgs e)
{
busyLabel.Show(); // this should start the animation timer inside the user control
BackgroundWorker worker = new BackgroundWorker();
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
string body = textBox_Details.Text;
body += "User-added addtional information:" + textBox_AdditionalInfo.Text;
var smtp = new SmtpClient
{
...
};
using (var message = new MailMessage(fromAddress, toAddress)
{
Subject = subject,
Body = body
})
{
smtp.Send(message);
}
}));
}
Relevant code in the user control ("BusyLabel"):
public void Show()
{
tb_Message.Text = Message;
mTimer = new System.Timers.Timer();
mTimer.Interval = Interval;
mTimer.Elapsed += new ElapsedEventHandler(mTimer_Elapsed);
mTimer.Start();
}
void mTimer_Elapsed(object sender, ElapsedEventArgs e)
{
this.Dispatcher.Invoke((Action)(() =>
{
int numPeriods = tb_Message.Text.Count(f => f == '.');
if (numPeriods >= NumPeriods)
{
tb_Message.Text = Message;
}
else
{
tb_Message.Text += '.';
}
}));
}
public void Hide()
{
mTimer.Stop();
}
Any ideas why it's locking up?
Using Dispatcher.Invoke in your worker_DoWork method is putting execution back on the UI thread, so you are not really doing the work asynchronously.
You should be able to just remove that, based on the code you are showing.
If there are result values that you need to show after the work is complete, put it in the DoWorkEventArgs and you will be able to access it (on the UI thread) in the worker_RunWorkerCompleted handler's event args.
A primary reason for using BackgroundWorker is that the marshalling is handled under the covers, so you shouldn't have to use Dispatcher.Invoke.

How to ignore user clicks in WinForms?

When a user clicks a button, it starts some task. I don't want to block the main application thread, so I run it in a separate thread. Now I need to forbid a user to click the button until my task finishes.
I could set
button.Enabled = false;
, but I'm looking for some way to ignore clicks on it.
I could add some check in click event handler:
if (executingThread != null) return;
, but I will have to do it for each handler which is bad idea.
I know that there is some way to filter user's messages. Could you point me how to do this? And I don't want to filter out all messages, because some other buttons must stay clickable, I need to filter out messages that come to particular controls (buttons,grids and etc).
SOLUTION
internal class MessagesFilter: IMessageFilter
{
private readonly IntPtr ControlHandler;
private const int WM_KEYUP = 0x0101;
public MessagesFilter(IntPtr ControlHandler)
{
this.ControlHandler = ControlHandler;
}
#region IMessageFilter Members
public bool PreFilterMessage(ref Message m)
{
// TODO: Add MessagesFilter.PreFilterMessage implementation
if (m.Msg == WM_KEYUP)
{
if (m.HWnd == ControlHandler)
{
Keys k = ((Keys) ((int) m.WParam));
if (k == Keys.Enter)
return true;
}
}
return false;
}
#endregion
}
As always, the UI should be presented in such a way that user understands what the application is doing and should talk to the user with UI elements.
As Adam Houldsworth suggested I would also prefer keeping the button either disabled or enabled but I would also suggest that the caption of the button should convey the message to the user that the long processing is in progress when the new thread starts..and so the caption of the button should be immediately changed to something like "Processing..Please wait..." (in addition to being disabled or even if you want to keep it enabled), and then if you have kept the button enabled just check the caption of the button (or a isProcessing bool flag) on its click event to return if it says "Processing..Please wait..." or (isProcessing == true).
Lots of the Websites which help users to upload files/images change the Upload button's caption to "Uploading..Please wait..." to inform the user to wait until the upload finishes and additionally some sites also disable the upload button so that the user is not able to click again on Upload button.
You would need to also revert back the caption to normal when the thread finishes long processing.
There may be other advanced ways but the idea is to keep it as simple and basic as possible.
Look at this example on Threading in Windows Forms which shows to disable the button while multi-threading.
+1 for all the suggestions so far. As CSharpVJ suggests - My idea was to additionally inform the user by changing the button's caption making the UI design more intuitive
This can be achieved elegantly with Backgroundworker component in Winforms [No hassles code]. Just copy-paste and HIT F5 (After creating a New Winforms Project with a Button and a Label on it)!
You do not have to check anything related to button here. Everything will be taken care by the appropriate event handlers. its just that you have to do correct stuffs int he resepctive event handlers. Try it !
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form3 : Form
{
private BackgroundWorker _worker;
public Form3()
{
InitializeComponent();
InitWorker();
}
private void InitWorker()
{
if (_worker != null)
{
_worker.Dispose();
}
_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
_worker.ProgressChanged += ProgressChanged;
}
/// do time consuming work here...
void DoWork(object sender, DoWorkEventArgs e)
{
int highestPercentageReached = 0;
if (_worker.CancellationPending)
{
e.Cancel = true;
}
else
{
double i = 0.0d;
for (i = 0; i <= 199990000; i++)
{
// Report progress as a percentage of the total task.
var percentComplete = (int)(i / 199990000 * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
// Report UI abt the progress
_worker.ReportProgress(percentComplete);
_worker.CancelAsync();
}
}
}
}
void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Enabled = true;
if (e.Cancelled)
{
// Display some message to the user that task has been
// cancelled
label1.Text = "Cancelled the operation";
}
else if (e.Error != null)
{
// Do something with the error
}
button1.Text = "Start again";
}
void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
}
private void OnStartClick(object sender, System.EventArgs e)
{
_worker.RunWorkerAsync();
button1.Text = "Processing started...";
button1.Enabled = false;
}
}
}
As mentioned in other answers, there is probably a better solution than what you are asking for.
To directly answer your question, check out the IMessageFilter interface
Create your filter to have it suppress the mouse messages you don't desire, apply it when necessary using Application.AddMessageFilter().
Something along these lines (this should probably compile...):
public class MouseButtonFilter : IMessageFilter
{
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_LBUTTONDBLCLK = 0x0203;
private const int WM_RBUTTONDOWN = 0x0204;
private const int WM_RBUTTONUP = 0x0205;
private const int WM_RBUTTONDBLCLK = 0x0206;
private const int WM_MBUTTONDOWN = 0x0207;
private const int WM_MBUTTONUP = 0x0208;
bool IMessageFilter.PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_LBUTTONDOWN:
/* case ... (list them all here; i'm being lazy) */
case WM_MBUTTONUP:
return true;
}
return false;
}
}

Wait for pending operations to finish without blocking UI thread

I have a MVP like application, all expensive operations are using Async calls and display an Ajax like gif that indicates the user that something is happening without blocking the main thread.
Example:
Data entry form, user clicks Save, an async operation takes place and when it finishes restores the screen to an editable form without blocking the UI thread (in other terms, not blocking other visible windows in the application).
Everything works fine in here, but given the following scenario:
User tries to close the Form, and gets a confirmation message that asks the user if he is sure that he is going to close if he prefers to Save before closing.
When the users clicks 'Save' the same logic explained before takes place, but I'm forced to wait for this call to finish in the UI thread (in case there are any errors in the async call or whatever) and I can`t find any way of doing it other way without blocking the UI thread.
Any suggestions? Thanks!
--- Edit ----
What I'm doing right now is waiting on all my WaitHandles in the Presenter with this loop:
while (!WaitHandles.All(h => h.WaitOne(1)))
Application.DoEvents();
It feels a little dirty.. but at least it simulates non blocking the thread. Is this something that for some reason I should not be doing?
Here is an example of the "hide method". Granted, it's not MVP, it's just an example.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;
class Form1 : Form
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
public Form1()
{
Text = "First Form";
Button button;
Controls.Add(button = new Button { Text = "Launch 2nd Form", AutoSize = true, Location = new Point(10, 10) });
button.Click += (s, e) => new Form2 { StartPosition = FormStartPosition.Manual, Location = new Point(Right, Top) }.Show(this);
}
}
class Form2 : Form
{
public Form2()
{
Text = "Second Form";
dirty = true;
}
private bool dirty;
protected override void OnClosing(CancelEventArgs e)
{
DialogResult result;
if (dirty && (result = new ConfirmSaveForm().ShowDialog(this)) != DialogResult.No)
{
if (Owner != null)
Owner.Activate();
Hide();
e.Cancel = true;
SaveAsync(result == DialogResult.Cancel);
}
base.OnClosing(e);
}
protected override void OnClosed(EventArgs e)
{
Trace.WriteLine("Second Form Closed");
base.OnClosed(e);
}
private void SaveAsync(bool fail)
{
SaveAsyncBegin();
var sad = new Action<bool>(PerformAsyncSave);
sad.BeginInvoke(fail, (ar) =>
{
try { sad.EndInvoke(ar); }
catch (Exception ex) { Invoke(new Action<Exception>(SaveAsyncException), ex); return; }
Invoke(new Action(SaveAsyncEnd));
}, null);
}
private void SaveAsyncBegin()
{
// Update UI for save
}
private void PerformAsyncSave(bool fail)
{
Trace.WriteLine("Begin Saving");
Thread.Sleep(1000); // Do some work
if (fail)
{
Trace.WriteLine("Failing Save");
throw new Exception("Save Failed");
}
dirty = false;
}
private void SaveAsyncEnd()
{
Trace.WriteLine("Save Succeeded");
Close();
}
private void SaveAsyncException(Exception ex)
{
Trace.WriteLine("Save Failed");
Show();
MessageBox.Show(this, ex.Message, "Save Failed", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
class ConfirmSaveForm : Form
{
public ConfirmSaveForm()
{
Text = "Confirm Save";
FormBorderStyle = FormBorderStyle.FixedDialog;
ControlBox = false;
ClientSize = new Size(480, 50);
StartPosition = FormStartPosition.CenterParent;
Controls.Add(new Button { Text = "Yes, Fail", DialogResult = DialogResult.Cancel, Size = new Size(150, 30), Location = new Point(10, 10) });
Controls.Add(new Button { Text = "Yes, Succeed", DialogResult = DialogResult.Yes, Size = new Size(150, 30), Location = new Point(160, 10) });
Controls.Add(new Button { Text = "No", DialogResult = DialogResult.No, Size = new Size(150, 30), Location = new Point(320, 10) });
AcceptButton = Controls[0] as IButtonControl;
}
}

Resources