Apply outer outlines to text in WPF - c#

Here there is a demo for a textBlock with an outlines.
With the code below I get this result
<local:OutlinedTextBlock Stroke="Red"
FontSize="16"
Fill="Transparent"
StrokeThickness="1">
abc
</local:OutlinedTextBlock>
The outline stands on the center of the border of the letter, how can I make the outline be out of the letters? I need the fill to be transparent and only the outline will have color.
Something like that:
My text is not fixed but can be changed by the user.

You need to push clip geometry, Just add 4 new lines to this code
protected override void OnRender(DrawingContext drawingContext)
{
EnsureGeometry();
var boundsGeo = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
var invertGeo = Geometry.Combine(boundsGeo, _TextGeometry, GeometryCombineMode.Exclude, null);
drawingContext.PushClip(invertGeo);
drawingContext.DrawGeometry(null, _Pen, _TextGeometry);
drawingContext.Pop();
drawingContext.DrawGeometry(Fill, null, _TextGeometry);
}
But then you would need to double the StrokeThickness, since only half of the stroke is visible.
result:

Related

how to check if an element is going out of its parent in c# wpf

I am creating shapes at run time on canvas in my app and except all the shapes, ellipse is going out of canvas. How do I restrict it to canvas? All other shapes are contained in canvas because of the control points at their vertices. How do I keep a check as to not let ellipse go out of canvas without clipping. I have used ClipToBounds and it doesn't meet my needs.
Also, an alternate solution is if I can add a controlpoint at the left side of ellipse of radiusX property. I can't add a controlpoint to left side of radiusX on ellipse. If you could help me with either of that?
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusYProperty, 1, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
//radiusXcp = new ControlPoint(this, EllipseGeom, EllipseGeometry.RadiusXProperty, 0, true, false);
//radiusXcp.RelativeTo = EllipseGeometry.CenterProperty;
//shape_ControlPoints.Add(radiusXcp);
//EllipseGeom.RadiusX = -EllipseGeom.RadiusX;
Here is a quick example of what i would do. It could be improved on and the code is mainly written to be easy to read and follow. It also does not handle the possibility to if the shape's size is bigger than the Canvas (not sure if that is a use case in your project).
For the example I used the "Loaded" event on the Canvas, to reset the position before drawing. You would want this check before you draw the Ellipse object.
private void TestCanvas_Loaded(object sender, RoutedEventArgs e)
{
//canvas = 450 x 800
Ellipse test_ellipse = new Ellipse();
test_ellipse.Width = 100;
test_ellipse.Height = 100;
test_ellipse.Fill = Brushes.Red;
Canvas.SetLeft(test_ellipse, 700);
Canvas.SetTop(test_ellipse, -500);
Reset_Ellipse_Bounds(TestCanvas, ref test_ellipse);
TestCanvas.Children.Add(test_ellipse);
}
private void Reset_Ellipse_Bounds(Canvas myCanvas, ref Ellipse myEllipse)
{
var left = Canvas.GetLeft(myEllipse);
var top = Canvas.GetTop(myEllipse);
//handle too far right
if (left + myEllipse.Width > myCanvas.ActualWidth)
Canvas.SetLeft(myEllipse, myCanvas.ActualWidth - myEllipse.Width);
//handle too far left
if(left < 0)
Canvas.SetLeft(myEllipse, 0);
//handle too far up
if (top < 0)
Canvas.SetTop(myEllipse, 0);
//handle too far down
if (top + myEllipse.Height > myCanvas.ActualHeight)
Canvas.SetTop(myEllipse, myCanvas.ActualHeight - myEllipse.Height);
}
For Completeness the XAML:
<Grid>
<Canvas x:Name="TestCanvas" Loaded="TestCanvas_Loaded" />
</Grid>
The idea is to check the bounding box against the Canvas edges. There are ways to improve this, but i figured the simplest solution is easier to follow.
Within each if statement you could add more logic or a method to do further processing, but this should answer the general question of knowing if it is outside the parent.
Just set ClipToBounds="true" to its father control, it avoids the canvas to be drawn outside of it.
In my case I set it to Grid as followed :
<Grid x:Name="MainGrid" Background="WhiteSmoke" ClipToBounds="true">
<Canvas Margin="10" Background="Transparent"
SizeChanged="ViewportSizeChanged"
MouseLeftButtonDown="ViewportMouseLeftButtonDown"
MouseLeftButtonUp="ViewportMouseLeftButtonUp"
MouseMove="ViewportMouseMove"
MouseWheel="ViewportMouseWheel">
<Canvas x:Name="canvas" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform"/>
</Canvas.RenderTransform>
</Canvas>
<Canvas x:Name="canvas2" Width="1000" Height="600"
HorizontalAlignment="Left" VerticalAlignment="Top">
<Canvas.RenderTransform>
<MatrixTransform x:Name="transform2"/>
</Canvas.RenderTransform>
</Canvas>
</Canvas>
</Grid>

Is there possible add aero effect in Control itself in WPF?

I have a WriteableBitmap that would display video in the Window.
Currently I need to covert the subtitle in the bottom. I want to use a aero effect, but only cover the bottom of the WriteableBitmap.
Which mean, this is the original :
And I want to add a Grid Control which has aero effect, like This:
But I don't know how, I already think about take the picture bottom place and use some effect , and then put it on the top, But It looks a low performance for writableBitmap, because which mean it need to process 25 around picture every second...
Update:
Here is my code:
<Grid>
<Image x:Name="imgFrame"/>
</Grid>
private WriteableBitmap bitmap;
private void OnVideoFrameRunning(FrameArgs e)
{
if (null == bitmap)
{
bitmap = new WriteableBitmap(e.Width, e.Height, 72, 72,PixelFormats.Bgr24, null);
imgFrame.Source = bitmap;
}
bitmap.WritePixels(new Int32Rect(0, 0, e.Width, e.Height), e.Buffer, e.Width * 3, 0, 0);
}
You could do something like this:
<Grid>
<Grid.Effect>
<BlurEffect Radius="18"/>
</Grid.Effect>
<TextBlock HorizontalAlignment="Center"
VerticalAlignment="Bottom"
FontSize="40"
Foreground="#444444"
Text="Some Subtitles"
Margin="0 0 0 20"/>
</Grid>
Radius="0":
Radius="18":

Rectangle visible only in certain grid area

How to make the green rectangle to be visible only inside the blue one? The blue is a border of a grid. I want to cut off everything that is not inside this grid. Notice that the green rectangle will be moving.
As there is no ClipToBounds property in Silverlight, you would have to set the Clip property to a RectangleGeometry.
When the Grid's size is fixed, you may simply set a fixed size rectangle:
<Grid>
<Grid.Clip>
<RectangleGeometry Rect="0,0,400,600"/>
</Grid.Clip>
...
</Grid>
When the Grid's size can change, you may set the Clip property in a SizeChanged handler:
<Grid SizeChanged="GridSizeChanged">
...
</Grid>
The handler code:
private void GridSizeChanged(object sender, SizeChangedEventArgs e)
{
((UIElement)sender).Clip =
new RectangleGeometry
{
Rect = new Rect(0, 0, e.NewSize.Width, e.NewSize.Height)
};
}
<Grid Width="200" Height="100">
<Grid.Clip>
<RectangleGeometry Rect="0, 0, 200, 100"/>
</Grid.Clip>
source: http://www.scottlogic.co.uk/blog/colin/2009/05/silverlight-cliptobounds-can-i-clip-it-yes-you-can/

Creating rotated text in WPF

I am trying to create some rotated text and save that image to a PNG file. The resulting PNG should be no larger than needed (or minimal padding). I have it working as long as there is no rotation, but as soon as I rotate the text, it is getting clipped off in the file. I am sure it has something to do with adjusting the either the CenterX and CenterY of the RotateTransform or creating a TranslateTransform, but I can't find anything on how to do it correctly and my trial-and-error testing has turned into trial-and-frustration.
My sample code is below. I looking for a solution that would work with an arbitrary angle and not just -45 degrees.
Finally, if someone knows how to meet these requirements, but say using an "old style" Graphics object instead of WPF tools, I am open to that solution to that too.
private static void CreateImageFile()
{
FormattedText ft;
Geometry textBox;
string fontName;
Typeface face;
DrawingVisual viz;
RotateTransform rt;
TranslateTransform tt;
Rect rect;
RenderTargetBitmap bmp;
PngBitmapEncoder encoder;
ft = CreateText("Lorem ipsum dolor sit amet, consectetur adipisicing" + Environment.NewLine + "elit, sed do eiusmod tempor", "Verdana", 12, false, false);
textBox = ft.BuildHighlightGeometry(new Point());
fontName = "Arial";
face = new Typeface(fontName);
// now create the visual we'll draw them to
viz = new DrawingVisual();
rt = new RotateTransform() { Angle = -45 };
rect = rt.TransformBounds(ft.BuildHighlightGeometry(new Point(0, 0)).Bounds);
using (DrawingContext dc = viz.RenderOpen())
{
dc.PushTransform(rt);
dc.DrawText(ft, new Point(0, 0));
dc.Pop();
}
bmp = new RenderTargetBitmap((int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Pbgra32);
bmp.Render(viz);
encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
using (FileStream file = new FileStream("TextImage.png", FileMode.Create))
encoder.Save(file);
}
private static FormattedText CreateText(string text, string typeface, double fontSize, bool bold, bool italic)
{
FontStyle fontStyle = FontStyles.Normal;
FontWeight fontWeight = FontWeights.Medium;
if (bold == true) fontWeight = FontWeights.Bold;
if (italic == true) fontStyle = FontStyles.Italic;
// Create the formatted text based on the properties set.
FormattedText formattedText = new FormattedText(
text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface(new FontFamily(typeface),
fontStyle,
fontWeight,
FontStretches.Normal),
fontSize,
Brushes.Black, // This brush does not matter since we use the geometry of the text.
null,
TextFormattingMode.Display
);
return formattedText;
}
Update
Based upon some of the suggestions below, I decided to try a different tack and experiment in the GUI. I created a window like this:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="160" Width="160" Loaded="Window_Loaded">
<Grid>
<Canvas Name="WorkCanvas" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="-45"/>
<TranslateTransform/>
</TransformGroup>
</TextBlock.RenderTransform>This is a test</TextBlock>
</Canvas>
</Grid>
</Window>
As you can see, it uses both the RotateTransform and the suggested RenderTransformOrigin, and the result is like the image below. And, as you can see, the text does not go through the middle of the Canvas. And that seems to be my entire problem. How to rotate the text and get it correctly centered.
Update 2
I decided to try a Grid instead of a Canvas this time and I can now get the text properly centered in the grid, but since I can't use a FormattedText object, I can't seem to measure the actual bounding box. All measurements of the TextBlock or the Grid come back as if it was not rotated at all (looking at ActualWidth, ActualHeight, and DesiredSize). If I can't get the rotated bounding box size, I can't save the PNG without it getting clipped.
Oh, and I tried rotating the text in an unrotated grid and rotating the grid itself, both give the same results when trying to determine the dimensions.
You could try to wrap your text in an element that has a rendertransformOrigin. Make you changes to that element. Try a canvas or a grid.
I think what you are missing is that you need to set RenderTransformOrigin to 0.5,0.5 so that your rotation transformation is around the center of you image and not the upper left-hand edge.
Update
In response to your update above. The problem is using canvas. If you remove your transform altogether, you'll see your TextBlock isn't centered to start with. It actually is rotating around it's center, it's just that the center isn't the center of the canvas. Try this:
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock TextAlignment="Center" RenderTransformOrigin="0.5,0.5">
<TextBlock.RenderTransform>
<RotateTransform Angle="-45"/>
</TextBlock.RenderTransform>
This is a test
</TextBlock>
</Grid>
After much poking around and with help from Matt and kbo4sho88, I finally found the correct way of doing it. In addition to the help from the other posters, I finally found that I need to call TransformToVisual and TransformBounds to get the bounding box that I need for the correct file size. But, before that, I had to call Measure and Arrange since these objects are not shown on a screen.
Phew!
private static void CreateImageFile()
{
Grid workGrid;
TextBlock workTextBlock;
RenderTargetBitmap bitmap;
PngBitmapEncoder encoder;
Rect textBlockBounds;
GeneralTransform transform;
workGrid = new Grid()
{
VerticalAlignment = VerticalAlignment.Center,
HorizontalAlignment = HorizontalAlignment.Center
};
workTextBlock = new TextBlock()
{
Text = "Lorem ipsum dolor sit amet, consectetur adipisicing" + Environment.NewLine + "elit, sed do eiusmod tempor",
FontFamily = new FontFamily("Verdana"),
FontSize = 36,
TextAlignment = TextAlignment.Center,
RenderTransformOrigin = new Point(0.5, 0.5),
LayoutTransform = new RotateTransform(-45)
};
workGrid.Children.Add(workTextBlock);
/*
* We now must measure and arrange the controls we just created to fill in the details (like
* ActualWidth and ActualHeight before we call TransformToVisual() and TransformBounds()
*/
workGrid.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
workGrid.Arrange(new Rect(0, 0, workGrid.DesiredSize.Width, workGrid.DesiredSize.Height));
transform = workTextBlock.TransformToVisual(workGrid);
textBlockBounds = transform.TransformBounds(new Rect(0, 0, workTextBlock.ActualWidth, workTextBlock.ActualHeight));
/*
* Now, create the bitmap that will be used to save the image. We will make the image the
* height and width we need at 96DPI and 32-bit RGBA (so the background will be transparent).
*/
bitmap = new RenderTargetBitmap((int)textBlockBounds.Width, (int)textBlockBounds.Height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(workGrid);
encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
using (FileStream file = new FileStream("TextImage.png", FileMode.Create))
encoder.Save(file);
}
Of course this is just a sample (but working) method and the final one will be parameterized.
Final part of the puzzle was found at WPF: Getting new coordinates after a Rotation
The way I generally handle custom rendering is using my own FrameworkElement and overriding the OnRender method. So, as an example:
using System;
using System.Windows; // For the FrameworkElement baseclass
using System.Windows.Media; // To get the drawing context
public class CustomThing : FrameworkElement
{
// Constructor
CustomThing()
{
}
// Custom render code called whenever control is invalidated
protected override OnRender(DrawingContext context)
{
if (!this.IsVisible || this.ActualHeight <= 0 || this.ActualWidth <= 0)
{
return; // Don't do anything if this thing isn't renderable
}
Typeface tf = new Typeface(this.FontFamily, FontStyles.Normal, this.FontWeight, FontStretches.Normal);
FormattedText fText = new FormattedText(this.Text, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight, tf, this.FontSize, this.Foreground);
// You could have accessors so that the various properties such as Fonts, etc. are
// Properties of the class, and using DependencyProperties, they can be set so they
// automatically cause an invalidation when they change.
double txWidth = fText.Width;
double txHeight = fText.Height;
// This measures the text
double w = this.ActualWidth;
double h = this.ActualHeight;
double w2 = w / 2.0;
double h2 = h / 2.0;
// Get the center point for the rotation
// In this case, the center of the control
Transform trans = new RotateTransform(-90.0, w2, h2);
// The transform is for counter-clockwise 90 degrees, centered
// in the center of the control.
context.PushTransform(trans);
// All drawing operations will be performed with the
// transformation applied
Point txPos = new Point(w2 - (txWidth / 2.0), h2 - (txHeight / 2.0));
// The render origin for the text is the upper left
// hand corner of the bounding box. This uses the same origin for
// the rotation, then shifts it by half the dimensions of the text.
context.DrawText(fText, txPos);
context.Pop();
// The pop method is only needed if you need to continue work
// with the drawing context and don't want the transform
// operating on future rendering.
}
}
There are other details, such as if you want the control to be mouse interactive (include a using System.Windows.Input), then you'll want to start the render by painting the entire control region a background color. The main thing is you don't need to measure the shape of the rotated text, only the unrotated text. If you want to position the final text in any other place besides the center of the control, just make sure your rotation center is also the same reference point as your text offset.
The other thing I typically do is to use Dependency Properties so that all the styling can be done in XAML. This way, the custom object can be previewed in the designer while changing the properties dynamically, a typical entry for an item would look like so:
public static readonly RoutedEvent TextChangedEvent = EventManager.RegisterRoutedEvent("TextChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<string>), typeof(CustomThing));
public event RoutedPropertyChangedEventHandler<string> TextChanged
{
add { AddHandler(TextChangedEvent, value); }
remove { RemoveHandler(TextChangedEvent, value); }
}
public static readonly DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(CustomThing), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.AffectsRender, new PropertyChangedCallback(OnTextChanged)));
public string Text
{
get => (string)GetValue(TextProperty);
set => SetValue(TextProperty, value);
}
private static void OnTextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
CustomThing cntrl = (CustomThing)obj;
RoutedPropertyChangedEventArgs<string> e = new RoutedPropertyChangedEventArgs<string>((string)args.OldValue, (string)args.NewValue, TextChangedEvent);
cntrl.OnTextChanged(e);
}
protected virtual void OnTextChanged(RoutedPropertyChangedEventArgs<string> e)
{
RaiseEvent(e);
}

How do I create a dashed border with rounded corners in WPF?

The Rectangle element has StrokeDashArray which allows it to be drawn with dashes, but it doesn't support rounded corners. The Border control supports nice thick lines with rounded corners, but will only draw solid lines.
What's the best way to achieve a dashed border with rounded corners, with any control?
Example of dashed border http://img524.imageshack.us/img524/3186/dashedborder.png
You are mistaken that Rectangle does not support this:
<Rectangle StrokeDashArray="0.5 1.0 0.3" Stroke="Black" StrokeThickness="2" RadiusX="10" RadiusY="10"/>
WPF Border control does not support dashed lines.
If you want to apply a dotted/dashed border for a control, you can simply decorate the control with an adorner.
Here is the sample adorner class. This is a generic adorner for any UIelement.
class DottedLineAdorner : Adorner
{
public UIElement AdornedElement { get; set; }
public DottedLineAdorner(UIElement adornedElement) : base(adornedElement)
{
AdornedElement = adornedElement;
}
protected override void OnRender(DrawingContext drawingContext)
{
Size eltSize = (AdornedElement as FrameworkElement).DesiredSize;
Pen pen = new Pen(Brushes.Blue, 2) { DashStyle = DashStyles.DashDot };
drawingContext.DrawRoundedRectangle(null, pen, new Rect(0, 0, eltSize.Width, eltSize.Height), 10, 10);
}
}
I have a simple textblock in my xaml and it is contained in a grid named 'LayoutGrid'.
Now, the border can be applied in the code behind
private void Window_Loaded(object sender, RoutedEventArgs e)
{
AdornerLayer.GetAdornerLayer(LayoutGrid).Add(new DottedLineAdorner(textblock));
}

Categories