【发布时间】:2017-09-06 13:26:59
【问题描述】:
我在 Winforms 应用程序中遇到了 Listbox 的问题,它以意外的顺序添加了两次条目。
private async void btnStart_Click(object sender, EventArgs e)
{
Job.Start();
await StartProgressTracking();
}
public async Task StartProgressTracking()
{
while (!Job.Progress.EndTime.HasValue)
{
await Task.Run(() => UpdateJobInformation());
await Task.Delay(TimeSpan.FromMilliseconds(500));
}
}
private void UpdateJobInformation()
{
this.UIThread(() =>
{
listStepHistory.SelectedIndex = -1;
listStepHistory.Items.Clear();
listStepHistory.Items.AddRange(new ListBox.ObjectCollection(listStepHistory, Job.Progress.StepHistory.ToArray()));
if (listStepHistory.Items.Count > 0)
{
//Select the last item (so that it scrolls to the bottom)
listStepHistory.SelectedIndex = listStepHistory.Items.Count - 1;
}
});
}
// The extension method that I'm using
public static void UIThread(this Control @this, Action code)
{
if (@this.InvokeRequired)
{
@this.BeginInvoke(code);
}
else
{
code.Invoke();
}
}
到目前为止的故障排除步骤
-
listStepHistory没有在代码中的其他任何地方引用(当然,除了 Designer.cs 文件)。它没有绑定到数据源。 - 我已经仔细检查了
Job.Progress.StepHistory属性,它不包含两次相同的消息。每个代码都只被提及一次,这是应该的。 - 最初,我认为这是同时发生多个更新的问题。但是,我已经将
Task.Delay(TimeSpan.FromMilliseconds(500))的值减少和增加到极值(1,10,100,1000,10000),并且从未见过超过两个条目,也从未少于两个。这似乎排除了并发 UI 更新作为问题的原因。
有趣的笔记
如果你回头看截图,你会看到 Winforms 选择了代码最高的行(数字),这在整个导入作业的过程中是一致的,最高的数字总是被选中的那一行。这是有道理的,因为我在处理代码之前已经对代码进行了排序,所以它们应该按数字顺序处理。
如果您查看所选项目之后的项目,您会发现这是一个较小的代码。这一行 (11004) 实际上与列表框顶部的第一行相同。同样,选中的行(73109)与列表框的底行相同。
换句话说,重复发生如下(并且bold被选中):
A B C D E A B C D E
这很奇怪,因为我已指示列表框选择最后一项。注意我的UpdateJobInformation() 方法的最后一步:
if (listStepHistory.Items.Count > 0)
{
//Select the last item (so that it scrolls to the bottom)
listStepHistory.SelectedIndex = listStepHistory.Items.Count - 1;
}
即使我错误地添加了两次项目,我希望列表框仍然选择其最后一个项目(无论它是否重复),但事实并非如此。
在我看来,重复的条目是“幻影条目”。它们被渲染到屏幕上,但它们不存在于 listStepHistory.Items 属性中。
我由此得出结论,列表框呈现的内容与其Items 属性中包含的内容不同。
谁能解释这种行为?
附录
根据要求,Job 和 Job.Progress 接口/类定义:
public interface IProgressTrackable
{
ImportProgress Progress { get; }
void Start();
bool Cancel();
}
public class ImportProgress
{
//redacted for brevity
public List<string> StepHistory { get; set; } = new List<string>();
}
【问题讨论】:
-
在我看来,您在循环中多次调用
StartProgressTracking()。循环有什么用?它只是每半秒轮询一次吗?我会尝试通过将 500 毫秒更改为 10,000 来排除重入。 -
@EdPlunkett:
StartProgressTracking()不是从循环内部调用的。此外,虽然在问题的示例中省略了,但启动按钮在单击后实际上会自行禁用,您无法启动它两次,也通过断点确认)我特别提到在我的第三个故障排除步骤中修改延迟时间。重复的条目(总是恰好是两倍)似乎排除了并发问题(然后我希望超过 2 个条目会出现非常短的延迟,并且问题会以足够大的延迟自行解决,但事实并非如此) -
选择“错误”项的原因是您添加了两次相同的实际对象实例列表。当您告诉它选择给定对象时,它会在
Items等于 中找到第一个对象并选择它。最后一个对象在那里两次,但第一次出现是它选择的那个。 -
抱歉,我的意思是
UpdateJobInformation()是从循环中调用的。 -
@EdPlunkett:注意我设置了
SelectedIndex,而不是SelectedItem。SelectedIndex不检查项目是否相等。另外,我在哪里第二次添加列表?