c# - UWP Composition - 带圆角的网格 DropShadow

标签 c# uwp composition windows-community-toolkit

我有一个 UWP 应用程序,我应该首先指出它使用很少的 XAML。这些 View 是根据从 API 接收到的 JSON 对象构建的。这意味着绝大多数事情都是在 C# 中完成的,因此给我的问题增加了一点复杂性。

我基本上想要一个可以有圆角并应用投影的面板(例如网格)。投影也应该有圆角,这可以在下面的示例中看到。

enter image description here

我查看了 DropShadowPanel 作为 Windows Community Toolkit 的一部分,但据我所知,这不会做圆角,除非我将内容更改为矩形或其他形状。

将其用作解决方案意味着 XAML 等同于以下内容:

<Grid>
    <toolkit:DropShadowPanel>
         <Rectangle />
    <toolkit:DropShadowPanel>
    <Grid CornerRadius="30">
        <!-- My Content -->
    </Grid>
</Grid>

对我来说,这似乎是 XAML 的低效使用!

我还发现了 Composition Pro Toolkit ,这对我来说看起来非常有趣,因为它是所有代码都在后面。特别是 ImageFrame 控件看起来实现了我所需要的基础——尽管比我的需要高级得多。

以下内容基于 ImageFrame,但不起作用(content 是我的网格):

protected FrameworkElement AddDropShadow(FrameworkElement content)
{
    var container = new Grid { HorizontalAlignment = content.HorizontalAlignment, VerticalAlignment = content.VerticalAlignment, Width = content.Width, Height = content.Height };

    var canvas = new Canvas { HorizontalAlignment = HorizontalAlignment.Stretch, VerticalAlignment = VerticalAlignment.Stretch };

    content.Loaded += (s, e) =>
        {
            var compositor = ElementCompositionPreview.GetElementVisual(canvas).Compositor;

            var root = compositor.CreateContainerVisual();
            ElementCompositionPreview.SetElementChildVisual(canvas, root);

            var shadowLayer = compositor.CreateSpriteVisual();
            var frameLayer = compositor.CreateLayerVisual();
            var frameContent = compositor.CreateShapeVisual();

            root.Children.InsertAtBottom(shadowLayer);
            root.Children.InsertAtTop(frameLayer);

            frameLayer.Children.InsertAtTop(frameContent);

            var rectangle = root.Compositor.CreateRoundedRectangleGeometry();
            rectangle.Size = new Vector2((float)content.ActualWidth, (float)content.ActualHeight);
            rectangle.CornerRadius = new Vector2(30f);

            var shape = root.Compositor.CreateSpriteShape(rectangle);
            shape.FillBrush = root.Compositor.CreateColorBrush(Colors.Blue);

            //var visual = root.Compositor.CreateShapeVisual();
            frameContent.Size = rectangle.Size;
            frameContent.Shapes.Add(shape);

            //create mask layer
            var layerEffect = new CompositeEffect
            {
                Mode = Microsoft.Graphics.Canvas.CanvasComposite.DestinationIn,
                Sources = { new CompositionEffectSourceParameter("source"), new CompositionEffectSourceParameter("mask") }
            };

            var layerEffectFactory = compositor.CreateEffectFactory(layerEffect);
            var layerEffectBrush = layerEffectFactory.CreateBrush();


            //CompositionDrawingSurface
            var graphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(compositor, new Microsoft.Graphics.Canvas.CanvasDevice(forceSoftwareRenderer: false));
            var frameLayerMask = graphicsDevice.CreateDrawingSurface(new Size(0, 0), Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized, Windows.Graphics.DirectX.DirectXAlphaMode.Premultiplied);
            layerEffectBrush.SetSourceParameter("mask", compositor.CreateSurfaceBrush(frameLayerMask));
            frameLayer.Effect = layerEffectBrush;

            var shadow = root.Compositor.CreateDropShadow();
            //shadow.SourcePolicy = CompositionDropShadowSourcePolicy.InheritFromVisualContent;
            shadow.Mask = layerEffectBrush.GetSourceParameter("mask");
            shadow.Color = Colors.Black;
            shadow.BlurRadius = 25f;
            shadow.Opacity = 0.75f;
            shadow.Offset = new Vector3(0, 0, 0);
            shadowLayer.Shadow = shadow;

            content.Opacity = 0; //hiding my actual content to see the results of this
        };


    container.Children.Add(canvas);
    container.Children.Add(content);
    return container;
}

在这些测试中,我同样低效地使用了对象,创建了另一个既有合成 Canvas 又有网格的容器。如果可能,我想将合成直接应用于原始内容网格。

我对作文完全陌生,因此欢迎任何想法、指示、明显错误或解决方案。

黑客解决方案?

我已将我的方法更改为以下内容,在视觉上它有效 - 但它是否正确?

protected FrameworkElement AddDropShadow(FrameworkElement content)
{
    var container = new Grid { HorizontalAlignment = content.HorizontalAlignment, VerticalAlignment = content.VerticalAlignment };
    var rectangle = new Rectangle { Fill = new SolidColorBrush(Colors.Transparent) };

    content.Loaded += (s, e) =>
        {
            rectangle.Fill = new SolidColorBrush(Colors.Black);
            rectangle.Width = content.ActualWidth;
            rectangle.Height = content.ActualHeight;
            rectangle.RadiusX = 30;
            rectangle.RadiusY = 30;

            var compositor = ElementCompositionPreview.GetElementVisual(rectangle).Compositor;
            var visual = compositor.CreateSpriteVisual();
            visual.Size = new Vector2((float)content.ActualWidth, (float)content.ActualHeight);

            var shadow = compositor.CreateDropShadow();
            shadow.BlurRadius = 30f;
            shadow.Mask = rectangle.GetAlphaMask();
            shadow.Opacity = 0.75f;
            visual.Shadow = shadow;

            ElementCompositionPreview.SetElementChildVisual(rectangle, visual);
        };

    container.Children.Add(rectangle);
    container.Children.Add(content);
    return container;
}

这里的概念是我的 container 网格包含一个 rectangle 和我的 content 网格(或其他元素)。

此方法的第一个错误是假设我的输入 FrameworkElement 是矩形的。我想这可以通过创建 content 的位图渲染来改进,如 blog 中突出显示的那样- 但这可能会非常昂贵。我还必须确保矩形的大小和形状与我的主要内容完全匹配!

在屏幕上绘制了一个矩形(即使被我的主要内容隐藏了),感觉很不对劲。该矩形纯粹用于创建 alpha mask ,所以我猜如果 mask 是根据内容的渲染创建的,它可能会被废弃。

我尝试将矩形的可见性设置为折叠以将其从可视化树中移除。这意味着我可以将视觉附加到容器:

ElementCompositionPreview.SetElementChildVisual(container, visual)

但是,这样做意味着阴影显示在主要内容的前面,这意味着我还需要一些其他 ui 元素来附加它 - 最好是矩形!

最佳答案

使用 Rectangle 的解决方案是我目前在 GridBorder 下需要圆形阴影的任何地方的解决方法。简单明了,我为什么要提示:) 但如果这不是您的选择,您可以绘制一个圆角矩形并对其进行模糊处理:

GraphicsDevice = CanvasComposition.CreateCompositionGraphicsDevice(Compositor, CanvasDevice.GetSharedDevice());                

var roudRectMaskSurface = GraphicsDevice.CreateDrawingSurface(new Size(SurfaceWidth + BlurMargin * 2, SurfaceHeight + BlurMargin * 2), DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied);
using (var ds = CanvasComposition.CreateDrawingSession(roudRectMaskSurface))
{
    ds.Clear(Colors.Transparent);
    ds.FillRoundedRectangle(new Rect(BlurMargin, BlurMargin, roudRectMaskSurface.Size.Width + BlurMargin, roudRectMaskSurface.Size.Height + BlurMargin), YourRadius, YourRadius, Colors.Black);
}

var rectangleMask = Compositor.CreateSurfaceBrush(roudRectMaskSurface);

现在您可以在具有模糊效果的EffectBrush 中应用此表面以获得自定义阴影。

BlurMargin - 对应于模糊量,您需要它,因为您的模糊表面将大于初始源矩形(以避免模糊裁剪)。

关于c# - UWP Composition - 带圆角的网格 DropShadow,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57232841/

相关文章:

c# - 持久数据库连接与打开和关闭

c# - 从 Entity Framework Core 中的导航属性选择对象列表

c# - 自定义音频效果如何在AudioFileInputNode完成播放后产生一些声音

javascript - compose 函数如何处理多个参数?

java - 使用 Java 8 Streams 映射、聚合和组合总计

组合类的c++复制构造函数

c# - 在 Xamarin.Forms MVVM 应用程序中加载数据的位置?

c# - 使用 linq 删除代码时出错

c# - 如何将实时高斯模糊效果应用于 UWP 应用程序?

xaml - 更改 UWP 评级控件的大小和间距