UWP Composition - Grid with rounded corners DropShadow - c#

I have a UWP app, which I should start by pointing out that it uses very little XAML. The views are built from JSON object recieved from an API. This means that the vast majority of everything is done in C#, and therefore adds a little complexity to my problem.
I basically want to have a panel (e.g. 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.
To use this as a solution would mean the XAML equivalent of something like:
<Grid>
<toolkit:DropShadowPanel>
<Rectangle />
<toolkit:DropShadowPanel>
<Grid CornerRadius="30">
<!-- My Content -->
</Grid>
</Grid>
To me, this seems like an inefficient use of XAML!
I have also discovered the Composition Pro Toolkit, which to me looks bery interesting as it is all code behind. In particular the ImageFrame control looks to achieve the basis of what I require - although far more advanced than my needs.
The below has been based on the ImageFrame, but doesn't work (content is my grid):
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).
The first error of this method is that is assumes my input FrameworkElement will be rectangular. 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. 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.
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!

Your solution to use Rectangle is my current workaround everywhere I need rounded shadow under Grid or Border. 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.
BlurMargin - corresponds to the blur amount, you need it because your blurred surface will be bigger than initial source rectangle (to avoid blur clip).

Related

XAML/UWP add drop shadow to shapes on canvas

So I am rendering a variety of shapes on a Canvas, think PowerPoint. The user can CRUD shapes on the Canvas.
I would now like to add a DropShadow to a Shape.
Only, I have no idea how to actually do that. Community Toolkit does not seem to allow adding a dropshadow in Code-Behind (at least there is no docu). The other few solutions found online seem to apply ways that are either not UWP-compatible, or deprecated...
Any help?
The DropShadow class provides means of creating a configurable shadow that can be applied to a SpriteVisual or LayerVisual (subtree of Visuals). You can try to use it to add drop shadow to your shapes. First create a new DropShadow and associate it to your visual. For example:
Compositor compositor = ElementCompositionPreview.GetElementVisual(YourShape).Compositor;
var shadowVisual = compositor.CreateSpriteVisual();
Vector2 newSize = new Vector2(0, 0);
if (YourShape is FrameworkElement element)
{
newSize = new Vector2((float)element.ActualWidth, (float)element.ActualHeight);
}
shadowVisual.Size = newSize;
var dropShadow = compositor.CreateDropShadow();
dropShadow.BlurRadius = 10;
dropShadow.Opacity = 0.3f;
dropShadow.Offset = new Vector3(10,10,10);
dropShadow.Color = Windows.UI.Colors.Black;
shadowVisual.Shadow = dropShadow;
ElementCompositionPreview.SetElementChildVisual(YourShape, shadowVisual);
For more details about DropShadow, you can refer to this document.

ArcObjects 10.3 Add Transparent Polygon to Map

This is an issue I have been trying to tackle for a while and decided to reach out for help. I am creating an ESRI ArcGIS Desktop Add-In that allows the user to draw a polygon and then have it added to the map. I am able to capture the polygon and add it to the map, the issue is the transparency. Currently and by default it is 100% opacity and solid. I want to make it around 50% opacity so the user can see the data behind it.
Here is the code I have so far:
public static void AddPolygonToMap(IActiveView ActiveViewInstance, IGeometry NewGeo)
{
//Local Variable Declaration
var fillShapeElement = default(IFillShapeElement);
var element = default(IElement);
var graphicsContainer = default(IGraphicsContainer);
var simpleFilleSymbol = default(ISimpleFillSymbol);
var newRgbColor = default(IRgbColor);
var lineSymbol = default(ILineSymbol);
//Use the IElement interface to set the Envelope Element's geo
element = new PolygonElement();
element.Geometry = NewGeo;
//QI for the IFillShapeElement interface so that the symbol property can be set
fillShapeElement = element as IFillShapeElement;
//Create a new fill symbol
simpleFilleSymbol = new SimpleFillSymbol();
//Create a new color marker symbol of the color black;
newRgbColor = new RgbColor();
newRgbColor.Red = 0;
newRgbColor.Green = 0;
newRgbColor.Blue = 0;
//Create a new line symbol so that we can set the width outline
lineSymbol = new SimpleLineSymbol();
lineSymbol.Color = newRgbColor;
lineSymbol.Width = 2;
//Setup the Simple Fill Symbol
simpleFilleSymbol.Color = newRgbColor;
simpleFilleSymbol.Style = esriSimpleFillStyle.esriSFSHollow;
simpleFilleSymbol.Outline = lineSymbol;
fillShapeElement.Symbol = simpleFilleSymbol;
//QI for the graphics container from the active view allows access to basic graphics layer
graphicsContainer = ActiveViewInstance as IGraphicsContainer;
//Add the new element at Z order 0
graphicsContainer.AddElement((IElement)fillShapeElement, 0);
//Show the new graphic
ActiveViewInstance.Refresh();
}
I know that this is possible somehow and I am sure it's just a line or two missing but any help would be much appreciated.
V/r,
Josh
This looks to be a graphic element that you are creating. Graphic elements do not support transparency other than 100% transparent or 0% transparent. This is outlined in the following documentation:
IColor.Transparency Property
http://help.arcgis.com/en/sdk/10.0/arcobjects_net/componenthelp/index.html#//001w000000nt000000
For graphic elements, 0 for transparent and 255 for opaque are the only supported values.
I hope this helps!

C# WPF - Adorner ZIndex

I have a Grid with a Adorner to provide some drawn pattern. See img: http://imgur.com/D649W
My problem is that this Adorner(dots on the Grid) is layered on top of everything. The white square are draggable but now when the Adorner are on top, I can't drag. I would like the layer to be behind every component added to the Grid. Any suggestions on how I can set the ZIndex?
Thanks.
Code below:
MyAdorner ad = new MyAdorner(grid);
AdornerLayer adLayer = AdornerLayer.GetAdornerLayer(grid);
adLayer.Add(ad);
I push my Button and this is adding the MyAdorner to the grid. MyAdorner looks like this:
public MyAdorner(Grid adornedGrid)
: base(adornedGrid) {
Height = adornedGrid.Height;
Width = adornedGrid.Width;
brush = new VisualBrush();
brush.Stretch = Stretch.Fill;
brush.TileMode = TileMode.Tile;
brush.Viewport = new Rect(0, 0, SnapDistance, SnapDistance);
brush.ViewportUnits = BrushMappingMode.Absolute;
brush.Viewbox = new Rect(0, 0, SnapDistance, SnapDistance);
brush.ViewboxUnits = BrushMappingMode.Absolute;
ellipse = new Ellipse() { Fill = new SolidColorBrush(Colors.Blue), Width = 2, Height = 2 };
brush.Visual = ellipse;
}
protected override void OnRender(System.Windows.Media.DrawingContext drawingContext) {
Pen renderPen = new Pen(new SolidColorBrush(Colors.Black), 0);
drawingContext.DrawRectangle(brush, renderPen, new Rect(new Point(0, 0), AdornedElement.DesiredSize));
}
If your problem is that the adorner is covering the elements you want to manipulate so that they become un-draggable etc, set .IsHitTestVisible = False on the adorner.
You can also set the adorner's opacity to some semi-transparent value to see the background through it if that is desirable.
Is this what you're looking for?
Panel.SetZIndex(ad, 20)
Attached properties of the framework are usually asignable from static methods of the UIElement that holds it.
EDIT:
Possible alternative: - make your own Panel
Easy and dirty way to make sure that your wanted elements are ALWAYS on top:
Declare a static in a Util library:
public static int ZIndexCount;
Then when you want an element on top you simply do:
SetZIndex(_viewbox, Util.ZIndexCount++);
Of course, if your application runs 5 years without being interrupted the ZIndexCount will go back to 0...
It works like a charm in my applications.
I know this is quite old but I thought I try anyway:
You can add a new AdornerDecorator to you visual tree hierarchy to render the controls at the right level. By default the root of the tree provides the AdornerDecorator but you can add as many as you want and your the components you add will be rendered in them. For more information - see here
<Grid>
<AdornerDecorator>
...your Adorners render here
</AdornerDecorator>
</Grid>
https://wangmo.wordpress.com/2008/10/19/relations-between-adorner-adornerlayer-and-adornerdecorator/

Merging Shapes and Attaching Partial Drag/Drop in WPF

I would like to draw two shapes in WPF and merge them together. Then, I'd like to attach a drag/drop event to ONE of the original shapes.
So basically, you can only drag if you click on a certain part of the shape, but it will drag the entire shape with you.
Here is some code:
// Set up some basic properties for the two ellipses
Point centerPoint = new Point(100, 100);
SolidColorBrush ellipseColor_1 = new SolidColorBrush(Color.FromArgb(255, 0, 0, 255));
double width_1 = 10; double height_1 = 10;
SolidColorBrush ellipseColor_2 = new SolidColorBrush(Color.FromArgb(50, 255, 0, 0));
double width_2 = 200; double height_2 = 200;
// Create the first ellipse: A small blue dot
// Then position it in the correct location (centerPoint)
Ellipse ellipse_1 = new Ellipse() { Fill = ellipseColor_1, Width = width_1, Height = height_1 };
ellipse_1.RenderTransform = new TranslateTransform(point.X - width_1 / 2, point.Y - height_1 / 2);
// Create the second ellipse: A large red, semi-transparent circle
// Then position it in the correct location (centerPoint)
Ellipse ellipse_2 = new Ellipse() { Fill = ellipseColor_2, Width = width_2, Height = height_2 };
ellipse_2.RenderTransform = new TranslateTransform(point.X - width_2 / 2, point.Y - height_2 / 2);
// ???
// How should I merge these?
// ???
// Now apply drag drop behavior to ONLY ellipse_1
MouseDragElementBehavior dragBehavior = new MouseDragElementBehavior();
dragBehavior.Attach(ellipse_1); // This may change depending on the above
// ...
// Add new element to canvas
This code creates two circles (a big one and a small one). I would like to only be able to drag if the small one is clicked, but I'd like to have them attached so they'll move together without having to manually add code that will take care of this.
If you put them both in a Grid (or Canvas, StackPanel, etc.), and set the drag behavior on the panel, they will be "merged". If you set IsHitTestVisible to false on ellipse_2, it won't respond to any mouse events, so effectively it won't be draggable.

WPF, animate over container boundaries

I'm learning WPF and have a specific goal.
Imagine you have a grid (3 rows by 3 columns), and a control, say a simple blue rectangle fills the middle cell. When I click on the cell I want the square to rotate smoothly through 180 degrees.
The specific issue I have at the moment is; as the rectangle rotates, it won't change its dimensions, so it will extend beyond the boundary of the cell. I don't want it to clip, i want it to appear on top, partially obscuring surrounding cells.
The second part of this is, if there is one cell that fills the entire window, and I click on the blue rectangle in that cell, can I make the rectangle rotate and extend beyond the sides of the form?
If that doesn't make sense, please ask. I'm finding it hard to google because I don't know the exact terms I should be using.
Thank you
The first part can be acomplished by using the attached property Panel.ZIndex, set it to a high value when you start the animation and a lower value when the animation is complete. The second part (having a control outside of the window) is more complicated. I tried a few things and this method seemed to be the best. It uses a full screen window instead of a Popup as I encountered cliping issues. A copy of the element is made using RenderTargetBitmap this is then placed in the same position. The original element is hidden whilst the copy is animated.
public void PopupAnimation(UIElement element)
{
double w = element.RenderSize.Width,h = element.RenderSize.Height;
var screen = new Canvas();
var pos = element.PointToScreen(new Point(0, 0));
var rtb = new RenderTargetBitmap((int)w,(int)h, 96, 96, PixelFormats.Pbgra32);
rtb.Render(element);
Image i = new Image { Source = rtb, Width = w, Height = h,Stretch=Stretch.Fill};
Canvas.SetLeft(i, pos.X);
Canvas.SetTop(i, pos.Y);
screen.Children.Add(i);
var window = new Window() {
Content = screen, AllowsTransparency = true,
Width=SystemParameters.PrimaryScreenWidth,Height=SystemParameters.PrimaryScreenHeight,
WindowStyle=WindowStyle.None,ShowInTaskbar=false,Topmost=true,
Background=Brushes.Transparent,ShowActivated=false,Left=0,Top=0
};
var transform = new RotateTransform();
i.RenderTransformOrigin = new Point(0.5, 0.5);
i.RenderTransform = transform;
var anim = new DoubleAnimation { To = 360 };
anim.Completed += (s,e) =>
{
element.Visibility = Visibility.Visible;
var delay = new Storyboard { Duration = TimeSpan.FromSeconds(0.1) };
delay.Completed += (s2, e2) => window.Close();
delay.Begin();
};
window.ContentRendered += (s, e) =>
{
transform.BeginAnimation(RotateTransform.AngleProperty, anim);
element.Visibility = Visibility.Hidden;
};
window.Show();
}

Categories

Resources