In C#, how do you lock a form? - c#

I'm trying to lock the main form while a please wait box is shown on the screen, but it won't work. Here's my dilemma.
I have 2 forms. The main form that the user clicks a refresh button to load the list of SQL Servers, and a Please wait form that shows while it's loading the list. The SQL Server thread is a separate thread by default while using C# and it locks out the main thread in order to process the SQL request.
I can add a background worker, but then I can't update my combo box to show the list as its a UI control. If I use a handler for that, my show_dialog() for the please wait box will stop locking down the main form.
How is it even possible to lock this form down without the left click queue being run after the main thread goes active again? I added the code that needs to be executed while the user waits.
public void PullServers()
{
bool ServersFound = false;
foreach (string Value in SQL.LocateSqlInstances())
{
this.cmbServer.Items.Add(Value);
ServersFound = true;
}
if (!ServersFound)
{
this.cmbServer.Items.Add(Strings.Lang("ddServerNoneFound"));
this.cmbServer.SelectedIndex = 0;
}
else
{
if (!s.empty(General.setting("SQLSERVER")))
{
this.cmbServer.Text = General.setting("SQLSERVER");
}
else
{
this.cmbServer.SelectedIndex = 0;
}
}
this.picRefreshServers.Image = Properties.Resources.Refresh;
}
public static Array LocateSqlInstances()
{
using (DataTable sqlSources = System.Data.Sql.SqlDataSourceEnumerator.Instance.GetDataSources())
{
string Servers = null;
foreach (DataRow source in sqlSources.Rows)
{
string instanceName = source["InstanceName"].ToString();
if (!s.empty(instanceName))
{
Servers += source["ServerName"].ToString() + "\\" + instanceName + "[[SERVBREAK]]";
}
}
string[] ServersList = Servers.Split(new string[] { "[[SERVBREAK]]" }, StringSplitOptions.RemoveEmptyEntries);
return ServersList;
}
}

I think you are on the right track with a BackgroundWorker. I have found the following pattern to work well for me.
In your main form, you need to perform the following steps.
Create a BackgroundWorker to perform the long running operation.
Start the BackgroundWorker.
Display the waiting form as a modal dialog.
// Step 1:
BackgroundWorker bg = new BackgroundWorker()
bg.DoWork += new DoWorkEventHandler(bg_DoWork);
bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
// Step 2:
bg.RunWorkerAsync();
// Step 3:
waitingForm = new WaitingForm();
waitingForm.ShowDialog();
As you know, you can't update the UI from the bg_DoWork handler since it does not run on the UI thread. So just get the data you need here and pass it on to the the bg_RunWorkerCompleted handler using the e.Result parameter.
private void bg_DoWork(object sender, DoWorkEventArgs e)
{
Array servers = SQL.LocateSqlInstances();
e.Result = servers;
}
The bg_RunWorkerCompleted runs on the UI thread so it is safe to update your controls here. This is where you should close the waiting form and then update your UI.
private void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Close the Waiting form.
waitingForm.Close();
// Retrieve the result of bg_DoWork().
Array servers = e.Result as Array;
bool ServersFound = false;
foreach (string Value in servers)
{
this.cmbServer.Items.Add(Value);
ServersFound = true;
}
if (!ServersFound)
{
this.cmbServer.Items.Add(Strings.Lang("ddServerNoneFound"));
this.cmbServer.SelectedIndex = 0;
}
else
{
if (!s.empty(General.setting("SQLSERVER")))
{
this.cmbServer.Text = General.setting("SQLSERVER");
}
else
{
this.cmbServer.SelectedIndex = 0;
}
}
this.picRefreshServers.Image = Properties.Resources.Refresh;
}

Related

BackgroundWorker questions on cancellation

I'm trying (yet again) to implement a Backgroundworker in my app so that the UI will be responsive. The user selects files to be processed which end up on a grid. When the processing starts it's like this:
for (int i = 0; i<Grid.Rows.Count; i++)
{
Worker work = new Worker();
//set up data for processing based on the current row in the grid
bw.RunWorkerAsync(work);
addStatusToGrid();
//clean up; indicate work on this file is done
work=null;
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
Worker work = (Worker)e.Argument;
if (work.doTheWork() == false)
{
//indicate failure
}
else
{
//indicate success
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//not sure what to do here
}
BUT what happens is that as soon as bw.RunWorkerAsync( ) is called the app immediately goes to the next line in the for loop and then starts the same process again. Now, I sort of want this but I get an error message, "This backgroundworker is currently busy and cannot run multiple tasks concurrently". If I just process one item, addStatusToGrid() gets called immediately and of course there is no status to add as the work is not done.
SEVERAL QUESTIONS: If I can use this mechanism to initiate several processing sessions at once, that would be a good thing. But how do I prevent addStatusToGrid() getting called immediately?
Right now, the UI does get updated a lot. I have a progress bar and it often shows updates. I cannot cancel the operations with a cancel button I guess because the UI thread is busy.
From your question it appears that you have multiple rows in the grid that need to be processed. Now the background worker executes the method in another thread thus freeing up the UI to perform additional actions. (IE status updates). As this is executed on another thread the UI is free to continue processing as normal. The UI thread will not wait for the BackgroundWorker to finish. If it did wait, there would be no use using the background worker. Additionally the BackgroundWorker must finish the operation before starting another operation (thus your error message).
Now it is hard to understand what you are doing in the work.doTheWork() but I suspect you would want to take another approach here.
Before we begin tackling the question the first thing you must understand is the Cancellation of a background worker doesn't actually cancel the thread but provides a "flag" for your code to check if the BackgroundWorker is pending calculation (MSDN on this flag ).
Moving forward. As the Grid is held on the UI thread you need to understand the data from the grid that we want to capture and pass it into the BackgroundWorker context. As this inst provided I am going to do a very basic example. Now you could run multiple background workers to process each row individually thus spawning 1 worker per row in the Grid. For your requirement this may be ideal but for larger grids you are essentially creating 1 thread per row to process and in a grid with hundreds of rows this could be disastrous.
So to being you can create a method something like below. I have commented the code to help you run through it. Bascially this shows the ability to cancel the worker, report progress and iterate through each row item individually. I also added some basic classes for use inside the worker. [Note this is demo code only]
class DoWorkTester
{
int currentIndex = 0;
GridRow[] rows;
public void ExecuteWorkers()
{
GridRow rowA = new GridRow
{
PropertyA = "abc",
PropertyB = "def"
};
GridRow rowB = new GridRow
{
PropertyA = "123",
PropertyB = "456"
};
GridRow rowC = new GridRow
{
PropertyA = "xyz",
PropertyB = "789"
};
rows = new GridRow[] { rowA, rowB, rowC };
currentIndex = 0;
runWorkers();
}
BackgroundWorker worker;
void runWorkers()
{
//done all rows
if (currentIndex >= rows.Length - 1)
return;
//is the worker busy?
if (worker != null && worker.IsBusy)
{
//TODO: Trying to execute on a running worker.
return;
}
//create a new worker
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.WorkerReportsProgress = true;
worker.ProgressChanged += (o, e) =>
{
//TODO: Update the UI that the progress has changed
};
worker.DoWork += (o, e) =>
{
if (currentIndex >= rows.Length - 1)
{
//indicate done
e.Result = new WorkResult
{
Message = "",
Status = WorkerResultStatus.DONE
};
return;
}
//check if the worker is cancelling
else if (worker.CancellationPending)
{
e.Result = WorkResult.Cancelled;
return;
}
currentIndex++;
//report the progress to the UI thread.
worker.ReportProgress(currentIndex);
//TODO: Execute your logic.
if (worker.CancellationPending)
{
e.Result = WorkResult.Cancelled;
return;
}
e.Result = WorkResult.Completed;
};
worker.RunWorkerCompleted += (o, e) =>
{
var result = e.Result as WorkResult;
if (result == null || result.Status != WorkerResultStatus.DONE)
{
//TODO: Code for cancelled \ failed results
worker.Dispose();
worker = null;
return;
}
//Re-call the run workers thread
runWorkers();
};
worker.RunWorkerAsync(rows[currentIndex]);
}
/// <summary>
/// cancel the worker
/// </summary>
void cancelWorker()
{
//is the worker set to an instance and is it busy?
if (worker != null && worker.IsBusy)
worker.CancelAsync();
}
}
enum WorkerResultStatus
{
DONE,
CANCELLED,
FAILED
}
class WorkResult
{
public string Message { get; set; }
public WorkerResultStatus Status { get; set; }
public static WorkResult Completed
{
get
{
return new WorkResult
{
Status = WorkerResultStatus.DONE,
Message = ""
};
}
}
public static WorkResult Cancelled
{
get
{
return new WorkResult
{
Message = "Cancelled by user",
Status = WorkerResultStatus.CANCELLED
};
}
}
}
class GridRow
{
public string PropertyA { get; set; }
public string PropertyB { get; set; }
}
Now if you wanted to process multiple rows at a time you will have to adapt the code to use some sort of Worker Pooling or pass all the row data to the first background worker thus removing the recursion.
Cheers.

Access any background worker created in runtime

Scenario: I have a DataGrid in my application in which onclikcing each row, the values get populated in the texboxes below. I'll update the values and when clicking save the process is taking more time to complete.
So i have written a backgroundworker to make the process run asynchronously. When each row is clikced an instance of a backgroundworker is created and the process is accomplished. During that update the user will select the second row of the grid and update that values. So this will create another instance and the process will run in background.
Now when both the update process is running if the user selects the first row of the grid there should be a message showing "The process is still running".
//Code:
' OnClick of the event
var bw = new BackgroundWorker();
bw.WorkerReportsProgress = true;
bw.DoWork += delegate {
SaveDetails();
};
bw.RunWorkerCompleted += delegate {
MessageBox.Show("Completed");
};
bw.RunWorkerAsync();
'Save method
public void SaveDetails()
{
for (int i = 0; i < 10;i++ )
{
System.Threading.Thread.Sleep(5000);
MessageBox.Show("Hi");
}
}
How can i access the previously created backgroundworker instance and check the status of the process and display the messgage?
Note: There may be many process running simultaneously, so i should be able to access any process.
Is this possible?
You can store any BackgrounWorker you create in a List to refer to them at any moment, but alternatively you can create a tasks Queue and store there any pending process, so you have only one BackgroundWorker at any time.
Storing the BackgroundWorkers in a List
Create a List(Of BackgroundWorker) which can be accesed anywhere you need in your code, for example. Every time you create a new BackgroundWorker, add it to the List:
mylist.Add(bw)
You a lot of options to access the correct backgroundworker later on. The easiest one is to create your own class which will have an identificator (the row of the DataGrid, for example) and the backgroundworker. This way, your list will be of this class instead of BackgroundWorkers:
myClass.BackgroundWorkerProperty = bw
myClass.id = myId
myList.Add(myClass)
Using a Queue to run the tasks
Create a Queue with a type which has the information to run the task. For example, the row of the DataGrid, if that's enought, that will be type Integer (the index), then add it everytime the backgroundworker is running:
myQueue.Add(myRow)
Everytime the backgroundworker finish, check the Queue and run the next task stored.
You can use the Tag property of the DataGridViewRow:
var bw = new BackgroundWorker();
row.Tag = bw;
So you can access it.
If you are using .NET 4.5 i would suggest to use Async/Await rather than Background thread to solve your problem, because its programming style is easy and efficient as given below:
public partial class Form1 : Form
{
List<Task<bool>> taskList = new List<Task<bool>>();
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
Task<bool> task = Task.Run(() => SaveDetails());
MessageBox.Show(task.Id + " started.");
taskList.Add(task);
var isSuccess = await task;
}
public bool SaveDetails()
{
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(5000);
//MessageBox.Show("Finishing.");
}
return true;
}
private void button2_Click(object sender, EventArgs e)
{
foreach (var task in taskList)
{
if (task.IsCompleted == true)
MessageBox.Show(task.Id + " Completed.");
}
}
}
i have created a second button click event to access and display all currently completed tasks.
Also, please make a note that you can use task.Id as the row identifier of your grid row.
Hope that helps.
well, that's rather easy. put the BackgroundWorker as a member in your form and then you can access it from everywhere:
BackgroundWorker bg;
public Form1()
{
bg = new BackgroundWorker();
}
private void button1_Click(object sender, EventArgs e)
{
if(bg.IsBusy)
{
// show the message
}
else
{
bw.WorkerReportsProgress = true;
bw.DoWork += delegate {
SaveDetails();
};
bw.RunWorkerCompleted += delegate {
MessageBox.Show("Completed");
};
bw.RunWorkerAsync();
}
}

WPF Control Hosted in WinForm Blocks WinForm controls from loaded even with thread

I have WPF Control hosted in winform which have Menu and some labels.
the WPF controls connect to internet to download some data.
I divide the code into two steps
first just set the control properties
second connect to net
the second runs inside thread,
but Winform controls doesn't take place on the form until the WPF control finish his two steps.
I have tried many approach to make it thread-able
but all ways goes to same destination.
CODE
1- Load WPF Control
private void MDI_Load(object sender, EventArgs e)
{
MenuManager.FillMenu(MainMenu); // I have filled WinForm Menu first, but it doesn't appear until WPF finish
#region = WPF Control =
wpfManager.AddweatherControl();
wpfManager.weatherManager.Start(); // This have to run in another thread
#endregion
}
2- wpfManager.weatherManager.Start
public void Start()
{
//var tsk = System.Threading.Tasks.Task.Factory.StartNew(GetWeather);
//tsk.ContinueWith(t => { MessageBox.Show(t.Exception.InnerException.Message); },
// System.Threading.CancellationToken.None, System.Threading.Tasks.TaskContinuationOptions.OnlyOnFaulted,
// System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext());
//System.Threading.Thread t = new System.Threading.Thread(
// () => weatherControl.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(GetWeather))
// );
//t.SetApartmentState(System.Threading.ApartmentState.STA);
//t.Start();
weatherControl.Dispatcher.BeginInvoke(new Action(GetWeather), new object[] { });
}
void GetWeather()
{
#region = Weather =
Yweather.Getweather(UserManager.CurrentUser.Preferences.WeatherCity);
if (Yweather.Online && Yweather.IDayForecast.Count > 0)
{
weatherControl.CurrentDegree.Text = Yweather.IDayForecast[0].CurrentTemperature.ToString();
weatherControl.WeatherTypeName.Text = Yweather.IDayForecast[1].WeatherText;
weatherControl.AllDayDegree.Text = Yweather.IDayForecast[1].LowTemperature + " - " + Yweather.IDayForecast[1].HighTemperature;
weatherControl.WeatherType.Source = wpfManager.BitmapToImageSource(Yweather.IDayForecast[0].Image);
xWeatherDay weatherday1 = weatherControl.OhterDaysPanel.Children[0] as xWeatherDay;
weatherday1.AllDayDegree.Text = Yweather.IDayForecast[2].LowTemperature + " - " + Yweather.IDayForecast[2].HighTemperature;
weatherday1.WeatherType.Source = wpfManager.BitmapToImageSource(Yweather.IDayForecast[2].Image);
}
else
{
weatherControl.CurrentDegree.Text = "0";
weatherControl.WeatherTypeName.Text = "NAN";
weatherControl.AllDayDegree.Text = "0 - 0";
weatherControl.WeatherType.Source = wpfManager.BitmapToImageSource(Yweather.OfflineImage);
}
#endregion
}
It looks like from the code you posted that the delay is due to running GetWeather on the UI thread. Assuming that weatherControl is an instance of the WPF control, this runs on the UI thread because that is the thread that its dispatcher belongs to.
If you want to run code on a background thread, one easy way to do so is to use a BackgroundWorker. You could use it something like this:
public void Start()
{
var worker = new BackgroundWorker();
worker.DoWork += (sender, args) =>
{
GetWeather();
// put the results of getting the weather in to args.Result
};
worker.RunWorkerCompleted += (sender, args) =>
{
// use args.Result to update the UI
};
worker.RunWorkerAsync();
}
The code in the DoWork event handler runs on a background thread, while the code in the RunWorkerCompleted event handler runs on the UI thread.

Winforms: Update label on Form from another class

I have a project where I need to update a labels text from inside another classes method. It's worth noting that this method is being called from a Background worker thread.
I have tried passing in the text to update as a UserState Obj in the Workers ReportProgress(); method and then updating the label when the workers progress changed event is fired off on the main form. This works, but obviously only updates the labels text when the progress changed event is raised.
I have code that's loading/removing proxies constantly and I need a label to show this as it happens (as opposed to only updating when the bg workers progress changed event fires). Hopefully someone can help.
Thanks
Edit* Here's some code to make the problem a little easier to understand: -
public string Request(string action)
{
if (string.IsNullOrWhiteSpace(action))
{
return "";
}
HttpWebRequest req;
string response = string.Empty;
while (response.Equals(string.Empty) && proxy != null)
{
try
{
req = (HttpWebRequest)WebRequest.Create(action);
req.Proxy = proxy;
response = new StreamReader(req.GetResponse().GetResponseStream()).ReadToEnd();
}
catch (Exception ex)
{
RemoveProxy(proxy);
MessageBox.Show("Proxy Removed: " + proxy.Address.ToString());
proxy = GenNewProx();
MessageBox.Show("New proxy" + proxy.Address.ToString());
}
}
return response;
}
^^^ - Where i need to set the labels text, using Msgboxs at the moment but updating a label on the main form is obviously preferable
foreach (string url in URLs)
{
result.URL = url;
result.Shares = grabber.GetFacebookShares(url);
result.Tweets = grabber.GetTweetCount(url);
result.PlusOnes = grabber.GetPlusOnes(url);
bgWorker.ReportProgress((outputGridView.Rows.Count * 100) / importGridView.Rows.Count, result);
}
^^^ - Inside the bg workers do_work method on the main form.
2nd Edit*
I'm a bit new to events but could i not fire off a custom event say Proxy_Changed everytime i switch proxys and pass in a string argument with the new proxy/msg w.e and then subscribe to this event in the main form, then set the label on the main forms text = the string args when this event fires off? I'm probably talking jibberish tbh though :/
Here's what I think the important parts of your class that needs to do the background works looks like:
public class Grabber
{
public event EventHandler<MyArgs> NotifyParentUI;
// other code.....
public string Request(string action)
{
if (string.IsNullOrWhiteSpace(action))
{
return "";
}
HttpWebRequest req;
string response = string.Empty;
while (response.Equals(string.Empty) && proxy != null)
{
try
{
req = (HttpWebRequest)WebRequest.Create(action);
req.Proxy = proxy;
response = new StreamReader(req.GetResponse().GetResponseStream()).ReadToEnd();
}
catch (Exception ex)
{
RemoveProxy(proxy);
NotifyParentUI(this, new MyArgs()
{ Message = string.Format("Proxy Removed: {0}", proxy.Address.ToString()) });
proxy = GenNewProx();
NotifyParentUI(this, new MyArgs()
{ Message = string.Format("New Proxy: {0}", proxy.Address.ToString()) });
}
}
return response;
}
}
In your main form you have a method to update your label that is thread-safe:
public void UpdateMyLabel(object sender, MyArgs ea)
{
this.Invoke(new MethodInvoker(
delegate()
{
labelControl1.Text = ea.Message;
}
));
}
Also in the main form you must create an instance of your "grabber":
Grabber grabber = new Grabber();
grabber.NotifyParentUI += UpdateMyLabel;
You should have a method that runs on its own thread:
public void ThreadProc()
{
// other code before this....
foreach (string url in URLs)
{
result.URL = url;
result.Shares = grabber.GetFacebookShares(url);
Thread.Sleep(0); // may want to take the Sleeps out
result.Tweets = grabber.GetTweetCount(url);
Thread.Sleep(0);
result.PlusOnes = grabber.GetPlusOnes(url);
Thread.Sleep(0);
}
}
Here's how you start the thread in the part of your main form:
Thread t = new Thread(new ThreadStart(ThreadProc));
t.Start();
As a side note, if you need to pass data to your Thread, look here:
Passing Data to Threads and Retrieving Data from Threads
Use Invoke method to run anonymous function on UI thread to update the label. For example:
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += (sender, args) =>
{
for (int i = 0; i < 10000; ++i)
{
DoSomeWorkInBackground();
// Update the label in UI thread
MyOtherFormInstance.Invoke((MethodInvoker)delegate()
{
MyOtherFormInstance.SetLabelText(i);
});
DoSomOtherWorkInBackground();
}
};
In your Form:
public void SetLabelText(int i)
{
myLabel.Text = i.ToString();
// not sure that this needed, but try:
myLabel.Invalidate();
}
It sounds from your question and subsequent comments to other answers that you are running your code within a WinForms project, correct me if I am wrong? In a winform the main program thread is usually always static (static void Main()) therefore you must make your EventHandler static also to avoid null exceptions. I believe this will resolve your issue as it sounds like the rest of your code is correct?

My Custom progressBar Form Hangs when i show it

I made a form that plays a progressbar role here's the code i made
public partial class PXProgressBar : Form
{
public delegate bool CancelEvent();
public event CancelEvent cancel_e;
public Boolean ProcessCancelled
{
get;
set;
}
public PXProgressBar(bool EnableCancel)
{
InitializeComponent();
ProcessCancelled = false;
progressBar1.Minimum = 0;
if (!EnableCancel)
Cancelbtn.Visible = false;
}
public void increament(int step)
{
if (progressBar1.Value < progressBar1.Maximum-1)
{
progressBar1.Value++;
progressBar1.Caption = progressBar1.Value.ToString() + " of " + progressBar1.Maximum;
progressBar1.Refresh();
}
else
{
progressBar1.Value++;
progressBar1.Caption = progressBar1.Value.ToString() + " of " + progressBar1.Maximum;
if (this.TopMost)
this.TopMost = false;
this.Update();
this.Hide();
this.WindowState = FormWindowState.Minimized;
// this.Dispose();
}
}
public void SetMaximum(int MaximumValue)
{
if (MaximumValue <= 0)
{
progressBar1.Maximum = 0;
return;
}
if (progressBar1.Minimum != 0 && MaximumValue < progressBar1.Minimum)
{
progressBar1.Maximum = progressBar1.Minimum;
return;
}
progressBar1.Maximum = MaximumValue;
}
public void SetMinimum(int MinimumValue)
{
progressBar1.Value = 0;
if (MinimumValue <= 0)
{
progressBar1.Minimum = 0;
return;
}
if (progressBar1.Maximum != 100 && MinimumValue > progressBar1.Maximum)
{
progressBar1.Minimum = progressBar1.Maximum;
return;
}
progressBar1.Minimum= MinimumValue;
}
public void SetTitle(string ProcessTitle)
{
this.ProgressTitlelb.Text =ProcessTitle;// ProcessTitle;
//this.ProgressTitlelb.Left = (this.panel1.Width - this.ProgressTitlelb.Width) / 2;
//this.ProgressTitlelb.Top = (this.panel1.Height - this.ProgressTitlelb.Height) / 2;
this.Update();
}
private void Cancelbtn_Click(object sender, EventArgs e)
{
ProcessCancelled = true;
bool disposeRequired =cancel_e();
if(disposeRequired)
this.Dispose();
}
private void PXProgressBar_Shown(object sender, EventArgs e)
{
this.Update();
}
}
and i call the form through this code
if (ProgressBar == null)
ProgressBar = new PXProgressBar(true);
ProgressBar.SetTitle("Saving ...");
ProgressBar.SetMinimum(0);
ProgressBar.SetMaximum(100);
ProgressBar.TopMost = true;
ProgressBar.Show();
Application.DoEvents();
regarding that the past few lines are in a unction that is called throught a thread
but when i run it the form hangs so i cant set a Cancel Button in the form to let the user cancel the operation
Your code looks like it should be fine so I can only assume that you are doing a long running operation on the UI thread which would cause the UI to look like its hung. You need to perform long running operations on a background thread so that the UI thread remains responsive enough to respond to button clicks etc. There are many many articles about this if you consult your friend Google.
More info on that here http://www.idevforfun.com/index.php/2010/01/10/windows-ui-threading/
I agree with Skizz on the DoEvents call ... there's only a very few rare cases where that call is needed and mostly its in the framework itself that it gets used.
You need to make sure the GUI elements are created on the main form thread and not from a separate thread. So, you need to get you thread that is doing the work to get the main form thread to display and update the progress bar. This is going to take a bit of refactoring.
So, in your worker thread:
void DoWork () // of whatever it's called
{
main_form.CreateProgressBar ();
while (doing stuff)
{
main_form.IncrementProgressBar ();
do stuff
}
main_form.DestroyProgressBar ();
}
And in the main form:
delegate void Callback ();
void CreateProgressBar ()
{
if (InvokeRequired)
{
Invoke (new Callback (CreateProgressBar));
}
else
{
progress_bar = CreateProgressBar ();
}
}
void IncrementProgressBar ()
{
if (InvokeRequired)
{
Invoke (new Callback (IncrementProgressBar ));
}
else
{
progress_bar.IncrementProgressBar ();
}
}
void DestroyProgressBar ()
{
if (InvokeRequired)
{
Invoke (new Callback (DestroyProgressBar));
}
else
{
progress_bar.Close ();
progress_bar = null;
}
}
The InvokeRequired determines if the calling thread is the same as the GUI thread. If the calling thread is not the GUI thread, the Invoke is used to changed thread context. This is the synchronous version and won't complete until the invoked method is finished. There is an asynchronous version called BeginInvoke but this isn't really needed for what your doing.
The problem might be the DoEvents method call. From this MSDN page:
Calling this method causes the current thread to be suspended while
all waiting window messages are processed. If a message causes an
event to be triggered, then other areas of your application code may
execute. This can cause your application to exhibit unexpected
behaviors that are difficult to debug. If you perform operations or
computations that take a long time, it is often preferable to perform
those operations on a new thread. For more information about
asynchronous programming, see Asynchronous Programming Overview.
I don't think the DoEvents call is necessary. If you need to halt the code after the Show for the operation to complete, then use a System.Threading.EventWaitHandle instead.
There's some link maybe helpful for you about progressbar:
How do I implement a progress bar in C#?
Hope this help.
Just create other thread for progressbar and use it in background

Categories

Resources