I want to run the animation on background worker thread. I am using key frame animation. I am unable to figure out how to fire the event CompositeTarget.Rendering from background worker thread which I think is created by UI thread.
My small piece of code.
BackgroundWorker bg;
public MainWindow()
{
bg = new BackgroundWorker();
bg.DoWork += new DoWorkEventHandler(bg_DoWork);
bg.WorkerReportsProgress = true;
bg.WorkerSupportsCancellation = true;
}
void bg_DoWork(object sender, DoWorkEventArgs e)
{
CompositionTarget.Rendering += StartAnimation;
}
private void StartAnimation(Object sender,EventArgs e)
{
//code
}
The composite rendering event is not firing from background worker thread. How should I do this.
Related
I need to have a progress bar to work(visible) at the time of the one process after that the visibility should be set to false. I am using a background worker for this process. but while using the visibility property the application is getting stalled other wise the application is running properly. I am using Devexpress progress bar .Please help me with this. This is the code which I am working.
private void Generate_Click(object sender, EventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.RunWorkerAsync();
}
private void BackgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int a = 0;
int b = 0;
ProgressBar.Visible = true;
ProgressBar.Properties.Step = 1;
ProgressBar.Properties.PercentView = true;
ProgressBar.Properties.Maximum = SpecInformations.TotalSPCOCount;
ProgressBar.Properties.Minimum = 0;
Method_Call(a,b, sender as BackgroundWorker);
}
private void BackgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
ProgressBar.PerformStep();
ProgressBar.Update();
}
private void BackgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
ProgressBar.Visible = false;
}
You cannot access Windows controls like ProgressBar from inside the DoWork method or any method it has called because the thread that runs this code is a background thread and not the same thread that created the Control. You will get an exception whose message states that the control is being access by a thread other than the thread that created it, if you try. This is an inviolable rule about windows controls; they must always only be accessed by the thread that created them
The BackgroundWorker has a WorkerReportsProgress property that must be set to true, and a ReportProgress() method that you can call with an int (and pass an optional object for more info) of the percentage complete. When you call this method in your DoWork, the BackgroundWorker will automatically raise the ProgressChanged event and critically, it does so using the foreground thread it was created with(the same thread your other controls were created with) so code inside your ProgressChanged event handler is run using the proper thread and can access the ProgressBar Control without causing an exception
In summary:
Set WorkerReportsProgress to true
Call ReportProgress inside DoWork, passing a percentage complete or using the int to indicate the process has reached some stage (it doesn't have to be a percentage)
Attach an event handler to your worker's ProgressChanged event
Move your ProgressBar code to the ProgressChanged event handler
the code can help you to use progress bar in BackgroundWorker:
private void cmdButton_Click(object sender, EventArgs e)
{
BackgroundWorker worker = new BackgroundWorker();
worker.WorkerReportsProgress = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerAsync();
}
private void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 101; i++)
{
worker.ReportProgress(i);
System.Threading.Thread.Sleep(1000);
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
lblProgress.Text = ("Progress: " + e.ProgressPercentage.ToString() + "%");
}
for additional info can use the link
I know that the UI can only be updated from the main thread; but why can I update a StatusStrip Label within my DoWork event? but when I try to update another control, the program throws a cross thread exception (as expected).
And since this is possible, is it a good idea/practice update this label from within event?
Here's my code:
public Form1()
{
InitializeComponent();
bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += bw_DoWork;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerCompleted += bw_WorkCompleted;
}
private void bw_DoWork(object sender, DoWorkEventArgs e) {
toolStripStatusLabel1.Text = "foo";
}
I have wcf servise that Update db it is takes 10-15 sec,and i wont to run/show my form with loading/waitting statusbar while servise working, and when service is finished i need to close the watting form.
My problem is when i run ShowDialog(); it is get stuck on it , and don't go to my service.
What i doing wrong here?
My code
My function
public static void UpdateSNXRATES(object sender, EventArgs e)
{
WaitForm waitF = new WaitForm();
waitF.ShowDialog();//here it stuck
using (var Server = new ServiceReference.Service1Client())
{
Server.ClientCredentials.Windows.ClientCredential.Domain = strDomain;
Server.ClientCredentials.Windows.ClientCredential.UserName = strUser;
Server.ClientCredentials.Windows.ClientCredential.Password = strPassword;
success=Server.UpdateSNXRATES();
}
waitF.Close();
}
My WaitForm code
public partial class WaitForm : Form
{
public WaitForm()
{
InitializeComponent();
}
private void WaitForm_Load(object sender, EventArgs e)
{
radWaitingBar1.StartWaiting();
radWaitingBar1.WaitingSpeed = 100;
radWaitingBar1.WaitingStep = 5;
}
}
ShowDialog() is a blocking call, i.e. the current thread will keep waiting on this line until the form is closed (by the user). You should show your WaitForm on a different thread than the main application thread, combined with Invoke() call to ensure that you don't do illegal cross-thread operations. You can use BackgroundWorker component to load and show your WaitForm on a different thread.
Alternately and preferably, you should move your service initialization and running code to the BackgroundWorker. That will ensure you don't need any Invokes.
Example
ServiceReference.Service1Client Server;
WaitForm waitF;
public static void UpdateSNXRATES(object sender, EventArgs e)
{
BackgroundWorker bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += bw_DoWork;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync();
waitF = new WaitForm();
waitF.ShowDialog();
}
static void bw_DoWork(object sender, DoWorkEventArgs e)
{
Server = new ServiceReference.Service1Client();
Server.ClientCredentials.Windows.ClientCredential.Domain = strDomain;
Server.ClientCredentials.Windows.ClientCredential.UserName = strUser;
Server.ClientCredentials.Windows.ClientCredential.Password = strPassword;
success = Server.UpdateSNXRATES();
}
static void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
waitF.Close()
}
I have created a splash screen for a WinForm app. Everything works well until the splash screen is just a form with a background image and a label which has a static text - "Loading..."
I want to update the label continuously (with small delay in between) with texts - "Loading","Loading.","Loading.." and "Loading...". For this I have put the following code in my SplashScreen form:
private void SplashScreen_Load(object sender, EventArgs e)
{
lblLoading.Refresh();
lblLoading.Text = "Loading.";
Thread.Sleep(500);
lblLoading.Refresh();
lblLoading.Text = "Loading..";
Thread.Sleep(500);
lblLoading.Refresh();
lblLoading.Text = "Loading...";
Thread.Sleep(500);
}
Now the Splash Screen doesn't appear until the label in it gets updated to "Loading..."
Please help to let me know what I am doing wrong.
Code in my HomeScreen:
public HomeScreen()
{
//....
this.Load += new EventHandler(HandleFormLoad);
this.splashScreen = new SplashScreen();
}
private void HandleFormLoad(object sender, EventArgs e)
{
this.Hide();
Thread thread = new Thread(new ThreadStart(this.ShowSplashScreen));
thread.Start();
//...Performing tasks to be done while splash screen is displayed
done = true;
this.Show();
}
private void ShowSplashScreen()
{
splashScreen.Show();
while (!done)
{
Application.DoEvents();
}
splashScreen.Close();
this.splashScreen.Dispose();
}
EDIT:
As suggested by some users here I have put the startup tasks in background thread and now I am displaying the Splash Screen from the main thread. But still the same issue persist. Here is the updated code of HomeScreen form:
public HomeScreen()
{
//...
this.Load += new EventHandler(HandleFormLoad);
}
private void HandleFormLoad(object sender, EventArgs e)
{
this.Hide();
SplashScreen sc = new SplashScreen();
sc.Show();
Thread thread = new Thread(new ThreadStart(PerformStartupTasks));
thread.Start();
while (!done)
{
Application.DoEvents();
}
sc.Close();
sc.Dispose();
this.Show();
}
private void PerformStartupTasks()
{
//..perform tasks
done = true;
}
and here's the Splash Screen :
private void SplashScreen_Load(object sender, EventArgs e)
{
lblLoading.Update();
lblLoading.Text = "Loading.";
Thread.Sleep(500);
lblLoading.Update();
lblLoading.Text = "Loading..";
Thread.Sleep(500);
lblLoading.Update();
lblLoading.Text = "Loading...";
Thread.Sleep(500);
}
You want a BackgroundWorker that posts ProgressChanged event, which will update the splash screen. The progress changed object could be a string, for example, that you'll display on your splash screen (back on the GUI thread).
You should handle the splash screen in the main thread, and the background work of initialising in the background thread (just as logixologist commented).
That said, the reason that your changed message doesn't show up is because the main thread is busy so it doesn't handle the events that redraws the control. Calling DoEvents in a background thread will only handle messages in that thread, and the messages to update the splash screen is in the main thread.
The Refresh method only invalidates the control, which would cause it to be redrawn if the main thread handled the event. You can use the Update method to force the redraw of the control:
private void SplashScreen_Load(object sender, EventArgs e)
{
lblLoading.Text = "Loading.";
lblLoading.Update();
Thread.Sleep(500);
lblLoading.Text = "Loading..";
lblLoading.Update();
Thread.Sleep(500);
lblLoading.Text = "Loading...";
lblLoading.Update();
}
But, this is only a workaround for the current code. You should really make the main thread handle the messages.
Thanks to all for answering my questions. Finally my issue is solved ! I changed my code such that start-up tasks are now being performed on a separate thread and splash screen is displayed from the Home Screen (main) thread. In the home screen I made use of Backgroundworker to update the 'Loading...' label.
I am posting my code here hoping it may also help someone in future..
For the Home Screen code plz see the EDIT part in my question. Here's the code of Splash Screen :
public SplashScreen()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = false;
lblLoading.Text = string.Empty;
}
private void SplashScreen_Load(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (true)
{
for (int i = 1; i <= 20; i++)
{
System.Threading.Thread.Sleep(200);
worker.ReportProgress(i);
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
string dots = string.Empty;
for (int k = 1; k <= e.ProgressPercentage; k++)
{
dots = string.Format("{0}..",dots);
}
lblLoading.Text = ("Loading" + dots);
}
You have to define a background worker in your splash screen form.
Here is an example of what your splash screen could look like :
public partial class SplashScreenForm<T> : Form
{
private BackgroundWorker _backgroundWorker;
private Func<BackgroundWorker, T> _func;
private T _funcResult;
public T FuncResult { get { return _funcResult; } }
public SplashScreenForm(Func<BackgroundWorker, T> func)
{
this._func = func;
InitializeComponent();
this.label1.Text = "";
this._backgroundWorker = new BackgroundWorker();
this._backgroundWorker.WorkerReportsProgress = true;
this._backgroundWorker.DoWork += new DoWorkEventHandler(_backgroundWorker_DoWork);
this._backgroundWorker.ProgressChanged += new ProgressChangedEventHandler(_backgroundWorker_ProgressChanged);
this._backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
_backgroundWorker.RunWorkerAsync();
}
private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
if (worker != null)
{
_funcResult = this._func.Invoke(worker);
}
}
private void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.UserState != null)
{
this.label1.Text = e.UserState.ToString();
}
}
private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.Close();
}
}
You can design it the way you want, maybe a pretty gif image, or whatever you can think of.
Call it this way :
private void HandleFormLoad(object sender, EventArgs e)
{
this.Hide();
var splash = new SplashScreenForm<bool>(PerformTask);
splash.ShowDialog(); // function PerformTask will be launch at this moment
this.Show();
}
private bool PerformTask(BackgroundWorker worker)
{
//...Performing tasks to be done while splash screen is displayed
worker.ReportProgress("loading..");
}
The first time I run my backgroundworker it runs correctly - updates a datatable in the background and then RunWorkerCompleted sets the datatable as a datagridview datasource.
If I then run it again, the datagridview clears and doesn't update. I can't work out why.
I've verified that the datatable contains rows when my code hits dgvReadWrites.DataSource.
private void btnGenerateStats_Click(object sender, EventArgs e)
{
dtJobReadWrite.Columns.Clear();
dtJobReadWrite.Rows.Clear();
dgvReadWrites.DataSource = dtJobReadWrite;
List<Tuple<string, string>>jobs = new List<Tuple<string, string>>();
foreach (ListViewItem job in lstJobs.SelectedItems)
{
jobs.Add(new Tuple<string, string>(job.Text, job.SubItems[2].Text));
}
BackgroundWorker bgw = new BackgroundWorker();
bgw.WorkerReportsProgress = true;
bgw.WorkerSupportsCancellation = true;
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
pbarGenStats.Style = ProgressBarStyle.Marquee;
pbarGenStats.MarqueeAnimationSpeed = 30;
pbarGenStats.Visible = true;
bgw.RunWorkerAsync(jobs);
}
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bgw = sender as BackgroundWorker;
List<Tuple<string, string>> jobs = (List<Tuple<string, string>>)e.Argument;
GetReadWriteStats(jobs);
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
BackgroundWorker bgw = sender as BackgroundWorker;
bgw.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.DoWork -= new DoWorkEventHandler(bgw_DoWork);
pbarGenStats.MarqueeAnimationSpeed = 0;
pbarGenStats.Value = 0;
pbarGenStats.Visible = false;
dgvReadWrites.DataSource = dtJobReadWrite;
dgvReadWrites.Visible = true;
dgvReadWrites.Refresh();
}
private void btnGenerateStats_Click(object sender, EventArgs e)
{
//...
dgvReadWrites.DataSource = dtJobReadWrite;
// etc...
}
That's a problem, you are updating dtJobReadWrite in the BGW. That causes the bound grid to get updated by the worker thread. Illegal, controls are not thread-safe and may only be updated from the thread that created them. This is normally checked, producing an InvalidOperationException while debugging but this check doesn't work for bound controls.
What goes wrong next is all over the place, you are lucky that you got a highly repeatable deadlock. The more common misbehavior is occasional painting artifacts and a deadlock only when you are not close. Fix:
dgvReadWrites.DataSource = null;
and rebinding the grid in the RunWorkerCompleted event handler, like you already do.
Because you unscubscribe from those events
bgw.RunWorkerCompleted -= new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
bgw.DoWork -= new DoWorkEventHandler(bgw_DoWork);
Remove those lines
Why are you creating a new BackgroundWorker every time you want to run it? I would like to see what happens with this code if you use one instance of BackgroundWorker (GetReadWriteWorker or something along those lines), subscribe to the events only once, and then run that worker Async on btnGenerateStats_Click.