0

I've got this code in a Windows CE app:

for (int i = listBoxWork.Items.Count - 1; i > -1; i--)
{
    if (listBoxWork.Items[i].ToString().IndexOf(listboxVal) != -1)
    {
        listBoxWork.Items.RemoveAt(i);
    }
}

It crashes with "Invalid Operation Exception at System.Collections.ArrayList.ArrayListEnumeratorSimple.MoveNext()..."

I try the same exact code in a "regular" Windows forms app, and it works perfectly. If this is something Windows CE can't handle in this way, how can I remove an item from a Listbox?

UPDATE

This is the "VS 2013 code" (Target framework == .NET Framework 4.5.1, Platform target == Any CPU) that works (the "blaINV" item in the listbox is removed):

private void button44_Click(object sender, EventArgs e)
{
    String listboxVal = "blaINV";
    for (int i = listboxWork.Items.Count - 1; i >= 0; --i)
    {
        if (listboxWork.Items[i].ToString().IndexOf(listboxVal) != -1)
        {
            listboxWork.Items.RemoveAt(i);
        }
    }
}

The using statements on that form (this is a "sandbox" form where I test out all kinds of things):

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.OleDb;
using System.DirectoryServices.AccountManagement;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Xml;

The "VS 2008" code follows.

Note first, though, that this project's Target framework should be .NET 3.5, but "Target Framework:" is grayed out in Project > Properties > Application tab; if I select Project > Change Target Platform, "Current platform:"== Windows CE)

But if I go to Project > Properties > Build tab, Configuration is set to "Active (Debug)", Platform is set to "Active (Any CPU)", and Platform target is set to "Any CPU" and is the only option in the dropdown.

On the Devices tab (Project > Properties > Devices), Target device is set to "Pocket PC 2003 SE Emulator" but I don't really use that. When I need to test, I just copy the .exe to the handheld device.

Anyway, here's the code (which fails):

private void UpdateGUIAfterTableSend(String listboxVal)
{
    for (int i = listBoxWork.Items.Count - 1; i >= 0; --i)
    {
        if (listBoxWork.Items[i].ToString().IndexOf(listboxVal) != -1)
        {
            listBoxWork.Items.RemoveAt(i);
        }
    }
}

As you can see, it is identical except that a "bogus" string to search for is provided in the working code. The actual listbox contains a value that matches "listboxVal"; yet, not only is it not removed, the exception as noted above occurs.

Here are the only using statements on the non-working form:

using System;
using System.Windows.Forms;

namespace HHS
{
    using System.Collections.Generic; // R# put this "using" here; like Don Henley in "The Last Resort," I don't know why

UPDATE 2

I moved the after-the-namespace using to above-the-namespace just to see if it would, by any chance, make any difference. It didn't.

UPDATE 3

I added a catch block to the code:

try
{
    for (int i = listBoxWork.Items.Count - 1; i >= 0; --i)
    {
        if (listBoxWork.Items[i].ToString().IndexOf(listboxVal) != -1)
        {
            listBoxWork.Items.RemoveAt(i);
        }
    }
}
catch (Exception ex)
{
    MessageBox.Show(String.Format(
        "Exception in UpdateGUIAfterTableSend(). Message == {0}; InEx == {1}; StackTrace == {2}", 
            ex.Message, ex.InnerException, ex.StackTrace));
}

...but I don't see the Message Box - the previous exception displays and, on its dismissal, the app crashes - even with a thin metallic "bing" sound, which I've never heard from the device before.

UPDATE 4

Okay, to try to get to the bottom of this, I sprinkled MessageBox.Show()s galore into the code:

private void UpdateGUIAfterTableSend(String listboxVal)
{
    try
    {
        MessageBox.Show("Made it before for loop in UpdateGUIAfterTableSend()");
        for (int i = listBoxWork.Items.Count - 1; i >= 0; --i)
        {
            MessageBox.Show("Made it before if condition in UpdateGUIAfterTableSend()");
            if (listBoxWork.Items[i].ToString().IndexOf(listboxVal) != -1)
            {
                MessageBox.Show("Made it before remove line in UpdateGUIAfterTableSend()");
                listBoxWork.Items.RemoveAt(i);
            }
        }
        MessageBox.Show("Made it before listBoxMessages.Items.Add() in     
          UpdateGUIAfterTableSend()");
        listBoxMessages.Items.Add(String.Format("{0} sent at {1}", listboxVal, 
          DateTime.Now.ToLongTimeString()));
        MessageBox.Show("Made it after listBoxMessages.Items.Add() in 
           UpdateGUIAfterTableSend()");
    }
    catch (Exception ex)
    {
        MessageBox.Show(String.Format(
            "Exception in UpdateGUIAfterTableSend(). Message == {0}; InEx == {1}; StackTrace 
               == {2}", ex.Message, ex.InnerException, ex.StackTrace));
    }
}

This is called like so:

private void menuItemSEND_Inventories_Click(object sender, EventArgs e)
{
    SendInventories();
}

private void SendInventories()
{
    Cursor curse = Cursor.Current;
    Cursor.Current = Cursors.WaitCursor;
    try
    {
        foreach (String tblname in listBoxWork.Items)
        {
            // Ignore DSD tables
            if (tblname.IndexOf("DSD") == 0) continue;
            int siteNum = HHSDBUtils.GetSiteNumForTableName(tblname);
            String fileName = HHSUtils.GetGeneratedINVFileName(siteNum);
            String xmlData = HHSDBUtils.SaveAndGetINVDataAsXMLFromTable(tblname, fileName);
            String uri = 
                String.Format("http:100.200.400.800:1500/api/inventory/sendXML/duckbill/platypus/{0}", fileName);
            RESTUtils.SendHTTPRequestNoCredentials(uri, RESTUtils.HttpMethods.POST, xmlData, 
               "application/xml");
            HHSDBUtils.DeleteTableReference(tblname, "INV");
            HHSDBUtils.DropSQLiteTable(tblname, siteNum);
            UpdateGUIAfterTableSend(tblname);
        }
    }
    finally
    {
        Cursor.Current = curse;
    }
}

So you can see, nothing happens after UpdateGUIAfterTableSend() is called. This is what I see before selecting Send > Inventories and setting off this chain of events:

enter image description here

...and this is what I see just before the crash (the listboxitem removal code actually is working, I can see now):

enter image description here

On dismissing that last "made it" MessageBox.Show(), it crashes as noted previously. Why?!?!? There's nothing else going on after that!?!

UPDATE 5

So I added a catch block to the method ( SendInventories()) that is actually seemingly the culprit:

catch (Exception ex)
{
    MessageBox.Show(String.Format(
        "Exception in SendInventories(); Message == {0}, InEx == {1}, StackTrace == {2}", 
            ex.Message, ex.InnerException, ex.StackTrace));
}  

...and I see this:

enter image description here

However, by catching the exception in this way, the app doesn't crash. And it seems harmless - what I want to accomplish has been accomplished. So, although certainly curious about the ins and outs of this exception (no pun intended), maybe I'll just suppress/swallow it for now...

UPDATE 6

So if I change my catch block to this:

catch (Exception ex)
{
    if (!ex.Message.Contains("InvalidOperationException"))
    {
        MessageBox.Show(String.Format(
            "Exception in SendInventories(); Message == {0}, InEx == {1}, StackTrace == {2}",
                ex.Message, ex.InnerException, ex.StackTrace));
    }
}            

...it works just fine (I see no exception, and both listboxes are updated as intended). I guess I'll just chalk it up to the vagaries of the wacky Windows CE world.

B. Clay Shannon-B. Crow Raven
  • 8,547
  • 144
  • 472
  • 862

1 Answers1

2

This code fails because for loop has saved total item in your listbox as 10.
Then, you are evil, by taking one item, leaving the listbox to continue looping until 10 while in reality, your listbox item counts only 9 (you have removed one item just now).

One way is to use:

if (listBoxWork.Items.Contains(listboxVal)
{
    Get just the index;
    Remove it from the listBoxWork;
}


UPDATE
Inside your for loop, you can set index = index - 1 just after you remove the item.
Visual Studio 2008 is still primitive, you cannot expect it to behave like VS 2013.

Cheers,

Jedi
  • 87
  • 2
  • Then why does it work in VS 2013? In the scenario where it's failing, there's only two items in the listbox. The second one, at index 1, is the one to be removed. The loop first looks at index 1 (count-1). If it finds "listboxval" it *should* remove that item. Now there is only one item left, at index 0, and the loop runs one more time, looks for a match, doesn't find it, and exits. That's what *should* happen. And that's what *does* happen in VS 2013. It's just the project in VS 2008 that's having the problem. Not sure why yet. – B. Clay Shannon-B. Crow Raven Nov 06 '14 at 05:14
  • without a loop on my device with windows CE 5 removeAt failed , but if you set selecteditem =-1 right after it, it does work – dwana Mar 12 '16 at 21:48