سلام.
پیرو صحبتهایی که داشتیم (در پیامهای خصوصی)، من کد شما رو بدین شکل تغییر دادم: (البته اینجا میذارم تا اگر افراد دیگری هم همین سوال رو داشتن، پاسخشون رو بگیرن).
private void button1_Click(object sender, EventArgs e)
{
textBox1.Text = "";
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork_1(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 1000; i++)
{
System.Threading.Thread.Sleep(5);
backgroundWorker1.ReportProgress(i * 100 / 1000, "Obj#" + i);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
string result = e.UserState as string;
textBox1.Text += result;
progressBar1.Value = e.ProgressPercentage;
label1.Text = e.ProgressPercentage.ToString();
label1.Refresh();
}
خوب. تغییرات چی بود؟
- Range ای که شما برای ProgressBar تعیین کرده بودید رو من حذف کردم، تا همون 0 تا 100 خودش باقی بمونه. چرا اینکارو کردم؟ چون ReportProgress یک عدد بین 0 تا 100 انتظار بهش بدیم، در صورتیکه کد شما ممکن بود تا عدد 1000 رو هم به این تابع بده!
- تغییر بعدی، حذف کلیه عملیاتی بود که شما روی TextBox و توی تابع DoWork انجام می دادید. شما اجازه ندارید به UI Element ها از درون این تابع دسترسی پیدا کنید. چرا؟ چون DoWork داره تو Context یک Thread دیگه اجرا میشه و به UI Element ها باید حتما از Thread ای که UI رو ایجاد کرده دسترسی پیدا کرد. این مساله در C++ و بقیه زبانها نیز صادقه و ربطی به C# نداره.
- به ReportProgress یک پارامتر دوم اضافه کردم، پارامتری که نیاز دارم توی ProgressChanged بهش دسترسی پیدا کنم. دقت کنید. اینجا تمام اون چیزی هستش که شما برای Marshaling داده ها بین این دو Thread نیاز دارید. من، string ای ساختم که i رو هر سری بهش اضافه میکنم و بعد پیشرفت رو با ReportProgress گزارش میدم. بدین ترتیب، توی متود backgroundWorker1_ProgressChanged، از روی پارامتر دوم، میتونم به این string (یا هر object ای که مایل بودید) دسترسی داشته باشم. چون string تعریف کرده بودم، با کد زیر هم اونو گرفتم:
string result = e.UserState as string;
و توی همین تابع، با خیالی راحت، textBox1.Text رو تغییر میدم. فقط اینجا یک نکته دیگه میمونه! اونم اینکه وقتی با این تغییراتی که گفتم برنامه رو اجرا کنید، متوجه میشید که Label تغییری نکرده و فقط تا شماره خاصی Refresh میشه و بعد روی عدد 100 (یعنی 100%) دوباره Refresh میشه و نمایش داده میشه. این ایراد چی هستش و چطور باید رفعش کرد؟
وقتی شما توی ProgressChanged دارید Label رو تغییر میدید، منظورم با این خط از کد هستش:
label1.Text = e.ProgressPercentage.ToString();
در واقع برنامه داره دستور مزبور رو توی صف پیامهای ویندوز میذاره. تا وقتی این پیامها پردازش نشن، label جدید مشاهده نمیشه... Sleep ای که شما گذاشته اید، Thread رو برای 5 میلی ثانیه متوقف میکنه، حالا 5 میلی ثانیه فرصت داره ویندوز تا توی یک Context Swithing، بره و Label شما رو Refresh کنه. اما قبل از اینکه اینکارو انجام بده، 5 میلی ثانیه طی شده و Context Switching به Worker Thread دوباره باعث میشه تا Main Thread نرسه که کارش رو انجام بده. اگر 5 میلی ثانیه رو بیشتر کنید، مشکل حل میشه، اما اگر همون 5 میلی ثانیه بخواهید نگهدارید، باید بعد تغییر Label اونو به زور Refresh کنید. به بیان دیگه، به Windows بفهمونید که الان باید Label شما رو Invalidate کنه و ...
اون خط از کد که من نوشتم برای Refresh بخاطر این بوده. البته، توصیه میکنم اون 5 میلی ثانیه رو افزایش بدید. قاعدتا با 20 میلی ثانیه، مشکلی مزبور پیش نمیاد و نیازی به Refresh کردن Label بصورت Explicit ندارید.
موفق باشید.