WPF TabControl tab changes not refreshed - c#

I have a WPF TabControl with two TabItems. I am trying to change the selected tab on code behind on a Button click event and execute some other code. In this example:
private void Button_Click(object sender, RoutedEventArgs e)
{
ConvertDataTabControl.SelectedIndex = 1;
System.Threading.Thread.Sleep(2000);
...
}
I would expect the UI to refresh and move from Tab 0 to Tab 1 and only then execute the Sleep method, but the UI is refreshed only after Button_Click finishes execution. I tried calling InvalidateVisual, but it does not work.
Is there a way to force the UI to refresh before executing Sleep?

Your code runs on the UI thread by default, so nothing else can be executed on the UI thread (such as updating the layout) until the thread finishes executing.
There are many ways of releasing control of the UI thread before the code finishes executing, but I find the simplest is to use a Task from the Task Parallel Library which can be used to run code on a separate thread.
For example,
Task.Factory.StartNew(() =>
{
Thread.Sleep(2000);
// Other code here
});
It should be noted that UI objects can only be modified on the UI thread, so if your "other code here" updates a UI object, you'll probably want to use the Dispatcher to execute code on the UI thread, like this:
Dispatcher.BeginInvoke(() =>
{
// Code to update the UI
});

try
Dispatcher.BeginInvoke(()=>
{
ConvertDataTabControl.SelectedIndex = 1;
});

The problem is you are doing your work (sleep) on the UI thread. You can use a task/backgroundworker/etc to do the work in an other thread and then do set the ui changes back to the UI thread:
private void Button_Click(object sender, RoutedEventArgs e)
{
Dispatcher callback = Dispatcher.CurrentDispatcher;
ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
{
//Do some work
System.Threading.Thread.Sleep(2000);
//callbackk to ui thread to do ui work. You can also use BeginInvoke...
callback.Invoke(new Action(() => {ConvertDataTabControl.SelectedIndex = 1;}));
//Do some more work
System.Threading.Thread.Sleep(2000);
...
}
}
It is just a example to get the idea.

Related

Updating progressbar created on different thread

I am currently trying to update a progress bar that is defined in another window that is created by a different thread.
I need to send progress updates from the main thread in order to have the correct value displayed on progress bar.
Any help is appreciated, thanks in advance. Below is the code:
private void StartLoadingWindow(object sender, RoutedEventArgs e)
{
var t = new Thread(ThreadLoadingWindow);
t.SetApartmentState(ApartmentState.STA); //Mandatory
t.Start();
}
private void ThreadLoadingWindow()
{
var w = new LoadingWindowsControl();
w.Closed += (sender, args) =>
{
//Exit Dispatcher when Window closes
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.SystemIdle);
Dispatcher.Run();
};
w.ShowDialog();
}
Inside the LoadingWindowsControl I have created a simple mvvm progress bar.
The typical way would be to use a Progress<T> object. If this is created on the UI thread it will invoke the ProgressChanged on the UI thread. Attach an event handler to this that updated your progress bar.
It looks however like you are creating multiple UI threads, and this is probably not a good idea. If you want to do something in the background you should use Task.Run, await the result, and update the UI. If you need continuous update you should follow the Progress<T> example. Create an object that you inform of the updates, and let it post the update work to the the UI thread.

C# Progressbar value in foreach()

I have this code:
private void button1_Click(object sender, EventArgs e)
{
var max = 0;
foreach (var address in textBox2.Text.Split(','))
{
max = +1;
}
var maxp = 100 / max;
foreach (var address in textBox2.Text.Split(','))
{
SendMessage(address);
progressBar1.Value = +maxp;
}
}
It calculates how many emails are in the textbox, and then makes a proportion. To each email sent adds the value of progress, the problem is that when I press the button, the progressbar does not move. When all emails are sent the progressbar moves to the end of stroke.
How can I do?
This happens because your loop and the ui are executing on the same thread. While the loop is busy, it can't update the ui
You can use a BackgroundWorker to run the loop on a different thread in the background, then use the BackgroundWorker's ProgressChanged event to update your progressbar. You can learn more about BackgroundWorkers here
Again you are executing code on a thread other than the UI thread. In WinForms you must invoke back to the UI thread and in WPF you must use application dispatcher.
WinForms:
Invoke((MethodInvoker) delegate {
DoSomethingOnUiThread();
});
WPF:
Application.Current.Dispatcher.Invoke(() =>{
DoSomethingOnUiThread();
});
You block the UI thread so it cannot refresh the UI until the processing leaves the method.
See three different solutions here with explicitly used threads, BackgroundWorker and async-await techniques.

Making a text appear under a button for half a second

I want to make a button to perform some action and THEN let user know it was done. I tried making a label ander a button, then pause, then making it disappear.
private void button1_Click(object sender, EventArgs e)
{
// some action
label1.Text = "Done!";
System.Threading.Thread.Sleep(500);
label1.Text = "";
}
But it doesn't work. What is my mistake?
As Grant says in his answer, you're blocking the UI thread. The simplest solution is to spawn a new Task which will do the update for you, thus releasing the UI thread.
The Task can simply use Invoke to push the update back to the UI thread after a Sleep.
In your case, this translates to something like this:
private void button1_Click(object sender, EventArgs e)
{
// some action
label1.Text = "Done!";
new TaskFactory().StartNew(() =>
{
Thread.Sleep(5000);
Invoke((Action)(() => label1.Text = string.Empty));
});
}
The call to Thread.Sleep() freezes the UI thread for a half-second, so that no updates to the UI can happen (including your update to the Label's text).
Here's a couple options:
You could use a BackgroundWorker, which has built-in mechanisms for executing long-running code in a separate thread, and then updating the UI (such as your Label) when it's done.
You could add a Windows.Forms.Timer to your Form, to perform the action and update the Label. Give it an interval of 500 (ms), and it'll wait roughly a half-second before firing.

Time-consuming task in MainForm, progress bar in another form

I have a main UI that doing some time-consuming work. When it is executing, I would like to open a second form with a progress bar (marquee style) to indicate "working on it".
I have seen people putting the time-consuming task in the BackgroundWorker, however, I would like to run in the main UI thread.
The time-consuming task will be executed in MainForm. I would like to reuse the progress bar for various process, so I am writing a second form ProgressBarForm with BackgroundWorker in it, that would start the _mainWork at the same time as showing progress bar, and will stop and close the ProgressBarForm when _mainWork is done.
Because forms are modals, I am thinking of showing ProgressBarForm in the BackgroundWorker in order not to block MainForm.
Please note that I am not running mainForm in BackgroundWorker. My backgroundWorker just show the form and perhaps report a timer.
public partial class ProgressBarFom : UControl
{
public delegate void MainWork();
private MainWork _mainWork;
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//progressBar.Hide();
this.OnClose(sender, e);
//
backgroundWorker.Dispose();
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//show this ProgressBarForm
this.ShowDialog();
//stop backgroundWorker
//calling this.Close() in RunWorkerComplete
if (backgroundWorker.CancellationPending == true)
{
e.Cancel = true;
return;
}
}
public void CallProgressBar(object sender, EventArgs e)
{
//progressBar.Show();
backgroundWorker.RunWorkerAsync();
_mainWork();
if (backgroundWorker.IsBusy)
backgroundWorker.CancelAsync();
}
}
In MainForm, I am passing mainwork and call ExecWithProgressBar
private void ExecWithProgressBar()
{
ProgressBarFom .MainWork mainWork = new ProgressBarFom .MainWork(ProgressBarMainWork);
ProgressBarFom prBar = new ProgressBarFom (mainWork);
prBar.CallProgressBar(null, null);
}
Some problems I encoutered
Inside DoWork, the same modal issue occurs. ShowDialog() will block the thread and therefore I never get to check CancellationPending to close ProgressBarForm.
ProgressBarForm starts later then the mainWork. I thought when I called CallProgressBar, the backgroundWorker should start well before my mainWork.
Is worker.Dispose() necessary in RunWorkerComplete?
Would it be a better choice to run mainWork in Worker? And why? I decided to let the main thread run this to not disturb the normal flow, what in Main thread will remain in Main thread, Progress bar is like an addon. If we bring it to the worker, would we need another thread to for progress bar itself?
Unless you do some very ugly hacks (like running more than one message loop inside your application) you cannot display a dialog if the thread running the main window is busy. All dialogs use the same thread to do the display update stuff in WinForms. In fact, they even must be running on the same thread.
There's one (sometimes acceptable) hack using Application.DoEvents(), but I wouldn't use it either, because it gets you into a lot of problems as well.
So the simple answer is: This doesn't work. Use a background worker to do lengthy processing.

Code in button click c# happens after the function finishes

I have some simple C# code that is triggered on a Button Press. The button press first clears some ListBoxes, then changes the text of a label, and then calls a function.
private void button1_Click(object sender, EventArgs e)
{
listBox1.Items.Clear();
listBox2.Items.Clear();
listBox3.Items.Clear();
listBox4.Items.Clear();
label5.Text = "Getting links...";
process(url);
label5.Text = "Finished";
}
But the Lists are cleared and the label is changed after process() is finished executing. This ruins the purpose as I'm changing the label so that the user is aware that some action is taking place. How can I make the initial label change before the function process() finishes?
If your process method is long-running, it can cause UI freezing and prevent UI from redrawing - that's why you don't see your label text immediate update.
Simpliest way to achieve your goal - is call label5.Refresh() right after label5.Text = "Getting links...";, this will immediately cause invalidation and redrawing of label.
Or even you can call this.Refresh() if more than one control should be updated - this will update whole usercontrol or form owns your controls.
But note - if your process method runs a very long time (more than a 2-3 seconds for example) - you should consider doing it asyncroniously in thread separate from UI. It is considered as "good style" because it will not cause UI freezing.
You can use async keyword. Only when accessing the UI from different thread you have to use the Invoke
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(
() =>
{
this.Invoke((MethodInvoker)delegate
{
listBox1.Items.Clear();
listBox2.Items.Clear();
listBox3.Items.Clear();
listBox4.Items.Clear();
myLabel.Text = "Getting links...";
});
});
this.process(url);
await Task.Run(
() =>
{
this.Invoke((MethodInvoker)delegate { myLabel.Text = "Finished"; });
});
}
What about using a Task to run process(url) in a "thread" separated from GUI?
In this way GUI will stay responsive to the user ang got "refreshed", then, when your task ends, you just have to update GUI label.
When you have ops that must take a bit of time, you should always separate them from GUI. My two cents.
You have 2 options. either use a different thread to run process(url), or add before it Application.DoEvents().

Categories