Table of Contents

Event Processing and the Application Main Loop

These are the v1 API docs. The v2 API docs are here.

See also Cross-platform Driver Model

The method Application.Run that we covered before will wait for events from either the keyboard or mouse and route those events to the proper view.

The job of waiting for events and dispatching them in the Application is implemented by an instance of the MainLoop class.

Mainloops are a common idiom in many user interface toolkits so many of the concepts will be familiar to you if you have used other toolkits before.

This class provides the following capabilities:

  • Keyboard and mouse processing
  • .NET Async support
  • Timers processing
  • Invoking of UI code from a background thread
  • Idle processing handlers
  • Possibility of integration with other mainloops.
  • On Unix systems, it can monitor file descriptors for readability or writability.

The MainLoop property in the the Application provides access to these functions.

When your code invokes Application.Run (Toplevel), the application will prepare the current Toplevel instance by redrawing the screen appropriately and then calling the mainloop to run.

You can configure the Mainloop before calling Application.Run, or you can configure the MainLoop in response to events during the execution.

The keyboard inputs is dispatched by the application class to the current TopLevel window this is covered in more detail in the Keyboard Event Processing document.

Async Execution

On startup, the Application class configured the .NET Asynchronous machinery to allow you to use the await keyword to run tasks in the background and have the execution of those tasks resume on the context of the main thread running the main loop.

Once you invoke Application.Main the async machinery will be ready to use, and you can merely call methods using await from your main thread, and the awaited code will resume execution on the main thread.

Timers Processing

You can register timers to be executed at specified intervals by calling the AddTimeout method, like this:

void UpdateTimer ()
{
	time.Text = DateTime.Now.ToString ();
}

var token = Application.MainLoop.AddTimeout (TimeSpan.FromSeconds (20), UpdateTimer);

The return value from AddTimeout is a token value that you can use if you desire to cancel the timer before it runs:

Application.MainLoop.RemoveTimeout (token);

Idle Handlers

You can register code to be executed when the application is idling and there are no events to process by calling the AddIdle method. This method takes as a parameter a function that will be invoked when the application is idling.

Idle functions should return true if they should be invoked again, and false if the idle invocations should stop.

Like the timer APIs, the return value is a token that can be used to cancel the scheduled idle function from being executed.

Threading

Like other UI toolkits, Terminal.Gui is generally not thread safe. You should avoid calling methods in the UI classes from a background thread as there is no guarantee that they will not corrupt the state of the UI application.

Generally, as there is not much state, you will get lucky, but the application will not behave properly.

You will be served better off by using C# async machinery and the various APIs in the System.Threading.Tasks.Task APIs. But if you absolutely must work with threads on your own you should only invoke APIs in Terminal.Gui from the main thread.

To make this simple, you can use the Application.MainLoop.Invoke method and pass an Action. This action will be queued for execution on the main thread at an appropriate time and will run your code there.

For example, the following shows how to properly update a label from a background thread:

void BackgroundThreadUpdateProgress ()
{
	Application.MainLoop.Invoke (() => {
		progress.Text = $"Progress: {bytesDownloaded/totalBytes}";
        });
}

Integration With Other Main Loop Drivers

It is possible to run the main loop in a way that it does not take over control of your application, but rather in a cooperative way.

To do this, you must use the lower-level APIs in Application: the Begin method to prepare a toplevel for execution, followed by calls to MainLoop.EventsPending to determine whether the events must be processed, and in that case, calling RunLoop method and finally completing the process by calling End.

The method Run is implemented like this:

void Run (Toplevel top)
{
	var runToken = Begin (view);
	RunLoop (runToken);
	End (runToken);
}

Unix File Descriptor Monitoring

On Unix, it is possible to monitor file descriptors for input being available, or for the file descriptor being available for data to be written without blocking the application.

To do this, you on Unix, you can cast the MainLoop instance to a UnixMainLoop and use the AddWatch method to register an interest on a particular condition.