Fit Border around arbitrary Path - c#

I have a Path based on a group of rectangle/ellipse Geometry. When I put an auto-sized Border around it, the border shrinks to fit the RectangleGeometry but does something weird with the EllipseGeometry, as shown below. Anyone know how to fix it so the Border (shown in blue) fits snugly around the whole thing?
Ultimately, I want a Border or a Panel that fits an arbitrary, runtime-generated Path (including the stroke thickness), and I need the geometry size maintained.
Sample code:
<Border BorderBrush="Blue" BorderThickness="2"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Stroke="Orange" StrokeThickness="5" Fill="Red">
<Path.Data>
<GeometryGroup>
<EllipseGeometry Center="-10,-10" RadiusX="12" RadiusY="12" />
<EllipseGeometry Center="70,0" RadiusX="70" RadiusY="10">
<EllipseGeometry.Transform>
<RotateTransform Angle="-25" CenterX="50" CenterY="0" />
</EllipseGeometry.Transform>
</EllipseGeometry>
<RectangleGeometry Rect="0,0,100,100" />
</GeometryGroup>
</Path.Data>
</Path>
</Border>

You may set the Path's Stretch property and bind its Width to the Width of the Bounds of the Geometry:
<Path ... Stretch="Uniform"
Width="{Binding Data.Bounds.Width, RelativeSource={RelativeSource Self}}">
...
</Path>
As the Geometries's Bounds do not contain the rendered Path's StrokeThickness, you may alternatively put the Border into a Canvas (which does not resize its child elements at all), and don't set the Path's Width. This may may however lead to problems when you want to center the Canvas in an outer Grid. It's actual size appears to be zero.
<Canvas>
<Border BorderBrush="Blue" BorderThickness="2">
<Path ... Stretch="Uniform">
...
</Path>
</Border>
</Canvas>

Related

How to imitate OuterGlowBitmapEffect using WPF effects?

I tried using DropShadowEffect but it's "glow" strength weakens when you increase BlurRadius. I want to have strong outer glow like the image below.
If I stack the same effect a dozen times I am able to get this, but the performance tanks after this. Is this possible to do using WPF with a single effect?
Depending on how large you want the radius of the blur to be and how smooth the result needs to be, you could stack different effects like gradient stops instead of repeatedly stacking the same effect.
Like you pointed out, the DropShadowEffect strength gets weaker as the BlurRadius increases.
<TextBox Text="DropShadowEffect">
<TextBox.Effect>
<DropShadowEffect BlurRadius="50"
ShadowDepth="0"
Color="Blue"
Opacity="1"/>
</TextBox.Effect>
</TextBox>
Additionally, applying effects directly to the TextBox impacts the rendering quality of the text. The proposed solution to the linked question (setting TextOptions.TextFormattingMode="Display" on the Window) also has layout implications. Instead, you can draw a Rectangle with a BlurEffect behind your element.
<Rectangle Fill="Blue"
Height={Binding ElementName=MyTextBox, Path=ActualHeight}"
Width={Binding ElementName=MyTextBox, Path=ActualWidth}">
<Rectangle.Effect>
<BlurEffect Radius="50"/>
</Rectangle.Effect>
</Rectangle>
<TextBox x:Name="MyTextBox" Text="Rectangle with BlurEffect"/>
You can then add an additional Rectangle for each gradient stop. Here there are two: one at 50 to define the overall size of the blur, and one at 30 to strengthen the glow around the control.
<Rectangle Fill="Blue"
Height={Binding ElementName=MyTextBox, Path=ActualHeight}"
Width={Binding ElementName=MyTextBox, Path=ActualWidth}">
<Rectangle.Effect>
<BlurEffect Radius="50"/>
</Rectangle.Effect>
</Rectangle>
<Rectangle Fill="Blue"
Height={Binding ElementName=MyTextBox, Path=ActualHeight}"
Width={Binding ElementName=MyTextBox, Path=ActualWidth}">
<Rectangle.Effect>
<BlurEffect Radius="30"/>
</Rectangle.Effect>
</Rectangle>
<TextBox x:Name="MyTextBox" Text="Two Rectangles with BlurEffect"/>
You asked about the perceived sharpness around the corners of the TextBox and I must admit I don't have a good solution. I initially considered rounding the corners of the blurred elements behind your control by using a Border instead of a Rectangle, but I honestly don't see much of a difference.
<!-- Remove the CornerRadius property for square corners. -->
<Border CornerRadius="10" Background="Blue">
<Border.Effect>
<BlurEffect Radius="50"/>
</Border.Effect>
</Border>
<Border CornerRadius="10" Background="Blue">
<Border.Effect>
<BlurEffect Radius="30"/>
</Border.Effect>
</Border>
Of course, you could always make the background objects larger than your control. Here they are in the same cell of a Grid but there is extra space for the Border to grow because the TextBox has a Margin. A smaller top/bottom margin and larger left/right margin means the glow will be more uniform around the control.
<!-- These items should be in the same cell of a Grid -->
<Border CornerRadius="10" Background="Blue">
<Border.Effect>
<BlurEffect Radius="50"/>
</Border.Effect>
</Border>
<Border CornerRadius="10" Background="Blue">
<Border.Effect>
<BlurEffect Radius="30"/>
</Border.Effect>
</Border>
<TextBox Text="TextBox has an 8px, 4px margin" Margin="8 4"/>

Resizable circle button xaml

I'm developping a universal app for Windows in XAML/C# and I can't manage to create a circle button that I can resize. I use an Ellipse with uniform stretch so as to make it circle and a ContentPresenter.
<ControlTemplate TargetType="Button">
<Grid>
<Ellipse Stretch="Uniform">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
The problem is a uniform ellipse is automatically aligned top, left, and it's impossible to make it stretch the grid. When I resize the button, the ContentPresenter stays in the center while the ellipse stays in the top left corner. I'd like to be able to resize the button and that the text stays in the center of the circle.
Thanks for help!
You may use a Path with a circular EllipseGeometry:
<ControlTemplate TargetType="Button">
<Grid>
<Path Stretch="Uniform" ...>
<Path.Data>
<EllipseGeometry RadiusX="1" RadiusY="1"/>
</Path.Data>
</Path>
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
You would however have to explicitly set the Width or Height of the Button, otherwise it would take up all available space.
I've also found another solution which is to use a ViewBox:
<ControlTemplate TargetType="Button">
<ViewBox>
<Grid>
<Ellipse Stretch="Uniform" Width="50" Height="50">
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</ViewBox>
</ControlTemplate>
The ViewBox automatically scales everything when resized. You have to set Width and Height, but it's only to set proportions. Very useful when using the user control with both Windows and Windows Phone.
Thanks for your answers!

How can I make multiple Path elements inside a Canvas scale to fit its parent?

I have created a series of shapes in Illustrator, and exported them to an .ai file. When I import this file in Blend for Visual Studio, I get something that looks like this:
<Canvas x:Name="ManyPaths" Grid.Row="0">
<Path Data="F1M0,53.135L0.004,102.44 22.03,151.59 55.042,0z" Fill="#FF8B1A34" Height="151.59" Canvas.Left="0.017" Stretch="None" Canvas.Top="182.406" Width="55.042"/>
<Path Data="F1M0.003,34.152L22.027,49.15 0,0z" Fill="#FF991937" Height="49.15" Canvas.Left="0.02" Stretch="None" Canvas.Top="284.846" Width="22.027"/>
</Canvas>
There are numerous Paths, I won't list them all here for brevity. When put together, they make a fixed size rectangle. I would like to use this rectangle at the top of my app as the header image. I can resize the Canvas element in Blend, but I cannot get the collective Paths to fit the Canvas, and be resized along with it. Since I'm designing for various size screens, I would like the header image to scale. I have tried nesting it in a Grid, but that doesn't work.
Looks like the traditional way to do Paths in C# is
<Path Grid.Row="1" Stroke="Black" StrokeThickness="2" Fill="Green">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigure StartPoint="100,100">
<QuadraticBezierSegment Point1="165,25" Point2="225,100" />
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
I'm not sure if there is a means to convert the first format to the second, but I would like to reuse my .ai files if possible, instead of recreating them from scratch.
There is no need to convert to the second format. Using the Data attribute is more concise and often clearer.
You can wrap your Canvas in a ViewBox to let it scale to its container. The Stretch attribute can control if it stretches uniformly or distorts (you probably want uniform)
<Viewbox Stretch="Uniform" Grid.Row="0">
<Grid x:Name="ManyPaths" >
<Path Data="F1M0,53.135L0.004,102.44 22.03,151.59 55.042,0z" Fill="#FF8B1A34" Height="151.59" Canvas.Left="0.017" Stretch="None" Canvas.Top="182.406" Width="55.042"/>
<Path Data="F1M0.003,34.152L22.027,49.15 0,0z" Fill="#FF991937" Height="49.15" Canvas.Left="0.02" Stretch="None" Canvas.Top="284.846" Width="22.027"/>
</Grid>
</Viewbox>

InkPresenter draw only in transparent area

I have an InkPresenter and this image with a transparent background. I want my strokes to be drawn only in the transparent area and ignore the black border of the shape. How is it possible?
here is an example using WPF, same applies to InkPresenter, you can use InkPresenter.Clip property to define the clip region
<Border BorderBrush="Green"
BorderThickness="1"
Width="200"
Height="200">
<Grid>
<InkCanvas>
<InkCanvas.Clip>
<EllipseGeometry RadiusX="98"
RadiusY="98"
Center="100,100" />
</InkCanvas.Clip>
</InkCanvas>
<Ellipse Stroke="Blue"
StrokeThickness="2" />
</Grid>
</Border>
result
I was able to solve my problem using Opacity Mask:
<InkPresenter.OpacityMask>
<ImageBrush ImageSource="{Binding ImageMask}" />
</InkPresenter.OpacityMask>

Change color in vector Images

I am trying to change the color of an vector image through a IValueConverter. My vector image has the following format:
Viewbox x:Key="ViewBoxName" Stretch="Uniform" x:Shared="False">
<Canvas Width="256" Height="256" ClipToBounds="True">
<Canvas />
<Canvas >
<Path Stroke="{DynamicResource GlobalColor}" StrokeThickness="10">
<Path.Data>
<PathGeometry FillRule="Nonzero" Figures="-------" />
</Path.Data>
</Path>
</Canvas>
</Canvas>
</Viewbox>
Figures value are available but i didn't want them here :)
When the parameter has a specified value I want to be able to change the GlobalColor from the viewbox to another for this image only and I want to do that inside the converter.
What I tried to do till now is to parse this viewbox and get to that color, change it and return the viewbox with the new color but on my interface the same color remains as before.
I think i need a new approach for this to work.
Regards.

Categories

Resources