Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
Question
Monday, June 11, 2012 3:30 PM
Hi All,
I am using VisualStudio 2010 - Framework 4.0.
In my C# .NET WinForms application, I have a long process running on click of a button.
I need to update the status of operation in the UI. I am working on an existing code so I cannot disturb the existing functionality.
My problem is that the long running method sits in a different project (P1) and the UI in other(P2). Also there is an interface which connects these two which sits in another project (P3).
So my requirement is to update a label in the UI (P2.Label) from P1.Method through P3.Interface.
Is there anyway I can update UI without threading? If threading/background process is the only workaround for this, then please explain it in my case. I am not comfortable with interfaces. What about using INotifyChanged which I am using in WPF?
Hope I explained well.
Thanks
Bian
All replies (24)
Monday, June 11, 2012 4:11 PM ✅Answered
Is there anyway I can update UI without threading? If threading/background process is the only workaround for this, then please explain it in my case. I am not comfortable with interfaces. What about using INotifyChanged which I am using in WPF?
No, at least not in a way that doesn't have other huge problems.
The best way to handle this is via Threading. The BackgroundWorker component can be used to dramatically simplify this, as it automatically marshals the events back into the proper thread for you, so you can update your UI in the progress changed and completion events.
Reed Copsey, Jr. - http://reedcopsey.com
If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
Monday, June 11, 2012 9:02 PM ✅Answered
Backgroundworker is not the only way. You can (as Reed already mensioned), by using Threading (even backgroundworker uses threading, but in its own way).
I can see from your code you use an inteface, and class which comes out of interface) and an event (on that class). What I dont see is where yu subscribe to this event.
Anyway, to use Threading (get all help on the link), you can update some control by using delegates.
--
From your 1st post I can read you do some time consuming work, and you would like to update a Label control on UI thread.
To do so, you can create an additional method, which will execute the code, and show the updates in the label.
Personally I would start a new thread on UI form, and then by creating a new instance of class1, call the methods on that class to do the time consuming code (and in between the code will call back to UI form, to show updates in label).
Will it go?
One more thing that I noticed inside your code:
private string LongOperationMethod() //string return type???
{
//... loop
}
}
Project2.Interface:
public interface IGenerate
{
bool LongOperationMethod(ref string message); //bool return type!!?
string MyDataProperty { get; set; }
}
What would it be? string or boolen return type? What is that anyway? Did you created it as a string return type, to return some string to update label on UI? If so, you dont need that, it can be without any return type, so lets create it void (void means that method returns nothing).
Mitja
Monday, June 11, 2012 5:39 PM
Hi Reed,
Thanks for the reply.
I have used INotifyPropertyChanged to have a try. But still it updates only after the long operation is finished. Here is what I am doing now:
Project1.Class1:
public String MyDataProperty
{
get { return myDataProperty; }
set
{
myDataProperty = value;
OnPropertyChanged("MyDataProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string info)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(info));
}
}
private string LongOperationMethod()
{
........
MyDataProperty = ObjectCount;
//long operation loop starts
}
Project2.Interface:
public interface IGenerate
{
bool LongOperationMethod(ref string message);
string MyDataProperty { get; set; }
}
Project3.UI:
private void Button_Click(object sender, EventArgs e)
{
.......
IGenerate generate = new Project1.Class1();
Label1.DataBindings.Add("Text", generate, "MyDataProperty");
return generate .LongOperationMethod(ref message);
}
If Backgroundworker is the only way then could you please explain how can I use it according to my code above? Since the actual update happens from a different project class, I am not able to access the UI control to update the change.
Appreciate your help.
Thanks
Bian
Monday, June 11, 2012 8:10 PM
Is there anyone who can help me? It is urgent.
Thanks
bian
Monday, June 11, 2012 9:53 PM
Thanks Mitja. I am sorry I missed to add some part of the code. Just because I don't want to add all junk of code here.
There is one more method which calls the LongOperationMethod() which returns a bool.
public bool CallLongOperationMethod(ref string message)
{
message = this.LongOperationMethod();
}
So in the interface:
Project2.Interface:
public interface IGenerate
{
bool CallLongOperationMethod(ref string message); //bool return type!!?
string MyDataProperty { get; set; }
}and UI will beProject3.UI:private void Button_Click(object sender, EventArgs e)
{.......IGenerate generate = new Project1.Class1();Label1.DataBindings.Add("Text", generate, "MyDataProperty");
return generate .CallLongOperationMethod(ref message);
}Hope you are clear now.It would be great if you can explain how to update the UI label control using delegates using a sample code.
Monday, June 11, 2012 10:21 PM
I am not willing (at this hour - it very late here), but I did check it a bit, and thats why I did an example code slightly based on yours.
I hope it will give you a clue how to make it work.
Here is my code:
using System.Threading; //add this namespace too (and others)
namespace Jun11_2
{
public delegate void MessageEventHandler(MessageEventArgs e);
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
label1.Text = "";
}
private void button1_Click(object sender, EventArgs e)
{
Thread t1 = new Thread(new ThreadStart(StartingThread));
t1.Start();
}
void StartingThread()
{
IGenerate generate = new Class1();
generate.OnMessageSending += new MessageEventHandler(generate_OnMessageSending);
generate.LongOperationMethod();
}
private void generate_OnMessageSending(MessageEventArgs e)
{
this.label1.BeginInvoke(new MethodInvoker(() => label1.Text = e.Message));
}
}
public interface IGenerate
{
void LongOperationMethod();
event MessageEventHandler OnMessageSending;
}
public class Class1 : IGenerate
{
public event MessageEventHandler OnMessageSending;
public void LongOperationMethod()
{
if (OnMessageSending != null)
{
MessageEventArgs me = new MessageEventArgs();
for (int i = 0; i <= 100; i++)
{
me.Message = string.Format("Passing number {0}, please wait...", i);
OnMessageSending(me);
//stopping my example code, to run slower
Thread.Sleep(50);
}
me.Message = "Work stopped!";
OnMessageSending(me);
}
else
{
throw new ArgumentException("Event hasn`t been rised, so we cannot continue working.");
}
}
}
public class MessageEventArgs : EventArgs
{
public string Message { get; set; }
}
}
Mitja
Monday, June 11, 2012 10:23 PM
Forgot to tell you: this is a full working code, only add a button1 and label1 on form (and copy paste this code, excluidng button event)!
Take a closer look how I use Threading and rising event each time inisde a loop (I used a simple for loop to perform a time consuming work - you put your own code instead of a loop).
Hope it helps,
bye
Mitja
Tuesday, June 12, 2012 2:48 PM
Thank you so much Mitja.
But I have some issue now. Since my code is scattered around different projects I am getting some errors on MessageEventHandler and MessageEventArgs. May be your code is sitting in a single namespace and that's why it is working fine.
I have added the delegate declaration in the interface project as global like this:
public delegate void MessageEventHandler(MessageEventArgs e);
public interface IPopulateMovie
{
....
}
This is how I solved the error I was getting with MessageEventHandler.
But again I have a reference issue with MessageEventArgs. This class is sitting in the Class1 project. So I am missing the reference in the interface. And I cannot have a reference of Class1 project in my interface project.
Also I had problems with the following thread statements:
Thread t1 = new Thread(new ThreadStart(StartingThread));
t1.Start();
I know this is because I have three parameters for my StartingThread method. So I have used the lamda expression for thread:
Thread t1 = new Thread(() => StartingThread (strPath, strFileName, ref message));
t1.Start();
Could you tell me is this right? Also it will be great if you can solve the reference issues.
Thanks
bian
Tuesday, June 12, 2012 3:53 PM
I am sorry, I hope I have resolved it myself.
Since I have the interface project references in both the UI and Class1 projects, I have added the MessageEventArgs class and the MessageEventHandler delegate declaration in the interface project itself. The code looks like this now:
public class MessageEventArgs : EventArgs
{
public string Message { get; set; }
}
public delegate void MessageEventHandler(MessageEventArgs e);
public interface IPopulateMovie
{
....
}
Please let me know if I am right. I have few more other issues now. I don't want to bug anyone. But if anyone can answer:
Now my issue is that I have a message showing "Finished operation" after the long operation completes. This message is now showing along with the label before it finishes the long operation.
thanks
bian
Tuesday, June 12, 2012 4:05 PM
Yes. You must declare the delegate out of any class. But inside a namespace.
About starting new thread, with parameters, you can pass them in the Start method:
int a = 1;
string b = 2;
Thread t1 = new Thread(new ThreadStart(StartingThread));
t1.Start(a, b);
void StartingThread(object obj)
{
object[] array = (object[])obj;
int a = int.Parse(array[0].ToString());
string b = array[1].ToString();
}
Hope it helps
Mitja
Tuesday, June 12, 2012 6:14 PM
Hi Mitja, you helped me a lot. Thanks.
Since I had more than one parameters the thread was giving errors and from the Threading link you provided before I read that the thread will accept only one argument. It says:
The limitation of ParameterizedThreadStart is that it accepts only one argument. And because it’s of type object, it usually needs to be cast.
So that's why I have used the lamda expression like the following:
Thread t1 = new Thread(() => StartingThread (strPath, strFileName, ref message));
t1.Start();
Is this right? or I need to try your code?
Last few questions if you could help.
1. What if I need to update more than one labels at different levels of long operation?
2. Is there anyway I can have a progress bar along with the labels update (I am updating the operation status)?
3. Should I need to abort the thread at any point of time or it will be killed by itself?
Appreciate your help.
thanks
bian
Tuesday, June 12, 2012 6:24 PM
ups, sorry, really sorry. Forgot to change my code to use ParametrizedThread:
Thread t1 = new Thread(new ParameterizedThreadStart(StartingThread));
--
0. Of course you can use lambda expression if you want. it sure will work.
Personally I dont use it in this occasion (but I use Linq (and Lambda Expression) a lot (maybe way too much :) )).
1. You can create another property to tell the code which control has to be updated, and then inisde "generate_OnMessageSending" method use if,else (or switch) statements to update the one needed.
Example:
1. Lets add a property to class:
public class MessageEventArgs : EventArgs
{
public string Message { get; set; }
pubic string MyControl {get; set;}
}
2. then when trying to pass an event, specify for which control is about:
me.Message = "some message to send";
me.MyControl = "label1"; //or label2 - specify for which control is update
OnMessageSending(me);
Just think of some names, and use them later in switch (as you will see bellow).
3. and last: speciy for which control is incoming update:
private void generate_OnMessageSending(MessageEventArgs e)
{
swith(e.MyControl)
{
case "label1":
this.label1.BeginInvoke(new MethodInvoker(() => label1.Text = e.Message));
break;
case "label2":
this.label2.BeginInvoke(new MethodInvoker(() => label2.Text = e.Message));
break;
}
}
I hope I was clear enough. This way you can simply update more controls at the same time (not exaclty, but your eye will think like so) :)
bye
Mitja
Tuesday, June 12, 2012 6:26 PM
You can even do it shortly:
new Thread(new ParameterizedThreadStart(StartingThread)).Start(param1, param2, param3); //the shortest way :)
Mitja
Tuesday, June 12, 2012 7:21 PM
Mitja you are amazing. Thanks a lot.
Sorry to bug you again but if you don't mind will you answer my 2nd and 3rd questions also?
The 3rd one is the most important one. I just need to know whether the thread will get killed by itself.
Thanks
bian
Tuesday, June 12, 2012 7:51 PM
2. Sure is. You have to set min and max value (min in usually 0, while max is some total number of something - you can still use Marquee progressBar, which will show only the continuos progress (not from 0-100, but it will circle around).).
3. When the work will be done, the tread will terminate by it self. Sure there is possible to terminate prematurely (before end).
You create some boolean flag which will inisde the "time-consuming-work" usiual set to default value - false.
As soon as you wanna terminate the thread (to stop it when you want), you will set this flag to true, and this how the thread will go out of the loop and fihish with this method (and the thread will terminate as well).
Example:
bool bFlag;
void WorkToWo()
{
for(int i = 0; i< 100; i++)
{
//this this loop as you loop for work
if(!bFlag)
break;
}
//reset flag for bext run here:
bFlag = false;
}
void buttonStopWork()
{
bFalg = true;
}
Hope it helps mate.
huu.. this was exhausting :)
Mitja
Tuesday, June 12, 2012 9:16 PM
OH I am sorry. But you really did a good job.
I still have a problem while updating the different controls at the same using the switch case statements.
I have four labels to update - L1, L2, L3 and L4. L1 and L2 will get values only one time. Rest of them are in loop and so L3 and L4 will keep on getting the values.
The issue is that at some point of time the L2's value is overwritten by some other labels value and in the last iteration happens L4's last value is overwritten by L3's value.
No clue how this happens. Every controls are in switch case right. If I debug I can see that all the values are in place. There is something happening while threading gets executed.
Hope you may not answer this time :) will you?
bian
Tuesday, June 12, 2012 9:38 PM
Hi, no worries mate.
Back to your project.
The issue is in the point number 2 (from 3 posts up). Where you specify which label you want to update.
You have to create you code that will that 2nd point will be seperate when to update label1, label2, or label3.
I will do another simple example, so you can understand what I have in mind:
for (int i = 0; i <= 100; i++)
{
if(i % 2 == 0) //show even numbers in label1
{
me.Message = "Even number " + i.ToString();
me.MyControl = "label1";
}
else if(i % 2 != 0) //show odd numbers in label2
{
me.Message = "Odd number " + i.ToString();
me.MyControl = "label2";
}
//stopping my example code, to run slower
Thread.Sleep(50);
OnMessageSending(me);
}
YOu see how I seperate the code when I need to pass data to label1 or label2? You have to do something similar inisde your code.
Mitja
Wednesday, June 13, 2012 6:16 PM
Yes you are right. I have added the Thread.Sleep(50); and it works fine. Thanks you so much. I hope these discussions will help someone else as well.
Wednesday, June 13, 2012 6:41 PM
You are welcome.
btw, for your work, remove Thread.Sleep() method out. I was only uisng it to stop the loop down a bit, so you can see the iteration.
best regards,
bye
Mitja
Wednesday, June 13, 2012 7:19 PM
Actually I don't want a Thread.Sleep(). Because it will slow down the process since it is there for each iteration. But if I remove it then the labels won't show the actual results.
Also is there any way we can stop the operation in the middle by clicking a button? So the command should go from UI through interface and stop at the current loop.
Sorry I am asking again :)
Wednesday, June 13, 2012 7:28 PM
You mean, that you intentionally termiante the thread?
If so, you can create a new boolean flag (true, false), which you set it to false (by default). This flag has to be in the scope where it the work (in loop if you look example bellow).
So when you intend to stop the thread (I call "time-consuming-work"), set this flag to true.
Example:
volatile bool bFlag; //false by default if not set otherwise
void YourMethod()
{
for (int i = 0; i <= 100; i++)
{
if(!bFlag) //since its false it will do the loop
{
if(i % 2 == 0) //show even numbers in label1
{
me.Message = "Even number " + i.ToString();
me.MyControl = "label1";
}
else if(i % 2 != 0) //show odd numbers in label2
{
me.Message = "Odd number " + i.ToString();
me.MyControl = "label2";
}
else
break;
//reset it back to default value for next run:
bFlag = false;
//stopping my example code, to run slower
Thread.Sleep(50);
OnMessageSending(me);
}
}
void ButtonTerminate()
{
//set it here to true:
bFlag = true;
}
Hope it helps,
bye
Mitja
Wednesday, June 13, 2012 7:36 PM
You mean, that you intentionally termiante the thread?
If so, you can create a new boolean flag (true, false), which you set it to false (by default). This flag has to be in the scope where it the work (in loop if you look example bellow).
I would strongly recommend not using this approach. The problem is that the second thread may not see the boolean value, since there's no memory barrier in place. You can work around this by marking it volatile ("volatile bool bFlag;"), but I'd recommend using the tools in the framework instead.
There's an entire set of classes built specifically for cancellation - and I'd recommend using them. In this case, CancellationTokenSource and CancellationToken are meant for cooperative cancellation, and handle this very cleanly. For details, see Cancellation: http://msdn.microsoft.com/en-us/library/dd997364.aspx
Reed Copsey, Jr. - http://reedcopsey.com
If a post answers your question, please click "Mark As Answer" on that post and "Mark as Helpful".
Wednesday, June 13, 2012 9:01 PM
Thx Reed, I completely forgot on volatile in the rush (else I use it).
About your suggestion - I heard of it, but since I use VS2008 I dont have this class available.
But it sure is a way better option (if you have at least VS 2010).
Mitja
Saturday, May 17, 2014 1:29 PM
I m Doing the same function to Show Messages on user screen but i m still facing problem to show messages one by one its working fine but its showing all messages all together please tell me the answer on my mail or intimate me thanks.
public delegate void MessageEventHandler(MessageEventArgs e);
public void generate_OnMessageSending(MessageEventArgs e)
{
lbltxt.Text += e.Message;
panelAdd.Controls.Clear();
panelAdd.Controls.Add(lbltxt);
}
public void WorkThreadFunction()
{
try
{
IGenerate generate = new Class1();
generate.OnMessageSending += new MessageEventHandler(generate_OnMessageSending);
generate.LongOperationMethod();
}
catch (Exception ex)
{
// log errors
}
}
public interface IGenerate
{
void LongOperationMethod();
event MessageEventHandler OnMessageSending;
}
public class Class1 : IGenerate
{
public event MessageEventHandler OnMessageSending;
public void LongOperationMethod()
{
while(true)//checking for every misec
{
MessageEventArgs me = new MessageEventArgs();
foreach (var item in collection)
{
Tuple<string, string, string, string, int> tup = item;
me.Message = "<br/>" + item.Item2 + " " + item.Item3 + " " + item.Item4;
OnMessageSending(me);
}
}
}
}
}
public class MessageEventArgs : EventArgs
{
public string Message { get; set; }
}