简体   繁体   English

由于 clear(),使用 ComboBox DropDownList 枚举 COM 端口会在 DropDown 事件中丢失当前选择

[英]Using a ComboBox DropDownList to enumerate COM ports keeps losing current selection in the DropDown event due to clear()

There is a function that automatically renews the serialPort in the DropDown event.在DropDown事件中有一个function会自动更新serialPort。 That's why we periodically clear() it.这就是我们定期清除()它的原因。 First {COM1, COM2, COM3}, then {COM1, COM2} when disconnected, maybe {COM1, COM2, COM3} when reconnected.首先是 {COM1, COM2, COM3},然后是断开连接时的 {COM1, COM2},重新连接时可能是 {COM1, COM2, COM3}。

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() .有人指出是因为使用了clear() But, it doesn't occur this problem when DropDownStyle is DropDown .但是,当DropDownStyleDropDown时,不会出现此问题。

I wrote the following code to make the comboBox1 ReadOnly .我编写了以下代码来使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.关键是comboBox除了用户选择值外不能输入。

Here is an alternative to using Clear .这是使用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.一种解决方案是拥有一个绑定列表,该列表是您的 ComboBox 的数据源。当您枚举可用的 COM 端口时(下面显示为模拟),您将根据可用性从绑定源中添加或删除 com 端口。 This should result in the behavior you want, because if a com port is "still" available the selection won't change.这应该会导致您想要的行为,因为如果 com 端口“仍然”可用,则选择不会改变。

截屏

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 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.我不习惯在同一线程上发布多个答案,但根据您的有用评论,您发布的“潜在”原因似乎是希望使ComboBox与当前连接的 COM 端口保持最新。

There might be a more optimal solution to the thing you were trying to do in the first place (this is known as an XY Problem ).对于您最初尝试做的事情,可能有更优化的解决方案(这被称为XY 问题)。 In my old job I used to do serial port and USB enumeration quite a bit.在我以前的工作中,我经常做串口和 USB 枚举。 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.我在此过程中学到的一个技巧是,每当发生物理事件时,WinOS 都会发布一条消息WM_DEVICECHANGE ,例如插入或断开 USB 串行端口设备。

In a WinForms app, it's straightforward to detect it by overriding WndProc :在 WinForms 应用程序中,可以通过覆盖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:我仍然主张将组合框的DataSource属性分配给BindingList<ComPort> ,其中ComPort是我们设计用来表示连接信息的 class:

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.现在不再需要组合框的DropDown事件。 Problem solved!问题解决了!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM