Reputation: 3
I already tried google to find an answer to my problem but haven't found a solution.
I working with C# and WinForms. I created a panel and added a label to it. This panel is set to myPanel.Visible = false
at first. I want to set it myPanel.Visible = true
when I click a button. The button is calling a function. During the function call I want to show a progessbar in the panel, so I set this myPanel.Visible = true
and at the end of the function I set it back to myPanel.Visible = false
.
The problem is, that the label isn't visible.
When I don't set myPanel.Visible = false
at the end of the function, the label is visible, but only at the end of the function.
I also tried to programmatically add the label in the function called, still not working. The second idea I tried was to use this.PerformLayout();
during the call of the function.
It seems like that the application is drawing the label only at the end of the function call, but I need it to be drawn during the function is called.
Thanks for any help.
private void buttonAdd_Click(object sender, EventArgs e)
{
//Adding label to panel
MyLabel label = new MyLabel();
label.Text = "Test";
label.Location = new Point(0, 0);
progressPanel.Controls.Add(label);
//Showing progressPanel
progressPanel.Visible = true;
progressBar1.Minimum = 1;
progressBar1.Value = 1;
progressBar1.Step = 1;
//Some Code
progressPanel.Visible = false;
}
Upvotes: 0
Views: 4042
Reputation: 43916
Your problem is obviously that you are performing your task in the UI thread. So the UI itself doesn't get repainted while this task is working. And when you finished, it's still invisible.
Try using a BackgroundWorker
instead. Use its ProgressChanged
event to update your progress bar.
If you show some code, I can go into details of implementation.
UPDATE:
I actually prefer sstan's answer, but I promised to show the BackgroundWorker
way. It may still be helpful if for some reason you cannot use async/await
:
public partial class Form1 : Form
{
private BackgroundWorker _backgroundWorker;
public Form1()
{
InitializeComponent();
_backgroundWorker.DoWork += DoWork;
_backgroundWorker.RunWorkerCompleted += WorkerCompleted;
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.ProgressChanged += WorkerProgressed;
}
private void buttonAdd_Click(object sender, EventArgs e)
{
//Adding label to panel
MyLabel label = new MyLabel();
label.Text = "Test";
label.Location = new Point(0, 0);
progressPanel.Controls.Add(label);
//Showing progressPanel
progressPanel.Visible = true;
progressBar1.Minimum = 0;
progressBar1.Maximum = 100;
progressBar1.Value = 0;
progressBar1.Step = 1;
// to avoid multiple starts
buttonAdd.Enabled = false;
// start working
_backgroundWorker.RunWorkerAsync();
}
private void DoWork(object sender, DoWorkEventArgs e)
{
_backgroundWorker.ReportProgress(1);
// work
_backgroundWorker.ReportProgress(50);
// more work
_backgroundWorker.ReportProgress(100);
}
private void WorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressPanel.Visible = false;
buttonAdd.Enabled = true;
}
private void WorkerProgress(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
}
So as you see, BackgroundWorker
s are fairly easy to use, but async/await
is even easier since you don't have to write so much code just for parallelization.
Upvotes: 2
Reputation: 36523
The behavior you see is normal. The changes in visibility only take effect when the UI thread gets a chance to repaint your window. But, while you function is running, the UI thread is busy, so it can't repaint your window. The UI thread only frees up at the end of your function call, so that's when the component's visibility changes take effect.
What you need to do is to perform your function's work on a different thread so that the UI thread is free to repaint your window while the function is still running.
One way to do this, is by using Task.Run() combined with the async/await keywords. Here is a basic example of how this could look like, using the code you posted:
async private void buttonAdd_Click(object sender, EventArgs e)
{
// ....
//Showing progressPanel
progressPanel.Visible = true;
progressBar1.Minimum = 1;
progressBar1.Value = 1;
progressBar1.Step = 1;
// this work will happen on separate thread,
// so the UI thread will be free to update the panel visibility
Progress<int> progress = new Progress<int>(percentage => progressBar1.Value = percentage);
await Task.Run(() => this.WorkToBePerformedOnSeparateThread(progress));
progressPanel.Visible = false;
}
private void WorkToBePerformedOnSeparateThread(IProgress<int> progress)
{
// do work...
progress.Report(25); // Report 25% completed...
// do more work
progress.Report(50); // Report 50% completed...
// more work
progress.Report(75); // Report 75% completed...
// etc...
}
As pointed out by Rene in the comments, just remember that you can only do UI work on the UI thread. So, in the example above, you'll notice that the progress reporting is performed through the Progress<T> class which allows you to change the progress bar value (UI work) from the separate thread, because it takes care of ensuring that the progress reporting happens on the UI thread.
Upvotes: 1