Skip to main content

Lifecycle Feature

The Lifecycle feature manages the window's life cycle: creation, readiness, closing, teardown, and cross-thread invocation. It also covers the event system for responding to window state changes.

Contents

Window States

A window goes through a deterministic state machine:

Created → Creating → Ready → CloseRequested → NativeClosed
→ TeardownPending → TeardownComplete → NativeHandleReleased → Disposed

You can check the current state:

InfiniFrameWindowLifecycleState state = window.Features.Lifecycle.State;
bool isClosed = window.Features.Lifecycle.IsClosedOrClosing();

Closing the Window

Close the window programmatically:

window.Close(); // Synchronous
await window.CloseAsync(); // Async
note

Close() initiates the close sequence but does not immediately destroy the window. If a WindowClosingRequested handler returns false, the close is rejected and the window stays open.

Waiting for Close

Block or await until the window is destroyed:

window.WaitForClose(); // Blocks calling thread
await window.WaitForCloseAsync(); // Async
await window.WaitForClosedCallbacksAsync(); // Wait for close callbacks
await window.WaitForTeardownAsync(); // Wait for full teardown

WaitForClose() is the most common pattern it blocks until the native window is fully destroyed and is typically the last call in your entry point:

var window = InfiniFrameWindowBuilder.Create()
.SetTitle("My App")
.SetStartPageUrl("https://example.com")
.Build();

window.WaitForClose(); // Blocks here until the user closes the window

Waiting for ready

Wait until the window is fully initialized and ready for interaction:

await window.WaitForReadyAsync();

STA Requirement (Windows)

WebView2 is COM-based and requires the thread that calls Build() to be STA. Without [STAThread], the window opens but the browser control renders as a black screen, and Build() now throws InvalidOperationException to surface this early.

internal class Program {
[STAThread]
static void Main(string[] args) {
var window = InfiniFrameWindowBuilder.Create()
.SetTitle("My App")
.SetStartPageUrl("https://example.com")
.Build();

window.WaitForClose();
}
}

Top-level statements cannot carry [STAThread] so use an explicit static void Main() as shown above.

warning

[STAThread] is silently ignored on async Task Main. The async continuation runs on thread pool threads (MTA). Never use async Task Main as the entry point for an InfiniFrame application.

Linux does not have this restriction because GTK has no COM apartment model. The native constructor calls gtk_init() itself and implicitly claims whichever thread calls Build() as the GTK main thread.

Cross-Thread Invocation

All UI operations must run on the window's thread. Use Invoke to marshal work from a background thread:

Task.Run(() => {
// Background thread
window.Invoke(() => {
// Runs on the window thread
window.Close();
});
});

Async dispatch

For non-blocking dispatch with timeout and cancellation support, use DispatchAsync:

InfiniFrameDispatchResult result = await window.DispatchAsync(() => {
window.SetTitle("Updated from background");
}, timeout: TimeSpan.FromSeconds(5), cancellationToken: ct);

switch (result) {
case InfiniFrameDispatchResult.Completed:
Console.WriteLine("Dispatch completed");
break;
case InfiniFrameDispatchResult.TimedOut:
Console.WriteLine("Dispatch timed out");
break;
case InfiniFrameDispatchResult.WindowClosed:
Console.WriteLine("Window was closed before dispatch");
break;
}

See the Invoke feature guide for full details.

Events

Events are available through IInfiniFrameWindowEvents, accessible via IInfiniFrameWindowBuilder.Events.

var builder = InfiniFrameWindowBuilder.Create();

builder.Events.WindowCreated.Add(() => Console.WriteLine("Window opened"));
builder.Events.WindowSizeChanged.Add(size => Console.WriteLine($"Resized to {size}"));
builder.Events.WindowLocationChanged.Add(loc => Console.WriteLine($"Moved to {loc}"));
builder.Events.WindowFocusIn.Add(() => Console.WriteLine("Focus gained"));
builder.Events.WindowFocusOut.Add(() => Console.WriteLine("Focus lost"));
builder.Events.WindowMaximized.Add(() => Console.WriteLine("Maximized"));
builder.Events.WindowMinimized.Add(() => Console.WriteLine("Minimized"));
builder.Events.WindowRestored.Add(() => Console.WriteLine("Restored"));
builder.Events.WebMessageReceived.Add(msg => Console.WriteLine($"Message: {msg}"));

var window = builder.Build();
window.WaitForClose();

Intercepting Window Close

Use WindowClosingRequested to cancel or intercept a close:

builder.Events.WindowClosingRequested.Add(() => {
// Return true to allow closing, false to cancel
return AskUserToConfirm();
});

Use WindowClosing to run cleanup before the window is destroyed:

builder.Events.WindowClosing.Add((window, cancel) => {
SaveAppState();
return false; // returning false here does not cancel; use WindowClosingRequested for that
});

See Also