Skip to content

Latest commit

 

History

History
296 lines (219 loc) · 10.5 KB

File metadata and controls

296 lines (219 loc) · 10.5 KB

Syntax & Architectural Principles

To master the ahk-xaml framework, it is crucial to understand both the surface-level AHK syntax and the underlying engine architecture.

1. The XAML_Generator Syntax

The XAML_Generator class is an Object-Oriented wrapper around XML tree construction. It ensures you never have to deal with mismatched tags, complex string escaping, or XML verbosity.

The Chainable API

Every element added to the UI returns an instance of XAMLElement. When you call a method that doesn't exist on XAMLElement, ahk-xaml intercepts the call dynamically and creates a XAML attribute.

; This:
btn := panel.Add("Button").Width(100).HorizontalAlignment("Center")

; Generates this:
; <Button Width="100" HorizontalAlignment="Center" />

Navigating the Hierarchy

The .Add() method always returns the newly created child. To add siblings, you must traverse back up the tree using .Parent().

grid := X.Add("Grid")

; Incorrect: TextBlock2 becomes a child of TextBlock1! (XAML Error)
grid.Add("TextBlock").Text("1").Add("TextBlock").Text("2") 

; Correct: 
grid.Add("TextBlock").Text("1").Parent()
    .Add("TextBlock").Text("2")

Property Name Translations

AutoHotkey v2 does not allow dots (.) in method names. In XAML, attached properties (like Grid.Row) require dots. The generator automatically translates underscores (_) into dots (.).

element.Grid_Row(1).Grid_Column(2).ScrollViewer_VerticalScrollBarVisibility("Auto")
; Generates: Grid.Row="1" Grid.Column="2" ScrollViewer.VerticalScrollBarVisibility="Auto"

Special Methods

Method Purpose
.Add("Type") Create a child element
.Parent() Return to parent element
.Name("id") Set x:Name for event binding and tracking
.Use("Template") Apply a pre-defined template
.SetProp("key", "val") Set an attribute with special characters
.InjectResources(xaml) Inject raw XAML into element's Resources
.SetDefaults("Type", map) Apply cascading defaults to children
.Compile() Serialize the AST to XAML markup

Naming Elements

The .Name() method assigns an x:Name attribute, which is required for:

  • Event binding: ui.OnEvent("BtnSave", "Click", callback)
  • State tracking: ui.Track("TxtUsername")
  • Dynamic updates: ui.Update("BtnSave", "Text", "Saved!")
; Named element — can be interacted with
parent.Add("TextBox").Name("TxtEmail").Text("user@example.com")

; Unnamed element — static display only
parent.Add("TextBlock").Text("Email:")

2. The Background Engine Architecture

Why a C# Engine?

AutoHotkey is single-threaded. Rendering complex, hardware-accelerated UIs on the main AHK thread causes massive latency, message blockages, and instability during heavy processing (like I/O or loops).

To solve this, ahk-xaml dynamically compiles a lightweight C# WPF application (ahk-xaml.dll) to a temporary directory.

  1. Your AHK script generates the XAML markup string.
  2. AHK launches the C# engine and passes the XAML to it.
  3. The C# engine parses the XAML and displays the Window on its own dedicated thread.
  4. The C# engine handles all Windows DWM rendering, native rounded corners, and complex animations independently of AHK.

The IPC Bridge (WM_COPYDATA)

AHK and the C# engine communicate synchronously via Win32 WM_COPYDATA messages.

  • When the user clicks a button, the C# engine captures the UI state and fires a WM_COPYDATA payload to the AHK script.
  • AHK parses the payload and triggers your registered .OnEvent() callbacks.
  • When AHK needs to update the UI (e.g., change text, toggle a checkbox), it sends a payload back to the C# engine.

This complete separation of concerns ensures that your AHK business logic never freezes the UI, and the UI never bottlenecks your scripts.

Payload Streaming

For large UIs, the XAML is too big for a single WM_COPYDATA message. Instead, the framework uses a two-step handshake:

  1. Engine starts → sends Ready event with a temporary message window handle.
  2. AHK sends XAML_PAYLOAD|<xaml>\n---AHK-XAML-EVENTS---\n<bindings> to that handle.
  3. Engine parses → displays window → sends LoadedHwnd event.

Dynamic Element Injection

After the window is loaded, new elements can be injected at runtime using AddXamlItem. The XAML string must include the full WPF namespace:

xaml := '<TextBlock xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Text="Dynamic!" />'
ui.Update("MyPanel", "AddXamlItem", xaml)

The C# engine parses the string via XamlReader.Parse(), registers any x:Name attributes in the window's NameScope, and adds the element to the target container.


3. Scoped Defaults & Templates

SetDefaults (CSS-Like Cascading)

To avoid massive chains of redundant properties, you can apply defaults to a parent container. All children of that container (and their descendants) will automatically inherit the properties unless explicitly overridden.

panel.SetDefaults("TextBlock", { Foreground: "White", FontSize: 14 })

panel.Add("TextBlock").Text("I am white and 14pt!")
panel.Add("TextBlock").Text("I am red and 14pt!").Foreground("Red") ; Override

Note: Defaults are strictly scoped. They expire the moment you navigate away from panel.

.Use() Templates

Templates are reusable sets of properties that can be applied to any element, regardless of its parent container. You define them globally on the Generator instance.

X.DefineTemplate("CardPanel", { Background: "{DynamicResource ControlBg}", BorderThickness: 1, CornerRadius: 8 })

; Apply it anywhere
app.Add("Border").Use("CardPanel")

Built-in Templates

Template Description
PrimaryBtn Accent-colored button with hover opacity
IconBtn Compact transparent button with rounded hover
CardPanel Rounded border with themed background
SubtitleText Small, bold, muted text
PageTitle Large, semi-bold heading
BodyText 13pt wrapping body text

4. Theming & ResourceDictionaries

ahk-xaml relies heavily on DynamicResource bindings to support hot-swapping themes. The base resources are defined in lib\xaml.components.xaml.

When applying colors, never hardcode them if you want theming to work.

; ❌ BAD: Hardcoded color
btn.Foreground("#FFFFFF").Background("#333333")

; ✅ GOOD: Dynamic bindings
btn.Foreground("{DynamicResource TextMain}").Background("{DynamicResource ControlBg}")

Core Theme Resources

Resource Key Purpose
BgColor Window background
SidebarColor Sidebar background
Accent Primary accent color
TextMain Primary text color
TextSub Secondary/muted text
ControlBg Control/card background
ControlBgHover Control hover state
ControlBorder Border color
DropdownBg Popup/dropdown background
WindowRadius Window corner radius
ScrollBarWidth Scrollbar width

Runtime Theme Changes

; Change the main text color to Red on the fly
ui.Update("Resource", "Brush:TextMain", "#FF0000")

; Change corner radius
ui.Update("Resource", "CornerRadius:WindowRadius", "12")

; Change DWM backdrop
ui.Update("Window", "DWM", "3,1")  ; MicaAlt + Dark Mode

Theme INI Format

[Dark Mica (Win 11)]
Window_DWM=2,1
Resource_Brush:BgColor=#01000000
Resource_Brush:Accent=#0A84FF
Resource_Brush:TextMain=#FFFFFF
Resource_Brush:TextSub=#A0FFFFFF
Resource_Brush:ControlBg=#15FFFFFF
Resource_Brush:ControlBgHover=#20FFFFFF
Resource_Brush:ControlBorder=#25FFFFFF

5. Component Lifecycle

Build → Compile → Show (Auto-Bind)

Composite components (KanbanBoard, NavigationView, NodeGraph, etc.) are automatically initialized during Compile(). No manual .Bind(ui) calls are needed — the framework discovers all components created via factory methods and binds them.

; 1. BUILD: Create the component via factory method
kb := panel.KanbanBoard("MyBoard")
kb.AddColumn("Todo", ["Task 1", "Task 2"])
kb.AddColumn("Done", ["Task 3"])
kb.EnableDrag()  ; Flag for auto-enable during compile

; 2. COMPILE: All components auto-bind here
ui := app.Compile()

; 3. SHOW: Launch the WPF engine
app.Show()

Flyouts & Command Palettes

Standalone components like XFlyout also auto-register when .Build() is called. Use .Hotkey() to set a toggle hotkey:

fly := XFlyout("Settings", "Left", "Push", 300)
fly.Build(layout).Grid_Column(0)
fly.Hotkey("^+S")  ; Stored, applied during auto-bind

cmdPal := app.overlay.CommandPalette("CmdPal")
cmdPal.Hotkey("^+P")
cmdPal.AddCommand("reload", "Reload", { Icon: Chr(0xE72C), Callback: (*) => Reload() })

What auto-bind does

Each component's internal .Bind(ui) method:

  1. Stores the ui host reference
  2. Registers internal event handlers (ui.OnEvent(...))
  3. Sets up state tracking (ui.Track(...))
  4. Enables drag/drop if .EnableDrag() was called

.On() vs .Track() vs auto-bind

Method Level What it does
.On("Click", fn) Element Registers a single event handler on one element
.Track() Element Includes element's value in every event's state map
Auto-bind Component Initializes a composite component's internal event system

Factory-Created vs Prototype Components

Factory-created (auto-bind, no manual initialization needed):

; Created via element method — auto-registered
kb := panel.KanbanBoard("MyBoard")
ng := canvas.NodeGraph("MyGraph")
nav := sidebar.NavigationView("MainNav")

Prototype extensions (chainable, inline):

; Just call directly on any element
panel.Toggle("MySwitch", "Dark Mode", true)
panel.StatCard("REVENUE", "$45K", "+12%", true)
panel.SkeletonLoader(200, 20)

6. Error Handling

AHK Line Tracking

Every element generated by XAML_Generator automatically records the AHK source line:

<!-- [ahk:42] --><Button Width="100" />

When the C# engine encounters a parsing error, it traces back to the nearest AHK line marker and reports:

Engine crashed while rendering AHK Line 42!

Common Pitfalls

Error Cause Fix
Cannot create unknown type Missing xmlns on dynamic XAML Add xmlns="http://schemas.microsoft.com/..."
Name already registered Duplicate x:Name values Use unique names per element
XamlParseException Invalid property or typo Check the XAML snippet in the error dialog
Timed out waiting for payload AHK message queue full Increase OnMessage MaxThreads