为什么删除列表框项目会在循环中第二次崩溃?
Why does removing a listbox Item crash the second time through the loop?
这是一个持续存在的问题;在下面的代码中,旧代码(也失败了)被注释掉了。不过,新代码的行为方式相同:
private void UpdateGUIAfterTableSend(String listboxVal)
{
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
try
{
//listBoxWork.DataSource = null; // <= This (at one time, anyway) seemed necessary to circumvent "Value does not fall within the expected range"
//// Failing ignominiously; question at
//for (int i = listBoxWork.Items.Count - 1; i >= 0; --i) // try a foreach instead?
//{
// if (listBoxWork.Items[i].ToString().Contains(listboxVal))
// {
// listBoxWork.Items.RemoveAt(i);
// }
//}
BindingSource bs = listBoxWork.DataSource as BindingSource;
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend#2");
for (int i = bs.Count - 1; i >= 0; --i)
{
if (bs[i].ToString().Contains(listboxVal))
{
bs.RemoveAt(i);
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend#3");
}
}
}
catch (Exception ex)
{
String msgInnerExAndStackTrace = String.Format("{0}; Inner Ex: {1}; Stack Trace: {2}", ex.Message, ex.InnerException, ex.StackTrace);
ExceptionLoggingService.Instance.WriteLog(String.Format("From frmMain.UpdateGUIAfterTableSend: {0}", msgInnerExAndStackTrace));
}
}
好像是第二次通过for循环,app突然崩溃了。我这样说是因为日志文件显示 "Reached frmMain.UpdateGUIAfterTableSend#3" 而不是来自 catch 块的日志消息。所以它一定是在四循环的第二次迭代中崩溃了(在再次记录 "Reached frmMain.UpdateGUIAfterTableSend#3" 之前)。所以一旦崩溃就晚安了,Irene:它崩溃得如此之快,以至于它甚至没有记录 catch 块的日志消息。还是有别的解释?
但更重要的是:为什么第二次检查 "Contains" 或第二次尝试删除(在我的测试用例中不应该发生,列表框只有一个项目)引爆了代码-消失了-野核装置?
这是崩溃后的最后一个日志文件:
Date: 2/11/2015 12:45:34 PM
Message: Reached frmMain.UpdateGUIAfterTableSend
Date: 2/11/2015 12:45:34 PM
Message: Reached frmMain.UpdateGUIAfterTableSend#2
Date: 2/11/2015 12:45:34 PM
Message: Reached frmMain.UpdateGUIAfterTableSend#3
Date: 2/11/2015 12:45:34 PM
Message: From application-wide exception handler: System.InvalidOperationException: InvalidOperationException
at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()
at HHS.frmMain.SendDeliveries()
at HHS.frmMain.menuItemSEND_Deliveries_Click(Object sender, EventArgs e)
at System.Windows.Forms.MenuItem.OnClick(EventArgs e)
at System.Windows.Forms.Menu.ProcessMnuProc(Control ctlThis, WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Form.WnProc(WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)
at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)
at System.Windows.Forms.Application.Run(Form fm)
at HHS.Program.Main()
更新
我在 "RemoveAt" 行之前添加了另一个日志消息,得到了我期望的结果:
Date: 2/11/2015 1:36:53 PM
Message: About to remove listbox value DSD_3_20150209151047000 at index 0
...但之后立即崩溃,所以 Rake36 一定是正确的。
更新 2
我有点相信这会奏效:
private void UpdateGUIAfterTableSend(String listboxVal)
{
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
try
{
ExceptionLoggingService.Instance.WriteLog(String.Format("About to remove listbox value {0}", listboxVal));
listBoxWork.Items.RemoveAt(listBoxWork.Items.IndexOf(listboxVal));
}
catch (Exception ex)
{
String msgInnerExAndStackTrace = String.Format("{0}; Inner Ex: {1}; Stack Trace: {2}", ex.Message, ex.InnerException, ex.StackTrace);
ExceptionLoggingService.Instance.WriteLog(String.Format("From frmMain.UpdateGUIAfterTableSend: {0}", msgInnerExAndStackTrace));
}
}
...但是当我听到这个故事 "bong!" 然后在日志文件中查找时,我的希望破灭了:
Message: About to remove listbox value DSD_3_20150209151047000
Date: 2/11/2015 1:58:18 PM
Message: From frmMain.UpdateGUIAfterTableSend: Specified argument was out of the range of valid values.; Inner Ex: ; Stack Trace: at System.Collections.ArrayList.RemoveAt(Int32 index)
at System.Windows.Forms.ListBox.ObjectCollection.RemoveAt(Int32 index)
at HHS.frmMain.UpdateGUIAfterTableSend(String listboxVal)
at HHS.frmMain.SendDeliveries()
at HHS.frmMain.menuItemSEND_Deliveries_Click(Object sender, EventArgs e)
at System.Windows.Forms.MenuItem.OnClick(EventArgs e)
at System.Windows.Forms.Menu.ProcessMnuProc(Control ctlThis, WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Form.WnProc(WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)
at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)
at System.Windows.Forms.Application.Run(Form fm)
at HHS.Program.Main()
Date: 2/11/2015 1:58:19 PM
Message: From application-wide exception handler: System.InvalidOperationException: InvalidOperationException
at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()
at HHS.frmMain.SendDeliveries()
at HHS.frmMain.menuItemSEND_Deliveries_Click(Object sender, EventArgs e)
at System.Windows.Forms.MenuItem.OnClick(EventArgs e)
at System.Windows.Forms.Menu.ProcessMnuProc(Control ctlThis, WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Form.WnProc(WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)
at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)
at System.Windows.Forms.Application.Run(Form fm)
at HHS.Program.Main()
我什至在 "RemoveAt" 行之前添加了这个:
listBoxWork.DataSource = null;
...但没有任何区别。
更新 3
这个(灵感来自 Ravi M Patel)也崩溃了:
private void UpdateGUIAfterTableSend(String listboxVal)
{
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
try
{
BindingSource bs = new BindingSource();
bs.DataSource = listBoxWork.DataSource;
for (int i = bs.Count - 1; i >= 0; i--)
{
if (bs[i].ToString().Contains(listboxVal))
{
ExceptionLoggingService.Instance.WriteLog(String.Format("About to remove listbox value {0} at index {1}", listboxVal, i));
bs.RemoveAt(i);
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend#3");
}
}
}
catch (Exception ex)
{
String msgInnerExAndStackTrace = String.Format("{0}; Inner Ex: {1}; Stack Trace: {2}", ex.Message, ex.InnerException, ex.StackTrace);
ExceptionLoggingService.Instance.WriteLog(String.Format("From frmMain.UpdateGUIAfterTableSend: {0}", msgInnerExAndStackTrace));
}
}
...日志文件的(相关)内容为:
Date: 2/11/2015 2:09:59 PM
Message: About to remove listbox value DSD_3_20150209151047000 at index 0
Date: 2/11/2015 2:09:59 PM
Message: Reached frmMain.UpdateGUIAfterTableSend#3
Date: 2/11/2015 2:09:59 PM
Message: From application-wide exception handler: System.InvalidOperationException: InvalidOperationException
at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()
at HHS.frmMain.SendDeliveries()
更新 4
有关真正的修复,请参阅
您不能在迭代时销毁列表中的元素。您需要先收集要删除的项目,然后遍历这些项目,在此过程中从原始列表中删除。
有用的模式:
Items.RemoveAt(Items.IndexOf(itemToDelete))
更新 2
将 try{} 块中的代码替换为以下代码:
BindingSource bs = listBoxWork.DataSource as BindingSource;
List<string> values = bs.DataSource as List<string>;
values.RemoveAll(v => v.Contains(listboxVal));
bs.ResetBindings(false);
更新
@TaW 说的对。我正在疯狂猜测这个问题。当您从数据源中删除一个元素时,它也会更新 ListBox。由于您要删除 selected 值,因此 UI 正在尝试 select 下一个值和 bang,还有none.
因此,要使这项工作成功,您应该这样做。假设您已将列表框绑定到名为 values
.
的数组列表
现在,在您的方法中,从 values
而不是 BindingSource 中删除项目。在您的情况下,如果确定列表中只有一个值。您所要做的就是 values.Remove("valuetoremove");
然后像这样重置绑定。
ArrayList values = bs.DataSource as ArrayList;
values.Remove("valuetoremove");
bs.ResetBindings(false);
你可以无视我下面的回答。我认为您要进行类型转换的数据源已经是 BindingSource 类型,因此您对原始声明很满意。 BindingSource bs = listBoxWork.DataSource as BindingSource;
我不知道,因为我们在这里看不到完整的代码。
如果您想要绑定源的正确行为。你最好这样用。
BindingSource bs = new BindingSource();
bs.DataSource = listBoxWork.DataSource;
用您的原始代码试试。
异常来自与您显示的逻辑完全不同的地方。触发的是应用程序范围的异常处理程序,而不是将创建跟踪的异常处理程序 "From frmMain.UpdateGUIAfterTableSend"。异常很可能是因为绑定有副作用。查看绑定源和附加到它的事件。
还要在删除后添加跟踪,以便您可以看到删除确实发生了,或者使用 debugger.Your 更新 3 显示删除确实发生了。
在 SendDeliveries 中添加更多跟踪。你的问题在那里
如果你能做到:
List<string> list = (List<string>) listBoxWork.DataSource;
你应该可以做到这一点:
for (int i = 0; i < list.Count; i++)
{
if (list[i].Contains(listboxVal))
{
list.RemoveAt(i);
}
}
listBox1.DataSource = null;
listBox1.Items.Clear();
listBox1.DataSource = list;
但我怀疑你的 DataSource
是 实际上是 List<string>
因为这根本行不通。 (只会显示字符串的长度!)
因此您应该修改代码以适合您的实际类型 DataSource
!
如果它是这样的:
List<AString> list = new List<AString>();
一个class喜欢:
class AString
{
public string s { get; set; }
public AString(string s_) { s = s_; }
public override string ToString() { return s; }
}
那么这会起作用:
for (int i = list.Count - 1; i > 0; i--)
{
if (list[i].s.Contains(listboxVal))
{
list.RemoveAt(i);
}
}
listBox1.DataSource = null;
listBox1.Items.Clear();
listBox1.DataSource = list;
注意ListBox
doesn't automatically update当DataSource
被修改。
您可以使用 BindingList
:
而不是进行这种令人不满意的往返
BindingList<AString> data = new BindingList<AString>();
foreach (AStrings in list) data.Add(s);
listBox1.DataSource = data;
for (int i = data.Count - 1; i > 0; i--)
{
if (data[i].s.Contains("8"))
{
data.RemoveAt(i); // (list[i]);
}
}
现在删除会立即反映出来..
这是一个持续存在的问题;在下面的代码中,旧代码(也失败了)被注释掉了。不过,新代码的行为方式相同:
private void UpdateGUIAfterTableSend(String listboxVal)
{
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
try
{
//listBoxWork.DataSource = null; // <= This (at one time, anyway) seemed necessary to circumvent "Value does not fall within the expected range"
//// Failing ignominiously; question at
//for (int i = listBoxWork.Items.Count - 1; i >= 0; --i) // try a foreach instead?
//{
// if (listBoxWork.Items[i].ToString().Contains(listboxVal))
// {
// listBoxWork.Items.RemoveAt(i);
// }
//}
BindingSource bs = listBoxWork.DataSource as BindingSource;
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend#2");
for (int i = bs.Count - 1; i >= 0; --i)
{
if (bs[i].ToString().Contains(listboxVal))
{
bs.RemoveAt(i);
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend#3");
}
}
}
catch (Exception ex)
{
String msgInnerExAndStackTrace = String.Format("{0}; Inner Ex: {1}; Stack Trace: {2}", ex.Message, ex.InnerException, ex.StackTrace);
ExceptionLoggingService.Instance.WriteLog(String.Format("From frmMain.UpdateGUIAfterTableSend: {0}", msgInnerExAndStackTrace));
}
}
好像是第二次通过for循环,app突然崩溃了。我这样说是因为日志文件显示 "Reached frmMain.UpdateGUIAfterTableSend#3" 而不是来自 catch 块的日志消息。所以它一定是在四循环的第二次迭代中崩溃了(在再次记录 "Reached frmMain.UpdateGUIAfterTableSend#3" 之前)。所以一旦崩溃就晚安了,Irene:它崩溃得如此之快,以至于它甚至没有记录 catch 块的日志消息。还是有别的解释?
但更重要的是:为什么第二次检查 "Contains" 或第二次尝试删除(在我的测试用例中不应该发生,列表框只有一个项目)引爆了代码-消失了-野核装置?
这是崩溃后的最后一个日志文件:
Date: 2/11/2015 12:45:34 PM
Message: Reached frmMain.UpdateGUIAfterTableSend
Date: 2/11/2015 12:45:34 PM
Message: Reached frmMain.UpdateGUIAfterTableSend#2
Date: 2/11/2015 12:45:34 PM
Message: Reached frmMain.UpdateGUIAfterTableSend#3
Date: 2/11/2015 12:45:34 PM
Message: From application-wide exception handler: System.InvalidOperationException: InvalidOperationException
at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()
at HHS.frmMain.SendDeliveries()
at HHS.frmMain.menuItemSEND_Deliveries_Click(Object sender, EventArgs e)
at System.Windows.Forms.MenuItem.OnClick(EventArgs e)
at System.Windows.Forms.Menu.ProcessMnuProc(Control ctlThis, WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Form.WnProc(WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)
at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)
at System.Windows.Forms.Application.Run(Form fm)
at HHS.Program.Main()
更新
我在 "RemoveAt" 行之前添加了另一个日志消息,得到了我期望的结果:
Date: 2/11/2015 1:36:53 PM
Message: About to remove listbox value DSD_3_20150209151047000 at index 0
...但之后立即崩溃,所以 Rake36 一定是正确的。
更新 2
我有点相信这会奏效:
private void UpdateGUIAfterTableSend(String listboxVal)
{
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
try
{
ExceptionLoggingService.Instance.WriteLog(String.Format("About to remove listbox value {0}", listboxVal));
listBoxWork.Items.RemoveAt(listBoxWork.Items.IndexOf(listboxVal));
}
catch (Exception ex)
{
String msgInnerExAndStackTrace = String.Format("{0}; Inner Ex: {1}; Stack Trace: {2}", ex.Message, ex.InnerException, ex.StackTrace);
ExceptionLoggingService.Instance.WriteLog(String.Format("From frmMain.UpdateGUIAfterTableSend: {0}", msgInnerExAndStackTrace));
}
}
...但是当我听到这个故事 "bong!" 然后在日志文件中查找时,我的希望破灭了:
Message: About to remove listbox value DSD_3_20150209151047000
Date: 2/11/2015 1:58:18 PM
Message: From frmMain.UpdateGUIAfterTableSend: Specified argument was out of the range of valid values.; Inner Ex: ; Stack Trace: at System.Collections.ArrayList.RemoveAt(Int32 index)
at System.Windows.Forms.ListBox.ObjectCollection.RemoveAt(Int32 index)
at HHS.frmMain.UpdateGUIAfterTableSend(String listboxVal)
at HHS.frmMain.SendDeliveries()
at HHS.frmMain.menuItemSEND_Deliveries_Click(Object sender, EventArgs e)
at System.Windows.Forms.MenuItem.OnClick(EventArgs e)
at System.Windows.Forms.Menu.ProcessMnuProc(Control ctlThis, WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Form.WnProc(WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)
at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)
at System.Windows.Forms.Application.Run(Form fm)
at HHS.Program.Main()
Date: 2/11/2015 1:58:19 PM
Message: From application-wide exception handler: System.InvalidOperationException: InvalidOperationException
at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()
at HHS.frmMain.SendDeliveries()
at HHS.frmMain.menuItemSEND_Deliveries_Click(Object sender, EventArgs e)
at System.Windows.Forms.MenuItem.OnClick(EventArgs e)
at System.Windows.Forms.Menu.ProcessMnuProc(Control ctlThis, WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Form.WnProc(WM wm, Int32 wParam, Int32 lParam)
at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)
at Microsoft.AGL.Forms.EVL.EnterMainLoop(IntPtr hwnMain)
at System.Windows.Forms.Application.Run(Form fm)
at HHS.Program.Main()
我什至在 "RemoveAt" 行之前添加了这个:
listBoxWork.DataSource = null;
...但没有任何区别。
更新 3
这个(灵感来自 Ravi M Patel)也崩溃了:
private void UpdateGUIAfterTableSend(String listboxVal)
{
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend");
try
{
BindingSource bs = new BindingSource();
bs.DataSource = listBoxWork.DataSource;
for (int i = bs.Count - 1; i >= 0; i--)
{
if (bs[i].ToString().Contains(listboxVal))
{
ExceptionLoggingService.Instance.WriteLog(String.Format("About to remove listbox value {0} at index {1}", listboxVal, i));
bs.RemoveAt(i);
ExceptionLoggingService.Instance.WriteLog("Reached frmMain.UpdateGUIAfterTableSend#3");
}
}
}
catch (Exception ex)
{
String msgInnerExAndStackTrace = String.Format("{0}; Inner Ex: {1}; Stack Trace: {2}", ex.Message, ex.InnerException, ex.StackTrace);
ExceptionLoggingService.Instance.WriteLog(String.Format("From frmMain.UpdateGUIAfterTableSend: {0}", msgInnerExAndStackTrace));
}
}
...日志文件的(相关)内容为:
Date: 2/11/2015 2:09:59 PM
Message: About to remove listbox value DSD_3_20150209151047000 at index 0
Date: 2/11/2015 2:09:59 PM
Message: Reached frmMain.UpdateGUIAfterTableSend#3
Date: 2/11/2015 2:09:59 PM
Message: From application-wide exception handler: System.InvalidOperationException: InvalidOperationException
at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()
at HHS.frmMain.SendDeliveries()
更新 4
有关真正的修复,请参阅
您不能在迭代时销毁列表中的元素。您需要先收集要删除的项目,然后遍历这些项目,在此过程中从原始列表中删除。
有用的模式:
Items.RemoveAt(Items.IndexOf(itemToDelete))
更新 2
将 try{} 块中的代码替换为以下代码:
BindingSource bs = listBoxWork.DataSource as BindingSource;
List<string> values = bs.DataSource as List<string>;
values.RemoveAll(v => v.Contains(listboxVal));
bs.ResetBindings(false);
更新
@TaW 说的对。我正在疯狂猜测这个问题。当您从数据源中删除一个元素时,它也会更新 ListBox。由于您要删除 selected 值,因此 UI 正在尝试 select 下一个值和 bang,还有none.
因此,要使这项工作成功,您应该这样做。假设您已将列表框绑定到名为 values
.
现在,在您的方法中,从 values
而不是 BindingSource 中删除项目。在您的情况下,如果确定列表中只有一个值。您所要做的就是 values.Remove("valuetoremove");
然后像这样重置绑定。
ArrayList values = bs.DataSource as ArrayList;
values.Remove("valuetoremove");
bs.ResetBindings(false);
你可以无视我下面的回答。我认为您要进行类型转换的数据源已经是 BindingSource 类型,因此您对原始声明很满意。 BindingSource bs = listBoxWork.DataSource as BindingSource;
我不知道,因为我们在这里看不到完整的代码。
如果您想要绑定源的正确行为。你最好这样用。
BindingSource bs = new BindingSource();
bs.DataSource = listBoxWork.DataSource;
用您的原始代码试试。
异常来自与您显示的逻辑完全不同的地方。触发的是应用程序范围的异常处理程序,而不是将创建跟踪的异常处理程序 "From frmMain.UpdateGUIAfterTableSend"。异常很可能是因为绑定有副作用。查看绑定源和附加到它的事件。 还要在删除后添加跟踪,以便您可以看到删除确实发生了,或者使用 debugger.Your 更新 3 显示删除确实发生了。
在 SendDeliveries 中添加更多跟踪。你的问题在那里
如果你能做到:
List<string> list = (List<string>) listBoxWork.DataSource;
你应该可以做到这一点:
for (int i = 0; i < list.Count; i++)
{
if (list[i].Contains(listboxVal))
{
list.RemoveAt(i);
}
}
listBox1.DataSource = null;
listBox1.Items.Clear();
listBox1.DataSource = list;
但我怀疑你的 DataSource
是 实际上是 List<string>
因为这根本行不通。 (只会显示字符串的长度!)
因此您应该修改代码以适合您的实际类型 DataSource
!
如果它是这样的:
List<AString> list = new List<AString>();
一个class喜欢:
class AString
{
public string s { get; set; }
public AString(string s_) { s = s_; }
public override string ToString() { return s; }
}
那么这会起作用:
for (int i = list.Count - 1; i > 0; i--)
{
if (list[i].s.Contains(listboxVal))
{
list.RemoveAt(i);
}
}
listBox1.DataSource = null;
listBox1.Items.Clear();
listBox1.DataSource = list;
注意ListBox
doesn't automatically update当DataSource
被修改。
您可以使用 BindingList
:
BindingList<AString> data = new BindingList<AString>();
foreach (AStrings in list) data.Add(s);
listBox1.DataSource = data;
for (int i = data.Count - 1; i > 0; i--)
{
if (data[i].s.Contains("8"))
{
data.RemoveAt(i); // (list[i]);
}
}
现在删除会立即反映出来..