C# - How to update Main UI from a thread in another class - c#

I'm actually learning (the hard way) c# and been fighting for days with a problem :
I'm writing my first c# application with WPF (dotNet 4.0). When I click on a button, a BackgroundWorker thread is used and call a method from an external class, this way my UI don't freeze -> my method run as expected.
Then I tried to update a ListView control from thos external class to get some kind of progress (text) and I miserably failed.
I understand that I need to use a delegate and the dispatcher to update my control.
I tried to use the solution offered here How to update UI from another thread running in another class . (I cannot comment on it because of my low rep) and I miss some parts of the puzzle.
What the YourEventArgs(status) is referring to ? I just don't get the way to fire an event and pass the content back to my UI while my method is running inside the BGW.
So far I have this piece of code (Updated from answer):
namespace AppMain
{
public partial class MainWindow
{
BackgroundWorker AppWorker = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
AppWorker.WorkerSupportsCancellation = true;
AppWorker.DoWork += AppWorker_DoWork;
AppWorker.RunWorkerCompleted += AppWorker_RunWorkerCompleted;
}
private void btnLoad_Click(object sender, RoutedEventArgs e)
{
lstTest.Items.Add("Processing data...");
AppWorker.RunWorkerAsync();
}
public void AppWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
SetXmlData xml = new SetXmlData();
xml.ProgressUpdate += (s, evt) =>
{
Dispatcher.BeginInvoke((Action)(() =>
{
lstTest.Items.Add("this is a test : " + evt.myData); //how to retrieve the myData property from evt ?
}));
};
xml.FlushData();
}
public void AppWorker_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
if (!(e.Cancelled))
{
lstTest.Items.Add("Done");
}
else
{
MessageBox.Show("Cancelled");
}
}
}
}
SetXmlData.cs
namespace AppMain
{
public class SetXmlData
{
public event EventHandler ProgressUpdate;
//update method
public void update(object input)
{
if (ProgressUpdate != null)
ProgressUpdate(this, new YourEventArgs { myData = (string)input });
}
//calculation method
public void FlushData()
{
MessageBox.Show("this is a test !");
update("test");
}
}
public class YourEventArgs : EventArgs
{
public string myData { get; set; }
}
}
Thanks for your help.

You can simply Invoke the ProgressUpdate event from the FlushData() method.
Simply call:
If (ProgressUpdate !=null )
{
ProgressUpdate(this,new YourEventArgs())
}
this is the source instance where the event originated from.
You could just create YourEventArgs by inheriting from EventArgs class.
public class YourEventArgs : EventArgs
{
//Put any property that you want to pass back to UI here.
}
When the event gets raised in the UI:
RaiseEvent.ProgressUpdate += (s, e) =>
{
Dispatcher.BeginInvoke((Action)(() =>
{
lstTest.Items.Add("this is a test : ");
//Add items to your UI control here...
}));
};
e will be of type YourEventArgs.
On a side note, you should never touch UI thread from a diffent thread (like background worker thread in your example). Since your event-handler already does the Dispatcher.BeginInvoke, that's safe.
Also, your ProgressUpdate event should be inside of your class SetXmlData.

try get;set; Example:
Form1:
public partial class Form1 : Form
{
static public string gettext { get; set; }
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Class1.send(); //call to class function
textBox1.Text = gettext; //items.add(gettext)
}
}
Class1:
class Class1
{
static public void send()
{
Form1.gettext = "Marko"; //Set gettext to string "Marko"
}
}

Related

How to add item to a listbox from another class and another thread?

I tried this:
partial class MainForm : Form
{
Logging logging= new Logging();
public MainForm()
{
InitializeComponent();
}
private void add()
{
while (true)
{
logging.add_data_to_listbox("sometext",listBox1);
}
}
private void help_button_Click(object sender, EventArgs e)
{
Thread t = new Thread(add);
}
}
public partial class Logging
{
public void add_data_to_listbox(string data, ListBox listbox)
{
MainForm mnfrm = new MainForm();
mnfrm.Invoke(new MethodInvoker(delegate ()
{
listbox.Items.Add(DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss.fff - ") + data);
}));
}
}
But i got this error message:
Additional information: Invoke or BeginInvoke cannot be called on a control until the window handle has been created.
The MainForm has got the LoggingClass as a property. Now you create the MainForm again inside the Logging?
I think you should create an EventHandler LogMessage in the Logging class and subscribe to this from the MainForm. Afterwards in the method you can handle the effent, like:
Logging:
// event handler
public event EventHandler<EventArgs> LogMessageReceived;
// send log message
public void add_data_to_listbox(string data, ListBox listbox)
{
LogMessageReceived?.Invoke(new EventArgs());
}
MainForm:
public MainForm()
{
InitializeComponent();
logging.LogMessageReceived += OnLogMessageReceived;
}
private void OnLogMessageReceived(object sender, EventArgs eventArgs)
{
// switch thread to STA thread?
Invoke(new MethodInvoker(delegate ()
{
listbox.Items.Add(DateTime.Now.ToString("yyyy/MM/dd hh:mm:ss.fff - ") + data);
}));
}
You still need to create your own custom event arguments.

C# How to raise an event to start timer from a class other than the main form?

In my main Form1, I have
int duration = 5;
public void timer1_Tick(object sender, EventArgs e)
{
duration--;
if (duration == 0)
{
timer1.Stop();
MessageBox.Show("timesup");
}
}
Elsewhere, (specifically the class I use for my UDP listener), I run an event to reference changes to the form
private MyProject.Form1 _form { get; set; }
public UDPListener(TapeReader.Form1 form)
{
_form = form;
}
Then I would try to call it when the incoming data meets my criteria
if (numberSize>paramSize)
{
if (_form.listBox1.InvokeRequired)
{
_form.listBox1.Invoke((MethodInvoker)delegate ()
{
//Below is where I would like the timer to start
_form.timer1.Start();
//This won't work as I need the timer1_Tick from the main. How can I run this from a different class other than the main form?
});
}
}
Like the other components of my form, I can reference it fine with _form but timer1_Tick is a method (void). Is there a way to do this?
Figured a solution. I just used this tutorial to help me https://msdn.microsoft.com/en-us/library/system.windows.forms.timer.tick(v=vs.110).aspx
In my other class, I have:
_form.timer1.Tick += new EventHandler(TimerEventProcessor);
and I reference it to
int duration = 5;
private void TimerEventProcessor(object myObject, EventArgs myEventArgs)
{
duration--;
if(duration==0)
{
MessageBox.Show("timesup");
}
}

Delegate with generic list signature for passing data to another form

I'm quite new in C#, so I'm struggling with this more than two days. I hope that some one can help me out with this one.
Below some simplified code from my application.
I want to pass a List from Form1 to Form2 using delegate and event.
How can I do this? I read tons of explanations about events and delegates, but I still can't figure it out, how this really works.
Form1:
public delegate List<string> ProfileImportEventHandler();
public event ProfileImportEventHandler ProfileImported;
private void btnImport_Click(object sender, EventArgs e)
{
// raise an event
OnProfileImported();
}
protected virtual void OnProfileImported()
{
if (ProfileImported != null) // check if there are subscribers
{
ProfileImported();
}
}
Form2:
public partial class Form2 : Form
{
Form1 frm1;
public Form1()
{
// Constructor logic
frm1.ProfileChanged += new Form1.ProfileImportEventHandler(Form1_OnProfileImported);
}
}
List<string> Form1_OnProfileImported()
{
// TO DO
}
UPDATE
None of the solutions worked so far. Here is what I have already tried:
Form 2
// use generic list for profiles that will be imported from USB-Stick
private List<string> profilePaths = new List<string>();
public delegate void ProfileImportEventHandler(object sender, ProfileImportEventArgs e);
public event ProfileImportEventHandler ProfileImported;
public delegate void ImportButtonClickedEventHandler();
public event ImportButtonClickedEventHandler ButtonImportClicked;
public delegate void HaveDataDelegate(IList<string> data);
public event HaveDataDelegate HaveData;
//....
private void btnImport_Click(object sender, EventArgs e)
{
// do something...
// raise an event
var ea = new ProfileImportEventArgs(profilePaths);
OnProfileImported(ea);
OnButtonImportClicked();
// When there is data:
var copy = HaveData; // Use copy to avoid race conditions
if (copy != null)
{
copy(profilePaths);
}
// close form
this.Dispose();
}
protected virtual void OnProfileImported(ProfileImportEventArgs ea)
{
if (ProfileImported != null) // check if there are any subscribers
{
ProfileImported(this, ea);
}
}
protected virtual void OnButtonImportClicked()
{
if (ButtonImportClicked != null)
{
// fire event
ButtonImportClicked();
}
}
Form 1
public partial class frm_1 : Form
{
// child form
frm_2 frm2;
public frm_1()
{
InitializeComponent();
// do something...
// not sure if this is correct code and the correct place for it
frm2 = new frm_2();
frm2.ProfileImported += new frm_2.ProfileImportEventHandler(frm2_OnProfileImported);
//frm2.ProfileImported += frm2_OnProfileImported;
frm2.ButtonImportClicked += new frm_2.ImportButtonClickedEventHandler(frm2_ButtonImportClicked);
// In creation/init:
frm2.HaveData += DataFromForm2;
}
void frm2_OnProfileImported(object sender, ProfileImportEventArgs e)
{
// do something
}
void frm2_ButtonImportClicked()
{
// do something
}
private void DataFromForm2(IList<string> data)
{
// Process the data from Form2.
}
}
What am I still missing? Thank you for your help.
frm1.ProfileChanged += new Form1.ProfileImportEventHandler(Form1_OnProfileImported);
[…]
List<string> frmLoadProfileUSB_OnProfileImported()
First those names do not match. Second, with matching signatures you do not need (since C#2 if I recall correctly) to explicitly create the delegate. Thus:
frm1.ProfileChanged += frmLoadProfileUSB_OnProfileImported;
However, I think you have the event in the wrong place. It appears it is Form2 trying to pass data to Form1. Thus the event needs to be on Form2, with a delegate that is passed the data. Thus:
In Form2
public delegate void HaveDataDelegate(IList<string> data);
public event HaveDataDelegate HaveData;
// When there is data:
var copy = HaveData; // Use copy to avoid race conditions
if (copy != null) {
copy(data);
}
In Form1
// In creation/init:
Form2Instance.HaveData += DataFromForm2;
private void DataFromForm2(IList<string> data) {
// Process the data from Form2.
}
It's better not to use strong coupling.
So best solution here would be to store data in database or create proxy-object (class/struct).
like:
public (static) class ProfileChangesMonitor
{
...your logic here
}
If you want to use event handlers, you should follow the general pattern, defining a class that inherits EventArgs (supposing you want to involve a list in the event) in this way:
// Event Args
public class ProfileImportEventArgs : EventArgs {
private IList<string> list;
public ProfileImportEventArgs(IList<string> list) {
this.list = list;
}
public IList<string> List {
get {
return this.list;
}
}
}
// Event Handler Delegate
public delegate void ProfileImportEventHandler(object sender, ProfileImportEventArgs e);
// Form1:
public event ProfileImportEventHandler ProfileImported;
// ...
private void btnImport_Click(object sender, EventArgs e)
{
// raise an event
List<string> list = new List();
// Add something to list if needed
var ea = new ProfileImportEventArgs(list);
OnProfileImported(ea);
// Use ea.list here if necessary
}
protected virtual void OnProfileImported(ProfileImportEventArgs ea)
{
if (ProfileImported != null) { // check if there are subscribers
ProfileImported(this, ea);
}
}
// Form2:
public partial class Form2 : Form
{
Form1 frm1;
public Form1()
{
// Constructor logic
// TODO: Instantiate frm1 first.
frm1.ProfileImported += new Form1.ProfileImportEventHandler(Form1_OnProfileImported);
}
}
private void frmLoadProfileUSB_OnProfileImported(object sender, ProfileImportEventArgs e)
{
// Use and/or modify e.List if needed
}

How to pass a single event from thread up to GUI form thread?

I am new to C# and am having trouble figuring out how to pass an event from a thread up to the GUI form thread. Any help would be appreciated. All of the examples I find are WAY too complicated. I just want to start with one event from the treat up to the GUI and have the GUI do something (right now, anything).
namespace testEvents
{
public delegate void StuffHappenedDel( MessageEventArgs e);
public partial class Form1 : Form
{
workerThread thread;
int j = 0;
public Form1()
{
InitializeComponent();
thread = new workerThread();
thread.Start();
}
private void button1_Click(object sender, EventArgs e)
{
thread.Stop();
}
private void StuffHappenedDel(Object seder, EventArgs e)
{
j++;
}
}
public class workerThread
{
Thread worker;
private bool _quit = false;
/* I don't think this next line is correct*/
public event StuffHappenedDel StuffHappened;
protected virtual void OnStuffHappened(MessageEventArgs e)
{
if (StuffHappened != null)
StuffHappened( e);
}
public void Start()
{
ThreadStart start = new ThreadStart(Run);
worker = new Thread(start);
worker.Start();
}
private void Run()
{
int i = 0;
while (!_quit)
{
Thread.Sleep(1000);
i++;
OnStuffHappened(new MessageEventArgs(false, "it worked!"));
Console.WriteLine(string.Format("Slept {0} seconds.",i));
}
Console.WriteLine("Thread exiting");
}
}
public class MessageEventArgs : EventArgs
{
public MessageEventArgs(bool Error, string message)
{
IsError = Error;
Message = message;
}
public bool IsError { get; set; }
public string Message { get; set; }
}
}
You need to register Form1 as a listener for the event. First, add a method like the following to Form1:
private void thread_SuffHappened(MessageEventArgs e)
{
MessageBox.Show("Stuff happened!");
}
And in Form1's constructor, register as a listener like so:
public Form1()
{
InitializeComponent();
thread = new workerThread();
thread.StuffHappened += new StuffHappenedDel(thread_StuffHappened);
thread.Start();
}
Do you have to use this custom threading system, or are you able to use BackgroundWorkers? BackgroundWorkers haven an event ProgressChanged which fires on the thread that created the BackgroundWorker.
Alternatively, if you attach a handler to a background event from the UI thread, the work is still done on the background thread.
thread.StuffHappenedDel += new EventHandler<MessageEventArgs>(StuffHappenedDel);
Therefore, you need to marshall the data to the UI thread. One way is by using BeingInvoke.
private void StuffHappenedDel(object sender, MessageEventArgs e)
{
this.myControl.BeginInvoke( new Action(
() =>
{
//UI thread work (likely anything that affects UI. Heavy
//processing can continue on the bg thread outside this code block
}));
}
Also, you can use if (myControl.InvokeRequired) to check if you need to marshall data when changing a particular control.
if (this.InvokeRequired)
{
this.Invoke((Action)(() =>
{
//UI thread stuff
}
));
}
Edit to clarify
Your thread object that you've created needs to attach an event handler to the StuffHappenedDel event. To do this, you use something like this
thread.StuffHappenedDel += new EventHandler<MessageEventArgs>(StuffHappenedDel);
before you call thread.Start(). Now, this handler is called
private void StuffHappenedDel(Object seder, MessageEventArgs e)
{
j++;
}
whenever your event is fired.
If you want to make changes to any UI elements, you need to use the Invoke method described above.
Look into the Background Worker Class. Also, you can always fire an event that is handled by your GUI Class (though not on the GUI Thread) and then call Invoke

Delegate doesn't notify the method

Would you look at my code and tell me where I went wrong? in following code I am trying to send a notification to myMethod() method when Form1 gets maximized.
Thanks!
namespace WindowsDelegate1
{
public delegate void ChangedEventHandler(object sender, EventArgs e);
class myForm : Form
{
public event ChangedEventHandler Changed;
protected virtual void OnChanged(EventArgs e)
{
if (Changed != null)
Changed(this,e);
}
public override System.Drawing.Size MaximumSize
{
//get
//{
// return base.MaximumSize;
//}
set
{
base.MaximumSize = value;
OnChanged(EventArgs.Empty);
}
}
}
}
namespace WindowsDelegate1
{
class EventListener
{
private myForm TheForm;
public EventListener(myForm theform)
{
TheForm = theform;
TheForm.Changed += new ChangedEventHandler(myMethod);
}
private void myMethod(object sender, EventArgs e)
{
MessageBox.Show("hey, window should be maximized now!");
}
public void Detach()
{
TheForm.Changed -= new ChangedEventHandler(myMethod);
TheForm = null;
}
}
}
Here is the testing unit / or main()
namespace WindowsDelegate1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
myForm f = new myForm();
EventListener listener = new EventListener(f);
f.ShowDialog();
f.WindowState = FormWindowState.Maximized;
listener.Detach();
}
}
}
What's probably happening is the event is either fired after your .Detach() call, or is never fired at all. I would start by removing the listener.Detach() call. Generally, you attach to events when the form is created or when it loads and detach when it is unloading.
Other than that, your Detach method is problematic because it tries to remove a different ChangedEventHandler instance than the one added. If you're wrapping your methods in ChangedEventHandler you need to store the instance you added.
Thank you for sharing your ideas!
I fixed it by removing the property (not idea why I used that!!) and using method instead by:
protected override void OnActivated(EventArgs e)
{
base.OnActivated(e);
OnChanged(EventArgs.Empty);
}
I have updated my source code above too

Categories

Resources