The Curious Incident of the MessageBox in the Silverlight App

I’ve been puzzling over how you’d actually do;

MessageBox.Show()

in a Silverlight 2 application. I don’t mean HtmPage.Window.Alert(“”). I mean something that stays within the bounds of the SL control but asks the user a question. Naturally, there’s many ways of doing this without a MessageBox class but it provided me something “interesting” to play with on the train.

The API isn’t part of the Silverlight framework so you need to do something yourself.

I’d want MessageBox to follow the traditional model as much as possible. That is, what I want to be able to do is just come along and do;

MessageBox.Show()

and I expect that to “just work” without me having to define anything in XAML or anything like that. It should “just work”.

However, I’d also like to be able to customise what the message box displayed actually looks like. That is, I’d like it to be a templated control in many ways but without having to define an instance of a control – a bit of a dilemma!

I want my MessageBox to appear “in front” of all other UI in my application, be modal, offer the user a Yes/No/Ok/Cancel/etc choice of buttons.

How do I get some UI “in front” of all the other parts of my UI without having to pre-define (in XAML or code) the fact that the application might be needing a MessageBox at some point?

The route I took was to attempt to replace the Application.Current.RootVisual. Rather than actually replacing it, I want my UI to appear in front of the RootVisual so I need to somehow take an approach something like;

  1. Grab the root visual
  2. Create some kind of panel.
  3. Disable hit testing on that root visual.
  4. Add the root visual to the background of that panel.
  5. Add my “message box UI” in front of that background.
  6. Set the root visual to be my grid.
  7. Wait for a button click.
  8. Reverse the process.

I made an assumption that the Application.Current.RootVisual would be a UserControl or derived from one. Then, I wanted to get the Content of that UserControl and replace it with my grid ( temporarily ). Now, t seems that UserControl.ContentProperty is a protected property of User Control so I ended up writing a bit of a hack like this that I’m not proud of. Perhaps there’s a better way?

  public class UserControlContentAccessor : UserControl
  {
    public static UIElement GetContent(UserControl uc)
    {
      return ((UIElement)uc.GetValue(UserControl.ContentProperty));
    }
    public static void SetContent(UserControl uc, UIElement element)
    {
      uc.SetValue(UserControl.ContentProperty, element);
    }
  }

That class only exists to derive from UserControl so that it can get at its ContentProperty. Not nice.

However, once I’d got over that I sketched out a “form” of MessageBox.Show() with a hard-coded UI just to see what it might look like. In this version, the MessageBox is just embedded as a resource into my application’s DLL so that I can load it up. The XAML embedded looks like this;

<?xml version="1.0" encoding="utf-8" ?>
<Grid
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Width="680.8"
  Height="290.4">
  <Border
    HorizontalAlignment="Stretch"
    Margin="10,10,10,10"
    VerticalAlignment="Stretch"
    BorderThickness="10,10,10,10"
    CornerRadius="3,3,3,3">
    <Border.BorderBrush>
      <LinearGradientBrush
        EndPoint="0.5,1"
        StartPoint="0.5,0">
        <GradientStop
          Color="#06000000" />
        <GradientStop
          Color="#14000000"
          Offset="0.628" />
      </LinearGradientBrush>
    </Border.BorderBrush>
    <Grid>
      <Grid.RowDefinitions>
        <RowDefinition
          Height="0.124*" />
        <RowDefinition
          Height="0.522*" />
        <RowDefinition
          Height="0.235*" />
        <RowDefinition
          Height="0.119*" />
      </Grid.RowDefinitions>
      <Grid.ColumnDefinitions>
        <ColumnDefinition
          Width="0.048*" />
        <ColumnDefinition
          Width="0.905*" />
        <ColumnDefinition
          Width="0.047*" />
      </Grid.ColumnDefinitions>
      <Rectangle
        Height="Auto"
        Width="Auto"
        RadiusX="8.4"
        RadiusY="8.4"
        Margin="0,0,0,0"
        Grid.ColumnSpan="3"
        Grid.RowSpan="4">
        <Rectangle.Stroke>
          <LinearGradientBrush
            EndPoint="0.5,1"
            StartPoint="0.5,0">
            <GradientStop
              Color="#B3FFFFFF" />
            <GradientStop
              Color="#B4FFFFFF"
              Offset="1" />
            <GradientStop
              Color="#FFFBF7F7"
              Offset="0.54500001668930054" />
          </LinearGradientBrush>
        </Rectangle.Stroke>
        <Rectangle.Fill>
          <LinearGradientBrush
            EndPoint="1.1599999666214,1.22000002861023"
            StartPoint="-0.152999997138977,-0.33899998664856">
            <GradientStop
              Color="#FFFFFFFF" />
            <GradientStop
              Color="#8CFFFFFF"
              Offset="1" />
            <GradientStop
              Color="#4CFFFFFF"
              Offset="0.339" />
            <GradientStop
              Color="#32FFFFFF"
              Offset="0.63599997758865356" />
            <GradientStop
              Color="#7AFFFFFF"
              Offset="0.81400001049041748" />
            <GradientStop
              Color="#04FFFFFF"
              Offset="0.93300002813339233" />
          </LinearGradientBrush>
        </Rectangle.Fill>
      </Rectangle>
      <Rectangle
        Height="Auto"
        HorizontalAlignment="Stretch"
        Margin="-1,-1,-0.200000002980232,0.200000002980232"
        VerticalAlignment="Stretch"
        Width="Auto"
        Grid.Column="1"
        Grid.Row="1"
        Grid.RowSpan="2"
        Fill="#FFFFFFFF"
        Stroke="#FF000000" />
      <TextBlock
        Height="Auto"
        HorizontalAlignment="Stretch"
        Margin="20,20,20,20"
        VerticalAlignment="Stretch"
        Width="Auto"
        Grid.Column="1"
        Grid.ColumnSpan="1"
        Grid.Row="1"
        Grid.RowSpan="1"
        FontFamily="Trebuchet MS"
        FontSize="18"
        Text="This is a message inside of a MessageBox!"
        TextWrapping="Wrap" />
      <Rectangle
        HorizontalAlignment="Stretch"
        Margin="1,0,1,1"
        Grid.RowSpan="1"
        Fill="#FFD3D3D3"
        Grid.ColumnSpan="1"
        Grid.Row="2"
        Grid.Column="1" />
      <StackPanel
        HorizontalAlignment="Right"
        Margin="0,0,10,0"
        Grid.RowSpan="1"
        Grid.ColumnSpan="1"
        Grid.Row="2"
        Grid.Column="1"
        Orientation="Horizontal">
        <Button
          Content="OK"
          Width="124.566"
          Height="40.844"
          Margin="5,0,0,0"
          HorizontalAlignment="Right"
          x:Name="buttonOk"/>
        <Button
          Height="40.844"
          Width="124.566"
          Content="Cancel"
          Margin="5,0,0,0"
          HorizontalAlignment="Right" 
          x:Name="buttonCancel"/>
      </StackPanel>
    </Grid>
  </Border>
</Grid>

and then the tentative MessageBox code to show/hide this looks like this ( note my app is called SilverlightApplication16 );

  public static class MessageBox
  {
    private static UIElement realVisual;
    private static Grid parentGrid;

    public static void Show(string text)
    {
      UserControl uc = Application.Current.RootVisual as UserControl;

      if (uc != null)
      {
        realVisual = UserControlContentAccessor.GetContent(uc);
        realVisual.IsHitTestVisible = false;

        parentGrid = new Grid();

        UserControlContentAccessor.SetContent(uc, parentGrid);
        parentGrid.Children.Add(realVisual);

        FrameworkElement dialogElement = LoadDialogResourceXaml();

        parentGrid.Children.Add(dialogElement);
      }
    }
    private static FrameworkElement LoadDialogResourceXaml()
    {
      FrameworkElement element = null;

      using (Stream stream =
        Assembly.GetExecutingAssembly().GetManifestResourceStream("SilverlightApplication16.dialog.xaml"))
      {
        using (StreamReader streamReader = new StreamReader(stream))
        {
          element = XamlReader.Load(streamReader.ReadToEnd()) as FrameworkElement;
          streamReader.Close();
        }
        stream.Close();
      }
      if (element != null)
      {
        Button okButton = element.FindName("buttonOk") as Button;
        Button cancelButton = element.FindName("buttonCancel") as Button;

        if (okButton != null)
        {
          okButton.Click += DismissDialog;
        }
        if (cancelButton != null)
        {
          cancelButton.Click += DismissDialog;
        }
      }
      return (element);
    }
    static void DismissDialog(object sender, EventArgs args)
    {
      UserControl uc = Application.Current.RootVisual as UserControl;

      if (uc != null)
      {
        parentGrid.Children.Clear();
        realVisual.IsHitTestVisible = true;
        UserControlContentAccessor.SetContent(uc, realVisual);
      }
    }
  }

So….

  1. We have a hard-coded UI in a piece of XAML embedded in an assemby ( even the MessageBox message is hard-coded right now 🙂 )
  2. We have no way of moving the MessageBox around the screen or doing non-modal.
  3. The programming model is a little bit wrong. Generally, MessageBox.Show() doesn’t return until the user’s clicked one of the buttons. Also, I’m not allowing choice of which buttons are shown/hidden and I’m not returning which button was pressed.

It looks like this;

image

How to move this forward? I figured that one way to do it ( and possibly the best way that I can come up with ) is to make the MessageBox.Show() display a control. Let’s say I create some class MessageBoxControl and if you call;

MessageBox.Show(“Hello World”);

then I’ll assume that what you want me to display is the standard MessageBoxControl. However, if you call something like;

MessageBoxControl myControl = new MyDerivedMessageBoxControl();

MessageBox.Show(“Hello World”, myControl);

then I can show your message box control rather than mine. Why impose a base class on you? It’s essentially so that I can be sure that you have some kind of event that fires when someone clicks a button on the control so that I would know when to dismiss the dialog.

I’d want my MessageBoxControl to be a templated control so that you could define one simply as a XAML resource and style it so that it doesn’t look like my message box but more like your message box.

This is a bit of a strange one because you wouldn’t necessarily need to use the control in your UI but you could just stick a template for it in a resource dictionary so that you can then load that and apply it.

I started to build the control, giving it just 4 simple parts initially – the yes, no, cancel buttons and the root element. I could see having parts such as the part that forms a border, a part that forms the dialog title, a part that might mimic the system menu and so on but I’ve not done that here. I just kept it simple;

  public enum MessageBoxResult
  {
    Yes,
    No,
    Cancel
  }
  public class MessageBoxResultEventArgs : EventArgs
  {
    public MessageBoxResult Result { get; set; }
    public object AsyncState { get; set; }
  }
  [TemplatePart(Name = RootElement, Type = typeof(Panel))]
  [TemplatePart(Name = YesButtonElement, Type = typeof(Button))]
  [TemplatePart(Name = NoButtonElement, Type = (typeof(Button)))]
  [TemplatePart(Name = CancelButtonElement, Type = (typeof(Button)))]
  public class MessageBoxControl : ContentControl 
  {
    public event EventHandler<MessageBoxResultEventArgs> MessageBoxDismissed;

    public MessageBoxControl()
    {
      DefaultStyleKey = typeof(MessageBoxControl);
    }
    public override void OnApplyTemplate()
    {
      if (yesButton != null)
      {
        yesButton.Click -= OnYesButton;        
      }
      if (noButton != null)
      {
        noButton.Click -= OnNoButton;
      }
      if (cancelButton != null)
      {
        cancelButton.Click -= OnCancelButton;
      }
      rootElement = base.GetTemplateChild(RootElement) as Panel;
      yesButton = base.GetTemplateChild(YesButtonElement) as Button;
      noButton = base.GetTemplateChild(NoButtonElement) as Button;
      cancelButton = base.GetTemplateChild(CancelButtonElement) as Button;

      if (yesButton != null)
      {
        yesButton.Click += OnYesButton;
      }
      if (noButton != null)
      {
        noButton.Click += OnNoButton;
      }
      if (cancelButton != null)
      {
        cancelButton.Click += OnCancelButton;
      }
    }
    void OnYesButton(object sender, EventArgs args)
    {
      FireDismissed(MessageBoxResult.Yes);
    }
    void OnNoButton(object sender, EventArgs args)
    {
      FireDismissed(MessageBoxResult.No);
    }
    void OnCancelButton(object sender, EventArgs args)
    {
      FireDismissed(MessageBoxResult.Cancel);
    }
    void FireDismissed(MessageBoxResult result)
    {
      if (MessageBoxDismissed != null)
      {
        MessageBoxDismissed(this, new MessageBoxResultEventArgs() { Result = result });
      }
    }
    Button yesButton;
    Button noButton;
    Button cancelButton;    
    Panel rootElement;
    public const string RootElement = "RootElement";
    public const string YesButtonElement = "YesButtonElement";
    public const string NoButtonElement = "NoButtonElement";
    public const string CancelButtonElement = "CancelButtonElement";
  }

and this is powered by a generic.xaml that I’ve embedded in my assembly and that XAML is really just an altered version of the initial XAML that I  had before. That is;

<?xml version="1.0" encoding="utf-8" ?>
<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
  xmlns:local="clr-namespace:SilverlightApplication16">  
  <Style
    TargetType="local:MessageBoxControl">
    <Setter
      Property="Template">
      <Setter.Value>
        <ControlTemplate
          TargetType="local:MessageBoxControl">
          <Grid Width="600" Height="300" x:Name="RootElement">
            <Border
              HorizontalAlignment="Stretch"
              Margin="10,10,10,10"
              VerticalAlignment="Stretch"
              BorderThickness="10,10,10,10"
              CornerRadius="3,3,3,3">
              <Border.BorderBrush>
                <LinearGradientBrush
                  EndPoint="0.5,1"
                  StartPoint="0.5,0">
                  <GradientStop
                    Color="#06000000" />
                  <GradientStop
                    Color="#14000000"
                    Offset="0.628" />
                </LinearGradientBrush>
              </Border.BorderBrush>
              <Grid>
                <Grid.RowDefinitions>
                  <RowDefinition
                    Height="0.124*" />
                  <RowDefinition
                    Height="0.522*" />
                  <RowDefinition
                    Height="0.235*" />
                  <RowDefinition
                    Height="0.119*" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                  <ColumnDefinition
                    Width="0.048*" />
                  <ColumnDefinition
                    Width="0.905*" />
                  <ColumnDefinition
                    Width="0.047*" />
                </Grid.ColumnDefinitions>
                <Rectangle
                  Height="Auto"
                  Width="Auto"
                  RadiusX="8.4"
                  RadiusY="8.4"
                  Margin="0,0,0,0"
                  Grid.ColumnSpan="3"
                  Grid.RowSpan="4">
                  <Rectangle.Stroke>
                    <LinearGradientBrush
                      EndPoint="0.5,1"
                      StartPoint="0.5,0">
                      <GradientStop
                        Color="#B3FFFFFF" />
                      <GradientStop
                        Color="#B4FFFFFF"
                        Offset="1" />
                      <GradientStop
                        Color="#FFFBF7F7"
                        Offset="0.54500001668930054" />
                    </LinearGradientBrush>
                  </Rectangle.Stroke>
                  <Rectangle.Fill>
                    <LinearGradientBrush
                      EndPoint="1.1599999666214,1.22000002861023"
                      StartPoint="-0.152999997138977,-0.33899998664856">
                      <GradientStop
                        Color="#FFFFFFFF" />
                      <GradientStop
                        Color="#8CFFFFFF"
                        Offset="1" />
                      <GradientStop
                        Color="#4CFFFFFF"
                        Offset="0.339" />
                      <GradientStop
                        Color="#32FFFFFF"
                        Offset="0.63599997758865356" />
                      <GradientStop
                        Color="#7AFFFFFF"
                        Offset="0.81400001049041748" />
                      <GradientStop
                        Color="#04FFFFFF"
                        Offset="0.93300002813339233" />
                    </LinearGradientBrush>
                  </Rectangle.Fill>
                </Rectangle>
                <Rectangle
                  Height="Auto"
                  HorizontalAlignment="Stretch"
                  Margin="-1,-1,-0.200000002980232,0.200000002980232"
                  VerticalAlignment="Stretch"
                  Width="Auto"
                  Grid.Column="1"
                  Grid.Row="1"
                  Grid.RowSpan="2"
                  Fill="#FFFFFFFF"
                  Stroke="#FF000000" />
                <ContentPresenter Grid.Column="1" Grid.Row="1" HorizontalAlignment="Stretch"
                                  VerticalAlignment="Stretch"/>
                <Rectangle
                  HorizontalAlignment="Stretch"
                  Margin="1,0,1,1"
                  Grid.RowSpan="1"
                  Fill="#FFD3D3D3"
                  Grid.ColumnSpan="1"
                  Grid.Row="2"
                  Grid.Column="1" />
                <StackPanel
                  HorizontalAlignment="Right"
                  Margin="0,0,10,0"
                  Grid.RowSpan="1"
                  Grid.ColumnSpan="1"
                  Grid.Row="2"
                  Grid.Column="1"
                  Orientation="Horizontal">
                  <Button
                    x:Name="YesButtonElement"
                    Content="Yes"
                    Width="124.566"
                    Height="40.844"
                    Margin="5,0,0,0"
                    HorizontalAlignment="Right"/>
                  <Button
                    x:Name="NoButtonElement"
                    Height="40.844"
                    Width="124.566"
                    Content="No"
                    Margin="5,0,0,0"
                    HorizontalAlignment="Right"/>
                  <Button
                    x:Name="CancelButtonElement"
                    Height="40.844"
                    Width="124.566"
                    Content="Cancel"
                    Margin="5,0,0,0"
                    HorizontalAlignment="Right" />
                </StackPanel>
              </Grid>
            </Border>
          </Grid>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

With that in place, I can make my MessageBox static class look a bit like;

  public static class MessageBox
  {    
    public static void ShowAsync(object content)
    {
      ShowAsync(content, null);
    }
    public static void ShowAsync(object content,
      EventHandler<MessageBoxResultEventArgs> callback)
    {
      ShowAsync(content, null, callback);
    }
    public static void ShowAsync(object content, object userState,
      EventHandler<MessageBoxResultEventArgs> callback)
    {
      ShowAsync(content, userState, callback, null);
    }
    public static void ShowAsync(object content, object userState,
      EventHandler<MessageBoxResultEventArgs> callback, Style controlTemplate)
    {
      MessageBoxControl control = new MessageBoxControl();
      control.Content = content;

      if (controlTemplate != null)
      {
        control.Style = controlTemplate;
      }
      ShowAsync(control, userState, callback);
    }
    public static void ShowAsync(MessageBoxControl control, object userState,
      EventHandler<MessageBoxResultEventArgs> callback)
    {
      UserControl uc = Application.Current.RootVisual as UserControl;      

      if (uc != null)
      {
        asyncState = userState;
        userCallback = callback;
        realVisual = UserControlContentAccessor.GetContent(uc);
        realVisual.IsHitTestVisible = false;

        parentGrid = new Grid();
        UserControlContentAccessor.SetContent(uc, parentGrid);
        parentGrid.Children.Add(realVisual);
        parentGrid.Children.Add(control);

        control.MessageBoxDismissed += OnDismissed;
      }
    }
    static void OnDismissed(object sender, MessageBoxResultEventArgs e)
    {
      MessageBoxControl control = sender as MessageBoxControl;

      UserControl uc = Application.Current.RootVisual as UserControl;

      if (uc != null)
      {
        parentGrid.Children.Clear();
        realVisual.IsHitTestVisible = true;
        UserControlContentAccessor.SetContent(uc, realVisual);
      }
      if (control != null)
      {
        control.MessageBoxDismissed -= OnDismissed;
      }
      try
      {
        if (userCallback != null)
        {
          userCallback(null, new MessageBoxResultEventArgs()
          {
            Result = e.Result,
            AsyncState = asyncState
          });
        }
      }
      finally
      {
        realVisual = null;
        parentGrid = null;
        asyncState = null;
        userCallback = null;
      }
    }    
    private static UIElement realVisual;
    private static Grid parentGrid;
    private static object asyncState;
    private static EventHandler<MessageBoxResultEventArgs> userCallback;
  }

and then I can start to make use of it with code such as;

      MessageBox.ShowAsync("Simple call, no callback, no state, no style");

or


      MessageBox.ShowAsync(new Ellipse()
      {
        Width = 96,
        Height = 96,
        Fill =
          new SolidColorBrush(Colors.Green)
      });

or


      MessageBox.ShowAsync("As previously but with a callback - hit NO", (s, e) =>
        {
          Debug.Assert(e.Result == MessageBoxResult.No);
        });

or

  MessageBox.ShowAsync("As previously but with state - hit YES", 101, (s, e) =>
        {
          Debug.Assert((e.Result == MessageBoxResult.Yes) && ((int)e.AsyncState == 101));
        });

 

or

      Style myStyle = this.Resources["myStyle"] as Style;

      MessageBox.ShowAsync("Using a different style altogether", null, null,
        myStyle);
 

That last one loads a different style from my resources which ends up looking like this;

image

which was designed more from the point of view of being “different” than being “pretty”. The styling for that ends up looking like this;

    <Style
      x:Name="myStyle"
      TargetType="local:MessageBoxControl">
      <Setter
        Property="Template">
        <Setter.Value>
          <ControlTemplate>
            <Grid
              x:Name="RootElement"
              Width="800" Height="600">  
              <Grid.Resources>
                <Style
                  x:Key="ButtonStyle1"
                  TargetType="Button">
                  <Setter
                    Property="IsEnabled"
                    Value="true" />
                  <Setter
                    Property="IsTabStop"
                    Value="true" />
                  <Setter
                    Property="Background"
                    Value="#FF003255" />
                  <Setter
                    Property="Foreground"
                    Value="#FF313131" />
                  <Setter
                    Property="MinWidth"
                    Value="5" />
                  <Setter
                    Property="MinHeight"
                    Value="5" />
                  <Setter
                    Property="Margin"
                    Value="0" />
                  <Setter
                    Property="HorizontalContentAlignment"
                    Value="Center" />
                  <Setter
                    Property="VerticalContentAlignment"
                    Value="Center" />
                  <Setter
                    Property="Cursor"
                    Value="Arrow" />
                  <Setter
                    Property="TextAlignment"
                    Value="Left" />
                  <Setter
                    Property="TextWrapping"
                    Value="NoWrap" />
                  <Setter
                    Property="FontSize"
                    Value="11" />
                  <Setter
                    Property="Template">
                    <Setter.Value>
                      <ControlTemplate
                        TargetType="Button">
                        <Grid>
                          <Grid.Resources>
                            <Color
                              x:Key="LinearBevelLightStartColor">#FCFFFFFF</Color>
                            <Color
                              x:Key="LinearBevelLightEndColor">#F4FFFFFF</Color>
                            <Color
                              x:Key="LinearBevelDarkStartColor">#E0FFFFFF</Color>
                            <Color
                              x:Key="LinearBevelDarkEndColor">#B2FFFFFF</Color>
                            <Color
                              x:Key="MouseOverLinearBevelDarkEndColor">#7FFFFFFF</Color>
                            <Color
                              x:Key="HoverLinearBevelLightStartColor">#FCFFFFFF</Color>
                            <Color
                              x:Key="HoverLinearBevelLightEndColor">#EAFFFFFF</Color>
                            <Color
                              x:Key="HoverLinearBevelDarkStartColor">#D8FFFFFF</Color>
                            <Color
                              x:Key="HoverLinearBevelDarkEndColor">#4CFFFFFF</Color>
                            <Color
                              x:Key="CurvedBevelFillStartColor">#B3FFFFFF</Color>
                            <Color
                              x:Key="CurvedBevelFillEndColor">#3CFFFFFF</Color>
                            <SolidColorBrush
                              x:Key="BorderBrush"
                              Color="#FF000000" />
                            <SolidColorBrush
                              x:Key="AccentBrush"
                              Color="#FFFFFFFF" />
                            <SolidColorBrush
                              x:Key="DisabledBrush"
                              Color="#A5FFFFFF" />
                            <LinearGradientBrush
                              x:Key="FocusedStrokeBrush"
                              EndPoint="0.5,1"
                              StartPoint="0.5,0">
                              <GradientStop
                                Color="#B2FFFFFF"
                                Offset="0" />
                              <GradientStop
                                Color="#51FFFFFF"
                                Offset="1" />
                              <GradientStop
                                Color="#66FFFFFF"
                                Offset="0.325" />
                              <GradientStop
                                Color="#1EFFFFFF"
                                Offset="0.325" />
                            </LinearGradientBrush>
                          </Grid.Resources>
                          <vsm:VisualStateManager.VisualStateGroups>
                            <vsm:VisualStateGroup
                              x:Name="CommonStates">
                              <vsm:VisualStateGroup.Transitions>
                                <vsm:VisualTransition
                                  Duration="0:0:0.2"
                                  To="MouseOver" />
                                <vsm:VisualTransition
                                  Duration="0:0:0.1"
                                  To="Pressed" />
                              </vsm:VisualStateGroup.Transitions>
                              <vsm:VisualState
                                x:Name="Normal" />
                              <vsm:VisualState
                                x:Name="MouseOver">
                                <Storyboard />
                              </vsm:VisualState>
                              <vsm:VisualState
                                x:Name="Pressed">
                                <Storyboard />
                              </vsm:VisualState>
                              <vsm:VisualState
                                x:Name="Disabled">
                                <Storyboard />
                              </vsm:VisualState>
                            </vsm:VisualStateGroup>
                            <vsm:VisualStateGroup
                              x:Name="FocusStates">
                              <vsm:VisualState
                                x:Name="Focused">
                                <Storyboard />
                              </vsm:VisualState>
                              <vsm:VisualState
                                x:Name="Unfocused">
                                <Storyboard />
                              </vsm:VisualState>
                            </vsm:VisualStateGroup>
                          </vsm:VisualStateManager.VisualStateGroups>
                          <Path
                            HorizontalAlignment="Stretch"
                            Margin="5.80000019073486,4.09999990463257,0.800999999046326,9.66100025177002"
                            VerticalAlignment="Stretch"
                            Fill="#FF06C8B3"
                            Stretch="Fill"
                            Stroke="#FF2400DA"
                            StrokeThickness="2"
                            Data="M8.2999935,10.199994 L6.299994,4.5999937 C6.299994,4.5999937 46.699978,23.399988 65.099976,8.9999876 C83.499969,-5.400013 49.899971,38.601345 58.69997,39.401375 C67.499969,40.201408 23.499971,28.200924 13.099981,37.801304 C2.6999912,47.401684 8.2999935,10.199994 8.2999935,10.199994 z" />
                          <ContentPresenter
                            Margin="4,5,4,4"
                            HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
                            Padding="{TemplateBinding Padding}"
                            VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
                            Content="{TemplateBinding Content}"
                            ContentTemplate="{TemplateBinding ContentTemplate}"
                            TextAlignment="{TemplateBinding TextAlignment}"
                            TextDecorations="{TemplateBinding TextDecorations}"
                            TextWrapping="{TemplateBinding TextWrapping}" />
                        </Grid>
                      </ControlTemplate>
                    </Setter.Value>
                  </Setter>
                </Style>

              </Grid.Resources>
                  <Grid.RowDefinitions>
                    <RowDefinition
                      Height="*" />
                  </Grid.RowDefinitions>
                  <Grid.ColumnDefinitions>
                    <ColumnDefinition
                      Width="*" />
                  </Grid.ColumnDefinitions>
                  <Path
                    HorizontalAlignment="Stretch"
                    Margin="53.5,30.5690002441406,35.4770011901855,19.6319999694824"
                    VerticalAlignment="Stretch"
                    Stretch="Fill"
                    StrokeThickness="9"
                    Data="M54,53.599998 C54,53.599998 453.20001,-1.6000016 550.79999,60.799999 C648.39996,123.2 531.59998,208.00238 594.79999,209.60242 C658,211.20244 404.39999,259.20325 334.79999,203.20233 C265.19998,147.20142 67.600006,196.00183 81.200012,172.8015 C94.800018,149.60118 54,53.599998 54,53.599998 z">
                    <Path.Fill>
                      <RadialGradientBrush>
                        <GradientStop
                          Color="#FF6EE7FF" />
                        <GradientStop
                          Color="#FF228A9F"
                          Offset="1" />
                      </RadialGradientBrush>
                    </Path.Fill>
                    <Path.Stroke>
                      <LinearGradientBrush
                        EndPoint="1.02400004863739,0.507000029087067"
                        StartPoint="0.0149999996647239,0.495000004768372">
                        <GradientStop
                          Color="#FF000000" />
                        <GradientStop
                          Color="#FF572AEF"
                          Offset="0.375" />
                        <GradientStop
                          Color="#FF25078C"
                          Offset="0.73900002241134644" />
                      </LinearGradientBrush>
                    </Path.Stroke>
                  </Path>
                  <Button
                    x:Name="YesButtonElement"
                    Height="49.6"
                    HorizontalAlignment="Right"
                    Margin="0,0,103.199996948242,47.2000007629395"
                    VerticalAlignment="Bottom"
                    Width="72"
                    Content="Dismiss"
                    Style="{StaticResource ButtonStyle1}"/>
                  <ContentPresenter
                    Height="70.4"
                    HorizontalAlignment="Stretch"
                    Margin="106.400001525879,66.4000015258789,130.399993896484,113.599998474121"
                    VerticalAlignment="Stretch"
                    FontFamily="Comic Sans MS"
                    FontSize="22"
                    FontStyle="Italic"
                    FontWeight="Bold"/>
                </Grid>
          </ControlTemplate>
        </Setter.Value>
      </Setter>
    </Style>

After all that, there’s a bunch more stuff that I could do here like;

  1. Adding some visual states to my control so that I can animate between them.
  2. Adding some more visual parts to my control so that (e.g.) there’s a title bar, a system menu and so on.
  3. Adding the ability to “move” the dialog around on the screen in response to mouse events on the UI.

but I’ll leave it there for now.

As usual, if you want the source to play with then it’s here for download.