Badged Control

Badged wraps a control and draws a small badge over one of its corners or edges — an unread count, a status, a short word.

The eight placement modes

<mah:Badged Badge="42">
    <Button Content="Notifications" />
</mah:Badged>

The badge is placed by BadgePlacementMode, which defaults to TopRight:

<mah:Badged Badge="{Binding UnreadCount}" BadgePlacementMode="BottomRight">
    <Button>
        <iconPacks:PackIconFontAwesome Kind="CommentOutline" />
    </Button>
</mah:Badged>

The iconPacks control in that sample comes from MahApps.Metro.IconPacks, a separate package.

The badge content

Badge is an object, so it takes anything a ContentControl would — a number, a string, an element, or a view model with a BadgeTemplate.

A number, a word, a string format, and no badge

<mah:Badged Badge="3" BadgeStringFormat="{}{0} new" />

The fourth panel has no Badge at all: when it is null or empty the badge is hidden and IsBadgeSet is false. Bind the count and the badge appears and disappears on its own — there is no visibility to manage.

Styling the badge

The default badge, a restyled one, and a disabled control

<mah:Badged Badge="42"
            BadgeBackground="{DynamicResource MahApps.Brushes.Accent}"
            BadgeForeground="{DynamicResource MahApps.Brushes.IdealForeground}"
            BadgeBorderBrush="{DynamicResource MahApps.Brushes.ThemeBackground}"
            BadgeBorderThickness="2"
            BadgeFontSize="13">
    <Button Content="Notifications" />
</mah:Badged>

A border in the window's background colour is the usual trick for a badge that overlaps busy content — it gives the disc a ring of clear space.

The pop when the value changes

This is the one property MahApps adds; everything else on this page comes from BadgedEx in ControlzEx, which Badged derives from.

Property Type Default
BadgeChangedStoryboard Storyboard a scale animation

When Badge changes, the storyboard is started on the badge container, so the disc springs briefly as the number goes up. The default scales X and Y through a SineEase; replace it to change the gesture, or set it to {x:Null} for no animation at all:

<mah:Badged Badge="{Binding UnreadCount}" BadgeChangedStoryboard="{x:Null}" />

A storyboard that cannot run on the badge container — one targeting a property the container does not have, for instance — is not swallowed. Badged catches the failure and rethrows it as a MahAppsException with the message "Uups, it seems like there is something wrong with the given Storyboard.", so a bad animation surfaces at the first badge change rather than silently doing nothing.

Properties

Property Type Default
Badge object null what the badge shows
BadgePlacementMode BadgePlacementMode TopRight where it sits
BadgeMargin Thickness 1 0 nudges the badge from that position
BadgeBackground Brush MahApps.Brushes.Badged.Background
BadgeForeground Brush MahApps.Brushes.Badged.Foreground
BadgeBorderBrush Brush null
BadgeBorderThickness Thickness 0
BadgeFontFamily, BadgeFontSize, BadgeFontStyle, BadgeFontStretch, BadgeFontWeight 11, DemiBold for the last two the badge's own font, independent of the content's
BadgeStringFormat string null format for a non-string Badge
BadgeTemplate, BadgeTemplateSelector null for a badge that is more than text
BadgeChangedStoryboard Storyboard a scale animation see above
IsBadgeSet bool, read-only whether there is anything to show
BadgeChanged RoutedEvent raised when Badge changes

What a screen reader is told

The badge is a mark on whatever the control wraps, so it belongs to that thing rather than standing beside it.

In a released version the badge arrives as a stray piece of text next to the content, with nothing tying the two together. A reader announces the inbox button and, somewhere after it, a lone "3".

On develop the control hands the badge over as the status of what it wraps, which is what ItemStatus is for, and says it is a group holding that content. A reader then has the count and the button in one place. Nothing about the drawing changes.

BadgePlacementMode

Eight values, all shown in the first figure: TopLeft, Top, TopRight, Right, BottomRight, Bottom, BottomLeft, Left.

The corner modes put the badge outside the control's corner; the edge modes centre it on that edge. Either way the badge hangs over the edge by half its own size, and a Badged asks for no more room than its content needs, so the badge is outside anything the layout knows about. Next to something else it will overlap. A margin on the Badged is what keeps the two apart.

On develop a badge on a tab header is no longer cut in half. A Badged takes no layout clip of its own any more, and the host that carries a tab header rounds the way the grid around it does. Between them those were what did it: the header asking for a tenth of a pixel more than the rounding handed out was enough for WPF to clip it, and a clip takes the whole overhanging badge with it. Which header it happened to came down to where the caption landed, which is why #2994 reads as though it picked its victims at random. A container that clips on purpose still clips.