I have a very simple StopWatch application in Silverlight. I have the following private properties in my MainPage class: _StopPressed (bool), _TimeStart, _Elapsed (string). I also have a "Start" and "Stop" button.
The "Start" button calls a method called UpdateTime that constantly updates _ElapsedTime until _StopPressed is true. When I originally wrote it, UpdateTime would block the UI so I couldn't press the Stop button, so I updated my code to use System.Threading.ThreadPool.QueueUserWorkItem with my UpdateTime method so that it updates _Elapsed on a background thread. That works great at updating the value.
However, if I try to set the .Text value of my TextBlock from within UpdateTime(), I get an UnauthorizedAccessException that has to do with one thread accessing the data in another thread.
What do I need to do to avoid getting this exception and appropriately update the UI without blocking it?
Use a DispatcherTimer instead of a timer, works nearly exactly the same but is used for updating UI elements.
Related
I'm working on a Windows Phone App.
I have a very performance intensive method which takes several seconds until the operation is done.
When the method is called I want to show an animated loading symbol which is defined in the xaml of my view. When the operation is finished it should disappear. I set the loading symbol to visible in the first line of this method.In the last line I set the visibility to collapsed.
The problem is that at first the whole code behind will be executed. Unfortunately nothing is to be seen, because the the visibiliy is set to visible after the code behind operations are executed and in the same moment its set to collapsed.
Has anybody an idea how to solve this problem? Thanks so much in advance.
The problem you have is that you're calling your method on the main (UI) thread. This means that your method blocks the UI from refreshing, and also means that (as you noted) by the time the UI does refresh, you've already hidden the icon again.
What you need to do instead is call your method on a background thread (there are a number of ways to handle this). You will need to push the UI update to the UI thread (using Dispatcher.Invoke), but the rest of your method will run on a separate thread.
You'll also need to use a callback of some kind - maybe a custom event - so that your UI thread knows when the background thread is completed.
Without seeing the code its hard to say for sure but if you use the dispatcher to run you intensive code after the busy indicator is set this would allow the ui thread time to change before running the code.
An example
//This assumes you are binding in xaml to the isbusy and it implements INotifyPropertyChanged
IsBusy = true;
Dispatcher.BeginInvoke(()=>{ //...performance intense here
});
That being Said Dan Puzey is right. You should only run this logic on the UI thread if for some reason your need to. even then be wary of this as it makes for a poor ui experience.
One way you could accomplish this and still have your dispatcher fire off when you need would be to pass a copy of the dispatcher into the background.
ThreadPool.QueueUserWorkItem (d => {
//...performance intense here
Dispatcher dispatcher = d as Dispatcher;
if(dispatcher != null){
dispatcher.BeginInvoke()()=>{//...ui updates here }
}
}, Dispatcher.CurrentDispatcher);//make sure this is called from your UI thread or you may not end up with the correct dispatcher
My form creates a backgroundworker that every 6 secs checks something. Result is 1-100 and I want to display this.
But if i do it straight forward i get som cross-thread errors so I after some research delegates is the way to do it.
ive created inside the Form1 class:
public delegate void SetProgressbarValueDelegate(int val);
but how do i "connect it" to actually update the progressbar?
Thanks
Assuming your are using WinForms, The BackgroundWorker class raises a ProgressChanged event which will automatically be marshalled back onto your UI thread. You should make updates to your User Interface within your handler for this event.
There are some simple examples of how to use BackgroundWorker within the MSDN documentation.
I have the follow button click event:
private void btnRun_Click(object sender, EventArgs e)
{
label1.Visible = true;
if (SelectDatabase())
{
if (string.IsNullOrEmpty(txtFolderAddress.Text))
MessageBox.Show("Please select a folder to begin the search.");
else
{
if (cbRecurse.Checked == false || Directory.GetDirectories(initialDirectory).Length == 0)
{
CheckSingleFolder();
}
else
{
CheckSingleFolder();
directoryRecurse(initialDirectory);
}
}
}
}
Effectively, it does a few checks and then starts some directory recursion looking for specific files. However, the very first line of code to make the label visible does not occur until after the directories have been recursed? Anybody know why this would be?
Thanks.
You're doing everything within the UI thread, which is a really bad idea - the UI doesn't get to update, react to events etc until you've finished.
You should use a background thread and update the UI with progress etc using Control.BeginInvoke, or perhaps use a BackgroundWorker.
Basically, there are two golden rules in WinForms (and similar with WPF/Silverlight):
Don't do anything which can take a significant amount of time in the UI thread
Don't touch any UI elements from any thread other than the UI thread
your whole method runs as a blocking unit currently - add an Application.DoEvents() as a workaround, but really you should be doing this kind of processing in a background thread, i.e. using a background worker.
The code is executing on the same thread which is drawing your user-interface. Therefore, while the code is executing, your UI is not being re-drawn. Once the button-click code has finished, the UI is redrawn and label1 gets drawn invisibly.
You can move your code into a separate thread using, for example, Task or BackgroundWorker. However, you cannot directly set UI properties from a different thread, so you will need to either be careful to set your UI properties from the UI thread or see this question about how to update the GUI from another thread.
The view is not updated until the code block finished. So I would propose a BackgroundWorker for the recursion part.
The explanation: the label is set to visible and it it is Invalidated (needs repainting) but the Windows message pump doesn't start repainting until it is running idle. So your code blocks it.
A simple solution is to call label1.Update() right after setting it visible.
A better solution is to move the time-consuming code to a thread (Backgroundworker).
I have a WinForm set up and a process that loops until a button is pressed on the form.
When I try to run my code, the form does not even display. I suspect this is because the code gets stuck in the loop and doesn't get far enough to display the WinForm. How can I get the form to display and the loop to run after that point?
If you're looping because you need to do something with the GUI periodically while waiting for input, I suggest using a Timer control and its Tick event.
If you want to do non-GUI things while waiting, a more traditional timer is better suited to the task,
You should probably run the loop in a background thread. The BackgroundWorker class makes this pretty easy to do.
Don't do that.
Windows Forms (like most modern user interface development toolkits) is an event-driven framework. You should never use a loop that "waits" for something to happen; instead you want to use an event that triggers something to happen.
Essentially what's happening is this: WinForms has a loop running a message pump that listens for events from Windows and triggers C# events in response to them. Your code is executing on the same thread as that message pump (it has to, since in WinForms only one thread is allowed to touch any given control). So if you put that thread into a loop, the WinForms code that should be pumping messages isn't, and your user interface appears to hang, since it isn't responding to any messages from Windows. (If you keep clicking it, you will fill up the message queue and get a dialog box that says "This application has stopped responding, do you want to terminate?" or something like that.)
The correct solution is to do one of the following:
Use a Timer
Use a BackgroundWorker
Use a ThreadPool
Another solution that would work, but is not a good idea is:
Use Application.DoEvents() -- but please don't actually do this
Your form loading is freezing because the UI of a windows form runs in a single thread. And the logic that you put on the Load event of this form is running on that thread.
You can run your loop on a separate thread easily by using a BackgroundWorker component on your windows form. On the event DoWork of your background worker, you place the code that has the loop that should run without block your UI. On the Form.Load event, you can start the background worker component by calling the method RunWorkerAsync. On the event handler of your button, you place a code to stop the background worker by calling CancelAsync method.
The article How to: Implement a Form That Uses a Background Operation shows exactly how to accomplish it.
About your comment on not being able to update the Text of a TextBox from a your background worker component. It happens because it is not allowed to modify the state of a windows forms control from a different thread (your background worker code is running on a separated thread) MSDN documentation says:
Access to Windows Forms controls is not inherently thread safe. If you have two or more threads manipulating the state of a control, it is possible to force the control into an inconsistent state. Other thread-related bugs are possible, such as race conditions and deadlocks. It is important to make sure that access to your controls is performed in a thread-safe way.
A sample of how you can update the state of your windows forms controls from your background thread will be similar to the one below (assuming that the new value is already stored on a String variable named text):
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
I borrowed this code snipped from How to: Make Thread-Safe Calls to Windows Forms Controls article. It can provide you more information about how to deal with multi-threaded windows forms.
You can use the form load event to trigger the start of the loop.
So it would handle the event Me.Load
However is it necessary for your loop to be happening inside of the UI?
This happens because your loop is keeping the window function from processing messages, such as those that tell it to repaint itself. Place a call to Application.DoEvents() inside of your loop to allow the UI to continue to function.
However, you need to ask yourself why you're looping like this in the first place. If you're, say, copying a bunch of files, this might make sense. For most tasks, though, responding to a timer tick should do the trick and won't block the UI.
You should run your loop in a background thread using the BackgroundWorker component.
Remember that the background thread cannot directly interact with the UI controls.
To report the progress on the UI, you should call the BackgroundWorker's ReportProgress method in the background thread, and handle the ProgressChanged event to update the UI.
You can call the CancelAsync method when the Button is clicked, and loop until the CancellationPending property is true.
Did some searches here & on the 'net and haven't found a good answer yet. What I'm trying to do is call a button twice within the same class in C#.
Here's my scenario -
I have a form with a button that says "Go". When I click it the 1st time, it runs through some 'for' loops (non-stop) to display a color range. At the same time I set the button1.Text properties to "Stop". I would like to be able to click the button a 2nd time and when that happens I would like the program to stop. Basically a stop-and-go button. I know how to do it with 2 button events, but would like to utilize 1 button.
Right now the only way to end the program is the X button on the form.
I've tried different things and haven't had much luck so far so wanted to ask the gurus here how to do it.
BTW, this is a modification of a Head First Labs C# book exercise.
Thanks!
~Allen
You would need to use Multithreading (launch the process intensive code asynchronously in a separate thread), for instance, using the BackgroundWorker object in .NET 2+. This would be necessary because your UI will not respond to the user's click until the loop running in the Start method is completed. It is quite irrelevant if you use the same button or another one to toggle the process, because the processor is busy processing the loop.
The BackgroundWorker has a property called WorkerSupportsCancellation which needs to be true in this scenario. When the user clicks Stop you would invoke the CancelAsync method of the BackgroundWorker.
See MSDN for a good example. Also DreamInCode has a good tutorial which seems quite similar to your requirement.
Why not create two buttons, hide one when the other is visible? That should be a lot of easier to handle.
Or you can add a bool field to indicate which operation branch to execute.
One simple solution would be to add a boolean member to your form that is, e.g., true when the button says "Go" and false when the button says "Stop".
Then, in your button's event handler, check that boolean value. If the value is true, then start your operation and set the value to false when you change the button's text to say "stop". Vice-versa for the other case. :)
There are other techniques that I might prefer if this were production code, perhaps including considering the design of the form more carefully, but as this is clearly a learning exercise I believe that a simple boolean flag indicating the current state of the form is just what you're looking for.
Note that I would strongly discourage you from checking the value of the button text to determine what state the object is in. Whenever possible, as a general rule of good design, you want your visual state to be "decoupled" from your underlying object's state. That is to say, your visual widgets can depend on your underlying objects, but your underlying objects should not depend on your visual widgets. If you tested the text of the button, your underlying logic would depend on your visual state and that would violate this general rule.
If your problem is related to the fact that you can't cancel the operation while it's being performed, you'll want to look into using a BackgroundWorker to perform your long-running activity.
Another option would be to check the current text on your button to determine what to do:
void btnStartStop_Click(Object sender, EventArgs e)
{
if (btnStartStop.Text == "Go")
{
btnStartStop.Text = "Stop";
// Go code here
}
else
{
btnStartStop.Text = "Go";
// Stop code here
}
}
Are you getting your second button click event? Put a breakpoint in your click handler and run your code. When you click the second time, do you ever hit your breakpoint?
If your loop is running continuously, and it is in your button click handler, then your loop is running in the UI thread. You probably don't get to "see" the second button click until after the loop is completed. In addition to the branch code that you see above, try either inserting a DoEvents in your loop processing (this is a place where your loop will temporarly give up control so that messages can be processed). Or, (better) have a look at the backgroundworker class -- do most of your processing in a different thread, so that you UI can remain responsive to button clicks.
Cerebrus is right about using the Background Worker thread. However if you are doing a WPF app then it won't be able to update the UI directly. To get around this you can call Dispatcher.BeginInvoke on the main control/window.
Given code like:
Private Delegate Sub UpdateUIDelegate(<arguments>)
Private Sub CallUpdateUI(<arguments>)
control.Dispatcher.BeginInvoke(Windows.Threading.DispatcherPriority.Background, New UpdateUIDelegate(AddressOf UpdateUI), <arguments>)
End Sub
Private Sub UpdateUI(<arguments>)
'update the UI
End Sub
You can call CallUpdateUI from the Background Worker thread and it will get the main thread to perform UpdateUI.
You could set the Tag property on the button to a boolean indicating whether the next action should be "Stop" or "Go", and reset it each time you click the button. It's an Object property, though, so you'll have to cast it to bool when you read it.