agentleFS
linuxdevellinuxdevel/Avalonia-skills/skills/avalonia/avalonia-services/SKILL.md

avalonia-services

Skill50 starsChanged 8 days agoMITView on GitHub

Use when accessing Avalonia platform services via TopLevel: clipboard read/write, file/folder/save dialogs via StorageProvider, URI/file launching via Launcher, screen and window info, focus manager, platform feedback (12.1+), in-app notifications via WindowNotificationManager, or IME input method access.

The file

Files
SKILL.md

Avalonia Platform Services

Overview

Version labels: see avalonia-versions. Platform services are accessed via the TopLevel class (root of visual tree — typically Window or PopupRoot).

var topLevel = TopLevel.GetTopLevel(this); // 'this' = any attached control

Returns null if control is not yet attached to visual tree. Call in OnAttachedToVisualTree or later.

Clipboard

var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;

// Write text
await clipboard!.SetTextAsync("Hello, world");

// Write custom data (12.0+)
var item = new DataTransferItem();
item.Set(DataFormat.Text, "fallback text");
item.Set(DataFormat.CreateInProcessFormat<MyType>("myapp/mytype"), myObject);
var data = new DataTransfer();
data.Add(item);
await clipboard.SetDataAsync(data);

// Read text (12.0+) — null if no text on clipboard
var text = await clipboard.TryGetTextAsync();

// Read custom data (12.0+)
var formats = await clipboard.GetDataFormatsAsync();           // IReadOnlyList<DataFormat>
var mine = await clipboard.TryGetValueAsync(myFormat);         // DataFormat<T> where T : class
var all = await clipboard.TryGetDataAsync();                   // IAsyncDataTransfer?

// Clear
await clipboard.ClearAsync();

SetTextAsync, TryGetTextAsync, TryGetFilesAsync, TryGetBitmapAsync, SetValueAsync, GetDataFormatsAsync are extension methods (ClipboardExtensions, namespace Avalonia.Input.Platform). On Windows, no BinaryFormatter: serialize custom objects explicitly (e.g. JSON string/bytes format).

(11.x) equivalents — removed in 12:

var data = new DataObject();
data.Set(DataFormats.Text, "fallback text");
await clipboard.SetDataObjectAsync(data);
var text = await clipboard.GetTextAsync();
var formats = await clipboard.GetFormatsAsync();     // string[]
var raw = await clipboard.GetDataAsync("application/x-myformat");

File Dialogs (StorageProvider)

var storage = TopLevel.GetTopLevel(this)?.StorageProvider;

Open file(s):

var files = await storage!.OpenFilePickerAsync(new FilePickerOpenOptions
{
    Title = "Open File",
    AllowMultiple = false,
    FileTypeFilter = new[]
    {
        new FilePickerFileType("Images")
        {
            Patterns = new[] { "*.png", "*.jpg", "*.jpeg", "*.webp" },
            MimeTypes = new[] { "image/png", "image/jpeg" }
        },
        new FilePickerFileType("All Files") { Patterns = new[] { "*" } }
    },
    SuggestedStartLocation = await storage.TryGetWellKnownFolderAsync(WellKnownFolder.Pictures)
});

if (files.Count > 0)
{
    await using var stream = await files[0].OpenReadAsync();
    var bitmap = new Bitmap(stream);
}

Built-in FilePickerFileTypes: All, TextPlain, Pdf, Images

Save file:

var file = await storage!.SaveFilePickerAsync(new FilePickerSaveOptions
{
    Title = "Save As",
    DefaultExtension = "txt",
    SuggestedFileName = "untitled",
    FileTypeChoices = new[]
    {
        new FilePickerFileType("Text Files") { Patterns = new[] { "*.txt" } },
        new FilePickerFileType("All Files") { Patterns = new[] { "*" } }
    }
});

if (file is not null)
{
    await using var stream = await file.OpenWriteAsync();
    await using var writer = new StreamWriter(stream);
    await writer.WriteAsync(content);
}

Open folder:

var folders = await storage!.OpenFolderPickerAsync(new FolderPickerOpenOptions
{
    Title = "Select Folder",
    AllowMultiple = false,
    SuggestedStartLocation = await storage.TryGetWellKnownFolderAsync(WellKnownFolder.Documents)
});

if (folders.Count > 0)
{
    var path = folders[0].Path.LocalPath; // platform file path string
}

Well-known folders: Desktop, Documents, Downloads, Music, Pictures, Videos

Navigate to path:

var folder = await storage.TryGetFolderFromPathAsync("/home/user/projects");
var file = await storage.TryGetFileFromPathAsync("/home/user/file.txt");

Launcher (Open URLs / Files)

var launcher = TopLevel.GetTopLevel(this)?.Launcher;

// Open URL in default browser
await launcher!.LaunchUriAsync(new Uri("https://avaloniaui.net"));

// Open file with default app
await launcher!.LaunchFileAsync(myStorageFile); // IStorageFile

Screen Information

var screens = TopLevel.GetTopLevel(this)?.Screens;

var primary = screens?.Primary;       // IScreen
var all = screens?.All;               // IReadOnlyList<IScreen>
var count = screens?.ScreenCount;     // int

// IScreen properties
var bounds = primary?.Bounds;         // PixelRect — full screen area
var workArea = primary?.WorkingArea;  // PixelRect — area excluding taskbar
var scaling = primary?.Scaling;       // double — DPI scaling factor (1.0 = 96dpi)
var isPrimary = primary?.IsPrimary;   // bool

Window position relative to screen:

if (this is Window window)
{
    var position = window.Position;     // PixelPoint
    var size = window.ClientSize;       // Size (in DIPs)
    window.WindowState = WindowState.Maximized;
}

Focus Manager

var fm = TopLevel.GetTopLevel(this)?.FocusManager;

var focused = fm?.GetFocusedElement(); // IInputElement?
fm?.Focus(myControl, NavigationMethod.Pointer);
// NavigationMethod: Unspecified, Tab, Pointer, Directional
myControl.Focus(NavigationMethod.Tab);   // or directly

// (12.0+) Focus Traversal
fm?.TryMoveFocus(NavigationDirection.Next);
var next = fm?.FindNextElement(NavigationDirection.Next);   // optional FindNextElementOptions
var first = fm?.FindFirstFocusableElement();

(11.x) KeyboardNavigationHandler.GetNext is removed in 12. See avalonia-input-interaction for cancellable focus (GettingFocus/LosingFocus).

Platform Feedback (12.1+)

Haptic/sound feedback on Android and iOS, configurable via the PlatformFeedback API (Avalonia.Controls). Not in 12.0 or 11.x. Built-in controls use it automatically; consult the 12.1 API reference before using it from custom controls.

In-App Notifications

Requires NuGet: Avalonia.Controls.Notifications

using Avalonia.Controls.Notifications;

// Create once, store as field
private WindowNotificationManager? _notifications;

protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
    base.OnAttachedToVisualTree(e);
    _notifications = new WindowNotificationManager(TopLevel.GetTopLevel(this))
    {
        Position = NotificationPosition.TopRight,
        MaxItems = 3,
        Margin = new Thickness(0, 0, 15, 40)
    };
}

// Show notification
_notifications?.Show(new Notification(
    title: "Success",
    message: "File saved successfully.",
    type: NotificationType.Success,
    expiration: TimeSpan.FromSeconds(4),
    onClick: () => Console.WriteLine("Clicked"),
    onClose: () => Console.WriteLine("Dismissed")));

NotificationType: Information, Success, Warning, Error NotificationPosition: TopLeft, TopCenter, TopRight, BottomLeft, BottomCenter, BottomRight

Input Method (IME)

var inputMethod = TopLevel.GetTopLevel(this)?.InputMethod;
inputMethod?.SetCursorRect(new Rect(x, y, 0, lineHeight));

Insider Info: TopLevel Properties Summary

PropertyTypePurpose
ClipboardIClipboard?Read/write clipboard
StorageProviderIStorageProvider?File/folder dialogs
LauncherILauncher?Open URIs and files
ScreensIScreens?Screen geometry/DPI
FocusManagerIFocusManager?Focus control
InputMethodIInputMethod?IME cursor position
PlatformSettings (11.x) / GetPlatformSettings() (12.0+)IPlatformSettings?OS theme, animation prefs
RendererDiagnosticsRendererDiagnosticsFPS overlay, dirty rects

Platform settings (dark mode, animations):

// (12.0+) extension method on Visual
var settings = this.GetPlatformSettings();
// (11.x) TopLevel.GetTopLevel(this)?.PlatformSettings
var isDark = settings?.GetColorValues().ThemeVariant == PlatformThemeVariant.Dark;
var reduceMotion = settings?.GetColorValues().ContrastPreference == ColorContrastPreference.High;

Community Dialog & Notification Libraries

The built-in StorageProvider handles file dialogs. For richer dialogs and notifications:

LibraryNuGetPurpose
MessageBox.AvaloniaMsgBox.AvaloniaSimple message box dialogs (missing from Avalonia core)
DialogHost.AvaloniaDialogHost.AvaloniaMaterial-style async overlay dialogs
MovereGitHub: jp2masa/MovereManaged dialog service with MVVM support
JamSoft.AvaloniaUI.DialogsJamSoft.AvaloniaUI.DialogsMVVM dialog service + Wizard control
HanumanInstitute.MvvmDialogsHanumanInstitute.MvvmDialogs.AvaloniaOpen dialogs from ViewModel without code-behind
Jc.PopupView.AvaloniaGitHub: jcsawyer/Jc.PopupView.AvaloniaAnimated toasts, alerts, popups
Notification.AvaloniaNotification.AvaloniaLINQ-style in-app notification display

Platform Essentials (MAUI-like APIs)

LibraryPlatformPurpose
Avae.Windows.EssentialsWindowsMAUI Essentials APIs on Windows
Avae.macOS.EssentialsmacOSMAUI Essentials APIs on macOS
Avae.Linux.EssentialsLinuxMAUI Essentials APIs on Linux
Avae.Browser.EssentialsWASMMAUI Essentials APIs in browser

Common Mistakes

  • TopLevel.GetTopLevel(this) returns null before attachment — never call in constructor; use OnAttachedToVisualTree or control-loaded events
  • IStorageFile is not a file path — must use OpenReadAsync()/OpenWriteAsync() streams; file.Path.LocalPath may fail on sandboxed platforms (iOS/Android/browser)
  • (12.0+) SetDataObjectAsync/GetTextAsync/GetFormatsAsync/GetDataAsync/DataObject — removed; use SetDataAsync/TryGetTextAsync/GetDataFormatsAsync/TryGetDataAsync/DataTransfer
  • Clipboard operations must be awaited — fire-and-forget silently fails or throws on some platforms
  • WindowNotificationManager created per-notification — creates multiple overlapping managers; create once and store as field
  • WindowNotificationManager must be created on UI thread — don't create from background tasks
  • LaunchUriAsync for file:// URIs — use LaunchFileAsync with IStorageFile instead for local files

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.