Search code examples
c#winformseto

How do I generate a call to the UI thread on an ETO application using C#?


I want to build a cross platform GUI app for linux and windows. ETO was recommended https://github.com/picoe/Eto I'm pretty used to winforms, but I'm stuck trying to get ETO to respond to a repeated call from a timer. The examples I've found require using the winforms this.InvokeRequired, which doesn't exist under Linux.

I want a timer to start up and send a signal to update the GUI textbox every 100ms.

I have this:

// This timer needs to kick off every 100ms to update the GUI.
Timer myTimer = new Timer(TimerTick, // the callback function
new object(),
0,
100); // Every 100ms

private static void TimerTick(object state)
{
    if (ProcessTics == true)
    { Task.Factory.StartNew(CallTheBackgroundFunctions); }

}


private static void CallTheBackgroundFunctions()
{
    OtherClass.ProcessTic();
    mainTextDisplay.Text = OtherClass.GetText(); //This errors out
}

The problem is the timer is a static instance and I've written my OtherClass as a static instance. If I provide a reference to the main form, this.Invalidate(); doesn't update the gui.

I believe I need to use ETO's invoke, but I'm really struggling to make it work.

Any suggestions for a good way to have a background thread update the GUI on an ETO form? Or suggestions for another cross platform GUI framework that supports C#? This is all for a text processing project.

Thank you for your time.


Solution

  • The real trick was to properly call Application.Instance.Invoke delegate.
    In case anyone else runs into a similar issue, here's how I arranged the classes to update a textbox from another thread.

    public static class TextRender
    {   
        public delegate void BroadCastText(object sender, EventArgs e, string message);
        public static BroadCastText Broadcasting;
    
    
    
        public static void ProcessTic()
        {       
            string outboundText = "";
            // do some behind the scenes work.
    
            if (Broadcasting != null) // Verify someone has subscribed to the event, otherwise null object error.
            {
                Broadcasting(null, EventArgs.Empty, outboundText);
            }
            else
            {
                Console.WriteLine("I'm talking but no one is listening!!");
            }
        }
    }
    
    public class MainForm : Form
    {
        Timer myTimer; // High resolution timer.
        UITimer UITimerTick = new UITimer(); // Low resolution timer, but runs on the UI thread.
        TextArea mainTextDisplay = new TextArea(); // Textbox that needs to be updated in realtime.
        
    
        
       private static void TimerTick(object state)
       {
           TextRender.ProcessTic();
       }
    
    
       /// <summary>
       /// Takes the incoming string and uses the UI thread to update the display.
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       /// <param name="message"></param>
       private void WriteTextDelegate(Object sender, EventArgs e, string message)
       {
           Application.Instance.Invoke(delegate {
               mainTextDisplay.Text = message;
           });
       }
    
       /// <summary>
       /// Toggles a timer that performs a callback to update the UI.
       /// </summary>
       private void Timer1()
       {
           if (Timer1Active)
           {
               TextRender.Broadcasting -= WriteTextDelegate;
               myTimer.Dispose();
               Timer1Active = false;
           }
           else
           {
               myTimer = new Timer(TimerTick, // the callback function
               new object(), 
               0, // the time to wait before the timer starts it's first tick
               50); // the tick interval
    
               TextRender.Broadcasting += WriteTextDelegate;  //  Subscribe to the event
               Timer1Active = true;
               Console.WriteLine("Timer 1 start");
           }
       }
    
       /// <summary>
       /// Toggles using the UI timer solution.  This is not intended to update faster than 1 per second.  
       /// </summary>
       private void Timer2()
       {
           if (Timer2Active)
           {
               UITimerTick.Stop();
               UITimerTick.Dispose();
               Timer2Active = false;
           }
           else
           {
               UITimerTick.Interval = 0.5;  //half second refresh.  Can't go any faster without window freezing.
               UITimerTick.Elapsed += (sender, ev) =>
               {
                   TextRender.ProcessTic();
                   mainTextDisplay.Text = TextRender.CachedText;
               };
               
               UITimerTick.Start();
               Timer2Active = true;
               Console.WriteLine("Timer 2 start.");
           }
       }
    
    }