简体   繁体   English

UWP 组合 - 带圆角的网格 DropShadow

[英]UWP Composition - Grid with rounded corners DropShadow

I have a UWP app, which I should start by pointing out that it uses very little XAML.我有一个 UWP 应用程序,我应该首先指出它使用很少的 XAML。 The views are built from JSON object recieved from an API.视图是根据从 API 接收到的 JSON 对象构建的。 This means that the vast majority of everything is done in C#, and therefore adds a little complexity to my problem.这意味着绝大多数事情都是在 C# 中完成的,因此给我的问题增加了一点复杂性。

I basically want to have a panel (eg Grid) that can have rounded corners and have a drop shadow applied to it.我基本上想要一个面板(例如网格),它可以有圆角并应用阴影。 The drop shadow should also have the rounded corners, this can be seen in the sample below.阴影也应该有圆角,这可以在下面的示例中看到。

在此处输入图片说明

I have looked at the DropShadowPanel as part of the Windows Community Toolkit , but this from what I can tell doesn't do the rounded corners unless I change the content to be a rectangle or some other shape.我已经将 DropShadowPanel 视为Windows Community Toolkit 的一部分,但是据我所知,除非我将内容更改为矩形或其他形状,否则它不会处理圆角。

To use this as a solution would mean the XAML equivalent of something like:将此用作解决方案意味着 XAML 等效于以下内容:

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

To me, this seems like an inefficient use of XAML!对我来说,这似乎是 XAML 的低效使用!

I have also discovered the Composition Pro Toolkit , which to me looks bery interesting as it is all code behind.我还发现了Composition Pro Toolkit ,在我看来它非常有趣,因为它是所有代码。 In particular the ImageFrame control looks to achieve the basis of what I require - although far more advanced than my needs.特别是 ImageFrame 控件看起来实现了我所需要的基础——尽管比我的需要先进得多。

The below has been based on the ImageFrame, but doesn't work ( content is my grid):以下内容基于 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;
}

In these tests, I am doing the same inefficient use of object, creating another container that has both the composition canvas, and also the grid.在这些测试中,我对对象进行了同样低效的使用,创建了另一个既有合成画布又有网格的容器。 If possible, I'd like to apply the composition directly to the original content grid.如果可能,我想将合成直接应用于原始内容网格。

I am completely new to composition, so any thoughts, pointers, glaring errors or solutions would be most welcomed.我对作文完全陌生,所以任何想法、指示、明显错误或解决方案都将受到欢迎。

A Hack Solution?黑客解决方案?

I have changed my method to the following, visually it works - but is it right?我已将我的方法更改为以下方法,在视觉上它有效 - 但对吗?

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;
}

The concept here is that my container grid holds a rectangle and my content grid (or other element).这里的概念是我的container网格包含一个rectangle和我的content网格(或其他元素)。

The first error of this method is that is assumes my input FrameworkElement will be rectangular.此方法的第一个错误是假设我的输入FrameworkElement是矩形的。 I imagine that this could be improved upon by creating a bitmap render of the content as highlighted in this blog - but this will likely be quite costly.我想这可以通过创建本博客中突出显示的content的位图渲染来改进 - 但这可能会非常昂贵。 I also have to ensure that the rectangle size and shape exactly matches that of my main content!我还必须确保矩形的大小和形状与我的主要内容完全匹配!

It feels very wrong that there is a rectangle drawn on the screen (even though hidden by my main content).屏幕上画了一个矩形感觉很不对(虽然被我的主要内容隐藏了)。 The rectangle is purely there to create the alpha mask so I guess it could be scrapped if the mask is created from the renderof the content.矩形纯粹是为了创建 alpha 蒙版,所以我想如果蒙版是从内容的渲染创建的,它可能会被废弃。

I've tried setting the visibility of the rectangle to collapsed to remove it from the visual tree.我尝试将矩形的可见性设置为折叠以将其从可视化树中删除。 This means that I can attach the visual to the container instead:这意味着我可以将视觉附加到容器:

ElementCompositionPreview.SetElementChildVisual(container, visual)

However, doing this means that the shadow displays in front of the main content, which means I need some other ui element to attach it too - may as well be the rectangle!但是,这样做意味着阴影显示在主要内容的前面,这意味着我也需要一些其他 ui 元素来附加它 - 也可能是矩形!

Your solution to use Rectangle is my current workaround everywhere I need rounded shadow under Grid or Border .您使用Rectangle解决方案是我当前在GridBorder下需要圆形阴影的任何地方的解决方法。 It's simple and it's plain, why should I complain :) But if it's not your choice you can draw a rounded rectangle and blur it:这很简单,很简单,我为什么要抱怨:)但是如果不是您的选择,您可以绘制一个圆角矩形并对其进行模糊处理:

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);

Now you can apply this surface in the EffectBrush with blur effect to obtain custom shadow.现在您可以在具有模糊效果的EffectBrush应用此表面以获得自定义阴影。

BlurMargin - corresponds to the blur amount, you need it because your blurred surface will be bigger than initial source rectangle (to avoid blur clip). BlurMargin - 对应于模糊量,您需要它,因为您的模糊表面将大于初始源矩形(以避免模糊剪辑)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM