9

My C# application uses the COM ports. I am having some difficulty that should be common to most programs. I need to get an event when the list of Portnames changes. I have a selection box where the user can choose from teh list of available port names. Does anyone have a snippet of code for this? Thank You.

user1389312
  • 101
  • 1
  • 2
  • 1
    I'd guess there's a general 'new USB device' or 'new PNP device' system event you could watch, but you might need to wait a second for the device to finish initialising before the new COM ports appeared, though. – Rup May 11 '12 at 11:46
  • 1
    @Rup - You are correct. There is a lag between device notification and when System.IO.Ports.SerialPort.GetPortNames() will reflect the change. That is why it would really be nice to be notified when that list gets updated. – GTAE86 Aug 25 '17 at 20:50

3 Answers3

7

It can also be done with help of "ManagementEventWatcher":

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Management;
using System.IO.Ports;
using System.Threading;
using System.Threading.Tasks;

namespace HmxFlashLoader
{
    /// <summary>
    /// Make sure you create this watcher in the UI thread if you are using the com port list in the UI
    /// </summary>
    [Export]
    [PartCreationPolicy(CreationPolicy.Shared)]
    public sealed class SerialPortWatcher : IDisposable
    {
        public SerialPortWatcher()
        {
            _taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
            ComPorts = new ObservableCollection<string>(SerialPort.GetPortNames().OrderBy(s => s));

            WqlEventQuery query = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent");

            _watcher = new ManagementEventWatcher(query);           
            _watcher.EventArrived += (sender, eventArgs) => CheckForNewPorts(eventArgs);
            _watcher.Start();       
        }

        private void CheckForNewPorts(EventArrivedEventArgs args)
        {
            // do it async so it is performed in the UI thread if this class has been created in the UI thread
            Task.Factory.StartNew(CheckForNewPortsAsync, CancellationToken.None, TaskCreationOptions.None, _taskScheduler);
        }

        private void CheckForNewPortsAsync()
        {
            IEnumerable<string> ports = SerialPort.GetPortNames().OrderBy(s => s);

            foreach (string comPort in ComPorts)
            {
                if (!ports.Contains(comPort))
                {
                    ComPorts.Remove(comPort);
                }
            }

            foreach (var port in ports)
            {           
                if (!ComPorts.Contains(port))
                {
                    AddPort(port);
                }
            }
        }

        private void AddPort(string port)
        {
            for (int j = 0; j < ComPorts.Count; j++)
            {
                if (port.CompareTo(ComPorts[j]) < 0)
                {
                    ComPorts.Insert(j, port);
                    break;
                }
            }

        }

        public ObservableCollection<string> ComPorts { get; private set; }

        #region IDisposable Members

        public void Dispose()
        {
            _watcher.Stop();    
        }

        #endregion

        private ManagementEventWatcher _watcher;
        private TaskScheduler _taskScheduler;
    }
}
Ibrahim
  • 183
  • 1
  • 6
huer12
  • 314
  • 3
  • 2
  • Very neat solution! Thanks mate! – Ian Jul 10 '15 at 19:48
  • I think this code will throw an exception when ComPorts gets updated while iterating ComPorts.... – GTAE86 Aug 25 '17 at 20:47
  • @GTAE86 Have you been able to confirm whether your guess is true? Would be helpful to know about it. – Vandrey Apr 17 '20 at 12:33
  • 1
    @Sunburst275 Just from analyzing the code, I would say yes - in CheckForNewPortsAsync, the first foreach is iterating over ComPorts, but the code inside the if-test updates ComPorts. Generally a no-no. If you code it up quickly, you will see it yields "System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute.'" – GTAE86 May 11 '20 at 17:30
  • @GTAE86 Aaah yes I see. That is indeed quite problematic. Should have seen that. Thank you! – Vandrey May 11 '20 at 17:45
  • AddPort() will not add a port to a blank collection (if you have constructed the SerialPortWatcher while no COM ports exist). I recommend moving the Insert() call after the loop, and scoping the index j outside the loop for its use. – Gutblender Apr 20 '21 at 17:16
3

COM ports changing is a rare event, not a common one.

The easiest way would be to have a timer and every 10-30 seconds enumerate the list of COM ports and if changed, update the list.

Better still, provide a "refresh list" button - the list will basically only change if the user has plugged a USB Serial adapter in.

Ben
  • 34,935
  • 6
  • 74
  • 113
  • 4
    http://www.codeproject.com/Articles/60579/A-USB-Library-to-Detect-USB-Devices will show you how to listen for USB attach/detach events. Also, 10-30 seconds is probably too long for the user, +1 vote for the refresh button. – Simon May 11 '12 at 11:45
3

Create a simple Form application and put the following code into the form:

protected override void WndProc(ref Message m)
{
    switch (m.Msg)
    {
        case 537: //WM_DEVICECHANGE
            var ports = SerialPort.GetPortNames().OrderBy(name => name);

            foreach (var portName in ports)
            {
                Debug.Print(portName);
            }
            break;
    }
    base.WndProc(ref m);
}
Oliver
  • 43,366
  • 8
  • 94
  • 151
  • Can this work inside a Windows Service message pump? OS: Win7. If not, then any workaround? – Adrian Salazar Dec 20 '12 at 22:09
  • @AdrianSalazar: Don't know, never tried. But i don't see any reason why it should not work. Simply try it out and if it doesn't work, ask a new question with your issue. – Oliver Dec 21 '12 at 07:33
  • The tiny little "override" keyword is the one I'm worried about. Never ever saw this specific method inside a Windows Service, so, noting to override. – Adrian Salazar Dec 21 '12 at 08:40
  • @AdrianSalazar: You're right. In this case take a look at [this SO question](http://stackoverflow.com/questions/2061167/how-to-receive-the-windows-messages-without-a-windows-form). – Oliver Dec 21 '12 at 11:09