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;
}
}
Related
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.
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;
}
}
I am using a wpf UserControl to replace text in files in selected drawing files of AutoCAD. The wpf control is to display a status (ProgressBar) indicating the number of files processed at any given time. So I put up the following code, but the ProgressBar simply does not show any progress. Here is the part of relevant code.
XAML:
<ProgressBar HorizontalAlignment="Stretch" Name="pgrSearch" Minimum="0" Maximum="{Binding Path=ProgressBarMaximum}"
Value="{Binding Path=ProgressBarCurrent}" Height="20" Margin="10" />
CodeBehind:
public partial class ReplaceUserControl : UserControl, INotifyPropertyChanged {
public ReplaceUserControl() {
InitializeComponent();
this.DataContext = this;
}
....
private int _progressBarMaximum;
public int ProgressBarMaximum {
get { return _progressBarMaximum; }
set { _progressBarMaximum = value; RaisePropertyChanged("ProgressBarMaximum"); }
}
private int _progressBarCurrent;
private int ProgressBarCurrent {
get { return _progressBarCurrent; }
set { _progressBarCurrent = value; RaisePropertyChanged("ProgressBarCurrent"); }
}
private void ReplaceTextInFiles() { //Called from Button_Click Handler
....
ProgressBarMaximum = filesList.Count - 1;
SearchReplaceWorker replaceWorker = new SearchReplaceWorker(); //The Work Horse
replaceWorker.FileProcessed += new FileProcessedEventHandler(worker_FileProcessed); //Raised by Work Horse when each file is processed
BackgroundWorker workerThread = new BackgroundWorker(); //The Background Worker Thread
workerThread.DoWork += (o, e) => {
replaceWorker.ReplaceTextInFiles(SearchText, ReplaceText, filesList, ReportFolderPath, MatchCase, MatchSubstring);
};
workerThread.RunWorkerAsync(); //Start the Background Thread Async
}
void worker_FileProcessed(object sender, EventArgs e) {
ProgressBarCurrent = ProgressBarCurrent + 1; //Update the ProgressBar status
}
Why doesn't the ProgressBar update itself when the ProgressBarCurrent is incremented as indicated above in code.
Edit:
In Order to process the ProgressBar update code on UI thread, I changed my code to use BackgroundWorker.ReportProgress() as given under.
CodeBehind for UserControl:
private void ReplaceTextInFiles() { //Called from Button_Click()
if (!Directory.Exists(SearchFolderPath)) {
MessageBox.Show("Invalid Directory Selected for Search");
return;
}
if (!Directory.Exists(ReportFolderPath)) {
MessageBox.Show("Invalid Directory Selected for Report File");
return;
}
List<string> filesList = null;
try {
if (LookInSubFolders) {
filesList = Directory.GetFiles(#SearchFolderPath, "*.dwg", SearchOption.AllDirectories).ToList();
}
else {
filesList = Directory.GetFiles(#SearchFolderPath, "*.dwg", SearchOption.TopDirectoryOnly).ToList();
}
}
catch (Exception ex) {
MessageBox.Show("Error Occurred getting the files list. Contact Admin");
}
pgrSearch.Visibility = Visibility.Visible;
ProgressBarMaximum = filesList.Count - 1;
SearchReplaceWorker replaceWorker = new SearchReplaceWorker();
BackgroundWorker workerThread = new BackgroundWorker();
workerThread.WorkerReportsProgress = true;
workerThread.ProgressChanged += (o, e) => { //This event handler gets called correctly.
ProgressBarCurrent++;
};
workerThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(workerThread_RunWorkerCompleted);
workerThread.DoWork += (o, e) => {
replaceWorker.ReplaceTextInFiles(workerThread, SearchText, ReplaceText, filesList, ReportFolderPath, MatchCase, MatchSubstring);
};
workerThread.RunWorkerAsync();
}
The BackgroundWorker:
public void ReplaceTextInFiles(BackgroundWorker workerThread, string searchText, string replaceText, List<string> filesList, string reportPath,
bool MatchCase, bool MatchSubstring) {
...
workerThread.ReportProgress(50);
}
Still the ProgressBar doesn't update itself.
I created a test project with your initial code. After a while I found that you have declared the ProgressBarCurrent property as private. After changing to public it worked for me. So it doesn't seem necessary to update the property on the UI thread. It looks like a Dispatcher.Invoke call is made internally when reading back the updated property value.
I have a WinForms application that is calling a business class method that performs some heavy duty action taking about 5 seconds for each call. The main form calls this method in a loop. This loop can run from 10 times to maybe up to 10 thousand times.
The WinForms application sends a parameter to the business class and has an area to display the time taken for each method call and what the value returned by the method. How do I inform my main window and update a text area in the main winform with what the method has returned for each call?
Currently the data comes all at once after all the threads have finished. Is there a way to update the UI for all the iterations of the loop once the each call is done? I don't mind if it is done sequentially also.
The FORM
HeavyDutyClass hd;
public Form1()
{
InitializeComponent();
hd = new HeavyDutyClass();
}
//BUTTON CLICK
private void Start_Click(object sender, EventArgs e)
{
int filecount = 5000; //BAD - opening 5000 threads! Any other approach?
hd.FileProcessed += new EventHandler(hd_FileProcessed);
var threads = new Thread[filecount];
for (int i = 0; i < filecount; i++)
{
threads[i] = new Thread(() => { hd.LongRunningMethod(); });
threads[i].Start();
}
}
//BUSINESS CLASS EVENT THAT FIRES WHEN BUSINESS METHOD COMPELTES
void hd_FileProcessed(object sender, EventArgs e)
{
if (dgv.InvokeRequired)
{
dgv.Invoke((MethodInvoker)delegate { UpdateGrid(); });
}
}
private void UpdateGrid()
{
dgv.Rows.Add(1);
int i = dgv.Rows.Count;
dgv.Rows [ i-1].Selected = true;
dgv.FirstDisplayedScrollingRowIndex = i - 1;
}
The business HeavyDuty class
public event EventHandler FileProcessed;
public HeavyDutyClass()
{
}
protected virtual void OnMyEvent(EventArgs e)
{
if (FileProcessed != null)
{
FileProcessed(this, e);
}
}
public bool LongRunningMethod()
{
for (double i = 0; i < 199990000; i++)
{
//time consuming loop
}
OnMyEvent(EventArgs.Empty);
return true;
}
Add a Winforms Project, Drop a Label Control on the Form , Copy-Paste this code and Hit F5
[EDIT]: Updated with the business class comment from the user
NB: My form class is named Form3. You may have to change your Program.cs or vice-versa.
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public class BusinessClass
{
public int MyFunction(int input)
{
return input+10;
}
}
public partial class Form3 : Form
{
private BackgroundWorker _worker;
BusinessClass _biz = new BusinessClass();
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;
_worker.RunWorkerAsync();
}
void DoWork(object sender, DoWorkEventArgs e)
{
int highestPercentageReached = 0;
if (_worker.CancellationPending)
{
e.Cancel = true;
}
else
{
double i = 0.0d;
int junk = 0;
for (i = 0; i <= 199990000; i++)
{
int result = _biz.MyFunction(junk);
junk++;
// Report progress as a percentage of the total task.
var percentComplete = (int)(i / 199990000 * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
// note I can pass the business class result also and display the same in the LABEL
_worker.ReportProgress(percentComplete, result);
_worker.CancelAsync();
}
}
}
}
void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
// Display some message to the user that task has been
// cancelled
}
else if (e.Error != null)
{
// Do something with the error
}
}
void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
}
}
}
With this you can achieve Cancel functionality also very easily.
Observe that during initialisation, I set the WorkerSupportsCancellation = true & then I check for _worker.CancellationPending in the DoWork. So, if you want to cancel the process by a Cancel Button click, then you will write this code in the button handler- _worker.CancelAsync();
I have a wpf application and I created a logon window which is used to build the application's connection string. I am having issues closing the first dialog and spinning open the MainWindow behind it. I think a close event is bubbling out of the logon dialog and getting stuck in the MainWindow because as soon as I create the MainWindow object in the codebehind and call Show() it just moves right past my Startup event handler and into my constructor then the onClosing handlers of the MainWindow without ever showing the window itself. The app.xaml has the ShutdownMode="OnMainWindowClose" specified.
private void Application_Startup(object sender, StartupEventArgs e)
{
try
{
Chooser thechooser = new Chooser();
thechooser.ShowDialog();
}
catch (Exception ex)
{
}
//initialize datalayer
dataLayer = new Mxxx41.DAL(this.CurrentConnectionString);
MainWindow appmainwindow = new MainWindow();
Application.Current.MainWindow = appmainwindow;
appmainwindow.Activate();
appmainwindow.Show();
}
private void LogInButton_Click(object sender, RoutedEventArgs e)
{
//get ip from listbox selection
XmlElement currentelement = (XmlElement)Listbox.SelectedItem;
string ip = ((string)currentelement.Attributes["IP"].Value);
string instancename = string.Empty;
if (!((string)currentelement.Attributes["InstanceName"].Value == string.Empty))
{
instancename = ((string)currentelement.Attributes["InstanceName"].Value);
}
//ping that IP
Boolean pingresult = ping.PingHost(ip);
Boolean sqlresult = false;
if (pingresult)
{
if (!(String.IsNullOrEmpty("instancename")))
{
ip = string.Format("{0}\\{1}", ip, instancename);
}
//build connection string with that IP
string connstr = BuildConnStr(ip);
//create datalayer
Mxxx41.DAL datalayer = new Mxxx41.DAL(connstr);
//validate credentials
DataSet data = datalayer.getDataSet("login_checkcredentials", CommandType.StoredProcedure, datalayer.CreateParameter("#username", SqlDbType.VarChar, this.UsernameTextbox.Text), datalayer.CreateParameter("#password", SqlDbType.VarChar, this.PasswordTextbox.Text));
if (data.Tables[0].Rows.Count > 0)
{
sqlresult = true;
//log in user
//build new user code omitted for brevity
App myAppReference = ((App)Application.Current);
myAppReference.CurrentUser = thisuser;
myAppReference.CurrentConnectionString = connstr;
//close window
this.Close(); //this is the close event I think is causing issues.
}
}
else
{
ErrorLabel.Content = string.Format("{0}{1}", "could not ping selected Host :", ip);
}
//return true
}
public MainWindow(){
this.InitializeComponent();
this.SideBarExpander.IsExpanded = true;
this.Loaded += onLoaded;
this.Closed += onClosed;
this.Closing += onClosing;
try
{
//this.DataLayer = ((Mxxx41.DAL)MyDemoApp.App.Current.Properties["DataLayer"]);
App myAppReference = ((App)Application.Current);
this.DataLayer = myAppReference.GetDataLayer();
}
catch //catch everything for the moment
{
this.DataBaseConnectionError = true;
}
ExceptionList = new List<Error>();
}
Can someone help me out with this behavior?
The problem is probably with ShutdownMode="OnMainWindowClose". Wpf considers the first window opened to be the "main window". In your case, wpf sees your logon window as the main window and exits your application when it closes.
Try changing the shutdown mode to OnLastWindowClose or OnExplicitShutdown.
From MSDN:
OnMainWindowClose: An application shuts down when either the main window closes, or Shutdown is called.
OnExplicitShutdown: An application shuts down only when Shutdown is called.