GUI Invoke with EventHandlers - c#

I have a class like that "ClientSocket.cs"
class ClientSocket {
public delegate void ConnectHandler(object sender, EventArgs e);
public event ConnectHandler ConnectEvent = delegate { };
protected void OnConnectEvent(EventArgs e) {
ConnectHandler ev = ConnectEvent;
ev(this, e);
}
}
And another class "myForm.cs"
public partial class myForm : Form {
private ClientSocket client;
private void button1_Click(object sender, EventArgs e) {
client = new ClientSocket();
client.ConnectEvent += myForm_OnConnectEvent;
client.connect();
}
// Handler for ConnectEvent
private void myForm_OnConnectEvent(object sender, EventArgs e) {
//this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); });
writeLog("Connected");
}
// Function that write a log string to a TextBox
public writeLog(string log) {
guiTextBox.AppendText(log);
}
}
Here the question.
I try to call writeLog with BeginInvoke or calling it directly. Sometimes I get an InvalidOperationException when writing to guiTextBox.
I don't understand why I receive that message. The event is fired by ClientSocket object, but the event handler is relative to the main UI-thread (myForm).
Can I avoid to use BeginInvoke/Invoke for each EventHandler of my class?
EDIT: I understand what's the difference, now I'm try to understand the best practice for calling the event.
Should I put the BeginInvoke/Invoke method when RAISING the event in the BASE class (ClientSocket in that case)
protected void OnConnectEvent(EventArgs e) {
ConnectHandler ev = ConnectEvent;
this.BeginInvoke((MethodInvoker)delegate { ev(this, e);});
}
or should I put that WHEN I'm using that object and add a listeners to that handler
// Handler for ConnectEvent used in GUI (myForm)
private void myForm_OnConnectEvent(object sender, EventArgs e) {
this.BeginInvoke((MethodInvoker)delegate { writeLog("Connected"); });
}
Cheers

the this.BeginInvoke inside ClientSocket does not exist. To be able to do the BeginInvoke it must be called on a object that has that method (your form in your case).
If you wanted the invoking to happen inside your ClientSocket class you would need to pass in a Control that has the BeginInvoke function.
However if I where writing this I would not do this approach. It adds a unnecessary requirement to ClientSocket that you must have a Control passed in (this is called Tightly Coupling and you should try to avoid it in your programming). Personally I would let the event pass along in whatever thread it wants to be raised in and let the consumer worry about doing any special invoking (if they even need to at all).
Here is how I would write myForm_OnConnectEvent, this pattern checks to see if we need to invoke and if we do it calls the function again with the same arguments but this time on the UI thread.
// Handler for ConnectEvent used in GUI (myForm)
private void myForm_OnConnectEvent(object sender, EventArgs e)
{
if(this.InvokeRequired)
{
this.BeginInvoke(new ConnectHandler(myForm_OnConnectEvent), new object[] {sender, e});
return;
}
writeLog("Connected");
}
As a side note, I don't know what writeLog is doing (it should have a capital W by the way) but if it is not interacting with the UI you don't need to do any invoking at all. If it interacts with a TextBox or something else on the UI, that is where I would do my invoking.
private void myForm_OnConnectEvent(object sender, EventArgs e)
{
writeLog("Connected");
}
private void writeLog(string logMessage)
{
if(logTextBox.InvokeRequired)
{
logTextBox.BeginInvoke(new Action<string>(writeLog), logMessage);
return;
}
var logLine = String.Format("{0:g}: {1}{2}", DateTime.Now, logMessage, Enviorment.NewLine);
logTextBox.AppendText(logLine);
}

The event handler is declared in myForm, but the thread, which executes handler, is defined by the logic of ClientSocket class. If this will be background thread, event handler will be raised from background thread, so, you'll need BeginInvoke to avoid cross-thread access to controls.
In other words: belonging of any method of any type isn't related to the thread, which will ever execute this method. These things (types and threads) are parallel universes.
By the way, you can replace this:
public delegate void ConnectHandler(object sender, EventArgs e);
public event ConnectHandler ConnectEvent = delegate { };
with this:
public event EventHandler ConnectEvent;
There's no need to make yet another delegate type.

Related

Is it possible to call this event handler in this case? [duplicate]

This question already has answers here:
WinForms: How to programmatically fire an event handler?
(4 answers)
Closed 2 years ago.
I am using C# in WinForms to use a trackbar as follows. At the beginning of the code I define the event handler:
this.trackBar1.Scroll += new System.EventHandler(this.trackBar1_Scroll);
And here is the implementation when one scrolls the trackbar:
private void trackBar1_Scroll(object sender, EventArgs e)
{
//do something...
}
So this works, but I need to call the above function from inside another event handler such as:
public void numericUpDown1_TextChanged(object sender, EventArgs e)
{
//what to do here to call trackBar1_Scroll ?
}
What could be done to call trackBar1_Scroll from inside numericUpDown1_TextChanged?
Very often you do not need the sender and e parameters. Therefore just create a parameterless method
private void DoSomething() // Hopefully with a better name
{
// do things ...
}
and then call it inside your event handlers.
private void trackBar1_Scroll(object sender, EventArgs e)
{
DoSomething();
}
public void numericUpDown1_TextChanged(object sender, EventArgs e)
{
DoSomething();
}
If you give the method a descriptive name, your code becomes easier to read. But of course you could as well just call the other event handler. The event handler is just a method after all
public void numericUpDown1_TextChanged(object sender, EventArgs e)
{
trackBar1_ValueChanged(sender, e);
}
Since both of these event handlers have the same signature (the same parameter list and return type), you could declare a single one and attach the same to both controls:
void HandleUpdates(object sender, EventArgs e)
{
// do things...
}
And assign it with
trackBar1.Scroll += HandleUpdates;
numericUpDown1.TextChanged += HandleUpdates;
You can also assign it in the properties window on the events tab. new System.EventHandler(...) is not necessary. C# does it automatically for you.

Generic method to register/unregister event handler

I try to figure out a way to keep my event registration and un-registration in sync from my initialization and cleanup.
What I want is to be able to call a generic method to register or unregister an event and only pass a Boolean to give the operation.
I don't want to use with Window, but that was the for an easy sample.
class EventSample
{
private Window myWindow;
public EventSample(Window window)
{
myWindow = window;
InitEvent(true);
}
~EventSample()
{
InitEvent(false);
}
private void InitEvent(bool register)
{
// I want a generic similar to that
RegisterEvent(register, myWindow.Activated, MyWindow_Activated);
RegisterEvent(register,myWindow.Closed , MyWindow_Closed);
RegisterEvent(register, myWindow.Closing ,MyWindow_Closing);
}
private void MyWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
}
private void MyWindow_Closed(object sender, EventArgs e)
{
}
private void MyWindow_Activated(object sender, EventArgs e)
{
}
}
I have faced similar problems with cleanup methods. I have solved this by keeping a list of actions to execute when disposing the window or control.
Something like this:
this.RegisterEvent
( () => this.Event += handler
, () => this.Event -= handler
);
The RegisterEvent executes (delayed in my case) the event attachment:
private List<Action> unregisterEvents = new List<Action>();
private void RegisterEvent(Action registerAction, Action unregisterAction)
{
registerAction.Invoke();
unregisterEvents.Add(unregisterAction);
}
On dispose, just walk over the unregister events:
foreach (Action a in unregisterEvents)
{
a.Invoke();
}

One handler for several events

I have four events:
View.AdditionPerformed += new EventHandler<EventArgs>(OnOperationPerformed);
View.SubtractionPerformed+=new EventHandler<EventArgs>(OnOperationPerformed);
View.DivisionPerformed+=new EventHandler<EventArgs>(OnOperationPerformed);
View.MultiplyPerformed+=new EventHandler<EventArgs>(OnOperationPerformed);
and one method:
private void OnOperationPerformed(object sender, EventArgs e)
{
}
How can I define which event raised my method? Something like this:
private void OnOperationPerformed(object sender, EventArgs e)
{
switch(event)
{
case MultiplyPerformed:{}
case DivisionPerformed:{}
...
}
}
Write your own EventArgs which has an enum inside, telling you the raised event.
enum MyEventEnum
{
AdditionPerformed,
SubtractionPerformed,
DivisionPerformed,
MultiplayPerformed
}
The EventArgs
class MyEventArgs : EventArgs
{
public MyEventEnum EventRaised { get; set; }
}
Define the Handlers
View.AdditionPerformed += new EventHandler<MyEventArgs>(OnOperationPerformed);
View.SubtractionPerformed+=new EventHandler<MyEventArgs>(OnOperationPerformed);
View.DivisionPerformed+=new EventHandler<MyEventArgs>(OnOperationPerformed);
View.MultiplyPerformed+=new EventHandler<MyEventArgs>(OnOperationPerformed);
When you call them:
this.AdditionPerformed(this, new MyEventArgs
{ EventRaised = MyEventEnum.AdditionPerformed };
I know it's pretty hardcoded, but there isn't any other way.
Instead of using EventArgs, you could use your own event argument class to pass in the necessary data to make the choice inside the handler.
It would then become available on your e variable inside the handler.
Cheers

Data Binding on multi thread application?

The following code is the simplified version of my problem:
public partial class Form1 : Form
{
BackgroundWorker bw;
Class myClass = new Class();
public Form1()
{
InitializeComponent();
bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
label1.DataBindings.Add("Text", myClass, "Text", true, DataSourceUpdateMode.Never);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
myClass.Text = "Hi";
}
private void button1_Click(object sender, EventArgs e)
{
bw.RunWorkerAsync();
}
}
public class Class : INotifyPropertyChanged
{
private string _Text;
private void SetText(string value)
{
if (_Text != value)
{
_Text = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string Text
{
get { return _Text; }
set { SetText(value); OnPropertyChanged(new PropertyChangedEventArgs("Text")); }
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, e);
}
}
What happens is that when I click the button1 (which invokes button1_Click) the text in label1 doesn't update. This is because the label1.Text property is internally trying to be changed from my BackgroundWorker's thread, which internally causes an exception. What would it be the best way, in general, to fix this kind of problem? Which part of this code would you change if you must update my Class.Text property from a different thread but must also have a control binded to it?
Try this:
//This is the handler to execute the thread.
void DoWork(object sender, EventArgs a) {
Action updateControl = ()=>myClass.Text = "Blah";
if(myForm.InvokeRequired) {
myForm.Invoke( updateControl);
}
else {updateControl();}
}
this routine executes on the background worker thread.
Fundamentally, you'll have to use either Invoke or BeginInvoke from within your worker thread to perform the actual update. It's illegal to interact directly with a control from within a thread other than the main UI thread (unless it's been written to account for this). With lambda syntax, using Invoke or BeginInvoke is somewhat cleaner than before. For example:
void bw_DoWork(object sender, DoWorkEventArgs e)
{
Invoke(new Action(() => myClass.Text = "Hi"));
}
This will execute the code on the UI thread. However, the BackgroundWorker class (which I'm assuming is what you're using, based on the names here) has a ReportProgress function, which raises the ProgressChanged event on the UI thread. It uses the same mechanism that I described above, but it might be a little cleaner looking. The choice is yours really.
Only the UI thread is allowed to change properties of UI controls. Your BackgroundWorker shouldn't be doing it.
Fortunately BackgroundWorker supports a ProgressChanged event. Your DoWork should raise that, and THAT can update the UI because the backgroundworker will marshal it back to the UI thread for you.

Simple C# event args question

Im using WPF which has a Storyboard class that has a Completed event.
I use it like so:
sb.Completed += AddControlToTaskbar;
private void AddControlToTaskbar(object sender, EventArgs args)
{
//...
}
How to I pass in the EventArgs to my method? Its always null, and I need it to be a custom class
Thanks
Mark
You don't pass the EventArgs to your method, the framework which dispatches the event does that. A common way to handle this is to wrap up your AddControlToTaskbar method in a class which stores the state, e.g.:
sb.Completed += new MyCustomClass(theStateYouNeedToStore).AddControlToTaskbar;
Your constructor stores the state.
class MyCustomClass<T> {
private T state;
public MyCustomClass(T state) {
this.state = state;
}
public void AddControlToTaskbar(object sender, EventArgs args) {
// Do what you need to do, you have access to your state via this.state.
}
}

Categories