xamluwpuwp-xamlwin2d

Win2D Shadows rendering on top of StackPanel (or any UIElement instead of behind)


I'm trying to implement drop shadows on a StackPanel or in any UIElement using Win2D APIs, but it seems as if the shadow is rendering on top of the elmment and not behind where it make sense for a shadow to be.

This is my XAML:

<Page
    x:Class="ShadowsDemo.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="using:ShadowsDemo"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
    mc:Ignorable="d">

    <Grid
        Background="LightBlue">
        <StackPanel
            x:Name="RootHolder"
            Width="400"
            Height="524"
            Margin="8"
            Background="White"
            Orientation="Vertical">
            <Image Width="400" Height="200" Source="Assets/LockScreenLogo.scale-200.png" />
            <StackPanel Orientation="Vertical">
                <TextBlock Margin="20,20,0,0" TextWrapping="Wrap">
                    <Underline>
                        <Run
                            FontSize="28"
                            Foreground="#333366"
                            Text="Some title text" />
                    </Underline>
                </TextBlock>
                <TextBlock
                    Margin="20"
                    FontSize="16"
                    Foreground="Black"
                    Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
                    TextWrapping="Wrap" />
            </StackPanel>
        </StackPanel>
    </Grid>
</Page>

And my code behind:

public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();

        var rootHolder = RootHolder;

        var visualHost = ElementCompositionPreview.GetElementVisual(rootHolder);
        var compositor = visualHost.Compositor;

        var dropShadow = compositor.CreateDropShadow();
        dropShadow.Offset = new Vector3(14, 16, 48);
        dropShadow.BlurRadius = 24.0f;
        dropShadow.Color = Color.FromArgb(128, 0, 0, 0);

        var spriteVisual = compositor.CreateSpriteVisual();
        spriteVisual.Size = new Vector2((float) rootHolder.Width, (float) rootHolder.Height);
        spriteVisual.Shadow = dropShadow;

        ElementCompositionPreview.SetElementChildVisual(rootHolder, spriteVisual);
    }

}

An the resulting application looks like this:

Rsulting UWP application With the shadow on top of the StackPanel instead of behind.


Solution

  • I have found an answer. It turns out order of rendering is very important in XAML.

    I got the answer taking a look at windows-toolkit's DropShadowPanel

    DropShadowPanel.xaml: https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls/DropShadowPanel/DropShadowPanel.xaml

    DropShadowPanel.cs: https://github.com/windows-toolkit/WindowsCommunityToolkit/blob/master/Microsoft.Toolkit.Uwp.UI.Controls/DropShadowPanel/DropShadowPanel.cs

    As you can see they are using a stand alone Border element with same Width and Height properties as in this case the ContentPresenter, in our case the StackPanel.

    Our new code should be like this:

    XAML:

    <Page
        x:Class="ShadowsDemo.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:local="using:ShadowsDemo"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
        mc:Ignorable="d">
    
        <Grid Background="LightBlue">
            <Border 
                x:Name="RootHolder" 
                Width="400"
                Height="524"/>
            <StackPanel
                    x:Name="Sp"
                    Width="400"
                    Height="524"
                    Margin="8"
                    Background="White"
                    Orientation="Vertical">
                <Image
                        Width="400"
                        Height="200"
                        Source="Assets/LockScreenLogo.scale-200.png" />
                <StackPanel Orientation="Vertical">
                    <TextBlock Margin="20,20,0,0" TextWrapping="Wrap">
                            <Underline>
                                <Run
                                    FontSize="28"
                                    Foreground="#333366"
                                    Text="Some title text" />
                            </Underline>
                    </TextBlock>
                    <TextBlock
                            Margin="20"
                            FontSize="16"
                            Foreground="Black"
                            Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
                            TextWrapping="Wrap" />
                </StackPanel>
            </StackPanel>
    
        </Grid>
    </Page>
    

    Code behind:

    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();
    
            var rootHolder = RootHolder;//var rootHolder = (StackPanel) GetTemplateChild("RootHolder");
    
            var visualHost = ElementCompositionPreview.GetElementVisual(rootHolder);
            var compositor = visualHost.Compositor;
    
            var dropShadow = compositor.CreateDropShadow();
            dropShadow.Offset = new Vector3(14, 16, 48);
            dropShadow.BlurRadius = 24.0f;
            dropShadow.Color = Color.FromArgb(128, 0, 0, 0);
    
            var spriteVisual = compositor.CreateSpriteVisual();
    
            spriteVisual.Size = new Vector2((float) Sp.Width, (float) Sp.Height);
            spriteVisual.Shadow = dropShadow;
    
            ElementCompositionPreview.SetElementChildVisual(rootHolder, spriteVisual);
        }
    
    }
    

    Of course this is demo code, should be trivial to bind Width and Height properties of the Border and the StackPanel (or any other UIElement) on a templated control or user control.

    The final result looks like this: enter image description here