CtrlK
BlogDocsLog inGet started
Tessl Logo

mahapps-metro

Build WPF applications with MahApps.Metro v2.4.x, MahApps.Metro.IconPacks, CommunityToolkit.Mvvm. Covers MetroWindow, theming, controls, dialogs (including MVVM DialogCoordinator), styles, and icon packs. Scoped to the AIPlanningPilot.Dashboard project.

SKILL.md
Quality
Evals
Security

MahApps.Metro -- WPF UI Skill

Purpose

Guide the implementation of WPF UI using MahApps.Metro v2.4.x with modern MVVM patterns (CommunityToolkit.Mvvm). This skill covers setup, theming, controls, dialogs, styles, and icon packs.

When to Use

  • Creating or modifying WPF views in AIPlanningPilot.Dashboard
  • Adding MahApps controls (Flyouts, ToggleSwitch, NumericUpDown, etc.)
  • Theming or styling work (light/dark, accent colors, custom brushes)
  • Implementing async dialogs (message, input, progress)
  • Using icon packs (PhosphorIcons)
  • Troubleshooting XAML binding or style issues with MahApps

When NOT to Use

  • Angular frontend code (use angular-* skills)
  • Backend .NET code without UI
  • Non-MahApps WPF (vanilla WPF or WPF-UI/Fluent)

Stack

PackageVersionPurpose
MahApps.Metro2.4.10MetroWindow, controls, dialogs, theming
MahApps.Metro.IconPacks.PhosphorIcons6.0.0Icons
CommunityToolkit.Mvvm8.4.0ObservableObject, [ObservableProperty], [RelayCommand]
Markdig.Wpf0.5.0.1Markdown rendering
AvalonEdit6.3.0.90Code syntax highlighting

1. Setup

App.xaml Resource Dictionaries (required, order matters)

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
            <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
            <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

For flat button styles, add: pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml

Gotcha: Resource file names are case sensitive.

XAML Namespace Declarations

xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
xmlns:Dialog="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"

2. MetroWindow

Basic Window

<mah:MetroWindow x:Class="MyApp.Views.MainWindow"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
                 Title="My App" Height="800" Width="1280"
                 TitleCharacterCasing="Normal"
                 WindowStartupLocation="CenterScreen"
                 GlowBrush="{DynamicResource MahApps.Brushes.Accent}"
                 BorderThickness="1">

Code-behind must inherit MetroWindow:

using MahApps.Metro.Controls;
public partial class MainWindow : MetroWindow { ... }

Key MetroWindow Properties

PropertyTypeDescription
GlowBrushBrushGlow effect around window edges
NonActiveGlowBrushBrushGlow when window is inactive
BorderThicknessThicknessBorder width (use with BorderBrush)
SaveWindowPositionboolPersist position/size across restarts
ShowTitleBarboolShow/hide title bar
TitleBarHeightintTitle bar height in pixels
TitleCharacterCasingCharacterCasingNormal, Upper, Lower
TitleAlignmentHorizontalAlignmentTitle text alignment
ShowIconOnTitleBarboolShow icon in title bar
WindowTransitionsEnabledboolEnable/disable animations

Window Commands (title bar buttons)

<mah:MetroWindow.LeftWindowCommands>
    <mah:WindowCommands>
        <Button ToolTip="Settings">
            <iconPacks:PackIconPhosphorIcons Kind="Gear" Width="18" Height="18" />
        </Button>
    </mah:WindowCommands>
</mah:MetroWindow.LeftWindowCommands>

<mah:MetroWindow.RightWindowCommands>
    <mah:WindowCommands>
        <Button Command="{Binding RefreshCommand}" ToolTip="Refresh">
            <StackPanel Orientation="Horizontal">
                <iconPacks:PackIconPhosphorIcons Kind="ArrowsClockwise" Width="16" Height="16"
                                                 VerticalAlignment="Center" Margin="0,0,6,0" />
                <TextBlock Text="Refresh" VerticalAlignment="Center" />
            </StackPanel>
        </Button>
    </mah:WindowCommands>
</mah:MetroWindow.RightWindowCommands>

Supported control types in WindowCommands: Button, ToggleButton, SplitButton, DropDownButton.


3. Theming

Theme API lives in ControlzEx.Theming. Theme name format: {Base}.{Accent}.

Bases: Light, Dark Accents: Red, Green, Blue, Purple, Orange, Lime, Emerald, Teal, Cyan, Cobalt, Indigo, Violet, Pink, Magenta, Crimson, Amber, Yellow, Brown, Olive, Steel, Mauve, Taupe, Sienna

Change theme at runtime

using ControlzEx.Theming;

// Application-wide
ThemeManager.Current.ChangeTheme(Application.Current, "Dark.Blue");

// Single window
ThemeManager.Current.ChangeTheme(this, "Light.Cobalt");

Sync with Windows OS theme

ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.SyncWithAppMode;
ThemeManager.Current.SyncTheme();

Generate theme from any color

ThemeManager.Current.AddTheme(
    RuntimeThemeGenerator.Current.GenerateRuntimeTheme("Dark", Colors.Red));

Key Brush Resource Keys (use with DynamicResource)

KeyUsage
MahApps.Brushes.AccentPrimary accent color
MahApps.Brushes.Accent2Lighter accent
MahApps.Brushes.Accent3Even lighter accent
MahApps.Brushes.Accent4Lightest accent
MahApps.Brushes.ThemeBackgroundWindow/page background
MahApps.Brushes.ThemeForegroundPrimary text color
MahApps.Brushes.IdealForegroundText on accent backgrounds
MahApps.Brushes.Gray1 through Gray10Gray scale
MahApps.Brushes.TextStandard text
MahApps.Brushes.TextBox.BorderTextBox border
MahApps.Brushes.TextBox.Border.FocusTextBox focused border
MahApps.Brushes.Button.BorderButton border
MahApps.Brushes.Validation5Validation error (#FFDC000C)

4. Controls

Flyouts (sliding overlay panels)

<mah:MetroWindow.Flyouts>
    <mah:FlyoutsControl>
        <mah:Flyout Header="Settings"
                    IsOpen="{Binding IsSettingsFlyoutOpen}"
                    Position="Right" Width="300"
                    Theme="Adapt">
            <!-- Content here -->
        </mah:Flyout>
    </mah:FlyoutsControl>
</mah:MetroWindow.Flyouts>
PropertyValues
PositionLeft, Right, Top, Bottom
ThemeAdapt, Inverse, Dark, Light, Accent

Gotcha: LeftWindowCommands, RightWindowCommands, and Icon are never shown over an opened Flyout.

ToggleSwitch

<mah:ToggleSwitch Header="Enable Feature"
                  IsOn="{Binding IsFeatureEnabled}"
                  OnContent="Active"
                  OffContent="Inactive" />

NumericUpDown

<mah:NumericUpDown Minimum="0" Maximum="10000"
                   Interval="100" StringFormat="N0"
                   Value="{Binding Quantity}" />
PropertyDescription
Minimum / MaximumValue range
IntervalStep per click
StringFormatDisplay format ("C2", "N0", "P1", "{0:N2} pcs")
HideUpDownButtonsHide +/- buttons
SpeedUpAcceleration when holding button (default: true)

ProgressRing

<mah:ProgressRing IsActive="{Binding IsLoading}" />

MetroAnimatedSingleRowTabControl (single-row animated tabs)

<mah:MetroAnimatedSingleRowTabControl SelectedIndex="{Binding SelectedTabIndex}">
    <TabItem Header="Dashboard">
        <!-- content -->
    </TabItem>
    <TabItem>
        <TabItem.Header>
            <StackPanel Orientation="Horizontal">
                <iconPacks:PackIconPhosphorIcons Kind="Files" Width="16" Height="16"
                                                 VerticalAlignment="Center" Margin="0,0,6,0" />
                <TextBlock Text="Files" VerticalAlignment="Center" />
            </StackPanel>
        </TabItem.Header>
        <!-- content -->
    </TabItem>
</mah:MetroAnimatedSingleRowTabControl>

MetroProgressBar

<mah:MetroProgressBar Value="{Binding Progress, Mode=OneWay}" Maximum="100"
                       Foreground="{DynamicResource MahApps.Brushes.Accent}" />

Gotcha: MetroProgressBar.Value binding defaults to TwoWay. Use Mode=OneWay when binding to read-only sources like KeyValuePair.Value.

DropDownButton

<mah:DropDownButton Content="Options"
                    ItemsSource="{Binding MenuItems}"
                    DisplayMemberPath="Name">
    <mah:DropDownButton.Icon>
        <iconPacks:PackIconPhosphorIcons Kind="List" Margin="6" />
    </mah:DropDownButton.Icon>
    <mah:DropDownButton.ItemContainerStyle>
        <Style BasedOn="{StaticResource {x:Type MenuItem}}" TargetType="{x:Type MenuItem}">
            <Setter Property="Command" Value="{Binding RelativeSource={RelativeSource FindAncestor,
                    AncestorType={x:Type mah:DropDownButton}}, Path=DataContext.ItemCommand}" />
            <Setter Property="CommandParameter" Value="{Binding}" />
        </Style>
    </mah:DropDownButton.ItemContainerStyle>
</mah:DropDownButton>

Uses ContextMenu internally. No SelectedItem.

SplitButton (button + dropdown with selection)

<mah:SplitButton SelectedIndex="0"
                 ItemsSource="{Binding Options}"
                 DisplayMemberPath="Name"
                 Command="{Binding ExecuteCommand}" />

Has SelectedItem, SelectedIndex, SelectionChanged. Uses ListBox internally.

Badged (badge overlay)

<mah:Badged Badge="{Binding UnreadCount}" BadgePlacement="TopRight">
    <Button Content="Notifications" />
</mah:Badged>

Tile

<mah:Tile Title="Mail" Background="Teal" HorizontalTitleAlignment="Right">
    <iconPacks:PackIconPhosphorIcons Kind="Envelope" Width="40" Height="40" />
</mah:Tile>

5. Dialogs (Async API)

All dialog methods are async extension methods on MetroWindow. Namespace: MahApps.Metro.Controls.Dialogs.

ShowMessageAsync

var result = await this.ShowMessageAsync(
    "Confirm Delete",
    "Are you sure?",
    MessageDialogStyle.AffirmativeAndNegative,
    new MetroDialogSettings
    {
        AffirmativeButtonText = "Delete",
        NegativeButtonText = "Cancel",
        DefaultButtonFocus = MessageDialogResult.Negative
    });

if (result == MessageDialogResult.Affirmative) { /* delete */ }

MessageDialogStyle: Affirmative (OK only), AffirmativeAndNegative (OK + Cancel), AffirmativeAndNegativeAndSingleAuxiliary (+ 1 extra), AffirmativeAndNegativeAndDoubleAuxiliary (+ 2 extra)

MessageDialogResult: Canceled (-1), Negative (0), Affirmative (1), FirstAuxiliary (2), SecondAuxiliary (3)

ShowInputAsync

var input = await this.ShowInputAsync("Rename", "Enter new name:",
    new MetroDialogSettings { DefaultText = "Current Name" });
if (input != null) { /* use input */ }

Returns null if cancelled.

ShowProgressAsync

var controller = await this.ShowProgressAsync("Working", "Please wait...", isCancelable: true);
controller.SetIndeterminate();

// Do work...
controller.SetProgress(0.5);
controller.SetMessage("Almost done...");

await controller.CloseAsync();

ProgressDialogController API: SetProgress(double), SetIndeterminate(), SetMessage(string), SetTitle(object), SetCancelable(bool), CloseAsync(), IsCanceled, IsOpen

ShowLoginAsync

var data = await this.ShowLoginAsync("Login", "Enter credentials:");
if (data != null) { /* data.Username, data.Password */ }

MetroDialogSettings (key properties)

PropertyDefaultDescription
AffirmativeButtonText"OK"OK button text
NegativeButtonText"Cancel"Cancel button text
FirstAuxiliaryButtonTextnullExtra button 1 text
DefaultButtonFocusNegativeWhich button gets focus
DefaultText""Input dialog default text
AnimateShow / AnimateHidetrueDialog animations
ColorSchemeThemeTheme or Accented
CancellationTokenNoneCancellation token

MVVM Dialogs (DialogCoordinator) -- no MetroWindow reference needed

XAML -- register ViewModel as dialog context:

<mah:MetroWindow xmlns:Dialog="clr-namespace:MahApps.Metro.Controls.Dialogs;assembly=MahApps.Metro"
                 Dialog:DialogParticipation.Register="{Binding}">

ViewModel:

using MahApps.Metro.Controls.Dialogs;

public class MyViewModel
{
    private readonly IDialogCoordinator _dialogCoordinator;

    public MyViewModel(IDialogCoordinator dialogCoordinator)
    {
        _dialogCoordinator = dialogCoordinator;
    }

    private async Task ConfirmAsync()
    {
        var result = await _dialogCoordinator.ShowMessageAsync(
            this, "Title", "Message",
            MessageDialogStyle.AffirmativeAndNegative);
    }

    private async Task ShowProgressAsync()
    {
        var ctrl = await _dialogCoordinator.ShowProgressAsync(this, "Working", "...");
        ctrl.SetIndeterminate();
        // ...
        await ctrl.CloseAsync();
    }
}

DI registration: services.AddSingleton<IDialogCoordinator>(DialogCoordinator.Instance);


6. Styles

Button Styles

Style KeyLook
(default)Standard metro button
MahApps.Styles.Button.CircleCircular
MahApps.Styles.Button.SquareSquare
MahApps.Styles.Button.Square.AccentSquare with accent background
<Button Style="{DynamicResource MahApps.Styles.Button.Circle}">
    <iconPacks:PackIconPhosphorIcons Kind="Plus" />
</Button>

TextBox Attached Properties (TextBoxHelper)

<TextBox mah:TextBoxHelper.Watermark="Search..."
         mah:TextBoxHelper.ClearTextButton="True"
         mah:TextBoxHelper.UseFloatingWatermark="True" />

<PasswordBox mah:TextBoxHelper.Watermark="Password"
             mah:TextBoxHelper.ClearTextButton="True" />
Attached PropertyDescription
TextBoxHelper.WatermarkPlaceholder text
TextBoxHelper.ClearTextButtonShow X button to clear
TextBoxHelper.UseFloatingWatermarkAnimated floating label
TextBoxHelper.SelectAllOnFocusSelect all on focus
TextBoxHelper.AutoWatermarkWatermark from DisplayAttribute
TextBoxHelper.ButtonCommandCustom button command
TextBoxHelper.ButtonContentCustom button content

Works on: TextBox, PasswordBox, ComboBox, NumericUpDown, DatePicker, TimePicker.

DataGrid Styles

<!-- Default (auto-applied) -->
<DataGrid ItemsSource="{Binding Items}" />

<!-- Azure style -->
<DataGrid Style="{StaticResource MahApps.Styles.DataGrid.Azure}" />

<!-- NumericUpDown column -->
<mah:DataGridNumericUpDownColumn Header="Price" Binding="{Binding Price}"
                                 StringFormat="C" Minimum="0" />

<!-- Styled CheckBox column -->
<DataGridCheckBoxColumn Header="Select"
    ElementStyle="{DynamicResource MetroDataGridCheckBox}"
    EditingElementStyle="{DynamicResource MetroDataGridCheckBox}" />

GroupBox, StatusBar, TabControl

All standard WPF controls are auto-styled by the Controls.xaml resource dictionary. No explicit Style= needed.


7. Icon Packs (PhosphorIcons)

xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"

Basic Usage

<iconPacks:PackIconPhosphorIcons Kind="House" Width="24" Height="24" />

Properties

PropertyDescription
KindIcon enum value (e.g. File, FolderSimple, MagnifyingGlass)
ForegroundIcon color brush
Width / HeightSize
FlipNormal, Horizontal, Vertical, Both
RotationAngleDegrees
SpinEnable spin animation
SpinDurationSeconds per rotation

Common Icons in This Project

Icon KindUsed For
ChartBarDashboard tab
FilesFiles tab, file count
ScalesDecisions tab
WarningRisks tab
MagnifyingGlassSearch tab
UserCircleHandover tab
ArrowsClockwiseRefresh button
FolderSimple / FolderOpenDirectory nodes
FileTextMarkdown files
TerminalShell scripts
FileJsJavaScript files
FileCodeC# files
BracketsCurlyJSON files
ArrowRightList item bullets
CheckCompleted items
UserTeam members

8. Common Patterns in This Project

Tab header with icon

<TabItem>
    <TabItem.Header>
        <StackPanel Orientation="Horizontal">
            <iconPacks:PackIconPhosphorIcons Kind="ChartBar" Width="16" Height="16"
                                             VerticalAlignment="Center" Margin="0,0,6,0" />
            <TextBlock Text="Dashboard" VerticalAlignment="Center" />
        </StackPanel>
    </TabItem.Header>
    <views:DashboardView DataContext="{Binding Dashboard}" />
</TabItem>

Status badge (colored pill)

<Border CornerRadius="3" Padding="8,2"
        Background="{Binding Status, Converter={StaticResource ActionStatusToColorConverter}}">
    <TextBlock Text="{Binding Status}" FontSize="11" Foreground="White" FontWeight="SemiBold" />
</Border>

KPI card (number + label)

<Border Background="White" CornerRadius="4" Padding="12,6">
    <StackPanel>
        <TextBlock Text="{Binding Count}" FontSize="22" FontWeight="Bold"
                   Foreground="{DynamicResource MahApps.Brushes.Accent}" HorizontalAlignment="Center" />
        <TextBlock Text="Open Decisions" FontSize="10"
                   Foreground="{DynamicResource MahApps.Brushes.Accent}" />
    </StackPanel>
</Border>

Accent header banner

<Border Background="{DynamicResource MahApps.Brushes.Accent}" CornerRadius="6" Padding="16">
    <TextBlock Text="{Binding Title}" FontSize="20" FontWeight="Bold" Foreground="White" />
</Border>

GroupBox with icon header

<GroupBox>
    <GroupBox.Header>
        <StackPanel Orientation="Horizontal">
            <iconPacks:PackIconPhosphorIcons Kind="UserCircle" Width="16" Height="16"
                                              VerticalAlignment="Center" Margin="0,0,6,0" />
            <TextBlock Text="Section Title" FontWeight="SemiBold" />
        </StackPanel>
    </GroupBox.Header>
    <!-- content -->
</GroupBox>

StatusBar with accent background

<StatusBar Background="{DynamicResource MahApps.Brushes.Accent}">
    <StatusBarItem>
        <StackPanel Orientation="Horizontal">
            <iconPacks:PackIconPhosphorIcons Kind="Files" Width="14" Height="14"
                                             Foreground="White" VerticalAlignment="Center" Margin="0,0,4,0" />
            <TextBlock Text="{Binding FileCount, StringFormat='{}{0} files'}" Foreground="White" />
        </StackPanel>
    </StatusBarItem>
</StatusBar>

9. Gotchas and Anti-Patterns

  1. MetroProgressBar.Value defaults to TwoWay binding. Always use Mode=OneWay when binding to read-only properties (e.g. KeyValuePair.Value).
  2. Resource dictionary filenames are case-sensitive: Light.Blue.xaml not light.blue.xaml.
  3. Flyouts cover WindowCommands -- LeftWindowCommands, RightWindowCommands, and Icon are never shown over an open Flyout.
  4. SaveWindowPosition can cause off-screen launch if a monitor is disconnected. Provide a reset option.
  5. ThemeManager is in ControlzEx.Theming, not MahApps.Metro.
  6. DynamicResource vs StaticResource -- Use DynamicResource for MahApps brushes/colors so they respond to runtime theme changes.
  7. DialogCoordinator context -- Must register the ViewModel via Dialog:DialogParticipation.Register="{Binding}" in the MetroWindow XAML.
  8. Code-behind inheritance -- The Window class must inherit MetroWindow, not Window.

References

See references/controls-reference.md for the full controls inventory and property tables.

Repository
salemaziel/AIPlanningPilot
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.