Showing busy indicator control inside a UI - c#

I have modified code, but now I have another problem. The InvalidOperation exception occurs inside if statement on validating user info. It says that the calling thread cannot access this object because a different thread owns it.Any sugestions?
private void finishConfigButton_Click(object sender, RoutedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
bool validated = false;
errorLabel.Visibility = System.Windows.Visibility.Collapsed;
validationProfile.IsBusy = true;
finishConfigButton.IsEnabled = false;
backToLoginHyperlink.IsEnabled = false;
worker.DoWork += (o, ea) =>
{
if (newUser.ValidateNewUserInformation(newNameTextBox.Text, newEmailTextBox.Text, newUsernameTextBox.Text, newPasswordPasswordBox.Password, ref errorLabel))
{
validated = true;
string activeDir = Environment.SystemDirectory.Substring(0, 1) + #":\Users\" + Environment.UserName + #"\My Documents\SSK\Users";
string newPath = System.IO.Path.Combine(activeDir, newUser.Username);
Directory.CreateDirectory(newPath);
newUser.SaveUserData(newUser);
newPath = System.IO.Path.Combine(activeDir, newUser.Username + #"\Settings");
Directory.CreateDirectory(newPath);
newUserSettings.SetDefaultValues();
newUserSettings.SaveSettings(newUser, newUserSettings);
}
else
validated = false;
if (worker.CancellationPending)
{
ea.Cancel = true;
return;
}
};
worker.RunWorkerCompleted += (o, ea) =>
{
validationProfile.IsBusy = false;
finishConfigButton.IsEnabled = true;
backToLoginHyperlink.IsEnabled = true;
};
worker.RunWorkerAsync(this);
if (validated)
{
IntelliMonitorWindow intelliMonitor = new IntelliMonitorWindow(newUser, newUserSettings);
intelliMonitor.Show();
this.Close();
}
else
errorLabel.Visibility = System.Windows.Visibility.Visible;
}

What you are doing here is running everything on the UI thread. This means that while the heavy code is running, you are blocking the UI from repainting, and hence the validationProfile is not updated untill the end of the method, where IsBusy is set to false.
What you need to do is to process the heavy code into a new thread, which can update the UI at the same time.
Take a look at this blog post written by Brian Lagunas, the creator of Extended Toolkit:
http://elegantcode.com/2011/10/07/extended-wpf-toolkitusing-the-busyindicator/
He explains how to use the BusyIndicator with a BackgroundWorker.

The busy indicator in your XAML code does not have any content. Put some control(s) into it:
<wpfet:BusyIndicator Name="validationProfile" IsBusy="False" BusyContent="Working...Please wait" DisplayAfter="0" Background="DimGray">
<Grid>
...
</Grid>
</wpfet:BusyIndicator>
If you change to busy, those controls will get disabled and the BusyIndicator will appear above them.
I suppose you want to wrap the whole <Grid Background="LightGray"> with the BusyIndicator.

Use a background worker or a new thread to run the heavy process and set the UI thread free. This helps you to update the UI even when the background process is running
Eg:
public void finishConfigButton_Click()
{
worker = new BackgroundWorker();
worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
//Do the heavy work here
};
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
//Things to do after the execution of heavy work
validationProfile.IsBusy = false;
};
validationProfile.IsBusy= true;
worker.RunWorkerAsync();
}
}

I finally figure it out. You can't use UI objects inside worker.DoWork block.I slightly modified the code and it now works.
private void finishConfigButton_Click(object sender, RoutedEventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
errorLabel.Visibility = System.Windows.Visibility.Collapsed;
validationProfile.IsBusy = true;
finishConfigButton.IsEnabled = false;
backToLoginHyperlink.IsEnabled = false;
bool validated = false;
string newName = newNameTextBox.Text;
string newEmail = newEmailTextBox.Text;
string newUsername = newUsernameTextBox.Text;
string newPassword = newPasswordPasswordBox.Password;
string errorMessage = "Unknown error.";
worker.DoWork += (o, ea) =>
{
if (newUser.ValidateNewUserInformation(newName, newEmail, newUsername, newPassword, ref errorMessage))
{
string activeDir = Environment.SystemDirectory.Substring(0, 1) + #":\Users\" + Environment.UserName + #"\My Documents\SSK\Users";
string newPath = System.IO.Path.Combine(activeDir, newUser.Username);
Directory.CreateDirectory(newPath);
newUser.SaveUserData(newUser);
newPath = System.IO.Path.Combine(activeDir, newUser.Username + #"\Settings");
Directory.CreateDirectory(newPath);
newUserSettings.SetDefaultValues();
newUserSettings.SaveSettings(newUser, newUserSettings);
validated = true;
}
else
ea.Cancel = true;
};
worker.RunWorkerCompleted += (o, ea) =>
{
if (validated)
{
IntelliMonitorWindow intelliMonitor = new IntelliMonitorWindow(newUser, newUserSettings);
intelliMonitor.Show();
this.Close();
}
validationProfile.IsBusy = false;
finishConfigButton.IsEnabled = true;
backToLoginHyperlink.IsEnabled = true;
errorLabel.Visibility = System.Windows.Visibility.Visible;
errorLabel.Content = errorMessage;
};
worker.RunWorkerAsync();
}

Related

Background worker isn't returning a value

I can't find out what is wrong in the code below. Since Parallel.For is freezing the form, I used a backgroundworker. However, it doesn't return the string
as intended. Also I'm not sure about how to use report for progressbar in backgroundworker. I don't think I need the variable int i; but without it, I can't report the progress.
private string trdoc(string str)
{
string alltrdoc;
var alldoc = str.Split('\n');
string[] alltrdoc1 = new string[alldoc.Length];
pb1.Maximum = alldoc.Length;//progressbar
pb1.Value = 0;
int i = 0;
BackgroundWorker bw = new BackgroundWorker();
// this allows our worker to report progress during work
bw.WorkerReportsProgress = true;
// what to do in the background thread
bw.DoWork += new DoWorkEventHandler(
delegate (object o, DoWorkEventArgs e)
{
Parallel.For(0, alldoc.Length, new ParallelOptions { MaxDegreeOfParallelism = 4 },
index =>
{
alltrdoc1[index] = translate(alldoc[index]);
bw.ReportProgress(i++);
});
e.Result = alltrdoc1;
});
bw.ProgressChanged += new ProgressChangedEventHandler(
delegate (object o, ProgressChangedEventArgs e)
{
if (pb1.Value <= pb1.Maximum)
{
pb1.Value++;
}
});
// what to do when worker completes its task (notify the user)
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(
delegate (object o, RunWorkerCompletedEventArgs e)
{
alltrdoc1 = e.Result as string[];
});
bw.RunWorkerAsync();
alltrdoc = string.Join(Environment.NewLine, alltrdoc1);
saveposcdic();
return alltrdoc;
}
The end of your trdoc() method is where the problem is in your current implementation:
bw.RunWorkerAsync();
alltrdoc = string.Join(Environment.NewLine, alltrdoc1);
saveposcdic();
return alltrdoc;
bw.RunWorkerAsync returns immediately, so alltrdoc will be an empty string. The alltrdoc1 array is only fully populated once RunWorkerCompleted executes. It´s there that you would create alltrdoc and continue.

How to remove Application.DoEvents() with better solution like BackgroundWorker in multithreading

This is my code. I am using Application.DoEvents() for waiting that UI thread is finished.
public override void Process(Crawler crawler, PropertyBag propertyBag)
{
AspectF.Define.
NotNull(crawler, "crawler").
NotNull(propertyBag, "propertyBag");
if (propertyBag.StatusCode != HttpStatusCode.OK)
return;
if (!IsHtmlContent(propertyBag.ContentType))
return;
m_Logger.Verbose("CefGlue started for url {0}", propertyBag.ResponseUri.ToString());
CefGlueBrowserForm cefGlueBrowserForm = new CefGlueBrowserForm(propertyBag.ResponseUri.ToString());
cefGlueBrowserForm.Show();
while (!cefGlueBrowserForm.Done)
Application.DoEvents();
string htmlSource = cefGlueBrowserForm.DocumentDomHtml;
propertyBag.GetResponse = () => new MemoryStream(Encoding.UTF8.GetBytes(htmlSource));
base.Process(crawler, propertyBag);
}
I am reading that Application.DoEvents() is evil. I am also getting sometimes stackoverflow exception. What to use instead of Application.DoEvents()?
I try something with BackgroundWorker but nothing works
Example:
AutoResetEvent waitHandle = new AutoResetEvent(false);
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += (sender, e) =>
{
if (!e.Cancel)
{
CefGlueBrowserForm cefGlueBrowserForm = new CefGlueBrowserForm(propertyBag.ResponseUri.ToString());
cefGlueBrowserForm.Show();
while (!cefGlueBrowserForm.Done)
Application.DoEvents();
e.Result = cefGlueBrowserForm.DocumentDomHtml;
cefGlueBrowserForm.Dispose();
waitHandle.Set();
}
};
bw.RunWorkerCompleted += (sender, e) =>
{
string htmlSource = e.Result.ToString();
propertyBag.GetResponse = () => new MemoryStream(Encoding.UTF8.GetBytes(htmlSource));
base.Process(crawler, propertyBag);
};
bw.RunWorkerAsync(TimeSpan.FromSeconds(10));
waitHandle.WaitOne(TimeSpan.FromSeconds(20));
What can I do?
EDIT:
added code how cefGlueBrowserForm.Done is set.
public partial class CefGlueBrowserForm : Form
{
public CefGlueBrowserForm(string url)
{
m_Logger = NCrawlerModule.Container.Resolve<ILog>();
m_url = url;
InitializeComponent();
CefManager.InitializeCef();
AddWebBrowser(m_url);
}
private void AddWebBrowser(string startUrl)
{
m_textBox = new TextBox
{
Dock = DockStyle.Bottom,
ReadOnly = true,
};
m_textBox.Parent = this;
Console.Box = m_textBox;
Console.WriteLine("Loading URL ...");
CefGlueBrowser = new ChromiumCefWebBrowser();
CefGlueBrowser.Dock = DockStyle.Fill;
CefGlueBrowser.BringToFront();
CefGlueBrowser.StartUrl = startUrl;
CefGlueBrowser.Parent = this;
Controls.Add(CefGlueBrowser);
Console.WriteLine("URL " + startUrl + " loaded.");
CefGlueBrowser.LoadEnd += Browser_LoadEnd;
}
private void Browser_LoadEnd(object sender, EventArgs e)
{
m_Logger.Verbose("Page load was ended for url {0}", m_url);
MyCefStringVisitor visitor = new MyCefStringVisitor(this, m_url);
((LoadEndEventArgs)e).Frame.Browser.GetMainFrame().GetSource(visitor);
}
private class MyCefStringVisitor : CefStringVisitor
{
#region Instance Fields
private CefGlueBrowserForm m_cefGlueBrowserForm;
private string m_url;
private ILog m_Logger;
#endregion
#region Constructors
public MyCefStringVisitor(CefGlueBrowserForm cefGlueBrowserForm, string url)
{
m_Logger = NCrawlerModule.Container.Resolve<ILog>();
m_cefGlueBrowserForm = cefGlueBrowserForm;
m_url = url.NormalizeUrl();
}
#endregion
#region Instance Methods
protected override void Visit(string value)
{
string currentUrl = m_cefGlueBrowserForm.CefGlueBrowser.Browser.GetMainFrame().Url.NormalizeUrl();
if (m_url.Equals(currentUrl, StringComparison.OrdinalIgnoreCase) || currentUrl.Contains(m_url))
{
m_Logger.Verbose("Getting html source for url {0} and closing Event", m_url);
m_cefGlueBrowserForm.DocumentDomHtml = value;
m_cefGlueBrowserForm.Done = true;
m_cefGlueBrowserForm.CefGlueBrowser.LoadEnd -= m_cefGlueBrowserForm.Browser_LoadEnd;
}
}
#endregion
}
#endregion
}
So what you want to do is execute the rest of the code after the form has been closed. You can do that by simply subscribing to the FormClosed event and running the rest of that code there, rather than blocking the UI thread until the form is closed:
CefGlueBrowserForm cefGlueBrowserForm =
new CefGlueBrowserForm(propertyBag.ResponseUri.ToString());
cefGlueBrowserForm.Show();
cefGlueBrowserForm.FormClosed += (sender, e) =>
{
string htmlSource = cefGlueBrowserForm.DocumentDomHtml;
propertyBag.GetResponse = () =>
new MemoryStream(Encoding.UTF8.GetBytes(htmlSource));
base.Process(crawler, propertyBag);
};
There's no need for other threads to do this.
Another approach is to leverage the await keyword to accomplish this same general task, but with a generally easier syntax. If we take the time to write a simple helper method that generates a Task that will be completed when the form is closed:
public static Task WhenClosed(this Form form)
{
var tcs = new TaskCompletionSource<bool>();
FormClosedEventHandler handler = null;
handler = (s, args) =>
{
tcs.TrySetResult(true);
form.FormClosed -= handler;
};
form.FormClosed += handler;
return tcs.Task;
}
Then we can write the method like this:
public async Task Process(Crawler crawler, PropertyBag propertyBag)
{
//...
CefGlueBrowserForm cefGlueBrowserForm =
new CefGlueBrowserForm(propertyBag.ResponseUri.ToString());
cefGlueBrowserForm.Show();
await cefGlueBrowserForm.WhenClosed();
string htmlSource = cefGlueBrowserForm.DocumentDomHtml;
propertyBag.GetResponse = () =>
new MemoryStream(Encoding.UTF8.GetBytes(htmlSource));
base.Process(crawler, propertyBag);
}
And while this looks like it's blocking until the form is closed, under the hood this is generating code that will act rather similarly to the code that we have above; it will run everything after the await as a continuation fired when the event fires.

Dispatcher timer not running

I'm trying to execute some Python scripts from my WPF app. The scripts are generating the log files and the code in the Tick event is reading them and displaying that as it is in a textbox.
My issue here is that, that LaunchProcess fires successfully, but the UI freezes. I have an indefinite progress bar, which too does not start animating. I'm a beginner with WPF and there is something very small I have to do to get this code working. I'm not getting any error/warnings. The scripts run fine and in the end I get to know the result too. But during the run, the UI of my app freezes.
private void LaunchProcess(string paramStr)
{
Process myProcess = new Process();
StartProgressBar();
try
{
dispatcherTimer = new DispatcherTimer();
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 0);
dispatcherTimer.Start();
myProcess.StartInfo.UseShellExecute = false;
// You can start any process
myProcess.StartInfo.FileName = "C:\\Python32\\python.exe";
myProcess.StartInfo.Arguments = "\""+paramStr+"\"";
myProcess.StartInfo.CreateNoWindow = true;
myProcess.StartInfo.RedirectStandardOutput = true;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.Start();
myProcess.WaitForExit();
// This code assumes the process you are starting will terminate itself.
// Given that is is started without a window so you cannot terminate it
// on the desktop, it must terminate itself or you can do it programmatically
// from this application using the Kill method.
dispatcherTimer.Stop();
}
catch
{
MessageBox.Show("Process Launch Failed!!", "Failure", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
//txtOutPut.Text = "";
txtOutPut.Text += "\n" + DateTime.Now.ToString();
if (File.Exists(scriptPath+"\\log.txt"))
{
//File.Copy("C:\\FlashAuto\\Execution_Logs\\log.txt", "C:\\FlashAuto\\Temp\\log.txt", true);
TextReader readLogs = new StreamReader(scriptPath + "\\log.txt");
string line = readLogs.ReadLine();
while (line != null)
{
txtOutPut.Text += "\n" + line;
line = readLogs.ReadLine();
txtOutPut.ScrollToEnd();
}
//CountLines = txtExecLog.LineCount - 1;
readLogs.Close();
// Forcing the CommandManager to raise the RequerySuggested event
txtOutPut.ScrollToEnd();
CommandManager.InvalidateRequerySuggested();
readLogs.Dispose();
}
else
{
txtOutPut.Text += "log file not found at: " + DateTime.Now.ToString();
}
}
In case you call LaunchProcess from the UI thread it will obviously be blocked at myProcess.WaitForExit().
You might simply remove the myProcess.WaitForExit() and dispatcherTimer.Stop() calls from the launch method and check if the process is still running in the timer Tick handler.
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
if (myProcess.WaitForExit(0)) // check with timeout zero
{
dispatcherTimer.Stop();
}
... // all your code
}
Calling LaunchProcess method asynchronously would resolve your UI Freeze Issue
public void LaunchProcessAsynchrousCall(string paramStr)
{
ThreadStart displayContentHandler = delegate()
{
LaunchProcess(paramStr)
};
Thread thread = new Thread(displayContentHandler);
thread.IsBackground = true;
thread.Start();
}

Background worker "cause" to freeze my form

In my app I want create thousands files in background, but when I execute this it always freeze my form(after all files were created I can work with my form again). How run it properly without hanging?
private void button5_Click(object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += new DoWorkEventHandler(
delegate(object o, DoWorkEventArgs args)
{
BackgroundWorker b = o as BackgroundWorker;
this.Invoke(new MethodInvoker(delegate
{
getValues();//load some text fields into strings
while (counter < counted)
{
text = richTextBox1.Text;
text = text.Replace("number", finalNumber);
//create copies
if (checkBox1.Checked == true)
{
while (createdCopies < copies)
{
createdCopies++;
File.WriteAllText(fileName, text);
overalCounter++;
b.ReportProgress(overalCounter);
}
counter++;
createdCopies = 0;
}
//dont create copies
else
{
File.WriteAllText(fileName, text);
counter++;
overalCounter++;
b.ReportProgress(overalCounter);
}
//info about number of files created
label6.Text = "created " + overalCounter.ToString() + " files";
}
label1.Text = "success";
}));
});
if (bw.IsBusy != true)
{
bw.RunWorkerAsync();
}
bw.ProgressChanged += new ProgressChangedEventHandler(
delegate(object o, ProgressChangedEventArgs args)
{
this.Text = string.Format("{0}% Completed", args.ProgressPercentage);
});
}
this.Invoke runs the code inside on the UI thread, blocking any UI updates.
Since you run everything in the Invoke method everything will run on the UI thread.
Create a separate Invoke around each place where you modify your UI controls and leave the heavy work outside the Invokes.
Do the actual work in your first delegate. The second delegate this.Invoke(...) executes on the thread of the form (=main thread) and therefore blocks you UI.

UI freezes on heavy calculation

I am loading huge files to the memory but on this calculation my application is freezes .
Any idea what is the issue with my code ?
public void Drop(DragEventArgs args)
{
BackgroundWorker worker = new BackgroundWorker();
string fileName = IsSingleTextFile(args);
if (fileName == null) return;
worker.DoWork += (o, ea) =>
{
try
{
StreamReader fileToLoad = new StreamReader(fileName);
string filecontent = fileToLoad.ReadToEnd();
fileToLoad.Close();
// providing the information to the UI thread
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => SfmLogFile = filecontent));
}
catch (Exception)
{
throw;
}
};
worker.RunWorkerCompleted += (o, ea) =>
{
args.Handled = true;
IsBusy = false;
};
// Mark the event as handled, so TextBox's native Drop handler is not called.
IsBusy = true;
worker.RunWorkerAsync();
}
I'd transform your sample to something like this:
public void Drop(DragEventArgs args)
{
string fileName = IsSingleTextFile(args);
if (fileName == null) return;
// It is better to create worker after check for file name.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (o, ea) =>
{
try
{
string filecontent = ReadAllText(fileName);
ea.Result = fileContent;
}
catch (Exception)
{
throw;
}
};
worker.RunWorkerCompleted += (o, ea) =>
{
var fileContent = ea.Result as string;
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => SfmLogFile = filecontent));
// if IsBusy propery is not on the UI thread, then you may leave it here
// otherwise it should be set using the dispatcher too.
IsBusy = false;
};
IsBusy = true;
worker.RunWorkerAsync();
// Mark the event as handled, so TextBox's native Drop handler is not called.
args.Handled = true;
}
I am not sure if it's the cause of your problem, but you are setting args.Handled in a callback for the background worker, so it will be called after the drop event handler has returned. It won't have the desired effect as it's set too late, and it might mess up something in the event handling.

Categories

Resources