How it works - ahatornn/clforms GitHub Wiki

Application launch

Let's see what happens when you start a console application with clforms. The following is an example of a program entry point description

/// <summary>
/// Class of entry point
/// </summary>
class Program
{
    /// <summary>
    /// Entry point of App
    /// </summary>
    private static void Main(string[] args)
        => AppLoader.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .Build()
            .Start(new MainWindow());
}

It all starts with the application loader. His task is to prepare everything necessary for work. AppLoader creates a default application loader that implements IAppBuilder by calling the CreateDefaultBuilder method with arguments to launch the console application. These arguments, you can get at the run-time referring to the global object Application.

var args = Application.StartupParameters;

IAppBuilder also provides access to Microsoft.Extensions.DependencyInjection.IServiceCollection where you can specifies the contract of service descriptors by calling the ConfigureServices method. However, it seems that the most convenient way to register your own services would be a separate place. For these purposes, there is an AppBuilderExtensions which registers any custom class implementing IStartup. This is exactly what we observe immediately after calling the CreateDefaultBuilder method.

.UseStartup<Startup>()

Its represents platform specific configuration that will be applied to a IAppBuilder when building an IApp. This class should be inherit from IStartup interface. Use ConfigureServices method for configure IoC of application. In runtime you can get specified instances like this:

using Microsoft.Extensions.DependencyInjection
var service = Application.ServiceProvider.GetService<instance_type>();

Also you can override core implementations of any types like ISystemColors, IEventLoop, IPseudographicsProvider, IControlLifeCycle, IEnvironment and IApp. The Configure method allow set additional properties of added instances. For example, if you want to set OutputEncoding of IPseudographicsProvider but haven't own implemented, do like this:

provider.SetConfigure<IPseudographicsProvider>(configure =>
{
    configure.OutputEncoding = Encoding.GetEncoding("Utf-8");
});

Look at the Startup file example below

/// <inheritdoc cref="IStartup"/>
public class Startup : IStartup
{
    /// <inheritdoc cref="IStartup.ConfigureServices"/>
    public void ConfigureServices(IServiceCollection services)
    {
        // Optional
        // Called by the application to configure the app's services.
    }

    /// <inheritdoc cref="IStartup.Configure"/>
    public void Configure(IServiceProvider provider)
    {
        provider.SetConfigure<IPseudographicsProvider>(configure =>
        {
            configure.OutputEncoding = Encoding.GetEncoding("Utf-8");
        });
    }
}

After preparing all the necessary services for future work, the application is built and the final assembly of all the dependencies is performed to start the life cycle.

.Build()

At this point, the collection of service descriptors is initialized and all user services are registered.

If you did not redefine the system classes that implement the interfaces from the list below, then their default implementation is registered

  • ISystemColors (Color of a Windows display element)
  • IEventLoop (Event queue processing)
  • IPseudographicsProvider (Represents the standard input, output, and error streams for pseudographics interface)
  • IControlLifeCycle (Represents the life cycle of controls)
  • IEnvironment (Provides information about, and means to manipulate, the current environment and platform)
  • IApp (Represents a configured application)

After that, the Configure method from the previously specified Startup class is executed where all the preparatory work is done with previously registered services. At the end, the Start method is called with a single parameter of the Window type, which initializes the main application window and starts the life cycle until the user closes the main window

.Start(new MainWindow());

После запуска программы и указания главного окна, вы всегда можете получить к нему доступ через Application

var wnd = Application.MainWindow;

Life cycle

Launch the application starts processing the event queue of the IEventLoop object. This handler cyclically processes the queue until the main window is closed. Its task is to extract the event from the queue and execute it. After that, check if a key has been pressed in the console window and calls the keypress handler.
life cycle of keyboard processing
It is checked. if the CTRL+SHIFT+R combination has been pressed, the application terminates. If any other key is pressed, then it is passed to the window handler.
The window checks if the CTRL+F4 combination has been pressed, then the current window closes. If this is the main window of the program, then application finish.

You can terminate the program at any time by calling the method below

Application.Terminate();

Next, the combination ALT+F1 is checked, which puts the input focus on the main menu of the window. If the menu is already in focus and the user presses ESCAPE then the active menu window closes or it is itself if there are no active menu windows.
If the menu is still not open, then the pressed key is passed to it to search for and call the item suitable for the key combination. If this is not found, then the status bar button associated with the key combination is searched and called up.

If you have a menu item and a status bar button with the same hotkey combination then the menu item will always be called, because its handler is higher than that of the status bar.

If all of the above does not happen, then press TAB or SHIFT+TAB to change the component with the focus of the input, or if any other key is pressed, it is transferred to the focused component.

However, the above does not say what actions into IEventLoop and what they are for. The answer is simple - the only kernel action is OnLoopEmptyHandler. Let's see what it is for.
life cycle of measure and visual processing

The last action, when the event queue is empty, is to call the OnLoopEmpty event, on which the kernel places a single action, which is the main one for processing system events and render window components.
First, there is a check for the current size of the console window. If it has changed since the last commit, the mechanism for redrawing the existing elements of each displayed window on the screen starts.
After that, for the current window, the presence of a request for resizing or appearance is checked. If at least one window element requested a resize, the Arrange recalculation of each element is started. If there are elements that have changed their size, or previously requested a redraw, the form is drawn in the buffer, after which an interface is formed in the console window. For more information on calculating the size of elements, see Measure and Visual.
Having finished with the drawing, the kernel tries to display the input carriage in the focused element, if it is implemented ICursorAdmit.

⚠️ **GitHub.com Fallback** ⚠️