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;
- Grab the root visual
- Create some kind of panel.
- Disable hit testing on that root visual.
- Add the root visual to the background of that panel.
- Add my “message box UI” in front of that background.
- Set the root visual to be my grid.
- Wait for a button click.
- 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….
- We have a hard-coded UI in a piece of XAML embedded in an assemby ( even the MessageBox message is hard-coded right now 🙂 )
- We have no way of moving the MessageBox around the screen or doing non-modal.
- 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;
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;
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;
- Adding some visual states to my control so that I can animate between them.
- Adding some more visual parts to my control so that (e.g.) there’s a title bar, a system menu and so on.
- 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.