Winforms ListView项目选择错误

1cosmwyk  于 2022-11-17  发布在  其他
关注(0)|答案(1)|浏览(225)

我正在制作一个使用MultiSelect = false控件的应用程序。在某些情况下,我需要阻止用户更改选定的项。我原以为这是一个简单的任务,但几个小时后,我仍在试图弄清楚发生了什么。
因此,为了能够选择“冻结”ListView的选择,我创建了一个自定义类CListView,它继承自ListView。如果FreezeSelection设置为true,那么每次用户更改选择时,我都会尝试将其改回来:

public class CListView : ListView
{
    public bool FreezeSelection { get; set; } = false;

    bool _applyingSelectionUpdates = false;

    protected override void OnSelectedIndexChanged(EventArgs e)
    {
        if (FreezeSelection)
        {
            if (_applyingSelectionUpdates)
                return;

            // for simplicity consider that the selected index while the selection is frozen is always 2
            int selectedIndex = 2;

            _applyingSelectionUpdates = true;
            try
            {
                SelectedIndices.Clear();
                if (selectedIndex >= 0)
                    SelectedIndices.Add(selectedIndex);
            }
            finally { _applyingSelectionUpdates = false; }

            return;
        }

        base.OnSelectedIndexChanged(e);
    }
}

问题是当我将FreezeSelection设置回false时,用户试图选择一个不同的项。首先,即使MultiSelect为false,从视觉上看,它似乎选择了两个项。但从程序上看,当用户更改选择时,有时似乎选择了正确的项,有时没有选择任何项。
这个行为显然是一个bug,我怀疑是什么导致了这个bug。当用户点击一个项目时,SelectedIndexChanged事件被触发了两次。一次是在SelectedIndices集合被清除之后,第二次是在点击的项目被添加到所选项目的集合之后。我认为这个bug是由于在这两次事件之间改变了所选项目而引起的。但我需要了解更多关于这方面的信息。如果MultiSelect为真,用户试图用Ctrl键选择项目,我没有问题。
要重现此错误,可以使用以下TestForm

public class TestForm : Form
{
    CListView listView;
    CheckBox checkBox;

    public TestForm()
    {
        listView = new() { Dock = DockStyle.Fill, View = View.Details, FullRowSelect = true, MultiSelect = false };
        listView.Columns.Add("col 1");
        listView.SelectedIndexChanged += ListView_SelectedIndexChanged;
        Controls.Add(listView);

        checkBox = new() { Dock = DockStyle.Right, Text = "freeze selection" };
        checkBox.CheckedChanged += CheckBox_CheckedChanged;
        Controls.Add(checkBox);

        listView.Items.Add("item 1");
        listView.Items.Add("item 2");
        listView.Items.Add("item 3");
        listView.Items.Add("item 4");
    }

    private void CheckBox_CheckedChanged(object? sender, EventArgs e)
    {
        listView.FreezeSelection = checkBox.Checked;
    }

    DateTime lastSelChangeTime = DateTime.MinValue;
    private void ListView_SelectedIndexChanged(object? sender, EventArgs e)
    {
        if ((DateTime.Now - lastSelChangeTime).TotalMilliseconds > 200)
            Debug.WriteLine(""); // this is just to group together what happens on a single user interaction

        var indices = listView.SelectedIndices.Cast<int>().ToArray();
        Debug.WriteLine("CListView fired selection changed event! "
            + DateTime.Now.ToString("h:m:s:fff") + " "
            + "{ " + string.Join(", ", indices) + " }");

        lastSelChangeTime = DateTime.Now;
    }
}

如果运行此表单:
1.选择第三项(索引为2)
1.勾选“冻结选择”
1.单击第四项
1.取消选中“冻结选择”
1.现在尝试更改所选项并观察错误
问题是如何解决这个bug或者如何实现我的最初目标(防止用户选择不同的项目)。

**更新:**为了澄清,我所说的“bug”并不是我在一次选择更改中得到两个事件(我对此没有意见),而是在我“解冻”选定的索引后UI和ListView.SelectedIndices之间的不一致行为。我将用下面的图片演示这个问题(注意,每个屏幕截图都是在我单击光标所在的位置后拍摄的;每次我得到SelectedIndexChanged事件时,输出窗口也会显示SelectedIndices):

我使用的是.NET 6.0。

vu8f3i0k

vu8f3i0k1#

正如其他人已经提到的,这里没有bug,如选择项目1,然后选择项目2的顺序所示(它首先通过取消选择项目1来更改选择)。

如果您不想让用户在执行某个任意任务(比如等待保存一个修改过的文档)时选择内容,为什么不在执行该任务时将ListView.Enabled设置为false呢?在下面引用的测试代码中,我为复选框更改时设置了一个一体化的设置,将SelectionIndices集合设置为'2',就像您的帖子中一样;

现在,返回到freeze selection未选中的状态并选择某个新项目不会有任何问题。

public TestForm()
{
    InitializeComponent();
    listView.MultiSelect = false;
    listView.Columns.Add("col 1");
    for (int i = 1; i <= 4; i++) listView.Items.Add($"Item {i}");

    listView.SelectedIndexChanged += (sender, e) =>
    {
        richTextBox.AppendLine(
            $"{DateTime.Now} : [{string.Join(", ", listView.SelectedIndices.Cast<int>())}]" );
        var sel =
            listView
            .SelectedItems
            .Cast<ListViewItem>();
        if (sel.Any())
        {
            foreach (var item in sel)
            {
                richTextBox.AppendLine(item);
                richTextBox.AppendLine();
            }
        }
        else richTextBox.AppendLine("No selections", Color.Salmon);
    };

    checkBox.CheckedChanged += (sender, e) =>
    {
        listView.Enabled = !checkBox.Checked;
        if (checkBox.Checked) doWork();
    };

    void doWork()
    {
        listView.SelectedIndices.Clear();
        listView.SelectedIndices.Add(2);
    }
}
  • 对RichTextBox使用此扩展 *
static class Extensions
{
    public static void AppendLine(this RichTextBox richTextBox) =>
        richTextBox.AppendText($"{Environment.NewLine}");
    public static void AppendLine(this RichTextBox richTextBox, object text) =>
        richTextBox.AppendText($"{text}{Environment.NewLine}");

    public static void AppendLine(this RichTextBox richTextBox, object text, Color color)
    {
        var colorB4 = richTextBox.SelectionColor;
        richTextBox.SelectionColor = color;
        richTextBox.AppendText($"{text}{Environment.NewLine}");
        richTextBox.SelectionColor = colorB4;
    }
}

相关问题