1
votes

I'm currently stuck with this problem: my project communicates via the TR.064 interface with the Fritz!Box (router) to get information about the user in my LAN.

This action is permanently done in the background using a BackgroundWorker.

The BW calls an object of the Communication class to gain the list of active hosts. A second list contains all user, that are on a certain watch-list. Users on this watchlist are highlighted.

Then, for every user a ToolStripMenuItem is created that displays the hostname and its IP address.

All items are added to a temporary static class variable, to pass it to the bw-RunCompleted method.

This method now clears the ContextMenuStrip of the NotifyIcon and adds a "starter menu" that has the standard buttons like quit, settings, help, etc.

After that, all items of the temporary class variable are then added to the ContextMenuStrip.

But while doing this, the list of the ToolStripItems of the temporary contextmenu are deleted after assigning them to the 'main'-ContextMenuStrip which causes an exception.

For example:

The bw gets a list of 8 hosts from the router. It then stores them correctly in the temporary menu. The temporary menu has now 8 items.

When I loop through the temporary menu to assign every item to the 'main' menu, this item gets deleted in the temporary list:

temp  |    main   |   items copied
8     |    0      |   0
7     |    1      |   1

--> throws Exception because list has changed (see picture)

private static void bw_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
    watchBoxCommunicator.askFritzBoxForHosts();

    // wait until Communicator finished running
    while (watchBoxCommunicator._isRunning) ;

    // now create a new menu if needed
    // --> only create new menu, if there are new hosts or hosts that went offline
    if (watchBoxCommunicator.hosts_on.Count > 0 || watchBoxCommunicator.hosts_off.Count > 0)
    {
        var tempMenu = new ContextMenuStrip();
        //tempMenu.Dock = DockStyle.Bottom;

        // first sort the elements in the onlineList
        // only sort the watchlist hosts, when there are objects in the list
        // the online user list must at least contain one user as the computer running this code is online
        List<GetGenericHostEntryResponse> tempListNonWatchlist = watchBoxCommunicator.onlineHosts.OrderBy(hostObject => hostObject.NewHostName).ToList();
        List<GetGenericHostEntryResponse> tempListWatchlist = new List<GetGenericHostEntryResponse>();
        try
        {
            tempListWatchlist = watchBoxCommunicator.hostsOnWatchlist.OrderBy(hostObject => hostObject.NewHostName).ToList();
        }
        catch (ArgumentNullException)
        {
            // do nothing
            tempListWatchlist = new List<GetGenericHostEntryResponse>();
        }
        finally
        {
            // back-associate the hosts to the watchBoxCommunicator object to grant access to the sorted lists by other methods
            watchBoxCommunicator.onlineHosts = tempListNonWatchlist;
            watchBoxCommunicator.hostsOnWatchlist = tempListWatchlist;
        }

        // add only the hosts that are not on the watchlist
        foreach (var host in watchBoxCommunicator.onlineHosts)
        {
            if (watchBoxCommunicator.hostsOnWatchlist.Contains(host)) continue;
            var menuItem = new ToolStripMenuItem();
            menuItem.Text = host.NewHostName + " (" + host.NewIPAddress + ")";
            menuItem.Enabled = false;
            if (host.NewInterfaceType.Equals("Ethernet"))
                menuItem.Image = Properties.Resources.network_cable_64px;
            else menuItem.Image = Properties.Resources.wi_fi_logo_64px;
            tempMenu.Items.Add(menuItem);
        }
        // add all hosts that are on the watchlist to display them at the top of the menu
        foreach (var host in watchBoxCommunicator.hostsOnWatchlist)
        {
            var menuItem = new ToolStripMenuItem();
            menuItem.Text = host.NewHostName + " (" + host.NewIPAddress + ")";
            if (host.NewInterfaceType.Equals("Ethernet"))
                menuItem.Image = Properties.Resources.network_cable_64px;
            else menuItem.Image = Properties.Resources.wi_fi_logo_64px;
            menuItem.BackColor = Color.FromArgb(255, 255, 0);
            tempMenu.Items.Add(menuItem);
        }

        // pass the menu to the RunCompleted to update the NotifyIcon's contextmenu
        argumentMenu = tempMenu;
    }
    else
    {
        argumentMenu = new ContextMenuStrip();
    }
}

private static void bw_RunCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
    // if the argument's type is a contextmenustrip, update the contextmenu
    // otherwise, jump to last line and restart backgroundworker
    if (argumentMenu.Items.Count > 0)
    {
        // interrupt layout updater
        wb_menu.SuspendLayout();
        wb_menu.Items.Clear();
        wb_menu.Items.AddRange(starterMenu.Items);


        // add <argumentMenu> item-list to the item-list of <wb_menu>
        //wb_menu.Items.AddRange(argumentMenu.Items);
        ToolStripItemCollection itemC = argumentMenu.Items;
        foreach (var item in itemC)
        {
            var itemN = item as ToolStripMenuItem;
            wb_menu.Items.Add(itemN);
        }

        // update the <wb_Icon> icon
        if (watchBoxCommunicator.hostsOnWatchlist.Count == 0)
        {
            wb_Icon.Icon = Properties.Resources.WatchBoxFullGreen;
        }
        else
        {
            wb_Icon.Icon = Properties.Resources.WatchBoxFullRed;
        }

        // continue layout updater
        wb_menu.ResumeLayout();
    }

    // start the background-worker again
    bw.RunWorkerAsync();
}

I really don't know why the list changes when I add the items to another list. Also the list is not touched or modified or read by any other thread at any time while 'copying' the items.