Icon badge overlay for notifications in silverlight/xaml

badge, notifications, silverlight, styles, xaml

Solution

Here's my solution for this. By default, the badge will show in the upper right corner. You can change this by setting the "BadgeMarginOffset" property. I've attached a couple of images to show how it appears. One, which shows the badge wrapping a Telerik RadRibbonButton. You can also change the background and foreground colors of the badge (BadgeBackground, BadgeForeground). The defaults are shown below.

The UserControl's XAML

<UserControl x:Class="Foundation.Common.Controls.Wpf.Badge"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:converters="clr-namespace:Foundation.Common.Controls.Wpf.Converters"
         Background="Transparent" x:Name="UserControl"
         mc:Ignorable="d" >
<UserControl.Resources>
    <converters:GreaterThanZeroBooleanConverter x:Key="GreaterThanZeroBooleanConverter" />
    <converters:GreaterThanZeroVisibilityConverter x:Key="GreaterThanZeroVisibilityConverter"/>
</UserControl.Resources>
<UserControl.Template>
    <ControlTemplate>
        <Grid HorizontalAlignment="Stretch" >
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Border CornerRadius="10"  
                    UseLayoutRounding="True"
                    x:Name="BadgeBorder"
                    Grid.ZIndex="99"
                    VerticalAlignment="Top"
                    HorizontalAlignment="Right"
                    Visibility="{Binding ElementName=UserControl, Path=Count, Mode=TwoWay, Converter={StaticResource GreaterThanZeroVisibilityConverter}}"
                    Grid.Row="0"
                    Margin="{Binding ElementName=UserControl, Path=BadgeMarginOffset}"
                    Height="22"
                    Padding="6.7,2,7,3" 
                    Background="{Binding ElementName=UserControl, Path=BadgeBackground}">
                <Border.Style>
                    <Style TargetType="Border">
                        <Style.Triggers>
                            <DataTrigger Binding="{Binding ElementName=UserControl, Path=Count, Mode=TwoWay, Converter={StaticResource GreaterThanZeroBooleanConverter}}" Value="True">
                                <DataTrigger.EnterActions>
                                    <BeginStoryboard>
                                        <Storyboard>
                                            <DoubleAnimation From="0.0"
                                                             To="1.0"
                                                             Duration="0:0:0.7"
                                                             Storyboard.TargetProperty="Opacity"/>
                                        </Storyboard>
                                    </BeginStoryboard>
                                </DataTrigger.EnterActions>
                            </DataTrigger>
                            <DataTrigger Binding="{Binding ElementName=UserControl, Path=ShowDropShadow}" Value="True">
                                <Setter Property="Effect">
                                    <Setter.Value>
                                        <DropShadowEffect BlurRadius="6" ShadowDepth="4" Color="#949494"/>
                                    </Setter.Value>
                                </Setter>
                            </DataTrigger>
                        </Style.Triggers>
                    </Style>
                </Border.Style>
                <TextBlock Text="{Binding ElementName=UserControl, Path=Count}"
                           Foreground="{Binding ElementName=UserControl, Path=BadgeForeground}"
                           FontWeight="Bold"
                           FontSize="12">
                </TextBlock>
            </Border>
            <ContentPresenter Grid.Row="0" 
                              Grid.RowSpan="2"
                              DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=DataContext}"
                              Content="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Content}" />
        </Grid>
    </ControlTemplate>
</UserControl.Template>

The UserControl's code behind

public partial class Badge : UserControl
{
    #region Dependency Properties

    public static readonly DependencyProperty CountProperty =
        DependencyProperty.Register("Count", typeof(int), typeof(Badge));

    public static readonly DependencyProperty ShowDropShadowProperty =
        DependencyProperty.Register("ShowDropShadow", typeof(bool), typeof(Badge), new PropertyMetadata(true));

    public static readonly DependencyProperty BadgeMarginOffsetProperty =
        DependencyProperty.Register("BadgeMarginOffset", typeof(Thickness), typeof(Badge));

    public static readonly DependencyProperty BadgeBackgroundProperty =
        DependencyProperty.Register("BadgeBackground", typeof(Brush), typeof(Badge), new PropertyMetadata(Brushes.Red));

    public static readonly DependencyProperty BadgeForegroundProperty =
        DependencyProperty.Register("BadgeForeground", typeof(Brush), typeof(Badge), new PropertyMetadata(Brushes.White));

    #endregion Dependency Properties

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="Badge"/> class.
    /// </summary>
    public Badge()
    {
        this.InitializeComponent();
    }

    #endregion Constructor

    #region Properties

    /// <summary>
    /// Gets or sets a value indicating whether [show drop shadow].
    /// </summary>
    /// <value>
    ///   <c>true</c> if [show drop shadow]; otherwise, <c>false</c>.
    /// </value>
    public bool ShowDropShadow
    {
        get => (bool)this.GetValue(ShowDropShadowProperty);
        set => this.SetValue(ShowDropShadowProperty, value);
    }

    /// <summary>
    /// Gets or sets the badge margin offset.
    /// </summary>
    /// <value>
    /// The badge margin offset.
    /// </value>
    public Thickness BadgeMarginOffset
    {
        get => (Thickness)this.GetValue(BadgeMarginOffsetProperty);
        set => this.SetValue(BadgeMarginOffsetProperty, value);
    }

    /// <summary>
    /// Gets or sets the badge background.
    /// </summary>
    /// <value>
    /// The badge background.
    /// </value>
    public Brush BadgeBackground
    {
        get => (Brush)this.GetValue(BadgeBackgroundProperty);
        set => this.SetValue(BadgeBackgroundProperty, value);
    }

    /// <summary>
    /// Gets or sets the badge foreground.
    /// </summary>
    /// <value>
    /// The badge foreground.
    /// </value>
    public Brush BadgeForeground
    {
        get => (Brush)this.GetValue(BadgeForegroundProperty);
        set => this.SetValue(BadgeBackgroundProperty, value);
    }

    /// <summary>
    /// Gets or sets the count.
    /// </summary>
    /// <value>
    /// The count.
    /// </value>
    public int Count
    {
        get => (int)this.GetValue(CountProperty);
        set => this.SetValue(CountProperty, value);
    }

    #endregion Properties
}

Sample code for the top two images

<wpf:Badge Count="108" Margin="20" HorizontalAlignment="Left" BadgeMarginOffset="0,-5,-5,0">
    <Button Height="100" Width="100">
        <Button.Content>
            <Image Source="Resources/about.png" />
        </Button.Content>
    </Button>
</wpf:Badge>

Problem

I've got a ribbon bar in my silverlight app and on one of the icons I would like for there to be a badge icon showing the number of items in the view that the icon activates. Picture the Mail icon in OS X showing the number of unread messages or the notifications counter on an IOS app icon. I don't know much about xaml styles, but it seems to me I could duplicate the default style for the ribbon bar button, then add to it with some sort of red circle, and a white text that took in its value from a new property on the ribbon bar button somehow so I would be able to bind to it. Does anyone have an example of something like this I can start from? Thanks Shawn for the answer. This is what I ended up doing: In the xaml: ``` <telerikRibbonBar:RadRibbonRadioButton Text="Expired Active Call Factors" Size="Large" LargeImage="/CallFactorDatabase.UI;component/Images/Ribbon/Large/ExpiredActiveView.png" Command="{Binding ActivateViewCommand}" CommandParameter="ExpiredActiveView"> <Grid> <Grid.Resources> <converters:BooleanToVisibilityConverter x:Key="visibleWhenTrueConverter" VisibilityWhenTrue="Visible" VisibilityWhenFalse="Collapsed" /> </Grid.Resources> <Grid Width="27" Height="27" Visibility="{Binding ExpiredActiveCallFactors, Converter={StaticResource visibleWhenTrueConverter}}" Margin="50,-40,0,0"> <Ellipse Fill="Black" Width="27" Height="27"/> <Ellipse Width="25" Height="25" VerticalAlignment="Center" HorizontalAlignment="Center"> <Ellipse.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Color="Coral" Offset="0.0" /> <GradientStop Color="Red" Offset="1.0" /> </LinearGradientBrush> </Ellipse.Fill> </Ellipse> <Viewbox Width="25" Height="25" VerticalAlignment="Center" HorizontalAlignment="Center" > <TextBlock Text="{Binding ExpiredActiveCallFactorsCount}" Foreground="White"/> </Viewbox> </Grid> </Grid> </telerikRibbonBar:RadRibbonRadioButton> ``` How it looks: No luck getting it in front of the ribbon button but oh well.

Original source