Skip to content

Instantly share code, notes, and snippets.

@michael-hawker
Created July 29, 2019 21:53
Show Gist options
  • Save michael-hawker/8f348441198fd8d9a3ffe2fac55ad01a to your computer and use it in GitHub Desktop.
Save michael-hawker/8f348441198fd8d9a3ffe2fac55ad01a to your computer and use it in GitHub Desktop.
Inline XAML Shadow Test
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using Windows.UI;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Hosting;
namespace Helpers
{
/// <summary>
/// Provides a Basic Rectangular DropShadow effect on a FrameworkElement via an Attached Property.
/// Must be in a parent container that is larger than the provide element to cast a shadow on.
/// Note: Doesn't provide animation support. https://docs.microsoft.com/en-us/windows/uwp/composition/composition-shadows#animating
/// </summary>
public class FrameworkElementExtensions
{
public static BasicDropShadowOptions GetBasicDropShadow(FrameworkElement obj)
{
return (BasicDropShadowOptions)obj.GetValue(BasicDropShadowProperty);
}
public static void SetBasicDropShadow(FrameworkElement obj, BasicDropShadowOptions value)
{
obj.SetValue(BasicDropShadowProperty, value);
}
// Using a DependencyProperty as the backing store for BasicDropShadow. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BasicDropShadowProperty =
DependencyProperty.RegisterAttached("BasicDropShadow", typeof(BasicDropShadowOptions), typeof(FrameworkElementExtensions), new PropertyMetadata(null, BasicDropShadow_PropertyChanged));
private static void BasicDropShadow_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is FrameworkElement element && e.NewValue is BasicDropShadowOptions options)
{
element.Loaded += Element_Loaded;
}
// TODO: Handle if options removed later.
}
private static void Element_Loaded(object sender, RoutedEventArgs e)
{
BasicDropShadowOptions options;
if (sender is FrameworkElement element && ((options = GetBasicDropShadow(element)) != null) && element.Parent is Panel parent)
{
// Create a child host for shadow to insert in parent next to / behind element
var shadowHost = new ShadowHost() { Width = element.ActualWidth, Height = element.ActualHeight, Margin = element.Margin };
// Position Shadow Host in Parent, TODO: Can we get around this?
switch (parent)
{
case Canvas _:
Canvas.SetLeft(shadowHost, Canvas.GetLeft(element));
Canvas.SetTop(shadowHost, Canvas.GetTop(element));
Canvas.SetZIndex(shadowHost, Canvas.GetZIndex(element));
break;
case Grid _:
Grid.SetRow(shadowHost, Grid.GetRow(element));
Grid.SetColumn(shadowHost, Grid.GetColumn(element));
Grid.SetRowSpan(shadowHost, Grid.GetRowSpan(element));
Grid.SetColumnSpan(shadowHost, Grid.GetColumnSpan(element));
break;
}
parent.Children.Insert(parent.Children.IndexOf(element), shadowHost);
// Get Parent's Visual to hold our shadow
var hostVisual = ElementCompositionPreview.GetElementVisual(shadowHost);
var compositor = hostVisual.Compositor;
var shadowVisual = compositor.CreateSpriteVisual();
shadowVisual.Size = new Vector2((float)element.ActualWidth, (float)element.ActualHeight);
////shadowVisual.RelativeSizeAdjustment = Vector2.One; // Set size of Visual
// Create Shadow
var shadow = compositor.CreateDropShadow();
shadow.BlurRadius = (float)options.BlurRadius;
shadow.Color = options.Color;
if (options.Offset != null)
{
shadow.Offset = options.Offset.ToVector3();
}
shadow.Opacity = (float)options.Opacity;
shadowVisual.Shadow = shadow;
// Attach Shadow
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);
}
}
}
public class BasicDropShadowOptions: DependencyObject
{
public double BlurRadius
{
get { return (double)GetValue(BlurRadiusProperty); }
set { SetValue(BlurRadiusProperty, value); }
}
// Using a DependencyProperty as the backing store for BlurRadius. This enables animation, styling, binding, etc...
public static readonly DependencyProperty BlurRadiusProperty =
DependencyProperty.Register(nameof(BlurRadius), typeof(double), typeof(BasicDropShadowOptions), new PropertyMetadata(9.0));
public Color Color
{
get { return (Color)GetValue(ColorProperty); }
set { SetValue(ColorProperty, value); }
}
// Using a DependencyProperty as the backing store for Color. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ColorProperty =
DependencyProperty.Register(nameof(Color), typeof(Color), typeof(BasicDropShadowOptions), new PropertyMetadata(Colors.Black));
public Vector3Container Offset { get; set; }
public double Opacity
{
get { return (double)GetValue(OpacityProperty); }
set { SetValue(OpacityProperty, value); }
}
// Using a DependencyProperty as the backing store for Opacity. This enables animation, styling, binding, etc...
public static readonly DependencyProperty OpacityProperty =
DependencyProperty.Register(nameof(Opacity), typeof(double), typeof(BasicDropShadowOptions), new PropertyMetadata(1.0));
}
public class ShadowHost : FrameworkElement
{
}
[Windows.Foundation.Metadata.CreateFromString(MethodName = "Helpers.Vector3Container.ConvertToVector3")]
public class Vector3Container
{
public float X;
//
// Summary:
// The Y component of the vector.
public float Y;
//
// Summary:
// The Z component of the vector.
public float Z;
public static Vector3Container ConvertToVector3(string rawString)
{
string[] coords = rawString.Split(',');
var vector = new Vector3Container();
if (coords.Length > 0)
{
float.TryParse(coords[0], out vector.X);
}
if (coords.Length > 1)
{
float.TryParse(coords[1], out vector.Y);
}
if (coords.Length > 2)
{
float.TryParse(coords[2], out vector.Z);
}
return vector;
}
public Vector3 ToVector3()
{
return new Vector3(X, Y, Z);
}
}
}
<Page
x:Class="Helpers.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:Helpers"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Canvas x:Name="Container" Background="LightSalmon">
<Rectangle Fill="Blue" Width="200" Height="200" Canvas.Left="200" Canvas.Top="50">
<local:FrameworkElementExtensions.BasicDropShadow>
<local:BasicDropShadowOptions BlurRadius="25" Offset="30,30,30" Color="Purple" Opacity="0.9"/>
</local:FrameworkElementExtensions.BasicDropShadow>
</Rectangle>
<Rectangle Fill="Green" Width="400" Height="50" Canvas.Left="100" Canvas.Top="150" Canvas.ZIndex="-1">
<local:FrameworkElementExtensions.BasicDropShadow>
<local:BasicDropShadowOptions BlurRadius="50" Offset="10,10,10" Color="Blue"/>
</local:FrameworkElementExtensions.BasicDropShadow>
</Rectangle>
</Canvas>
</Page>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment