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

fnatzsnv  于 2023-01-26  发布在  其他
关注(0)|答案(2)|浏览(131)

DropDown事件中有一个函数可以自动更新serialPort,所以我们定期清除(),先清除{COM 1,COM 2,COM 3},断开连接后再清除{COM 1,COM 2},重新连接后可能会清除{COM 1,COM 2,COM 3}。
问题是每当事件发生时,先前选择的值消失。有人指出这是由于使用clear()。但是,当DropDownStyleDropDown时,不会出现此问题。
我编写了下面的代码来生成comboBox1ReadOnly

comboBox1.DropDownStyle = ComboBoxStyle.DropDownList;

我还有这样的代码:

private void comboBox1_DropDown(object sender, EventArgs e)
    {
        comboBox1.Items.Clear();
        // Logic to automatically add items to comboBox1
        .
        .
        .
    }

我该怎么解决这个问题呢?关键是除了用户选择值之外,它不能在组合框中输入。

hjqgdpho

hjqgdpho1#

我没有在同一个线程上发布多个答案的习惯,但根据您的帮助性评论,似乎您发布的"潜在"原因是希望保持ComboBox与当前连接的COM端口最新。
对于你一开始尝试做的事情,可能有一个更好的解决方案(这被称为X-Y Problem)。在我以前的工作中,我经常做串行端口和USB枚举。我在这一过程中学到的一个技巧是,每当物理事件发生时,WinOS都会发布一条消息WM_DEVICECHANGE,比如插入或断开USB串行端口设备。
在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();
    }
}
    • 绑定列表**

我仍然主张将组合框的DataSource属性赋给BindingList<ComPort>,其中ComPort是我们设计用来表示连接信息的类:

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}";
    }
}
    • 枚举**

这种方法需要做一些额外的工作,但枚举会生成一个包含其他有用信息的描述符:

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
    }

现在不再需要组合框的DropDown事件。问题解决了!

vc9ivgsu

vc9ivgsu2#

下面是使用Clear的替代方法。
一个解决方案是有一个绑定列表,它是你的组合框的数据源。当你枚举可用的COM端口时(如下面的模拟所示),你将根据可用性从绑定源中添加或删除com端口。这应该会产生你想要的行为,因为如果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);
}

相关问题