Search code examples
c#asynchronoustimertreeview

Accessing UI thread while using System.Timer and TreeView


I have a TreeView in-which some of the nodes have a tag property that contains an IP address.

The TreeView has an ImageList that contains 3 entries, a red circle, a green circle and an orange circle.

I want to ping the appropriate IP address every x seconds (I currently have a timer that send a 'pulse' every minute) and then update the ImageIndex of the associated Node to represent the correct output (failed = red, etc).

What I am stuck on is updating the UI thread during this operation, I'm sure that the Timer works asynchronously and so it won't be on the same thread as the UI.

I assumed that calling a method that isn't asynchronous would allow me to update the UI however I'm getting an error that I am on the wrong thread to edit the UI.

Here's my method that runs on the Timer's thread, which iterates through each list and sends a ping request to each IP.

private void OnTimedEvent(Object source, ElapsedEventArgs e)
{
    // Iterate through all root nodes
    foreach(TreeNode tn in mainTree.Nodes)
    {
        // Iterate through all the children of the 'root' nodes
        foreach(TreeNode child in tn.Nodes)
        {
            // Extract all nodes from these children
            TreeNodeCollection myNodes = ((TreeNode)child).Nodes;

            // Create ping object
            System.Net.NetworkInformation.Ping pinger = new();
            PingReply pingReply;

            // Iterate through each of the nodes, send a ping request and then update the UI based on the result of the ping
            foreach(TreeNode node in myNodes)
            {
                if(node.Tag != null)
                {
                    pingReply = pinger.Send(node.Tag.ToString());

                    if (pingReply.Status.ToString().Contains("Success"))
                    {
                        UpdateUI(node, 0); // If successful, set the image index to show green 
                    } 
                    else if (pingReply.Status.ToString().Contains("Failed"))
                    {
                        UpdateUI(node, 1);
                    }
                }
            }
        }
             
    }
}

How I'm trying to update the UI:

public static void UpdateUI(TreeNode node, int image) // int image = the index of the image 0 = green, 1 = red, 2 = orange for UI elements
{
    node.ImageIndex = image;
}

The Timer:

private void StartTimer()
{
    aTimer = new System.Timers.Timer(60000); // Set interval to every minute
    aTimer.Elapsed += OnTimedEvent;
    aTimer.AutoReset = true;
    aTimer.Enabled = true;
}

Initialising the timer:

public Form1()
{
    InitializeComponent();

    StartTimer();

    // Instantiate TreeViewSerializer class for persistence
    TreeViewSerializer tvS = new TreeViewSerializer();
    // Deserealize tree view for persistence
    tvS.DeserializeTreeView(mainTree, xmlFile);
    mainTree.ImageList = imageList1;
    mainTree.ExpandAll();
}

How do I access the UI thread in order to be able to update the UI using the values returned from a successful/failed ping?


Solution

  • Try to change your method:

    public static void UpdateUI(TreeNode node, int image) // int image = the index of the image 0 = green, 1 = red, 2 = orange for UI elements
    {
        node.ImageIndex = image;
    }
    

    to:

    public static void UpdateUI(TreeNode node, int image) // int image = the index of the image 0 = green, 1 = red, 2 = orange for UI elements
    {
        this.Invoke(new Action(() =>
        {
           node.ImageIndex = image;
        }));
    }