Should you unsubscribe from static self events? - c#

I have multiple MacroPanels which are custom UI elements, I wanted that at a given time only a single one could be active (indicating that a macro is loaded to a hotkey), regardless of the current UI window.
internal MacroPanel()
{
InitializeComponent();
MacroPanel.Selected += MacroPanel_Selected;
this.Disposed += MacroPanel_Disposed;
}
private void MacroPanel_Disposed(object? sender, EventArgs e)
{
MacroPanel.Selected -= MacroPanel_Selected;
this.Disposed -= MacroPanel_Disposed;
}
private void MacroPanel_Selected(object? sender, EventArgs e)
{
if (this == sender)
return;
this.BackColor = Color.Yellow;
}
internal void SelectPanel()
{
this.BackColor = Color.Orange;
Selected?.Invoke(this, EventArgs.Empty);
}
I achieved the desired behaviour by having a static event inside the MacroPanel that every Macro panel instance listens to.
I know that subscribing to a static event can cause memory leaks.
The question is should MacroPanel instance unsubscribe from its own static
event after disposal?
Would it cause memory leaks or other issues if
I would not unsubscribe from the Selected event?
Is subscribing to the Disposed event a proper way to achieve this or is ther any other
probably more beneficial way?

Related

Do you have to ubsubscribe from Disposed? [duplicate]

This question already has answers here:
Do I need to unsubscribe events in my Form?
(4 answers)
Unsubscribing from an event
(3 answers)
C# .NET proper event subscribing and un-subscribing
(3 answers)
What best practices for cleaning up event handler references?
(6 answers)
Closed 3 years ago.
I've heard that you are supposed to unsubscribe from events on controls that get disposed.
TextBox textBox;
void Initialize()
{
textBox = new TextBox();
textBox.Click += ClickHandler;
this.Disposed += DisposeControl;
}
void ClickHandler(object sender, EventArgs e)
{
this.foo = bar;
}
void DisposeControl(object sender, EventArgs e)
{
textBox.Click -= ClickHandler;
this.Disposed -= DisposeControl; // DOES THIS LINE MAKE SENSE?
}
There's no reference to the control after it has been disposed so I would assume there is no need to unregister any event handlers. Yet, as I mentioned people are saying that you're supposed to. Does that include unregistering the dispose event itself?
To answer you question "DOES THIS LINE MAKE SENSE?", in your example it does not. That is because the event source and target are both the same. I am going to rewrite your code a bit.
class SOTest1 : Control
{
private bool bar = true;
private bool foo { get; set; }
TextBox textBox;
void Initialize()
{
textBox = new TextBox();
textBox.Click += ClickHandler;
this.Disposed += DisposeControl;
}
void ClickHandler(object sender, EventArgs e)
{
this.foo = bar;
}
void DisposeControl(object sender, EventArgs e)
{
textBox.Click -= ClickHandler;
this.Disposed -= DisposeControl; // DOES THIS LINE MAKE SENSE?
}
}
In this case, since Control implements IDisposable which calls the Disposed event at the end of the disposal, both the object with the event and the event handler are the same object. So, the C# garbage collector is going to see that the object and all references have been disposed and will clean it all up.
Also, since textBox and its event handlers are contained within that object, the textBox.Click -= ClickHandler is also redundant, as it will all be cleaned up by the GC when the SOTest1 object is disposed.
It's not a bad practice to clean up event subscribers, but, it does add code that is unnecessary much of the time. C# does a great job of cleaning up resources when your code has finished with them.
There are some cases where memory leaks can happen, like if the lifecycle of the object with the handler is shorter than the lifecycle of the object with the event. The main answer in this article has a good example of that case: What best practices for cleaning up event handler references?
I prefer use IDisposable to handle this case
List<IDisposable> eventsToDispose = new List<IDisposable>();
eventsToDispose.Add(Disposable.Create(() =>
{
textBox.Click += ClickHandler;
}));
Then you can dispose handler
eventsToDispose.ForEach(o => o.Dispose());

Kinect for windows: status change event can't be triggered

I add a event handler to the StatusChanged event, but the handler never executed:
My WPF file MainWindow.xaml.cs:
public MainWindow()
{
InitializeComponent();
this.Loaded += this.MainWindow_Loaded;
//...
}
protected void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
KinectSensor.KinectSensors.StatusChanged += KinectSensors_StatusChanged;
//...
}
void KinectSensors_StatusChanged(object sender, StatusChangedEventArgs e)
{
switch (e.Status)
{
case KinectStatus.Connected:
MessageBox.Show("CONNECTED");
break;
case KinectStatus.Disconnected:
MessageBox.Show("DISCONNECTED");
break;
case KinectStatus.Initializing:
MessageBox.Show("INITIALIZING");
break;
case KinectStatus.Error:
MessageBox.Show("ERROR");
break;
}
//...
}
I can run Kinect with this program, no matter what I do to the Kinect, the status changed can't be triggered.What should I do?
It is possible that KinectSensor.KinectSensors.StatusChanged returns a new object each time you call it rather than returning a global object, or it returns an object which it only caches internally with a weak reference.
If it does either of these things, then the KinectSensorCollection instance is not rooted by your code, and may get collected before the event gets chance to fire. Try storing the reference to the KinectSensorCollection instance in a member variable of your MainWindow and see if the event fires.

In my dll, I expose logging msgs by an event. How can I properly update a listbox on my form with log msgs?

In my dll I expose an event like so:
public delegate void LogMsgEventHandler(object sender, LogEventArgs e);
public event LogMsgEventHandler newLogMessage;
public class LogEventArgs : EventArgs
{
public string logMsg;
}
protected virtual void OnChanged(LogEventArgs e)
{
if (newLogMessage != null)
newLogMessage(this, e);
}
The various methods fire the event to log operation details. On my main form that uses the dll I output any log msgs to a listbox:
slotUtil.newLogMessage += new slotUtils.LogMsgEventHandler(slotUtil_newLogMessage);
..
void slotUtil_newLogMessage(object sender, slotUtils.LogEventArgs e)
{
lbDebug.Items.Add(e.logMsg);
}
The problem is if the logging event is fired too rapidly, the form freezes up. I assume this is a threading problem? How can I fix this design where the form updates fluidly? Is this a bad design? My alternate design id was too store all logging in a private string in the dll and then only dump the log when a particular method is called. Thoughts?
Thanks!
It is not a Threading issue if instances of classes from your dll aren't in another thread. The event handler
void slotUtil_newLogMessage(object sender, slotUtils.LogEventArgs e)
{
lbDebug.Items.Add(e.logMsg);
}
executes in the same thread where it is registered.
So if your instances are in the UI thread this event handler is executing in the UI thread also. It adds new item to the ListBox which triggers new listbox redraw. If events are firing faster than the drawing occurs, then event handlers will pile up in a queue waiting for Invalidates to finish. If this is the case you should try to, at least do something like this:
System.Windows.Forms.Timer t;
public Form1()
{
InitializeComponent();
t = new System.Windows.Forms.Timer();
t.Interval = 1000;
t.Tick += new EventHandler(t_Tick);
}
void t_Tick(object sender, EventArgs e)
{
for(int i = 0;i<count;i++)//you must add here only those valid strings, can't use foreach
lbDebug.Items.Add(item);
count = 0;
}
static int count = 0;
static string[] items = new string[5];
void slotUtil_newLogMessage(object sender, slotUtils.LogEventArgs e)
{
t.Stop();
items[count++] = e.logMsg;
if (count >= items.Length)
{
foreach (string item in items)
lbDebug.Items.Add(item);
count = 0;
}
else
{
t.Start();
}
}
The adding of the items to the listbox will happen either when five logs are ready or a second has elapsed after last log. You can add whatever number of logs in the array (just change it's size) before they are added to the listbox.

Avoiding `ObjectDisposedException` while calling `Invoke`

I have 2 forms, one is MainForm and second is DebugForm. The MainForm has a button that sets up and shows the DebugForm like this, And passes a reference to an already opened SerialPort:
private DebugForm DebugForm; //Field
private void menuToolsDebugger_Click(object sender, EventArgs e)
{
if (DebugForm != null)
{
DebugForm.BringToFront();
return;
}
DebugForm = new DebugForm(Connection);
DebugForm.Closed += delegate
{
WindowState = FormWindowState.Normal;
DebugForm = null;
};
DebugForm.Show();
}
In the DebugForm, I append a method to handle the DataReceived event of the serialport connection (in DebugForm's constructor):
public DebugForm(SerialPort connection)
{
InitializeComponent();
Connection = connection;
Connection.DataReceived += Connection_DataReceived;
}
Then in the Connection_DataReceived method, I update a TextBox in the DebugForm, that is using Invoke to do the update:
private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
_buffer = Connection.ReadExisting();
Invoke(new EventHandler(AddReceivedPacketToTextBox));
}
But I have a problem. As soon as I close the DebugForm, it throws an ObjectDisposedException on the Invoke(new EventHandler(AddReceivedPacketToTextBox)); Line.
How can I fix this? Any tips/helps are welcome!
UPDATE
I found out if I remove the event in a button event click , and close the form in that button click, everything is fine and my debugform gets closed without any exception...how odd!
private void button1_Click(object sender, EventArgs e)
{
Connection.DataReceived -= Connection_DebugDataReceived;
this.Close();
}
Closing a form disposes of the Form object but cannot forcibly remove references that other classes have to it. When you register your form for events, you are basically giving a reference to your form object to the source of the events (the SerialPort instance in this case).
This means that, even though your form is closed, the event source (your SerialPort object) is still sending events to the form instance and the code to handle these events is still being run. The problem then is that when this code tries to update the disposed form (set its title, update its controls, call Invoke, &c.) you will get this exception.
So what you need to do is ensure that the event gets deregistered when your form closes. This is as simple as detecting that the form is closing and unregister the Connection_DataReceived event handler. Handily you can detect the form is closing by overriding the OnFormClosing method and unregistering the event in there:
protected override OnFormClosing(FormClosingEventArgs args)
{
Connection.DataReceived -= Connection_DataReceived;
}
I would also recommend moving the event registration to an override of the OnLoad method as otherwise it may receive events before the form has been fully constructed which could cause confusing exceptions.
You haven't shown the code for the AddReceivedPacketToTextBox method.
You could try checking for a disposed form in that method:
private void AddReceivedPacketToTextBox(object sender, EventArgs e)
{
if (this.IsDisposed) return;
...
}
Detaching the DataReceived event handler when closing the form is probably a good idea, but isn't sufficient: there is still a race condition which means your AddReceivedPacketToTextBox can be called after the form is closed/disposed. The sequence would be something like:
Worker thread: DataReceived event fired, Connection_DataReceived starts executing
UI thread: Form closed and disposed, DataReceived event detached.
Worker thread: calls Invoke
UI thread: AddReceivedPacketToTextBox executed while form is disposed.
I found out if I remove the event in a button event click , and close the form in that button click, everything is fine and my debugform gets closed without any exception...how odd!
That's not odd. Multithreading bugs ("Heisenbugs") are timing-related and small changes like that can affect the timing. But it's not a robust solution.
The problem could be solved by adding a timer:
bool formClosing = false;
private void Connection_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (formClosing) return;
_buffer = Connection.ReadExisting();
Invoke(new EventHandler(AddReceivedPacketToTextBox));
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
base.OnFormClosing(e);
if (formClosing) return;
e.Cancel = true;
Timer tmr = new Timer();
tmr.Tick += Tmr_Tick;
tmr.Start();
formClosing = true;
}
void Tmr_Tick(object sender, EventArgs e)
{
((Timer)sender).Stop();
this.Close();
}
Thanks to JohnWein from MSDN

Trigger control's event programmatically

Assume that I have a WinFoms project. There is just one button (e.g. button1).
The question is: is it possible to trigger the ButtonClicked event via code without really clicking it?
Button controls have a PerformClick() method that you can call.
button1.PerformClick();
The .NET framework uses a pattern where for every event X there is a method protected void OnX(EventArgs e) {} that raises event X. See this Msdn article. To raise an event from outside the declaring class you will have to derive the class and add a public wrapper method. In the case of Button it would look like this:
class MyButton : System.Windows.Forms.Button
{
public void ProgrammaticClick(EventArgs e)
{
base.OnClick(e);
}
}
You can just call the event handler function directly and specify null for the sender and EventArgs.Empty for the arguments.
void ButtonClicked(object sender, EventArgs e)
{
// do stuff
}
// Somewhere else in your code:
button1.Click += new EventHandler(ButtonClicked);
// call the event handler directly:
ButtonClicked(button1, EventArgs.Empty);
Or, rather, you'd move the logic out of the ButtonClicked event into its own function, and then your event handler and the other code you have would in turn call the new function.
void StuffThatHappensOnButtonClick()
{
// do stuff
}
void ButtonClicked(object sender, EventArgs e)
{
StuffThatHappensOnButtonClick();
}
// Somewhere else in your code:
button1.Click += new EventHandler(ButtonClicked);
// Simulate the button click:
StuffThatHappensOnButtonClick();
The latter method has the advantage of letting you separate your business and UI logic. You really should never have any business logic in your control event handlers.
Yes, just call the method the way you would call any other. For example:
private void btnSayHello_Click(object sender, EventArgs e)
{
MessageBox.Show("Hello World!");
}
private void btnTriggerHello_Click(object sender, EventArgs e)
{
btnSayHello_Click(null, null);
}
button1.PerformClick();
But if you have to do something like this maybe it's better to move the code you have under the event on a new method ?
Why don't you just put your event code into a Method. Then have the Event execute the method. This way if you need to execute the same code that the Event rises, you can, but simply just calling the "Method".
void Event_Method()
{
//Put Event code here.
MessageBox.Show("Hello!");
}
void _btnSend_Click(object sender, EventArgs e)
{
Event_Method();
}
void AnotherMethod()
{
Event_Method();
}
Make sense? Now the "Click" event AND anywhere in code you can trigger the same code as the "Click" event.
Don't trigger the event, call the method that the event calls. ;)
In most cases you would not need to do that. Simply wrap your functionality in functions related to a specific purpose (task). You call this function inside your event and anywhere else it's needed.
Overthink your approach.
I recently had this problem where I wanted to programatically click a button that had multiple event handlers assigned to it (think UserControl or derived classes).
For example:
myButton.Click += ButtonClicked1
myButton.Click += ButtonClicked2;
void ButtonClicked1(object sender, EventArgs e)
{
Console.WriteLine("ButtonClicked1");
}
void ButtonClicked2(object sender, EventArgs e)
{
Console.WriteLine("ButtonClicked1");
}
When you click the button, both functions will get called. In the instances where you want to programmatically fire an event handler for a function from a form (for example, when a user presses enter in a Text field then call the InvokeOnClick method passing through the control you. For example
this.InvokeOnClick(myButton, EventArgs.Empty);
Where this is the Form instance you are in.
use a for loop to call the button_click event
private void btnadd_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i <= 2; i++)
StuffThatHappensOnButtonClick();
}
void StuffThatHappensOnButtonClick()
{
........do stuff
}
we assume at least one time you need click the button

Categories