Paint 3D Dialog Box using Composition Shadow

paint3d.JPG

After analyzing the Dialog Box from Paint 3D (DBP3), I decided to create one by myself. I think the DBP3 should use:

  1. The Content Dialog class instead of a custom one and trying to disable elements. It has appear and disappear transitions so it works pretty well.
  2. The Composition shadow instead 7 borders around the control.

The first thing we have to do is customize the parameters of the content dialog. To do that, you can find the file or just use UWP Technical guide:

(If you find the App useful consider about tapping the  menu – smile – remove ads, it really helps.)

contentdialogstyle.JPG

With that you know what are the properties to change and ThemeResources to replace its values.

Styles

So now in the App.xaml:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.ThemeDictionaries>
            <ResourceDictionary x:Key="Dark">
                <SolidColorBrush x:Key="ApplicationPageBackgroundThemeBrush">#3111</SolidColorBrush>
                <Thickness x:Key="ContentDialogContentScrollViewerMargin">0</Thickness>
                <Thickness x:Key="ContentDialogPadding">0</Thickness>
            </ResourceDictionary>
            <ResourceDictionary x:Key="Light">
                <SolidColorBrush x:Key="ApplicationPageBackgroundThemeBrush" Opacity="0.5">3EEE</SolidColorBrush>
                <Thickness x:Key="ContentDialogContentScrollViewerMargin">0</Thickness>
                <Thickness x:Key="ContentDialogPadding">0</Thickness>
            </ResourceDictionary>
        </ResourceDictionary.ThemeDictionaries>
    </ResourceDictionary>
</Application.Resources>

Now in the Page we are, or here in App.xaml if you prefer, add the following style to override all paddings and margins:

<Page.Resources>
<Style TargetType="ContentDialog" x:Key="PaintDialogBoxStyle" x:Name="PaintDialogBoxStyle">
        <Setter Property="Padding" Value="12"/>
        <Setter Property="MaxHeight" Value="400" />
        <Setter Property="MinHeight" Value="0" />
        <Setter Property="MaxWidth" Value="500" />
        <Setter Property="MinWidth" Value="0" />
        <Setter Property="Padding" Value="0"/>
        <Setter Property="BorderBrush" Value="Transparent"/>
        <Setter Property="BorderThickness" Value="0"/>
        <Setter Property="FullSizeDesired" Value="False"/>
        <Setter Property="Background" Value="Transparent"/>
    </Style>

</Page.Resources>

The dialog

XAML

Let’s create a UserControl or a Templated Control with the following XAML structure:

<Grid Width="492" Height="340" Background="Transparent">
    <Canvas x:Name="ShadowHost" Margin="18" Background="Transparent"/>
    <Grid x:Name="LayoutRoot"  Margin="20"  Background="#FFF0F2F3">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="1*"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
        <Border BorderThickness="1" BorderBrush="{ThemeResource ContentDialogBorderBrush}" Grid.RowSpan="3"/>
        <Image Source="ms-appx:///Assets/Images/Paint_Logo_with_Trademark_ABOUT_POPUP.png" Height="80" VerticalAlignment="Top" Margin="0,42,0,0"/>
            <StackPanel Grid.Row="1" Margin="0,12,0,0">
                <HyperlinkButton Foreground="#FF4D4D75" Content="Contoso Software License Terms" FontSize="12" Padding="0" HorizontalAlignment="Center"/>
                <HyperlinkButton Foreground="#FF4D4D75" Content="Privacy Statement" FontSize="12" Padding="0" HorizontalAlignment="Center"/>
                <HyperlinkButton Foreground="#FF4D4D75" Content="Contoso Services Agreement" FontSize="12" Padding="0" HorizontalAlignment="Center"/>
                <TextBlock Foreground="#FF4D4D75" Text="1.1701.12017.0" FontSize="12" Padding="0" HorizontalAlignment="Center"/>
            </StackPanel>
            <Button Click="CloseDialog" Grid.Row="2" Margin="0,0,0,24" Width="200" Content="OK" HorizontalAlignment="Center"/>
        </Grid>
</Grid>

To explain the values:

  1. The original DBP3 has a size of 484×332 so to avoid adding many values I decided to set that size + 2×4 and the Margins 20 and 18 make the game to have the panel inside and the shadow outside.
  2. The rule of creating a Canvas for the host is based on the latest documentation from MSDN about The Visual Layer with XAML.
  3. To make easy the position of the button I decided to set it inside and avoid the buttons the DialogContent has.
  4. The rest is from the values the Visual Tree of Paint 3D.

Code

We have to make transparent the rectangle that appears when a Content Dialog is shown and when the button is Clicked make it disappear.

Note: Clicked is for (keyboard enter, mouse click and touch tap), and Tapped is for (tapped and click).

public sealed partial class AboutElement : UserControl
    {
        public AboutElement()
        {
            this.InitializeComponent();

            this.Loading += AboutElement_Loading;
            this.Loaded += AboutElement_Loaded;
        }

        private void AboutElement_Loading(FrameworkElement sender, object args)
        {
            var popup = VisualTreeHelper.GetOpenPopups(Window.Current).FirstOrDefault(p=> p.Child is Rectangle);

            if(popup?.Child is Rectangle rectangle)
            {
                rectangle.Fill = new SolidColorBrush(Colors.Transparent);
            }
        }

        private void AboutElement_Loaded(object sender, RoutedEventArgs e)
        {
            InitializeDropShadow(ShadowHost);
        }

        private void InitializeDropShadow(UIElement shadowHost)
        {
            Visual hostVisual = ElementCompositionPreview.GetElementVisual(shadowHost);
            Compositor compositor = hostVisual.Compositor;

            // Create a drop shadow
            var dropShadow = compositor.CreateDropShadow();
            dropShadow.Color = Color.FromArgb(128, 0, 0, 0);
            dropShadow.BlurRadius = 10.0f;
            dropShadow.Offset = new Vector3(0.0f);

            var shadowVisual = compositor.CreateSpriteVisual();
            shadowVisual.Shadow = dropShadow;

            ElementCompositionPreview.SetElementChildVisual(shadowHost, shadowVisual);

            // Make sure size of shadow host and shadow visual always stay in sync
            var bindSizeAnimation = compositor.CreateExpressionAnimation("hostVisual.Size");
            bindSizeAnimation.SetReferenceParameter("hostVisual", hostVisual);

            shadowVisual.StartAnimation("Size", bindSizeAnimation);
        }

        private void CloseDialog(object sender, RoutedEventArgs e)
        {
            var popup = VisualTreeHelper.GetOpenPopups(Window.Current).FirstOrDefault(p => p.Child is ContentDialog);

            if (popup?.Child is ContentDialog dialog)
            {
                dialog.Hide();
            }

        }
    }

Calling

And finally to call it from a view:

private async void ShowDialog()
{
    var dialog = new ContentDialog { Content = new AboutElement(), Style = PaintDialogBoxStyle};
    await dialog.ShowAsync();
}

So with the existing API and without inventing new panels and controls, you can create the Dialog using the standard methods and not experiments like I explained in previous posts.

I prefer setting the border to transparent like the following, but it’s just my opinion:

paint3dprefer

Hope you find it useful, get ready for Feb 8, let’s see what happens.

@juanpaexpedite

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s