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.
CodePudding user response:
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
BindingList<ComPort> ComPorts = new BindingList<ComPort>();
protected override void onl oad(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!
CodePudding user response:
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);
}