Display busyindicator in wpf application - wpf

I have a BusyIndicator from the wpf extended toolkit and I'm running a function that takes a while to complete. If I run the time consuming task in a separate thread, I get a NotSupportedException because I'm attemping to insert objects into an ObservableCollection from that different thread. I don't really want to spend a lot of time refactoring the code, if possible... Is there a way that I can set the visibility of the indicator in a separate thread instead?
EDIT
ThreadStart start = delegate()
{
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
IsBusy = true;
}));
};
new Thread(start).Start();
longRunningFunction();
This did not work for me either.

You should be able to use the Dispatcher for things like that. e.g.
Application.Current.Dispatcher.Invoke((Action)(() =>
{
_indicator.Visibility = Visibility.Visible;
}));
This will cause the code to be run on the UI-Thread.
There is more info (including how to "properly" do this, with CheckAccess and such) on it in the threading model reference.

You cannot access UI controls from a background worker. What you normally do is set the IsBusy to true before you call BackgroundWorker.RunWorkerAync(), then in the BackgroundWorker.RunWorkerCompleted event handler you would set the IsBusy to false. Seomthing like:
Backgroundworker worker = new BackgroundWorker();
worker.DoWork += ...
worker.RunWorkerCompleted += delegate(object s, RunWorkerCompletedEventArgs args)
{
IsBusy = false;
};
IsBusy = true;
worker.RunWorkerAsync();
You can use the Dispatcher to add items to your ObservableCollection while in the DoWork event hanlder.
EDIT: Here is the complete solution
private void Button_Click(object sender, RoutedEventArgs e)
{
//on UI thread
ObservableCollection<string> collection;
ThreadStart start = delegate()
{
List<string> items = new List<string>();
for (int i = 0; i < 5000000; i++)
{
items.Add(String.Format("Item {0}", i));
}
System.Windows.Application.Current.Dispatcher.Invoke((Action)(() =>
{
//propogate items to UI
collection = new ObservableCollection<string>(items);
//hide indicator
_indicator.IsBusy = false;
}));
};
//show indicator before calling start
_indicator.IsBusy = true;
new Thread(start).Start();
}

Related

Difference between Thread in WinForm and WPF

I have a Task that update UI in a window. But I don't understand behavior of Thread in WinForm and WPF.
In MainWindow I have two buttons, one that starts Task and one that opens a new window.
private void btnOpenWindow_Click(object sender, RoutedEventArgs e)
{
var window = new Window();
OpenedWindow = window;
window.ShowDialog();
}
private void btnRunTask_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>
{
//If I call UpdateOpenedWindow() with Dispatcher, CheckAccess() is true
//this.Dispatcher.Invoke(() => UpdateOpendedWindow());
UpdateOpenedWindow();
});
}
private void UpdateOpenedWindow()
{
if (!OpenedWindow.Dispatcher.CheckAccess())
{
//Update UI like this really visual update UI
OpenedWindow.Dispatcher.Invoke(() => OpenedWindow.IsEnabled = false);
Thread.Sleep(1000);
OpenedWindow.Dispatcher.Invoke(() => OpenedWindow.IsEnabled = true);
//But if I use only one Invoke block, UI not visual update.. Why? In WinForm this work with no problem
//OpenedWindow.Dispatcher.Invoke(() =>
//{
// OpenedWindow.IsEnabled = false
// Thread.Sleep(1000);
// OpenedWindow.IsEnabled = true
//});
}
else
{
//This not work in WPF. Window seems to stay in enabled state ever
OpenedWindow.IsEnabled = false;
Thread.Sleep(1000);
OpenedWindow.IsEnabled = true;
}
}
In WinForm all comment statements work well and update UI. In WPF not happens. And I would like to understand why and why setting IsEnabled to false and then to true without invoke, task, ecc..., not really update UI in WPF. Thanks

How to correctly coordinate Windows load animations and C# events?

My WPF application has a window load animation which I created using Blend. The actual animation works fine, but if I add logic to my window load event (using C#) the animation skips to end when the window finally renders.
My initial plan was to use Threading to solve this, but this too didn't work:
private void MyWindow_Loaded(object sender, RoutedEventArgs e)
{
dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
dispatcherTimer.Interval = new TimeSpan(0, 0, 30);
dispatcherTimer.Start();
lstRecipients.Visibility = Visibility.Hidden;
windowAdorner = new TransparentAdorner(BorderGrid);
if (!StaticHelpers.AWSConfigurationExists())
{
this.IsEnabled = false;
GettingStarted gettingStarted = new GettingStarted(this);
gettingStarted.Owner = this;
gettingStarted.ShowDialog();
this.IsEnabled = true;
}
else
{
Task SetAWSLabelsTask = new Task(new Action(() => SetAWSLabels()));
SetAWSLabelsTask.Start();
}
Task bounceHandler = new Task(new Action(() => processBounce()));
bounceHandler.Start();
//processBounce();
Task unSubscribeHandler = new Task(new Action(() => handleUnsubscriptions()));
unSubscribeHandler.Start();
}
I'm assuming the system is so busy creating the threads, and the creation is handled by the UI thread, that the animation has already finished by the time the Window is rendered.
What I'm missing is a good way to coordinate the animation, so that any business logic I have in MyWindow_Loaded occurs only after the animation has finished loading.
Is this possible?
EDIT: I also tried a thread sleep, and this too didn't work.
Ok Solved the problem.
In the XAML added a new event handler to the storyboard:
<Storyboard x:Key="SESLogoLoad" Completed="StoryCompleted">
Then created a method manually using C#:
private void StoryCompleted(object sender, EventArgs e)
{
//Windows onload stuff goes here...
}

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.

WPF Threading Grab Bag

I have developed an interesting WPF control that is currently slowing down my entire application :) On my custom control, I have an image control that I need to update everytime a backend event occurs. This backend event is firing twice a second (very fast). When the event fires I need to pull a Bitmap object out of a 3rd party control, convert to a BitmapSource object and then bind it to my Image control. Each time my event is fired, I am queuing a new work item in the ThreadPool. The item will then fetch the Bitmap and do the conversion in a background worker object. This is done everytime the event fires. I am using the dispatcher to update my image control source with BeginInvoke but I still get an unresponsive app. Please let me know what I can do to make this process better performing and help to make my app more responsive:
Here is the code in my event:
void inSight_ResultsChanged(object sender, EventArgs e)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessEvent), ((InSightViewer)this.DataContext).CvsDisplay);
}
Here is the code from delegate:
void ProcessEvent(object display)
{
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync(display);
}
Here is the code in my background worker DoWork event:
void bw_DoWork(object sender, DoWorkEventArgs e)
{
3rdPartyControl displayControl = new 3rdPartyControl();
displayControl.ImageHost = (ImgHost)e.Argument;
Bitmap b = displayControl.GetBitmap();
var mBitmap = b.GetHbitmap();
BitmapSource bs;
try
{
bs = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(
mBitmap,
IntPtr.Zero,
Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
bs.Freeze();
}
catch (System.Exception ex) { throw ex; }
finally
{
DeleteObject(mBitmap);
}
e.Result = bs;
}
Here is the code in RunWorkerCompleted event:
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle, (ThreadStart)delegate()
{
this.imgSource.Source = (BitmapSource)e.Result;
});
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.ApplicationIdle, (ThreadStart)delegate()
{
this.imgSource.Source = (BitmapSource)e.Result;
});
}
System.Windows.Threading.DispatcherPriority.ApplicationIdle means Operations are processed when the application is idle.
What if the application is always busy and never idle? The requests will be queued and the app slows down.
However I haven't tested this because I don't have the source code of your app.

WPF & Multi-threading questions

I'm working on building a multi-threaded UI. I would like long processes to be handled by the BackgroundWorker class, and have a small timer on the UI to keep track of how long the process is taking. It's my first time building such a UI, so I'm reading up on related resources on the web. My test code is thus:
private BackgroundWorker worker;
private Stopwatch swatch = new Stopwatch();
private delegate void simpleDelegate();
System.Timers.Timer timer = new System.Timers.Timer(1000);
string lblHelpPrevText = "";
private void btnStart_Click(object sender, RoutedEventArgs e)
{
try
{
worker = new BackgroundWorker(); //Create new background worker thread
worker.DoWork += new DoWorkEventHandler(BG_test1);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(BG_test1end);
worker.RunWorkerAsync();
simpleDelegate del = new simpleDelegate(clockTicker);
AsyncCallback callBack = new AsyncCallback(clockEnd);
IAsyncResult ar = del.BeginInvoke(callBack, null);
lblHelpText.Text = "Processing...";
}
finally
{
worker.Dispose(); //clear resources
}
}
private void clockTicker()
{
//Grab Text
simpleDelegate delLblHelpText = delegate()
{ lblHelpPrevText = this.lblHelpText.Text; };
this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delLblHelpText);
//Start clock
timer.Elapsed += new ElapsedEventHandler(clockTick);
timer.Enabled = true;
swatch.Start();
}
private void clockTick(object sender, ElapsedEventArgs e)
{
simpleDelegate delUpdateHelpTxt = delegate()
{ this.lblHelpText.Text = String.Format("({0:00}:{1:00}) {2}", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds, lblHelpPrevText); };
this.Dispatcher.BeginInvoke(DispatcherPriority.Send, delUpdateHelpTxt);
}
private void BG_test1(object sender, DoWorkEventArgs e)
{
//this.lblHelpText.Text = "Processing for 10 seconds...";
Thread.Sleep(15000);
}
private void BG_test1end(object sender, RunWorkerCompletedEventArgs e)
{
this.lblHelpText.Text = "Process done.";
this.timer.Enabled = false;
this.swatch.Stop();
this.swatch.Reset();
}
static void clockEnd(IAsyncResult ar)
{
simpleDelegate X = (simpleDelegate)((AsyncResult)ar).AsyncDelegate;
X.EndInvoke(ar);
}
The idea is when the button is clicked, we take the status text from a Label (e.g. "Processing...") then append the time onto it every second. I could not access the UI elements from the Timer class as it's on a different thread, so I had to use delegates to get and set the text.
It works, but is there a better way to handle this? The code seems much for such a basic operation. I'm also not fully understanding the EndInvoke bit at the bottom. I obtained the snippet of code from this thread Should One Always Call EndInvoke a Delegate inside AsyncCallback?
I understand the idea of EndInvoke is to receive the result of BeginInvoke. But is this the correct way to use it in this situation? I'm simply worried about any resource leaks but when debugging the callback appears to execute before my timer starts working.
Don't use a separate timer to read the progress of your BackgroundWorker and update the UI. Instead, make the BackgroundWorker itself "publish" its progress to the UI directly or indirectly.
This can be done pretty much anyway you want to, but there's a built-in provision exactly for this case: the BackgroundWorker.ProgressChanged event.
private void BG_test1(object sender, DoWorkEventArgs e)
{
for(var i = 0; i < 15; ++i) {
Thread.Sleep(1000);
// you will need to get a ref to `worker`
// simplest would be to make it a field in your class
worker.ReportProgress(100 / 15 * (i + 1));
}
}
This way you can simply attach your own handler to ProgressChanged and update the UI using BeginInvoke from there. The timer and everything related to it can (and should) go.
You can use timer to update UI. It is normal practice. Just instead of System.Timer.Timer I suggest use System.Windows.Threading.DispatcherTimer. The DispatcherTimer runs on the same thread as the Dispatcher. Also, instead of BackgroundWorker you can use ThreadPool.
Here is my sample:
object syncObj = new object();
Stopwatch swatch = new Stopwatch();
DispatcherTimer updateTimer; // Assume timer was initialized in constructor.
void btnStart_Click(object sender, RoutedEventArgs e) {
lock (syncObj) {
ThreadPool.QueueUserWorkItem(MyAsyncRoutine);
swatch.Start();
updateTimer.Start();
}
}
void updateTimer_Tick(object sender, EventArgs e) {
// We can access UI elements from this place.
lblHelpText.Text = String.Format("({0:00}:{1:00}) Processing...", swatch.Elapsed.Minutes, swatch.Elapsed.Seconds);
}
void MyAsyncRoutine(object state) {
Thread.Sleep(5000);
lock (syncObj)
Dispatcher.Invoke(new Action(() => {
swatch.Stop();
updateTimer.Stop();
lblHelpText.Text = "Process done.";
}), null);
}
private void button1_Click(object sender, EventArgs e)
{
string strFullFilePath = #"D:\Print.pdf";
ProcessStartInfo ps = new ProcessStartInfo();
ps.UseShellExecute = true;
ps.Verb = "print";
ps.CreateNoWindow = true;
ps.WindowStyle = ProcessWindowStyle.Hidden;
ps.FileName = strFullFilePath;
Process.Start(ps);
Process proc = Process.Start(ps);
KillthisProcess("AcroRd32");
}
public void KillthisProcess(string name)
{
foreach (Process prntProcess in Process.GetProcesses())
{
if (prntProcess.ProcessName.StartsWith(name))
{
prntProcess.WaitForExit(10000);
prntProcess.Kill();
}
}
}

Resources