WPF Icon Overlays

Window code, note that we are binding the Overlay to the ImageSource property of the Counter.

 

<Window x:Class="IMAPChecker.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:main="clr-namespace:IMAPChecker"
>
      <Window.TaskbarItemInfo>
        <TaskbarItemInfo 
            Overlay="{Binding ElementName=Counter, Path=ImageSource}" 
            />         
    </Window.TaskbarItemInfo>
    


    <Grid Margin="10">
       …
            <main:CountControl x:Name="Counter"  
                               DisplayCount="{Binding Path=Unseen}"                               
                               HasIssue="{Binding Path=HasError}"
                               IsChecking="{Binding Path=IsChecking}"
                               />

</Grid> </Window>

The counter control Xaml is mainly defining brushes.

<UserControl x:Class="IMAPChecker.CountControl"
             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:cc="clr-namespace:IMAPChecker"
             mc:Ignorable="d" Height="16" Width="16">

    <UserControl.Resources>
        <BooleanToVisibilityConverter x:Key="boolToVisibility"/>

        <RadialGradientBrush  x:Key="EmptyBrush"
                              GradientOrigin="0.5,0.1" Center="0.5,0" RadiusX="2" RadiusY="0.9" >
            <GradientStop Color="#a0ffffff" Offset="0" />
            <GradientStop Color="#40ffffff" Offset="0.6"/>
            <GradientStop Color="#40000000" Offset="0.7"/>
            <GradientStop Color="#00000000" Offset="1.25"/>
        </RadialGradientBrush>

        <RadialGradientBrush  x:Key="HighlightBrush"
                              GradientOrigin="0.5,0.1" Center="0.5,0" RadiusX="2" RadiusY="0.9" >
            <GradientStop Color="LightGreen" Offset="0" />
            <GradientStop Color="Green" Offset="0.6"/>
            <GradientStop Color="DarkGreen" Offset="0.7"/>
            <GradientStop Color="Green" Offset="1.25"/>
        </RadialGradientBrush>

        <RadialGradientBrush  x:Key="MutedBrush"
                              GradientOrigin="0.5,0.1" Center="0.5,0" RadiusX="2" RadiusY="0.9" >
            <GradientStop Color="LightYellow" Offset="0" />
            <GradientStop Color="CornflowerBlue" Offset="0.6"/>
            <GradientStop Color="Blue" Offset="0.7"/>
            <GradientStop Color="Gray" Offset="1.25"/>
        </RadialGradientBrush>

        <RadialGradientBrush  x:Key="CheckingBrush"
                              GradientOrigin="0.5,0.1" Center="0.5,0" RadiusX="2" RadiusY="0.9" >
            <GradientStop Color="LightYellow" Offset="0" />
            <GradientStop Color="Gold" Offset="0.6"/>
            <GradientStop Color="GoldenRod" Offset="0.7"/>
            <GradientStop Color="Gold" Offset="1.25"/>
        </RadialGradientBrush>

        <RadialGradientBrush  x:Key="IssueBrush"
                              GradientOrigin="0.5,0.1" Center="0.5,0" RadiusX="2" RadiusY="0.9" >
            <GradientStop Color="LightPink" Offset="0" />
            <GradientStop Color="Red" Offset="0.6"/>
            <GradientStop Color="DarkRed" Offset="0.7"/>
            <GradientStop Color="Red" Offset="1.25"/>
        </RadialGradientBrush>
    </UserControl.Resources>
    <Border BorderThickness="1" Name="ColourBorder"
        CornerRadius="5" 
        HorizontalAlignment="Stretch"
        VerticalAlignment="Stretch"
        Background="{StaticResource CheckingBrush}">

        <Viewbox HorizontalAlignment="Center">
            <TextBlock Name="TextBlock"
                    HorizontalAlignment="Stretch" VerticalAlignment="Center"
                    TextAlignment="Center" TextWrapping="NoWrap" 
                    Foreground="Gray" FontWeight="Bold"  
                    Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type cc:CountControl}}, Path=DisplayCount}"
                    />
            <!--Text="1"-->
        </Viewbox>

        <Border.BorderBrush>
            <LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
                <GradientStop Color="LightGray" Offset="0" />
                <GradientStop Color="DarkSlateGray" Offset="1" />
            </LinearGradientBrush>
        </Border.BorderBrush>
    </Border>

</UserControl>

The fun begins in the code behind, first there is state logic mapping the state to brushes, which I could alternatively do via a Convertor but the logic seemed to be part of the control so made sense here

public partial class CountControl : UserControl
 {
     public CountControl()
     {
         InitializeComponent();
         InitializeBitmapGeneration();
         DisplayCount = 40;
     }

     public static readonly DependencyProperty DisplayCountProperty = DependencyProperty.Register(
         "DisplayCount",
         typeof(int),
         typeof(CountControl),
         new UIPropertyMetadata(0,
             (d, e) => ((CountControl)d)._OnDisplayChanged()));

     public static readonly DependencyProperty HasIssueProperty = DependencyProperty.Register(
      "HasIssue",
      typeof(bool),
      typeof(CountControl),
      new UIPropertyMetadata(false,
          (d, e) => ((CountControl)d)._OnDisplayChanged()));

     public static readonly DependencyProperty IsCheckingProperty = DependencyProperty.Register(
   "IsChecking",
   typeof(bool),
   typeof(CountControl),
   new UIPropertyMetadata(false,
       (d, e) => ((CountControl)d)._OnDisplayChanged()));

     public static readonly DependencyProperty HasVolumeProperty = DependencyProperty.Register(
   "HasVolume",
   typeof(bool),
   typeof(CountControl),
   new UIPropertyMetadata(false,
       (d, e) => ((CountControl)d)._OnDisplayChanged()));

     public int DisplayCount
     {
         get { return (int)GetValue(DisplayCountProperty); }
         set { SetValue(DisplayCountProperty, value); }
     }

     public bool HasIssue
     {
         get { return (bool)GetValue(HasIssueProperty); }
         set { SetValue(HasIssueProperty, value); }
     }

     public bool HasVolume
     {
         get { return (bool)GetValue(HasVolumeProperty); }
         set { SetValue(HasVolumeProperty, value); }
     }

     public bool IsChecking
     {
         get { return (bool)GetValue(IsCheckingProperty); }
         set { SetValue(IsCheckingProperty, value); }
     }

     private void _OnDisplayChanged()
     {
         ImageSource = null;
         if (IsChecking)
         {
             ColourBorder.Background = (Brush)Resources["CheckingBrush"];
             TextBlock.Foreground = Brushes.Black;
             return;
         }

         if (HasIssue)
         {
             ColourBorder.Background = (Brush)Resources["IssueBrush"];
             TextBlock.Foreground = Brushes.White;
             return;
         }

         if (HasVolume == false)
         {
             ColourBorder.Background = (Brush)Resources["MutedBrush"];
             TextBlock.Foreground = Brushes.Black;
             return;
         }

         if (DisplayCount == 0)
         {
             ColourBorder.Background = (Brush)Resources["EmptyBrush"];
             TextBlock.Foreground = Brushes.Black;
         }
         else
         {
             ColourBorder.Background = (Brush)Resources["HighlightBrush"];
             TextBlock.Foreground = Brushes.White;
         }


     }

And then we have the fun part where we convert the Control into an image.

   #region Should be in base class

        protected void InitializeBitmapGeneration()
        {
            LayoutUpdated += (sender, e) => _UpdateImageSource();
        }

        public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register(
           "ImageSource",
           typeof(ImageSource),
           typeof(CountControl),
           new PropertyMetadata(null));

        /// <summary>
        /// Gets or sets the ImageSource property.  This dependency property 
        /// indicates ....
        /// </summary>
        public ImageSource ImageSource
        {
            get { return (ImageSource)GetValue(ImageSourceProperty); }
            set { SetValue(ImageSourceProperty, value); }
        }

        private void _UpdateImageSource()
        {
            if (ActualWidth == 0 || ActualHeight == 0)
            {
                return;
            }
            ImageSource = GenerateBitmapSource(this, 16, 16);
        }

        public static BitmapSource GenerateBitmapSource(ImageSource img)
        {
            return GenerateBitmapSource(img, img.Width, img.Height);
        }

        public static BitmapSource GenerateBitmapSource(ImageSource img, double renderWidth, double renderHeight)
        {
            var dv = new DrawingVisual();
            using (DrawingContext dc = dv.RenderOpen())
            {
                dc.DrawImage(img, new Rect(0, 0, renderWidth, renderHeight));
            }
            var bmp = new RenderTargetBitmap((int)renderWidth, (int)renderHeight, 96, 96, PixelFormats.Pbgra32);
            bmp.Render(dv);
            return bmp;
        }

        public static BitmapSource GenerateBitmapSource(Visual visual, double renderWidth, double renderHeight)
        {
            var bmp = new RenderTargetBitmap((int)renderWidth, (int)renderHeight, 96, 96, PixelFormats.Pbgra32);
            var dv = new DrawingVisual();
            using (DrawingContext dc = dv.RenderOpen())
            {
                dc.DrawRectangle(new VisualBrush(visual), null, new Rect(0, 0, renderWidth, renderHeight));
            }
            bmp.Render(dv);
            return bmp;
        }
        #endregion

    }
}