Home > database >  Event Handler Fires Twice When Plugging/Unplugging USB Serial Port
Event Handler Fires Twice When Plugging/Unplugging USB Serial Port

Time:09-27

I am writing a program which is supposed to detect when a USB serial device is plugged in and then log on to the new com port. The code below works wonderfully, but I have noticed in debugging the code and stepping through it that the event handler "DetectChange" fires twice. I'm not sure that this is normal, or an action of the debugger.

In any case, the code works, but I am new at event handling and I would like to make sure that I am not going to cause any issues as I add more code to actually read and write from the serial port.

(I got some of this code from stackoverflow, but I have misplaced my paper with names for attribution. If you see your code below, my heartfelt thanks.)

    using System;
    using System.IO.Ports;
    using System.Linq;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using System.Management;
    using System.Threading;

    namespace SerialTest
    {
        public partial class Form1 : Form
        {
            SerialMethods serialMethods = new SerialMethods();
      
            public Form1()
            {
                InitializeComponent();
            }

            private void Form1_Load(object sender, EventArgs e)
            {
                loadCmdBox();
            }

            private void CmdBoxPort_SelectedIndexChanged(object sender, EventArgs e)
            {
                handleComPort();
            }

            private void handleComPort()
            {
                // Set the right port for the selected item.
                // The portname is based on the "COMx" part of the string (SelectedItem)
                string item = CmdBoxPort.SelectedItem.ToString();
                // Search for the expression "(COM" in the "selectedItem" string
                if (item.Contains("(COM"))
                {
                    // Get the index number where "(COM" starts in the string
                    int indexOfCom = item.IndexOf("(COM");
                    // Set PortName to COMx based on the expression in the "selectedItem" string
                    // It automatically gets the correct length of the COMx expression to make sure 
                    // that also a COM10, COM11 and so on is working properly.
                    string PortName = item.Substring(indexOfCom   1, item.Length - indexOfCom - 2);

                    if (serialMethods._serialPort.IsOpen)
                    {
                        serialMethods._serialPort.Close();
                        serialMethods.Connect(PortName);
                        label5.Text = "Active Port:  "   PortName;
                    }
                    else
                    {
                        serialMethods.Connect(PortName);
                        label5.Text = PortName;
                    }
                }
                else
                    return;
                }

            private void loadCmdBox()
            {
                // Get all serial (COM)-ports you can see in the devicemanager
                ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\cimv2",
                    "SELECT * FROM Win32_PnPEntity WHERE ClassGuid=\"{4d36e978-e325-11ce-bfc1-08002be10318}\"");
                // Sort the items in the combobox 
                CmdBoxPort.Sorted = true;
                // Add all available (COM)-ports to the combobox
                foreach (System.Management.ManagementObject queryObj in searcher.Get().Cast<ManagementObject>())
                {
                    CmdBoxPort.Items.Add(queryObj["Caption"]);
                }
                SerialPortService.PortsChanged  = (sender1, changedArgs) => DetectChange(changedArgs.EventType);
                label2.Text = "";
                label3.Text = "";
                label4.Text = "";
            }
        
            protected Task<Task> getSerPorts()
            {
                CmdBoxPort.Text = "";
                CmdBoxPort.Update();
                if (!String.IsNullOrEmpty(CmdBoxPort.Text))
                {
                    handleComPort();
                    return Task.FromResult(Task.CompletedTask);
                }
                else
                {
                    loadCmdBox();
                    return Task.FromResult(Task.CompletedTask);
                }
            }
        
            private void ExitButton_Click(object sender, EventArgs e)
            {
                SerialPortService.CleanUp();
                this.Close();
            }

            private void RefreshButton_Click(object sender, EventArgs e)
            {
                refresh();
            }

            protected Task<Task> refresh()
            {
                label2.Text = "";
                label3.Text = "";
                label4.Text = "";
                CmdBoxPort.Items.Clear();
                getSerPorts();
                return Task.FromResult(Task.CompletedTask);
            }

            protected virtual void DetectChange(EventType changedArgs)
            {
                if (changedArgs == EventType.Insertion)
                {
                    try
                    { 
                        Task tr = (Task)Invoke(new Action( () => { getSerPorts(); }));
                        Task rr = (Task)Invoke(new Action(() => { refresh(); })); 
                    }
                    catch (Exception ex) { MessageBox.Show("Exception at insertion invoke method "   ex, "Exception", MessageBoxButtons.OK); }
                }
                else if (changedArgs == EventType.Removal)
                {
                    try
                    {
                        Task tr = (Task)Invoke(new Action( () => { getSerPorts(); }));
                        Task rr = (Task)Invoke(new Action(() => { refresh(); }));
                    }
                    catch (Exception ex) { MessageBox.Show("Exception at removal invoke method "   ex, "Exception", MessageBoxButtons.OK); }
                 }
                 return;
            }
        }
        public static class SerialPortService
        {
            private static SerialPort _serialPort;
            private static string[] _serialPorts;
            private static ManagementEventWatcher arrival;
            private static ManagementEventWatcher removal;
            private static readonly SerialMethods SD = new SerialMethods();
            static SerialPortService()
            {
                _serialPorts = SerialPort.GetPortNames();
                MonitorDeviceChanges();
            }

        
            public static void CleanUp()
            {
                arrival.Stop();
                removal.Stop();
            }

            public static event EventHandler<PortsChangedArgs> PortsChanged;

            private static void MonitorDeviceChanges()
            {
                try
                {
                    var deviceArrivalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
                    var deviceRemovalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
                    arrival = new ManagementEventWatcher(deviceArrivalQuery);
                    removal = new ManagementEventWatcher(deviceRemovalQuery);
                    arrival.EventArrived  = (o, args) => RaisePortsChangedIfNecessary(EventType.Insertion);
                    removal.EventArrived  = (sender, eventArgs) => RaisePortsChangedIfNecessary(EventType.Removal);
                    // Start listening for events
                    arrival.Start();
                    removal.Start();
                }
                catch (ManagementException err)
                {
                    MessageBox.Show("Management exception = "   err, "Info", MessageBoxButtons.OK);
                }
            }

            private static void RaisePortsChangedIfNecessary(EventType eventType)
            {
                lock (_serialPorts)
                {
                    var availableSerialPorts = SerialPort.GetPortNames();
                    if (eventType == EventType.Insertion)
                    {
                        var added = availableSerialPorts.Except(_serialPorts).ToArray();
                        _serialPorts = availableSerialPorts;
                        PortsChanged.Raise(null, new PortsChangedArgs(eventType, added));
                    }
                    else if (eventType == EventType.Removal)
                    {
                        var removed = _serialPorts.Except(availableSerialPorts).ToArray();
                        _serialPorts = availableSerialPorts;
                        PortsChanged.Raise(null, new PortsChangedArgs(eventType, removed));
                    }
                }
            }

            public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs
            {
                handler?.Invoke(sender, args);
            }
        }

        public enum EventType
        {
            Insertion,
            Removal,
        }

        public class PortsChangedArgs : EventArgs
        {
            private readonly EventType _eventType;
            private readonly string[] _serialPorts;
            public PortsChangedArgs(EventType eventType, string[] serialPorts)
            {
                _eventType = eventType;
                _serialPorts = serialPorts;
            }

            public string[] SerialPorts => _serialPorts;
            public EventType EventType => _eventType;
        }
    }

CodePudding user response:

Just took a short look at this. It seems like getSerPorts() will always execute loadCmdBox() (CmdBoxPort.Text = ""; ... if (!String.IsNullOrEmpty(CmdBoxPort.Text))) that will attach a new event handler (previous attached event handlers will not be removed by attaching a new one).

You should either remove the existing event handler befor attaching a new one or only attach the event handler once.

  • Related