i want to make my application can refresh serial port in c#. when list of port(In ComboBox) is empty and i hit button refresh, it's work perfectly and show the list of active port. but if i disconnected the Serial Port and i hit refresh button, actually it's must make the list of port(In Combobox) is empty because the serial port is disconnected. So how to make when i hit refresh button and the condition is disconnected, we make all of port list(in Combobox ) is empty ?
this is my code in refresh button:
private void button2_Click_2(object sender, EventArgs e)
{
if(String.IsNullOrEmpty(cboPort.Text))
{
comm.SetPortNameValues(cboPort);
for (int i = 0; i < cboPort.Items.Count; i++)
{
string value = cboPort.GetItemText(cboPort.Items[i]);
if (String.IsNullOrEmpty(value))
{
string a = cboPort.SelectedIndex.ToString();
return;
}
else
{
cboPort.SelectedIndex = 0;
}
}
}
else if ((cboPort.Text) != " " && cboPort.SelectedIndex == -1)
{
cboPort.Text = " ";
return;
}
}
this is my code in setportnamevalues :
public void SetPortNameValues(object obj)
{
foreach (string str in SerialPort.GetPortNames())
{
((ComboBox)obj).Items.Add(str);
}
}
my expetation is :
1. i connect serial port
2. i run my app
3. i disconnect serial port
4. i hit refresh
5. final result is port list empty in combobox
thanks for helping and responses, i am still new in c#. Greetings!
i Finally get the answer.
this is modification of setportnamevalues :
public void SetPortNameValues(object obj)
{
string[] ports = SerialPort.GetPortNames(); // load all name of com ports to string
((ComboBox)obj).Items.Clear(); //delete previous names in combobox items
foreach (string port in ports) //add this names to comboboxPort items
{
((ComboBox)obj).Items.Add(port); //if there are some com ports ,select first
}
if (((ComboBox)obj).Items.Count > 0)
{
((ComboBox)obj).SelectedIndex = 0;
}
else
{
((ComboBox)obj).Text = " "; //if there are no com ports ,write Empty
}
}
in here modification in button action :
private void button2_Click_2(object sender, EventArgs e)
{
comm.SetPortNameValues(cboPort);
}
yeah, finally i get what i want.
My solution as below
Initialize COM list
Add serialPort_OnClick event for combobox so that whenever user click on combobox, the COM items will be reloaded
private void InitializePortSetting()
{
mDateTime = DateTime.Now;
//1. Setting port list
// Get a list of serial port names.
portList = SerialPort.GetPortNames();
Console.WriteLine("The following serial ports were found:");
// Display each port name to the console.
foreach (string port in portList)
{
port_name.Items.Add(port);
}
}
private void serialPort_OnClick(object sender, EventArgs e)
{
port_name.Items.Clear();
port_name.Text = "";
//port_name.Dispose();
// Get a list of serial port names.
portList = SerialPort.GetPortNames();
Console.WriteLine("The following serial ports were found:");
// Display each port name to the console.
foreach (string port in portList)
{
port_name.Items.Add(port);
}
}
Related
There is a function that automatically renews the serialPort in the DropDown event. That's why we periodically clear() it. First {COM1, COM2, COM3}, then {COM1, COM2} when disconnected, maybe {COM1, COM2, COM3} when reconnected.
The problem is disappear that a previously selected value whenever an event occurs. Someone points out that it is because of the use of clear(). But, it doesn't occur this problem when DropDownStyle is DropDown.
I wrote the following code to make the comboBox1 ReadOnly.
comboBox1.DropDownStyle = ComboBoxStyle.DropDownList;
And, I also have the code like this:
private void comboBox1_DropDown(object sender, EventArgs e)
{
comboBox1.Items.Clear();
// Logic to automatically add items to comboBox1
.
.
.
}
How do I should solve this problem? The key is that it can't input in comboBox other than user selecting value.
I don't make a habit of posting multiple answers on the same thread, but based on your helpful comment, it appears that your "underlying" reason for posting is wanting to keep the ComboBox up to date with COM ports that are currently connected.
There might be a more optimal solution to the thing you were trying to do in the first place (this is known as an X-Y Problem). In my old job I used to do serial port and USB enumeration quite a bit. One trick I learned along the way is that WinOS is going to post a message WM_DEVICECHANGE whenever a physical event occurs, things like plugging in or disconnecting a USB serial port device.
In a WinForms app, it's straightforward to detect it by overriding WndProc:
public MainForm() => InitializeComponent();
const int WM_DEVICECHANGE = 0x0219;
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if(m.Msg == WM_DEVICECHANGE)
{
onDeviceChange();
}
}
BindingList
I would still advocate for assigning the DataSource property of the combo box to a BindingList<ComPort> where ComPort is a class we design to represent the connection info:
class ComPort
{
public string? PortName { get; set; }
public string? FriendlyName { get; set; }
public string? PnpDeviceId { get; set; }
public override string ToString() => PortName ?? "None";
public string GetFullDescription()
{
var builder = new List<string>();
builder.Add($"{PortName}");
builder.Add($"{FriendlyName}");
builder.Add($"{nameof(PnpDeviceId)}: ");
builder.Add($"{PnpDeviceId}");
return $"{string.Join(Environment.NewLine, builder)}{Environment.NewLine}{Environment.NewLine}";
}
}
Enumeration
There's a little extra work this way but the enumeration yields a descriptor with additional useful information:
BindingList<ComPort> ComPorts = new BindingList<ComPort>();
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
comboBoxCom.DropDownStyle = ComboBoxStyle.DropDownList;
comboBoxCom.DropDownClosed += (sender, e) => ActiveControl = null;
comboBoxCom.DataSource = ComPorts;
ComPorts.ListChanged += onComPortChanged;
enumerateComPorts();
}
bool _isDeviceChange = false;
private void onComPortChanged(object? sender, ListChangedEventArgs e)
{
if(_isDeviceChange) BeginInvoke(() => richTextBox.Clear());
ComPort comPort;
switch (e.ListChangedType)
{
case ListChangedType.ItemAdded:
comPort = ComPorts[e.NewIndex];
using (ManagementClass manager = new ManagementClass("Win32_PnPEntity"))
{
foreach (ManagementObject record in manager.GetInstances())
{
var pnpDeviceId = record.GetPropertyValue("PnpDeviceID")?.ToString();
if (pnpDeviceId != null)
{
var subkey = Path.Combine("System", "CurrentControlSet", "Enum", pnpDeviceId, "Device Parameters");
var regKey = Registry.LocalMachine.OpenSubKey(subkey);
if (regKey != null)
{
var names = regKey.GetValueNames();
if (names.Contains("PortName"))
{
var portName = regKey.GetValue("PortName");
if (Equals(comPort.PortName, portName))
{
var subkeyParent = Path.Combine("System", "CurrentControlSet", "Enum", pnpDeviceId);
var regKeyParent = Registry.LocalMachine.OpenSubKey(subkeyParent);
comPort.FriendlyName = $"{regKeyParent?.GetValue("FriendlyName")}";
comPort.PnpDeviceId = pnpDeviceId;
}
}
}
}
}
}
break;
case ListChangedType.ItemDeleted:
comPort = _removedItem!;
break;
default: return;
}
BeginInvoke(() =>
{
if (_isDeviceChange)
{
richTextBox.SelectionColor =
e.ListChangedType.Equals(ListChangedType.ItemAdded) ?
Color.Green : Color.Red;
richTextBox.AppendText($"{e.ListChangedType}{Environment.NewLine}");
}
else
{
richTextBox.SelectionColor = Color.Blue;
richTextBox.AppendText($"Detected{Environment.NewLine}");
}
richTextBox.SelectionColor = Color.FromArgb(0x20, 0x20, 0x20);
richTextBox.AppendText(comPort?.GetFullDescription());
});
}
int _wdtCount = 0;
private ComPort? _removedItem = null;
private void onDeviceChange()
{
_isDeviceChange = true;
int captureCount = ++_wdtCount;
Task
.Delay(TimeSpan.FromMilliseconds(500))
.GetAwaiter()
.OnCompleted(() =>
{
if(captureCount.Equals(_wdtCount))
{
// The events have settled out
enumerateComPorts();
}
});
}
private void enumerateComPorts()
{
string[] ports = SerialPort.GetPortNames();
foreach (var port in ports)
{
if (!ComPorts.Any(_ => Equals(_.PortName, port)))
{
ComPorts.Add(new ComPort { PortName = port });
}
}
foreach (var port in ComPorts.ToArray())
{
if (!ports.Contains(port.PortName))
{
_removedItem = port;
ComPorts.Remove(port);
}
}
BeginInvoke(()=> ActiveControl = null); // Remove focus rectangle
}
The DropDown event of the combo box is now no longer required. Problem solved!
Here is an alternative to using Clear.
One solution is to have a binding list that is the data source of your ComboBox. When you enumerate the available COM ports (shown below as a simulation) you will add or remove com ports from the binding source based on availability. This should result in the behavior you want, because if a com port is "still" available the selection won't change.
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
comboBoxCom.DropDownStyle= ComboBoxStyle.DropDownList;
comboBoxCom.DataSource = MockDataSource;
refreshAvailableComPorts(init: true);
comboBoxCom.DropDown += onComboBoxComDropDown;
}
BindingList<string> MockDataSource = new BindingList<string>();
private void onComboBoxComDropDown(object? sender, EventArgs e)
{
refreshAvailableComPorts(init: false);
}
/// <summary>
/// This is a simulation that will choose a different
/// set of "available" ports on each drop down.
/// </summary>
private void refreshAvailableComPorts(bool init)
{
if(init)
{
MockDataSource.Clear();
MockDataSource.Add("COM2");
}
else
{
string
selB4 = string.Join(",", MockDataSource),
selFtr;
do
{
var flags = _rando.Next(1, 0x10);
if ((flags & 0x1) == 0) MockDataSource.Remove("COM1");
else if(!MockDataSource.Contains("COM1")) MockDataSource.Add("COM1");
if ((flags & 0x2) == 0) MockDataSource.Remove("COM2");
else if (!MockDataSource.Contains("COM2")) MockDataSource.Add("COM2");
if ((flags & 0x4) == 0) MockDataSource.Remove("COM3");
else if (!MockDataSource.Contains("COM3")) MockDataSource.Add("COM3");
if ((flags & 0x8) == 0) MockDataSource.Remove("COM4");
else if (!MockDataSource.Contains("COM4")) MockDataSource.Add("COM4");
selFtr = string.Join(",", MockDataSource);
if (!selFtr.Equals(selB4))
{
break;
}
} while (true);
}
}
private Random _rando = new Random(5);
}
I Have created some code for communicating with a device over the serialport. By sending certain command with serialPort1.WriteLine I should receive answer with serialPort1.ReadExisting(). Well I'm able to detect the device but I need to read some other information but to get this information correct I only get it when I place the code in a button_Click function. The funny thing is that All is written in a function and I wish to get the information after the device is detected immediately. When I call the function after the device has been detected it doesn't work and when I call the function after the button is pressed I get the correct information. I do not understand why this is so.
The function for detecting the device is like this:
private void detectVM25ToolStripMenuItem_Click(object sender, EventArgs e)
{
// Detect VM25 and show connection is established
String device;
//Search all portnames
String[] ports = SerialPort.GetPortNames();
int totalPorts = ports.Length;
int count = 0 ;
//Test which enabled port is the VM25.
foreach (string port in ports)
{
count = count + 1;
serialPort1.PortName = port;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("#S" + Environment.NewLine);
answer = serialPort1.ReadExisting();
if (answer != "")
{
device = answer.Substring(0, 4);
if (device == "VM25")
{
getRecordings();
statusLblDevice.ForeColor = Color.LawnGreen;
statusLblDevice.Text = port + " - " + device + " - Connected";
VM25Port = port;
}
}
else if (answer == "")
{
serialPort1.Close();
if (count == totalPorts)
{
MessageBox.Show("No device found");
}
}
}
}
}
The function getRecordings() should give me data. If I place this function in my form and get called after a button is pressed I get the correct info but when it is inside the above function it doesn't do anything.
private void getRecordings()
{
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("#H" + Environment.NewLine);
memoryInfo = serialPort1.ReadExisting();
label1.Text = memoryInfo;
}
}
Does anybody knows why this is the case? I would like not to have press a button and get this information after it has detected the device. I also tried to create a delay with `Task.Delay()' unfortunately this did not help
It's surely because you have no synchronization whatsoever. .ReadExisting() does not get an entire response, it gets only what's already been received. So calling it right after .WriteLine(...)... well your data hasn't even reached the device yet, so there definitely won't be an answer ready to read.
Since you know the length of your expected answer (or at least the prefix you're interested in), set the read timeout and then call .Read() (or better, .BaseStream.ReadAsync()) with that length.
Even that is only going to mostly work when the port is freshly opened... for subsequent commands you risk getting data in your buffer from the tail end of a reply to an earlier command. Correct use of a serial port really requires the two ends to agree on some sort of framing, whether that is "a message contains only ASCII and ends with CR+LF" or "a message starts with the length of its payload" or some less common scheme.
with the point out of #Ben Voigt I discovered I should tackle this issue different.
I have use the method serialPort1.ReadTo() because I know that each message end with "/a".
therefore I fixed the function as follows
private void detectVM25ToolStripMenuItem_Click(object sender, EventArgs e)
{
// Detect VM25 and show connection is established
String device;
//Search all portnames
String[] ports = SerialPort.GetPortNames();
int totalPorts = ports.Length;
int count = 0 ;
//Test which enabled port is the VM25.
foreach (string port in ports)
{
count = count + 1;
serialPort1.PortName = port;
serialPort1.Open();
if (serialPort1.IsOpen)
{
serialPort1.WriteLine("#S" + Environment.NewLine);
answer = serialPort1.ReadExisting();
if (answer != "")
{
device = answer.Substring(0, 4);
if (device == "VM25")
{
statusLblDevice.ForeColor = Color.LawnGreen;
statusLblDevice.Text = port + " - " + device + " - Connected";
VM25Port = port;
serialPort1.WriteLine("#H" + Environment.NewLine);
string input = serialPort1.ReadTo("/a");
label1.Text = input;
string totalMeasurements = input.Substring(0, 11);
string totalFFT = input.Substring(11, 11);
statusLblMeas.Text = totalMeasurements;
statusLblFFT.Text = totalFFT;
}
}
else if (answer == "")
{
serialPort1.Close();
if (count == totalPorts)
{
MessageBox.Show("No device found");
}
}
}
}
}
Note the changes of reading the serialport where I stated that it should read up to "/a"
I would like some advice on the structure of my windows form application. My application will allow the user to open a SerialPort in order to read data from a USB device.
Currently, the application will open into the main form, the user would then open another form frmPortConfig in order to configure the port, this form would then be closed and the user would return to the main form. As it stands, the user selects the port, clicks open, the port info is then passed to another port config class and is setup.
How would I then pass this data back to the main form?
Is this the correct/most efficient method of achieving this?
port config form:
public partial class frmPortConfig : Form
{
public frmPortConfig()
{
InitializeComponent();
//center the form
this.CenterToScreen();
//get serial ports
getPorts();
}
public void getPorts()
{
//stop user from editing the combo box text
cmbPortList.DropDownStyle = ComboBoxStyle.DropDownList;
//get the available ports
string[] ports = SerialPort.GetPortNames();
//add the array of ports to the combo box within the
cmbPortList.Items.AddRange(ports);
}
private void btnOpenPort_Click(object sender, EventArgs e)
{
//get name of port
string port = cmbPortList.SelectedItem.ToString();
//if the port string is not null
if (port != null)
{
//if port can be opened (evoke open port code in port class)
if (clsPortConfig.openPort(port))
{
//inform user that port has been opened
lblPortStatus.Text = port + " opened successfully";
}
else
{
//inform user that port could not be opened
lblPortStatus.Text = port + " could not be opened";
}
}
}
private void btnClose_Click(object sender, EventArgs e)
{
//close the form
this.Close();
}
port config class:
class clsPortConfig
{
public static bool openPort(string port)
{
try
{
//create new serial port
SerialPort serialPort = new SerialPort();
//serial port settings
serialPort.PortName = port;
serialPort.BaudRate = 9600;
serialPort.Parity = Parity.None;
serialPort.StopBits = StopBits.One;
serialPort.Handshake = Handshake.None;
//attempt to open serial port
serialPort.Open();
serialPort.ReadTimeout = 200;
//add data received handle to serial port
serialPort.DataReceived += new SerialDataReceivedEventHandler(serialPort_DataReceived);
//if serial port is now open
if (serialPort.IsOpen)
{
return true;
}
else
{
//inform user that the port could not be opened
return false;
}
}
catch
{
return false;
}
}
public static void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
//set sender up as serial port
SerialPort serialPort = (SerialPort)sender;
//get data from serial port
string data = serialPort.ReadExisting();
}
}
How should I send the received data back to my main form?
Thanks
how would I then pass this data back to the main form?
Since you catch the data asynchronously from the device via an event. You don't know when it will arrive. So you would need an event which you can fire from the clsPortConfig.
class clsPortConfig
{
public delegate void EventHandler(string s);
public static event EventHandler TransmitEvent;
// all the other stuff
public static void serialPort_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
//set sender up as serial port
SerialPort serialPort = (SerialPort)sender;
//get data from serial port
string data = serialPort.ReadExisting();
if (TransmitEvent != null)
{
TransmitEvent(data);
}
}
}
and register it in the form:
public frmPortConfig()
{
InitializeComponent();
//center the form
this.CenterToScreen();
//get serial ports
getPorts();
clsPortConfig.TransmitEvent += MyTransmitEvent;
}
private void MyTransmitEvent(string s)
{
// in s you will find the data
}
Is this the correct/most efficient method of achieving this?
I would doubt that. There are a lot of ways to do that. You chose a rather convoluted one. The easiest would probably be to have everything in the Form class. Have the SerialPort there, register the DataReceived event also and use the BeginInvoke method to access display controls like TextBox if you want to show the received data. Because it will arrive on a different thread then the control is created in.
I have a very small code that shows available COM ports.
My question is:
Is there an easy way to have the program to run in the tray and only popup when a new COM port is available and is it possible to add the name for the COM port that you can see in device manager ec "USB serial port"?
I often add/remove a USB->RS232 comverter and find it a pain in the ass because I must go into the device manger to see what COM port it is assigned to. It's not the same each time
Maybe there already is a small app that can do this but I havent found it on Google yet
using System;
using System.Windows.Forms;
using System.IO.Ports;
namespace Available_COMports
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
//show list of valid com ports
foreach (string s in SerialPort.GetPortNames())
{
listBox1.Items.Add(s);
}
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
}
}
}
public static void Main()
{
// Get a list of serial port names.
string[] ports = SerialPort.GetPortNames();
Console.WriteLine("The following serial ports were found:");
// Display each port name to the console.
foreach(string port in ports)
{
Console.WriteLine(port);
}
Console.ReadLine();
}
Take a look at this question. It uses WMI to find available COM ports. You could keep track of what COM ports exist, and only notify about new ones.
To find out when devices are hot-plugged, you want to handle WM_DEVICECHANGE. Call RegisterDeviceNotification to enable delivery of these notifications.
The code to get the COM number of certain device.
List<USBDeviceInfo> devices = new List<USBDeviceInfo>();
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\CIMV2",
"SELECT * FROM Win32_PnPEntity");
foreach (ManagementObject queryObj in searcher.Get())
{
devices.Add(new USBDeviceInfo(
(string)queryObj["DeviceID"],
(string)queryObj["PNPDeviceID"],
(string)queryObj["Name"]
));
}
foreach (USBDeviceInfo usbDevice in devices)
{
if (usbDevice.Description != null)
{
if (usbDevice.Description.Contains("NAME OF Device You are Looking for")) //use your own device's name
{
int i = usbDevice.Description.IndexOf("COM");
char[] arr = usbDevice.Description.ToCharArray();
str = "COM" + arr[i + 3];
if (arr[i + 4] != ')')
{
str += arr[i + 4];
}
break;
}
}
}
mySerialPort = new SerialPort(str);
the listview renders the row item quite fast at normal state. but when I call a function to write to a COM port (POLE DISPLAY) . the load time of list view increases and slows down whole process.
I've first called a function to add an item to the listview. The list view uses addrange method to add items.
then the vfd function is called which will open a COM port first and initialize a port then write to the port.
Here is a full sample code:
//function to add new row item in the listview
private void ListFunction(int id)
{
ListViewItem checkitem = listView1.FindItemWithText(GetProduct(id));
if (checkitem != null)
{
//MessageBox.Show("It Already Exist");
int itemvalue = int.Parse(checkitem.SubItems[2].Text) + 1;
checkitem.SubItems[2].Text = itemvalue.ToString();
int ttlamt = GetPrice(id) * int.Parse(checkitem.SubItems[2].Text);
string totalamount = ttlamt.ToString();
checkitem.SubItems[4].Text = totalamount;
}
else
{
// if it doesnot exist
int ttlamt = GetPrice(id);
ListViewItem item1 = new ListViewItem("1");
item1.SubItems.Add(GetProduct(id));
item1.SubItems.Add("1");
item1.SubItems.Add(GetPrice(id).ToString());
string totalamount = ttlamt.ToString();
item1.SubItems.Add(totalamount);
listView1.Items.AddRange(new ListViewItem[] { item1 });
}
// Write to port
public void VFD(int id)
{
SerialPort VFD = new SerialPort("COM5", 9600, Parity.None, 8, StopBits.One);
VFD.Open();
VFD.Write(initialize);
VFD.Write(GetProduct(id) + ":" + GetPrice(id));
VFD.Write("\x1B\x6c\x09\x02");
VFD.Write("Total: " + totalAmt.Text);
VFD.Close();
}
private void button1_Click(object sender, EventArgs e)
{
int id = 10001;
//add to list
ListFunction(id);
// VFD Display
VFD(id);;
}
All of the items are called from the database.
This slows down the list render performance.
How to get rid of this?
You could send the data to the COM port on a separate thread so this one isn't held up, but be careful to make your code thread-safe.