mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
162 lines
5.1 KiB
Markdown
162 lines
5.1 KiB
Markdown
# Event Processing and the Application Main Loop
|
|
|
|
_See also [Cross-platform Driver Model](drivers.md)_
|
|
|
|
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`](~/api/Terminal.Gui/Terminal.Gui.Application.yml)
|
|
provides access to these functions.
|
|
|
|
When your code invokes `Application.Run (Toplevel)`, the application
|
|
will prepare the current
|
|
[`Toplevel`](~/api/Terminal.Gui/Terminal.Gui.Toplevel.yml) 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](keyboard.md) 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:
|
|
|
|
```csharp
|
|
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:
|
|
|
|
```csharup
|
|
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.
|