Implementing FancyZones Window Layout Zones: PowerToys Development Guide

FancyZones splits monitors into configurable zones using a three-layer architecture where WorkArea objects manage zone snapping, layouts persist as JSON files, and hot-key events route through FancyZones.cpp to snap windows programmatically.

FancyZones is the window management module in Microsoft PowerToys that enables users to organize desktop space through customizable snap zones. When implementing FancyZones window layout zones, developers interact with a C++ core engine, a WPF editor, and JSON-based persistence layers. This guide examines the source code architecture in microsoft/PowerToys to explain how zones are created, applied, and extended.

Architecture Overview

The FancyZones implementation follows a strict three-layer separation to isolate window management logic from UI and persistence concerns.

Layer 1: Module Entry Point – src/modules/fancyzones/FancyZones/FancyZones.cpp implements the IFancyZones interface and hosts the main window-message loop. This layer receives global hook events, hot-key input, and orchestrates updates across all monitors.

Layer 2: Work-Area Model – src/modules/fancyzones/FancyZonesLib/WorkArea.h and WorkArea.cpp represent a unique monitor + virtual-desktop pair. Each WorkArea owns a ZoneSet and exposes methods to create, snap, cycle, and update windows for that specific display context.

Layer 3: Layout Data – FancyZonesDataTypes.h, FancyZonesData.h, and CustomLayouts.h define the serializable structures (Zone, ZoneSet, CustomLayoutData) that describe grid, canvas, and custom layouts. The module communicates with the FancyZones editor (a separate WPF app) via JSON files read/written by FancyZonesEditorIO.cs.

Work-Area Lifecycle

Understanding the lifecycle of a WorkArea is essential for implementing FancyZones window layout zones correctly.

First, the system detects monitors and virtual desktops using MonitorUtils::IdentifyMonitors() and VirtualDesktop::instance().GetCurrentVirtualDesktopIdFromRegistry(). For each unique pair, WorkArea::Create instantiates a new work-area via AddWorkArea in FancyZones.cpp.

Next, the engine assigns a ZoneSet loaded from AppliedLayouts.json (or defaults) using AppliedLayouts::instance().ApplyLayout. When users trigger a hot-key or a new window appears, WorkArea::Snap calculates the target position and resizes the window accordingly.

Finally, whenever a display change, virtual-desktop switch, or layout reload occurs, FancyZones::OnDisplayChange evaluates ShouldWorkAreasBeRecreated to determine whether existing objects can be reused or must be rebuilt from scratch.

Layout Types and Data Models

FancyZones supports four distinct layout types defined in FancyZonesDataTypes.h.

Grid layouts use GridLayoutInfo to define uniform rows and columns with optional per-cell sizing percentages. Canvas layouts store arbitrary rectangles via CanvasLayoutInfo, allowing free-form zone placement. PriorityGrid extends the grid concept with directional ordering for cursor-based zone selection. Custom layouts wrap any of the above under a UUID stored in CustomLayoutData.

All layout data serialize to JSON using the helper methods in FancyZonesEditorCommon/Data/FancyZonesJsonContext.cs. The editor writes to AppliedLayouts.json, CustomLayouts.json, and LayoutHotkeys.json, while the main module watches these files via FileWatcher (common/SettingsAPI/FileWatcher.h) to reload configurations dynamically.

Hot-Key Processing Pipeline

The low-level keyboard hook resides in KeyboardInput.cpp. When a snap hot-key is pressed, FancyZones::ShouldProcessSnapHotkey validates the foreground window and current layout before delegating to WindowKeyboardSnap or WindowMouseSnap.

WindowKeyboardSnap calculates the target ZoneIndexSet based on arrow-key direction and invokes WorkArea::Snap to execute the move. This pipeline ensures that window positioning logic remains decoupled from raw input handling, allowing developers to inject custom snapping behaviors at the WorkArea layer.

Inter-Process Communication

The FancyZones module launches the editor using ShellExecuteEx (see ToggleEditor in FancyZones.cpp). Before launch, EditorParameters::Save writes monitor rectangles and the "span-across-monitors" flag to editor-parameters.json.

Both processes remain synchronized through shared JSON files monitored by FileWatcher. When the editor closes, the main module receives a WM_PRIV_EDITOR message, reloads the configuration, and rebuilds its WorkArea objects to reflect any layout changes made by the user.

Extending FancyZones: Step-by-Step Implementation

When adding a new zone type or custom behavior to FancyZones, follow this precise development flow:

  1. Define the data model – Extend FancyZonesDataTypes.h with a new struct (e.g., RadialLayoutInfo).
  2. Add JSON serialization – Update FancyZonesJsonContext.cs to read and write the new fields.
  3. Create a layout class – Derive from Layout (found in FancyZonesLib/WorkArea.h) and implement CalculateZones and Snap.
  4. Update the layout registry – Modify CustomLayouts::GetLayout to recognize the new CustomLayoutData::type.
  5. Expose UI controls – Add XAML elements in FancyZonesEditor.xaml and bind them via MainWindowSettingsModel.

Practical Implementation Examples

Programmatically Add a Custom Grid Layout

The following C# example demonstrates creating a 2x3 grid layout and persisting it for the primary monitor:

using Microsoft.PowerToys.FancyZonesEditor.Common;
using FancyZonesEditor.Models;

// Build a GridLayoutInfo (rows = 2, columns = 3)
var grid = new GridLayoutModel("MyCustomGrid", LayoutType.Custom)
{
    Rows = 2,
    Columns = 3,
    RowPercents = new List<int> { 50, 50 },
    ColumnPercents = new List<int> { 33, 34, 33 },
    Zones = new List<Rectangle>()
};

// Convert to JSON-serializable structure
var layoutData = new CustomLayoutData
{
    name = grid.Name,
    type = CustomLayoutType.Grid,
    info = grid.ToGridLayoutInfo()
};

// Persist to CustomLayouts.json
FancyZonesEditorIO.SerializeCustomLayout(layoutData);

Key files: FancyZonesEditor.Common/FancyZonesDataIO.cs, FancyZonesEditor.Models/GridLayoutModel.cs

Apply a Layout from Native C++ Code

To apply a layout programmatically after a display change:

#include <FancyZonesLib/FancyZonesData.h>
#include <FancyZonesLib/FancyZonesLib.h>

void ApplyMyLayout(const FancyZonesDataTypes::WorkAreaId& waId)
{
    // Retrieve layout by UUID
    auto layoutOpt = CustomLayouts::instance().GetLayout(myLayoutId);
    if (!layoutOpt) return;

    // Apply to work-area
    if (AppliedLayouts::instance().ApplyLayout(waId, layoutOpt.value()))
    {
        // Refresh visual zones
        FancyZones::RefreshLayouts();
    }
}

Key files: FancyZonesLib/CustomLayouts.h, FancyZonesLib/AppliedLayouts.h, FancyZones.cpp

Hook a New Hot-Key for Multi-Monitor Spanning

Register and handle a custom hot-key in the main module:

// Registration in FancyZones::Run()
constexpr int HOTKEY_SPAN = 4;
auto hk = FancyZonesSettings::settings().spanZonesAcrossMonitorsHotkey;
RegisterHotKey(m_window, HOTKEY_SPAN, hk.get_modifiers(), hk.get_code());

// Handling in WndProc
case WM_HOTKEY:
    if (wparam == HOTKEY_SPAN)
    {
        FancyZonesSettings::instance().UpdateSetting(
            SettingId::SpanZonesAcrossMonitors,
            !FancyZonesSettings::settings().spanZonesAcrossMonitors);
    }
    break;

Key files: FancyZones.cpp, FancyZonesSettings.h

Key Source Files

Path Role
src/modules/fancyzones/FancyZones/FancyZones.cpp Core module implementation, hook handling, and work-area orchestration.
src/modules/fancyzones/FancyZonesLib/WorkArea.h / WorkArea.cpp Per-monitor/desktop zone management, snap logic, and cycling.
src/modules/fancyzones/FancyZonesLib/FancyZonesDataTypes.h Data models for zones, layouts, and virtual-desktop IDs.
src/modules/fancyzones/FancyZonesLib/CustomLayouts.h Registry for user-defined layouts (load, query, apply).
src/modules/fancyzones/editor/FancyZonesEditor/MainWindow.xaml.cs WPF editor UI and JSON persistence logic.
src/modules/fancyzones/editor/FancyZonesEditor/Common/FancyZonesDataIO.cs Serialization helpers for layout data.
src/settings-ui/Settings.UI/ViewModels/FancyZonesViewModel.cs Settings UI glue for hot-keys and toggles.

Summary

  • FancyZones uses a three-tier architecture separating module logic (FancyZones.cpp), per-display state (WorkArea), and data models (FancyZonesDataTypes.h).
  • WorkArea objects manage the lifecycle of zones for each monitor/virtual-desktop pair, reloading when OnDisplayChange detects configuration changes.
  • JSON files (AppliedLayouts.json, CustomLayouts.json) serve as the communication bridge between the C++ engine and the WPF editor, watched by FileWatcher.
  • Hot-key processing flows from KeyboardInput.cpp through validation routines to WorkArea::Snap for execution.
  • Extensions require updating the data model, JSON context, layout class, and editor UI to maintain consistency across the codebase.

Frequently Asked Questions

How does FancyZones detect monitor layout changes?

FancyZones monitors system events through MonitorUtils::IdentifyMonitors() and VirtualDesktop::instance().GetCurrentVirtualDesktopIdFromRegistry(). When a change occurs, FancyZones::OnDisplayChange evaluates ShouldWorkAreasBeRecreated to decide whether to reuse existing WorkArea objects or reconstruct them with updated zone configurations.

What is the difference between Grid and Canvas layouts?

Grid layouts (GridLayoutInfo) divide screens into uniform rows and columns with percentage-based sizing, ideal for structured workflows. Canvas layouts (CanvasLayoutInfo) store arbitrary pixel rectangles, allowing free-form zone placement anywhere on the display. Both types serialize to JSON and instantiate ZoneSet objects through CustomLayouts::GetLayout.

Can I trigger zone snapping programmatically without user input?

Yes. Native code can invoke WorkArea::Snap directly after retrieving a layout via CustomLayouts::instance().GetLayout and applying it through AppliedLayouts::instance().ApplyLayout. For C# scenarios, use FancyZonesEditorIO.SerializeCustomLayout to persist layouts that the engine will load automatically.

Where are custom layouts stored on disk?

Custom layouts persist in %LocalAppData%\Microsoft\PowerToys\FancyZones\CustomLayouts.json, while active monitor assignments reside in AppliedLayouts.json. Hot-key mappings use LayoutHotkeys.json. Both the C++ module and the WPF editor watch these files via FileWatcher to synchronize state changes immediately.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →