I have a text block called "findListText". Here, I am updating the text in it:
private void InstantSearch(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
{
HitEnter = true;
}
findListText.Text = "Processing request. Please wait...";
Find(bool.Parse("False" as string));
}
However, the next set of code is a search function that can take up to 10 seconds, and at the end of it, it changes the text in findListText again.
private void Find(bool? bForward = true)
{
{
//Lots and lots of code
}
findListText.Text = "Search completed."
}
The problem is, the textblock never seems to update to "Processing request. Please wait...". The textblock is in it's original state and 10 seconds later updates to "Search completed.", seemingly skipping out the middle man.
I'm using C# - WPF. What am I doing wrong here?
Doesn't matter what technology I think.
The code is running on the same thread, meaning the the UI won't be updated untill all the code on that thread is completed. You should address a different thread to update that textblock.
In that case, you will have 2 thread:
The origininal thread, executing the "lots and lots of code"
The second (extra) created thread, which will handle updating the textblock's text while the other thread is executing the other code.
I've created a little something that should resolve your problem, it's based on this Stack Overflow page
Since this is WPF, try the following: after changing the text to "Processgin", call:
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { this.UpdateLayout(); }));
This will tell the thread to update the UI as soon as possible.
Here is how to run your find method in its own thread.
private void InstantSearch(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
{
HitEnter = true;
}
findListText.Text = "Processing request. Please wait...";
BackgroundWorker tempWorker = new BackgroundWorker();
tempWorker.DoWork += delegate
{
Find(bool.Parse("False" as string));
};
tempWorker.RunWorkerAsync();
}
If you try that, you'll get an error because you access your UI thread from the background thread. So you'll need to update your find method as well.
private void Find(bool? bForward = true)
{
{
//Lots and lots of code
}
Dispatcher.BeginInvoke((Action) delegate {
findListText.Text = "Search completed."
});
}
You should look into the UI threading concept of WPF. Invoke the Dispatcher to modify the textbox. Also the search should run with ThreadPool.QueueWorkerItem.
// Start worker thread
ThreadPool.QueueUserWorkItem(state =>
{
// Long running logic here
findListText.Dispatcher.BeginInvoke(() => findListText.Text = "Processing request. Please wait...");
Find(bool.Parse("False" as string));
// Tip: Run change on GUI thread from the worker using the dispatcher
findListText.Dispatcher.BeginInvoke(() => findListText.Text = "Search completed.");
});
I came back to this just now, and had another browse across the internet for similar problems. For something as simple as pushing a single message before a long running process occurs, I'm surprised no-one suggested "Application.DoEvents();". Yes, I know it has it's flaws, and my UI will still hang, but this suits me perfectly for my situation, at least.
Related
I'm trying to download some files with the WebClient.DownloadFileAsync-Method.
It works fine as long as the UI istn't shown.
The UI is a Form with a Label and a ProgressBar on it.
In the DownloadProgressChanged-Event i want to show the current progress.
In order to do this i call a method with an int-parameter.
These are the methods for downloading:
private void DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
if(progressDialog!=null){
progressDialog.setFileProgress(e.ProgressPercentage);
}
Trace.WriteLine(String.Format("downloaded {0} of {1} bytes. {2} % complete...",
e.BytesReceived,
e.TotalBytesToReceive,
e.ProgressPercentage));
}
private void DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
{
if(progressDialog!=null){
progressDialog.setFileProgress(100);
}
are.Set();
}
private AutoResetEvent are = new AutoResetEvent(false);
public void DownloadFiles(List<DownloadObject> objects){
Trace.WriteLine("Start Download");
wc.DownloadProgressChanged += DownloadProgressChanged;
wc.DownloadFileCompleted += DownloadFileCompleted;
try{
foreach(DownloadObject dlo in objects){
currentFile = dlo;
String url = dlo.DownloadURL;
String path = dlo.LocalPath;
Uri uri = new Uri(url);
//GET
Thread thread = new Thread(() => wc.DownloadFileAsync(uri,path));
//thread.SetApartmentState(ApartmentState.STA);
thread.Start();
are.WaitOne();
DeleteFile(dlo.ID);
}
Trace.WriteLine("FileDownload finished");
}catch(Exception ex){
Trace.WriteLine("FileDownload failed: "+ex.Message);
}finally{
wc.Dispose();
}
}
These are the relevant methodsinside the ProgressDialog-Form:
public delegate void dummy();
public void setFileProgress(int progress){
if(prgFile.InvokeRequired){
Trace.WriteLine("Invoke required");
prgFile.Invoke(new dummy(() => prgFile.Value = progress));
}else{
Trace.WriteLine("Invoke not required");
prgFile.Value = progress;
}
}
public static ProgressDialog getInstance(IWin32Window owner){
ProgressDialog pd = new ProgressDialog();
//pd.Show(owner); //
return pd;
}
Now what happens is this:
If pd.Show() is not called everything is just fine. The Progress gets changed and i get the Output "Invoke not required" as well as every step of the download.
However if the pd.Show() is called, i get the Output "Invoke required" multiple times without any download messages between.
So i debugged through that part of the code and it seems that progressDialog.setFileProgress() gets called but the DownloadProgressChanged-Event fires again directly after the prgFile.Invoke-method is called.
If i switch the Invoke-call to BeginInvoke, i get all the correct Messages again, but the ProgressDialog freezes until all downloads are finished and i don't get any progress shown.
What am i missing there? I read through tons of questions and threads about this but can't get it running.
I'm using SharpDevelop with .Net-Framework 4.0
Because your 'DownloadFiles' method is synchronous, which blocks the UI thread. You start a new thread to download the file but
are.WaitOne();
blocks the current thread (the UI thread) until are is set (in the download completed event).
The correct solution is put those work after downloading (e.g. DeleteFile) in the complete event.
Because the DownloadFiles-method is synchronous and is running in the UI thread. Therefore, if you execute the method, your UI thread will be blocked until the Method is finished.
There are two approaches about this:
Multithreading
Just letting the Method run in a different task will solve your UI-Freezing. However, as you mentioned, in your case this will be a horrible approach because you can not manipulate your UI cross-thread. If you want to make status updates you should not go for this.
Async/Await
The better approach here. Although it seems very simple, async/await is actually very hard to do right. There are lots of resources on the internet and I would suggest starting on dotnetperls, which is a detailed introduction into async/await. It will take some time but is definetly the best approach about this.
I have been developing a windows forms application in vc# express 2013. I have a statusstrip control where i have a label and a progress bar. I want to show the progress of progressbar in label but it is not updating with the progress bar value. it only gets updated when the progress bar is complete i.e. when value is 100 and the job is finished. The strange thing is that the progress bar is updated but the label is not... Any ideas what might be happening here? By the way, I raise a progress event in my own code which is handled and the progress is updated accordingly...
Thanks
I raise the event ProgressTick in MGFDataHandler Class
if ((hundredthValue*progressMultiplier == lineCounter) || (lineCounter==totalLines))
{
//Raise the progress bar event!
EventHandler handler = ProgressTick;
if (handler != null)
{
handler(this, e);
}
progressMultiplier++;
}
I then handle it in windowsform as follows:
void msDataHandler_ProgressTick(object sender, EventArgs e)
{
var progress = (int)((MGFDataHandler)sender).progressMultiplier;
if (progress < 100)
{
this.progressStatusString.Text = "Progress: " + progress + "%";
this.progressBar.Visible = true;
this.progressBar.Value = progress;
}
else
{
this.progressStatusString.Text = "Done";
this.filterMSDataToolStripMenuItem.Enabled = true;
}
}
The function which starts the whole thing is in the windowsform_activated event and MGFDataHandler object is an attribute of the windowsform.
private void GlycoMSPipelineMainFrame_Activated(object sender, EventArgs e)
{
switch (this.whoActivatedMe)
{
case WhoActivatedMe.MSFilterWizard:
{
this.whoActivatedMe = WhoActivatedMe.Me;
foreach (String currentmgffilename in this.currentMGFFileNames)
{
mgfDataHandler.Filter_MS_Data(currentmgffilename);
}
this.populateTreeViews();
break;
}
default:
{
break;
}
}
}
It is the Filter_MS_Data that raises the event.
Try using a background worker (found in System.componentModel) to run the task that you are performing. When updating the label or progress back you will need to check if invoke is required.
Something like this.
If (label.InvokeRequired) {
label.Invoke(new MethodInvoker(delegate
{
label.Text = progress.Value;
});
}
Invoke is required to prevent cross threading operations which in turn can produce race conditions.
Forgive the formatting i typed this out on my phone.
You will need to perform the population using a background worker. I think this will resolve your issue.
Edit:
The background worker provides a do work event which will run on another thread. It also provides a finished event so you know when the task is complete.
I think what is happening here is that your program is performing work on the ui thread which prevents the ui from fully updating.
I do need some more information for a full answer, but the direction in which this is heading to is quite clear to me.
The problem is that you're probably doing everything in the same thread. When you have a big job you need to use a background thread to do the work, and let the main thread (i.e. a UI thread) handle the UI.
This is a big topic and I'm just scratching the surface here, so first of all read the following links and try to create a thread for your job and invoke the update of the progress bar yourself. If not successful, then post a new question with more information.
The links you should read are:
Threads in C#
ProgressBar in C#
I realize this is a very old question, but i found it with the same issue in VS2019. I found that rather than adding a thread, you can call the Refresh method of the container of the label and ProgressBar.
void msDataHandler_ProgressTick(object sender, EventArgs e)
{
var progress = (int)((MGFDataHandler)sender).progressMultiplier;
if (progress < 100)
{
this.progressStatusString.Text = "Progress: " + progress + "%";
this.progressBar.Visible = true;
this.progressBar.Value = progress;
}
else
{
this.progressStatusString.Text = "Done";
this.filterMSDataToolStripMenuItem.Enabled = true;
}
this.progressStatusString.Owner.Refresh();
}
I read about how to update page UI asynchronously on internet and got that we can update page UI by calling dispatcher and invoking method like
Dispatcher.BeginInvoke(() =>
{
MessageBox.Show("some message");
});
But, it is not working.I think that I am missing some conceptual basics.
I want update status textbox with respect to phone's internet connectivity.
async void HomePage_Loaded(object sender, RoutedEventArgs e)
{
if (App.Connectivity == -1)
{
await ConnectionInitialization();
}
}
private async Task ConnectionInitialization()
{
if(NetworkInterface.GetIsNetworkAvailable())
{
SystemTray.ProgressIndicator = new ProgressIndicator();
Deployment.Current.Dispatcher.BeginInvoke(() => SetProgressIndicator(true));
Deployment.Current.Dispatcher.BeginInvoke(() => TextBoxAppStatus.Text = "Connecting...");
}
else
{
Deployment.Current.Dispatcher.BeginInvoke(() => TextBoxAppStatus.Text = "Disconnected");
await ConnectionInitialization();
}
}
Here, it gets into infinite loop, without displaying anything on the screen, neither progress bar nor textbox. Please tell me where I am conceptually wrong.
I will be highly thankful,if you can also provide me resources for making User friendly stud app.Thanks
In your code you have infinite recursion if no connection available
About threads: if you use async on UI, code execution continues on UI thread, so you don't need to use Dispatcher
Use DeviceNetworkInformation.NetworkAvailabilityChanged event to track changes in connection status.
i'm using wpf, there's a button on my ui.
when the user clicks it, i have a for loop that runs a new method, on a new thread using autoresetevent.
in that method on that new thread, i'm using a label, let's call it lblStatus. i want to update that label on this thread that's not on the ui. using wpf, i have to use Dispatcher.Invoke.
here's a sample of my code:
Thread thread= new Thread(StartLooking);
thread.Start();
_waitHandle.WaitOne();
private void StartLooking(object value)
{
if (lblStatus.Dispatcher.Thread == Thread.CurrentThread)
{
lblStatus.Content = "Scanning>...";
}
else
{
lblStatus.Dispatcher.Invoke(DispatcherPriority.Background, new Action(() => lblStatus.Content = "Scanning>>>>>"));
}
_waitHandle.Set();
}
the program just stops here. it doesn't change the content of the label, it returns to my ui, but blocks it.
i've tried
lblStatus.Dispatcher.Invoke(DispatcherPriority.Normal, new LblStatusThreadCheck(lblStatusThreadCheck), "Scanning...");
as well, but that isn't working also. any ideas?
The problem is that you're making it impossible for this to execute, since you're using Invoke.
Dispatcher.Invoke will not return until the UI thread processes. However, you've blocked the UI thread by calling _waitHandle.WaitOne();, and don't set the wait handle until AFTER this processes. The two effectively cause a dead lock.
If you switch this to use BeginInvoke instead, the UI will queue the element, the wait handle will set, THEN the label will update. It will work and not block, however.
Since the two previous posts already cover the problem in your code, just a suggestion: instead of
if (lblStatus.Dispatcher.Thread == Thread.CurrentThread)
try using
if (!lblStatus.CheckAccess())
It's cleaner and has the exact intent you want. Just read about it here.
You probably want to use BeginInvoke instead. Invoke will block the thread that called it until the UI thread has run the Action, and since you're setting the priority to Background, this could take some time.
Best solution I have found for .net 4.5+ is using SynchronizationContext Post
Example (Task.Run's can be as many as you want in parallel accessing UI):
private void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
var context = SynchronizationContext.Current;
Task.Run(() =>
{
var i = 0;
while (true)
{
context.Post((tmp) =>
{
uiText.Text = $"{i}";
}), this);
Thread.Sleep(1000);
i++;
}
});
}
I have the following code:
private void button1_Click(object sender, EventArgs e)
{
var answer =
MessageBox.Show(
"Do you wish to submit checked items to the ACH bank? \r\n\r\nOnly the items that are checked and have the status 'Entered' will be submitted.",
"Submit",
MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1);
if (answer != DialogResult.Yes)
return;
button1.Enabled = false;
progressBar1.Maximum = dataGridView1.Rows.Count;
progressBar1.Minimum = 0;
progressBar1.Value = 0;
progressBar1.Step = 1;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if ((string) row.Cells["Status"].Value == "Entered")
{
progressBar1.PerformStep();
label_Message.Text = #"Sending " + row.Cells["Name"].Value + #" for $" + row.Cells["CheckAmount"].Value + #" to the bank.";
Thread.Sleep(2000);
}
}
label_Message.Text = #"Complete.";
button1.Enabled = true;
}
This is a test I am creating to port over to my application. Everything works fine but the label_Message.text being set. It never shows up on the screen. It is being set, I did a console.write on it to verify. It's just not refreshing the screen. I get the "Complete" at the end also.
Anyone have any ideas?
You're performing a lengthy operation on the UI thread. You should move it to a background thread (via BackgroundWorker for instance) so the UI thread can do things like repaint the screen when needed. You can cheat and execute Application.DoEvents, but I'd really recommend against it.
This question and answer are basically what you're asking:
Form Not Responding when any other operation performed in C#
The Label doesn't re-paint until you give the UI thread back to the message loop. Try Label.Refresh, or better yet, try putting your lengthy operation in a background thread as other posters have suggested.
This operation is executed in UI thread. UI won't update until it's finished. To make it update during sending you must perform sending in separate thread and update the label from there
This usually happens when you're doing intensive calculations/iterations in the same thread as where the user interface elements are running. To work around this you're going to need to have a separate thread do the work and from there update the label's value accordingly. I'd post a complete source sample but I'm away from my dev machine at the moment.
Just to add to this answer, I ran into an issue with our splash screen form.
We had code like this:
SplashScreen.Initialize(this, SplashScreenImage);
SplashScreen.Show();
// Db init, log init etc.
... Further in our app ...
Application.Run(new MainWindowForm());
The in Initialize(this, SplashScreenImage); we updated some controls and then we ran refresh on those controls;
public void Initialize(this, SplashScreenImage)
{
...
lblVersion.Text = GetVersionString();
lblEnv.Text = GetEnvironmentString();
// Refresh (does not work)
lblVersion.Refresh()
lblEnv.Refresh()
}
Unfortunately, this does not work. The problem here was that although we call control.Refresh() explicitly, form.show() was called after we called control.refresh. This does not work.
The fix was simple:
SplashScreen.Show(); // First call show
SplashScreen.Initialize(this, SplashScreenImage); // Now contorl.Refresh() works
I know this question is old but I had this same issue. I tried Refresh() and many other things but nothing worked. If I put the text into a Messagebox.show then it worked in the message box but not the form so I knew I had the data. As I had people waiting to use the app I was getting desperate and was just about to do away with the class temporarily to get it working when I thought of trying Invoke. So I tried
Invoke(new Action(() =>
{
lbltxt.Text = text;
}));
For now it works but still not sure if this is a long term fix or just a plaster till I find a better solution.