Get ScaleTransform and RotateTransform from System.Windows.Shapes.Path - wpf

I'm trying to get RotateTransform value (degrees) and ScaleTransform from code parsing XAML nodes, in this case a System.Windows.Shapes.Path.
<Path Data="M272,0 L0,0" Height="12.274" Canvas.Left="17.997" StrokeStartLineCap="Flat"
StrokeEndLineCap="Flat" Stroke="White" StrokeThickness="3" StrokeLineJoin="Miter"
Canvas.Top="44.53" Width="146.499" Stretch="Fill" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="-1"/>
<SkewTransform/>
<RotateTransform Angle="-90"/>
<TranslateTransform/>
</TransformGroup>
</Path.RenderTransform>
</Path>
Using RenderTransform, I get a Matrix Value and I'm not able to get angle and scaleX from that.
myPath.RenderTransform.Value
How can I get these informations?
Thank you

Ok this is the solution:
TransformGroup tg = myPath.RenderTransform as TransformGroup;
if (tg != null)
{
// Get rotation angle
RotateTransform rt = tg.Children[2] as RotateTransform;
// DoSomething with rt.Angle
// Get scale factor
ScaleTransform st = tg.Children[0] as ScaleTransform;
// DoSomething with st.ScaleX or st.ScaleY
}

Related

Difficulty in correctly locating mouse position in child element of a canvas

WPF
In the below code, I have a Canvas with child images. The child images are added by drag and drop and the Canvas itself is contained within an outer Grid. When using a magnifier over the child images, the source of the area being magnified is higher and left of the magnifier. (Source code adapted from: A Magnifier)
How is this fixed?
The resulting image. The center of the Magnifier should be over the "S", not down to the right of it.
XAML:
<ScrollViewer Name="TheScrollViewer" Grid.Row ="2" Grid.RowSpan="2" Grid.Column="1" Grid.ColumnSpan="2" VerticalScrollBarVisibility="Auto" >
<Grid ShowGridLines="True" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="60"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Canvas Grid.Column="1" Grid.Row="1" Background="Blue" Name="mainUI" PreviewMouseMove="OnMoveOverMainUI"
Panel.ZIndex="{Binding ImageLayer.ZIndex}" Visibility="{Binding ImageLayer.Visibility}" >
<i:Interaction.Behaviors>
<b:ImageCanvasBehavior Source ="{Binding ImageLayer.Source}" />
</i:Interaction.Behaviors>
</Canvas>
<Canvas HorizontalAlignment="Left" VerticalAlignment="Top" Grid.Column="1" Grid.Row="1" Panel.ZIndex="1000" >
<Canvas Name="magnifierCanvas" IsHitTestVisible="False"
Visibility="{Binding ElementName=checkEnableMagnifier,Path=IsChecked, Converter={StaticResource BoolToVis}}">
<Line StrokeThickness="30" X1="200" Y1="200" X2="300" Y2="300">
<Line.Stroke>
<LinearGradientBrush StartPoint="0.78786,1" EndPoint="1,0.78786">
<GradientStop Offset="0" Color="DarkGreen" />
<GradientStop Offset="0.9" Color="LightGreen" />
<GradientStop Offset="1" Color="Green" />
</LinearGradientBrush>
</Line.Stroke>
</Line>
<Ellipse Width="250" Height="250" Fill="White" />
<Ellipse Width="250" Height="250" Name="magnifierEllipse" StrokeThickness="3">
<Ellipse.Fill>
<VisualBrush ViewboxUnits="Absolute" Viewbox="0,0,50,50"
ViewportUnits="RelativeToBoundingBox" Viewport="0,0,1,1"/>
</Ellipse.Fill>
<Ellipse.Stroke>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="#AAA" />
<GradientStop Offset="1" Color="#111" />
</LinearGradientBrush>
</Ellipse.Stroke>
</Ellipse>
<Ellipse Canvas.Left="2" Canvas.Top="2" StrokeThickness="4" Width="246" Height="246">
<Ellipse.Stroke>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="#555" />
<GradientStop Offset="1" Color="#EEE" />
</LinearGradientBrush>
</Ellipse.Stroke>
</Ellipse>
</Canvas>
</Canvas>
</Grid>
</ScrollViewer>
Code-Behind:
private void ZoomChanged(object sender, EventArgs e)
{
if (magnifierEllipse != null)
{
VisualBrush b = (VisualBrush)magnifierEllipse.Fill;
Rect viewBox = b.Viewbox;
double val = sliderTargetSize.Value;
viewBox.Width = val;
viewBox.Height = val;
b.Viewbox = viewBox;
}
}
private void OnMoveOverMainUI(object sender, MouseEventArgs e)
{
VisualBrush b = (VisualBrush)magnifierEllipse.Fill;
Point pos = e.MouseDevice.GetPosition(mainUI);
Rect viewBox = b.Viewbox;
double xoffset = viewBox.Width / 2.0;
double yoffset = viewBox.Height / 2.0;
viewBox.X = pos.X - xoffset;
viewBox.Y = pos.Y - yoffset;
b.Viewbox = viewBox;
Canvas.SetLeft(magnifierCanvas, pos.X - magnifierEllipse.Width / 2);
Canvas.SetTop(magnifierCanvas, pos.Y - magnifierEllipse.Height / 2);
}
Fixed the problem. This procedure works well when in its own window--not so when in a user control inside a grid.
private void OnMoveOverMainUI(object sender, MouseEventArgs e)
{
VisualBrush b = (VisualBrush)magnifierEllipse.Fill;
Point pos = e.MouseDevice.GetPosition(mainUI);
Rect viewBox = b.Viewbox;
double xoffset = viewBox.Width / 2.0;
double yoffset = viewBox.Height / 2.0;
viewBox.X = pos.X - xoffset;
viewBox.Y = pos.Y - yoffset;
b.Viewbox = viewBox;
Canvas.SetLeft(magnifierCanvas, pos.X - magnifierEllipse.Width / 2);
Canvas.SetTop(magnifierCanvas, pos.Y - magnifierEllipse.Height / 2);
}
The corrected version I needed is:
private void OnMoveOverMainUI(object sender, MouseEventArgs e)
{
// Get position of the mainUI to its ancestor the MainGrid
Point relativePoint = mainUI.TransformToAncestor(MainGrid)
.Transform(new Point(0, 0));
// Get position of the Mouse relative to the mainUI
Point pp = e.MouseDevice.GetPosition(mainUI);
// Get position of the mouse relative to the MainGrid.
Point pos = new Point(pp.X + relativePoint.X, pp.Y + relativePoint.Y);
VisualBrush b = (VisualBrush)magnifierEllipse.Fill;
Rect viewBox = b.Viewbox;
double xoffset = viewBox.Width / 2.0;
double yoffset = viewBox.Height / 2.0;
viewBox.X = pos.X - xoffset;
viewBox.Y = pos.Y - yoffset;
b.Viewbox = viewBox;
Canvas.SetLeft(magnifierCanvas, pos.X - magnifierEllipse.Width / 2 - relativePoint.X);
Canvas.SetTop(magnifierCanvas, pos.Y - magnifierEllipse.Height / 2 - relativePoint.Y);
}

Wpf path does not render

I wand to draw a curve
When I draw it from xaml - everything is good:
I can see the curve
<Grid x:Name="mainGrid">
<Path Stroke="#255468" StrokeThickness="12" Opacity="20" >
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure x:Name="pf" StartPoint="60,60">
<PathFigure.Segments>
<PathSegmentCollection>
<PolyBezierSegment Points="70,60 100,300 150,150 200,200 "/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
<Button Content="Button" HorizontalAlignment="Left" Height="54" Margin="21,0,0,24" VerticalAlignment="Bottom" Width="59" Click="Button_Click"/>
</Grid>
But when I draw the same line from code behind - it does not work :
<Grid x:Name="mainGrid">
<Path x:Name="orangePath" Stroke="Orange" StrokeThickness="12" Opacity="20"/>
<Button Content="Button" HorizontalAlignment="Left" Height="54" Margin="21,0,0,24" VerticalAlignment="Bottom" Width="59" Click="Button_Click"/>
</Grid>
private void Button_Click(object sender, RoutedEventArgs e)
{
orangePath = new Path();
PathFigure pathFigure = new PathFigure();
pathFigure.StartPoint = new Point(60, 60);
var ppp = new PolyBezierSegment();
ppp.Points.Add(new Point(70, 60));
ppp.Points.Add(new Point(100, 300));
ppp.Points.Add(new Point(150, 150));
ppp.Points.Add(new Point(200, 200));
pathFigure.Segments.Add(ppp);
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures = new PathFigureCollection();
pathGeometry.Figures.Add(pathFigure);
orangePath.Data = pathGeometry;
}
What could be a problem ?
I have to add the points dynamically. At the runtime.
Thank you
The problem is you are creating a new Path. Remove this line:
orangePath = new Path();
And it should work just fine.

Get visible square of image/rectangle

There is a canvas(Gray color) with an image on it (Blue color). I make some manipulations with the image and want to find the visible part(Red color) of the image after manipulations.
For that i use this method:
private double GetSquare()
{
Rect rect = GetBounds(Image, Canvas);
var canvasRect = RootGeometry.Rect;
canvasRect.Intersect(rect);
return canvasRect.Height * canvasRect.Width;
}
private static Rect GetBounds(FrameworkElement of, FrameworkElement from)
{
// Might throw an exception if of and from are not in the same visual tree
GeneralTransform transform = of.TransformToVisual(from);
return transform.TransformBounds(new Rect(0, 0, of.ActualWidth, of.ActualHeight));
}
XAML:
<Canvas Grid.Row="1" x:Name="ImageCanvas" Background="Gray">
<Canvas.Clip>
<RectangleGeometry x:Name="RootGeometry"/>
</Canvas.Clip>
<Image x:Name="Image" Stretch="Fill"
ManipulationStarted="OnManipulationStarted"
ManipulationDelta="OnManipulationDelta"
ManipulationCompleted="OnManipulationCompleted" Source="{...}"
ImageOpened="OnImageOpened">
<Image.RenderTransform>
<TransformGroup>
<MatrixTransform x:Name="previousTransform" />
<TransformGroup x:Name="currentTransform">
<ScaleTransform x:Name="scaleTransform" />
<RotateTransform x:Name="rotateTransform" />
<TranslateTransform x:Name="translateTransform" />
</TransformGroup>
</TransformGroup>
</Image.RenderTransform>
</Image>
</Canvas>
The method works correct for 2-d picture. But for 3-th image the method returns incorrect result. Maybe someone can explain why the method returns incorrect result and also explain the way how to get the visible part of image (3-th)?

Draw formatted text within a DrawingImage

I'm trying to include some formatted text as part of a drawing in XAML. Is this possible? How is it done?
Example:
<DrawingImage>
<DrawingImage.Drawing>
<!-- Can text be drawn here? -->
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,10,10"></RectangleGeometry>
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<VisualBrush>
<VisualBrush.Visual>
<StackPanel>
<TextBlock Text="Tyco" FontSize="16" FontWeight="999" Foreground="Black"></TextBlock>
</StackPanel>
</VisualBrush.Visual>
</VisualBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
Yes. Use a GlyphRunDrawing as part of the DrawingGroup or as the Drawing itself, that is the source of your DrawingImage. To construct the GlyphRun in Xaml is possible, and also in code behind:
Typeface typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretches.Normal);
if (!typeface.TryGetGlyphTypeface(out _glyphTypeface))
return;
_glyphIndexes = new ushort[text.Length];
_advanceWidths = new double[text.Length];
double textWidth = 0;
for (int ix = 0; ix < text.Length; ix++)
{
ushort glyphIndex = _glyphTypeface.CharacterToGlyphMap[text[ix]];
_glyphIndexes[ix] = glyphIndex;
double width = _glyphTypeface.AdvanceWidths[glyphIndex] * FontSize;
_advanceWidths[ix] = width;
textWidth += width;
double textHeight = _glyphTypeface.Height * FontSize;
}

How can I achieve a dashed or dotted border in WPF?

I have a ListViewItem that I am applying a Style to and I would like to put a dotted grey line as the bottom Border.
How can I do this in WPF? I can only see solid color brushes.
This worked great in our application, allowing us to use a real Border and not mess around with Rectangles:
<Border BorderThickness="1,0,1,1">
<Border.BorderBrush>
<DrawingBrush Viewport="0,0,8,8" ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0,50,50" />
<RectangleGeometry Rect="50,50,50,50" />
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.BorderBrush>
<TextBlock Text="Content Goes Here!" Margin="5"/>
</Border>
Note that the Viewport determines the size of the dashes in the lines. In this case, it generates eight-pixel dashes. Viewport="0,0,4,4" would give you four-pixel dashes.
You can create a dotted or dashes line using a rectangle like in the code below
<Rectangle Stroke="#FF000000" Height="1" StrokeThickness="1" StrokeDashArray="4 4"
SnapsToDevicePixels="True"/>
Get started with this and customize your listview according to your scenario
A bit late to the party, but the following solution worked for me. It is slightly simpler/better than both other solutions:
<Border BorderThickness="1">
<Border.BorderBrush>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle StrokeDashArray="4 2" Stroke="Gray" StrokeThickness="1"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"/>
</VisualBrush.Visual>
</VisualBrush>
</Border.BorderBrush>
<TextBlock Text="Whatever" />
</Border>
Xaml
<Grid>
<Grid.RowDefinitions><RowDefinition Height="auto"/></Grid.RowDefinitions>
<Grid.ColumnDefinitions><ColumnDefinition Width="auto"/></Grid.ColumnDefinitions>
<Rectangle RadiusX="9" RadiusY="9" Fill="White" Stroke="Black" StrokeDashArray="1,2"/>
<TextBlock Padding = "4,2" Text="Whatever"/>
</Grid>
Our team got this as a requirement lately and we solved it by creating a custom control, DashedBorder which extends Border and adds the dashed border feature.
It has 3 new dependency properties
UseDashedBorder (bool)
DashedBorderBrush (Brush)
StrokeDashArray (DoubleCollection)
Usable like this
<controls:DashedBorder UseDashedBorder="True"
DashedBorderBrush="#878787"
StrokeDashArray="2 1"
Background="#EBEBEB"
BorderThickness="3"
CornerRadius="10 10 10 10">
<TextBlock Text="Dashed Border"
Margin="6 2 6 2"/>
</controls:DashedBorder>
And produces a result like this
When UseDashedBorder is set to true it will create a VisualBrush with 2 rectangles and set that as BorderBrush (that's why we need an extra property for the color of the actual BorderBrush). The first one is to create the dashing and the second of is to fill in the gaps with the Background of the border.
It maps the Rectangle dashing properties to the DashedBorder properties like this
StrokeDashArray => StrokeDashArray
Stroke => DashedBorderBrush
StrokeThickness => BorderThickness.Left
RadiusX => CornerRadius.TopLeft
RadiusY => CornerRadius.TopLeft
Width => ActualWidth
Height => ActualHeight
DashedBorder.cs
public class DashedBorder : Border
{
private static DoubleCollection? emptyDoubleCollection;
private static DoubleCollection EmptyDoubleCollection()
{
if (emptyDoubleCollection == null)
{
DoubleCollection doubleCollection = new DoubleCollection();
doubleCollection.Freeze();
emptyDoubleCollection = doubleCollection;
}
return emptyDoubleCollection;
}
public static readonly DependencyProperty UseDashedBorderProperty =
DependencyProperty.Register(nameof(UseDashedBorder),
typeof(bool),
typeof(DashedBorder),
new FrameworkPropertyMetadata(false, OnUseDashedBorderChanged));
public static readonly DependencyProperty DashedBorderBrushProperty =
DependencyProperty.Register(nameof(DashedBorderBrush),
typeof(Brush),
typeof(DashedBorder),
new FrameworkPropertyMetadata(null));
public static readonly DependencyProperty StrokeDashArrayProperty =
DependencyProperty.Register(nameof(StrokeDashArray),
typeof(DoubleCollection),
typeof(DashedBorder),
new FrameworkPropertyMetadata(EmptyDoubleCollection()));
private static void OnUseDashedBorderChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
DashedBorder dashedBorder = (DashedBorder)target;
dashedBorder.UseDashedBorderChanged();
}
private Rectangle GetBoundRectangle()
{
Rectangle rectangle = new Rectangle();
rectangle.SetBinding(Rectangle.StrokeThicknessProperty, new Binding() { Source = this, Path = new PropertyPath("BorderThickness.Left") });
rectangle.SetBinding(Rectangle.RadiusXProperty, new Binding() { Source = this, Path = new PropertyPath("CornerRadius.TopLeft") });
rectangle.SetBinding(Rectangle.RadiusYProperty, new Binding() { Source = this, Path = new PropertyPath("CornerRadius.TopLeft") });
rectangle.SetBinding(Rectangle.WidthProperty, new Binding() { Source = this, Path = new PropertyPath(ActualWidthProperty) });
rectangle.SetBinding(Rectangle.HeightProperty, new Binding() { Source = this, Path = new PropertyPath(ActualHeightProperty) });
return rectangle;
}
private Rectangle GetBackgroundRectangle()
{
Rectangle rectangle = GetBoundRectangle();
rectangle.SetBinding(Rectangle.StrokeProperty, new Binding() { Source = this, Path = new PropertyPath(BackgroundProperty) });
return rectangle;
}
private Rectangle GetDashedRectangle()
{
Rectangle rectangle = GetBoundRectangle();
rectangle.SetBinding(Rectangle.StrokeDashArrayProperty, new Binding() { Source = this, Path = new PropertyPath(StrokeDashArrayProperty) });
rectangle.SetBinding(Rectangle.StrokeProperty, new Binding() { Source = this, Path = new PropertyPath(DashedBorderBrushProperty) });
Panel.SetZIndex(rectangle, 2);
return rectangle;
}
private VisualBrush CreateDashedBorderBrush()
{
VisualBrush dashedBorderBrush = new VisualBrush();
Grid grid = new Grid();
Rectangle backgroundRectangle = GetBackgroundRectangle();
Rectangle dashedRectangle = GetDashedRectangle();
grid.Children.Add(backgroundRectangle);
grid.Children.Add(dashedRectangle);
dashedBorderBrush.Visual = grid;
return dashedBorderBrush;
}
private void UseDashedBorderChanged()
{
if (UseDashedBorder)
{
BorderBrush = CreateDashedBorderBrush();
}
else
{
ClearValue(BorderBrushProperty);
}
}
public bool UseDashedBorder
{
get { return (bool)GetValue(UseDashedBorderProperty); }
set { SetValue(UseDashedBorderProperty, value); }
}
public Brush DashedBorderBrush
{
get { return (Brush)GetValue(DashedBorderBrushProperty); }
set { SetValue(DashedBorderBrushProperty, value); }
}
public DoubleCollection StrokeDashArray
{
get { return (DoubleCollection)GetValue(StrokeDashArrayProperty); }
set { SetValue(StrokeDashArrayProperty, value); }
}
}
Working on a user control....
I have been trying a storyboard for a marching ants border. The basic grid with a rectangle and text works fine since there is no interaction. When trying to put a button inside the grid, then either the rectangle or button is visible but never both of them.
From another post:
Advanced XAML Animation effects. Pulse, Marching ants, Rotations. Alerts
Using dotNet's solution for the VisualBrush shifted the rectangle to the border with a button inside. This worked perfectly.
<UserControl.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type TextBlock}" x:Key="LOC_DG_Cell_Mid" BasedOn="{StaticResource DG_TextBlock_Mid}" >
<Setter Property="Margin" Value="5 0"/>
</Style>
<Storyboard x:Key="MarchingAnts">
<DoubleAnimation BeginTime="00:00:00"
Storyboard.TargetName="AlertBox"
Storyboard.TargetProperty="StrokeThickness"
To="4"
Duration="0:0:0.25" />
<!-- If you want to run counter-clockwise, just swap the 'From' and 'To' values. -->
<DoubleAnimation BeginTime="00:00:00" RepeatBehavior="Forever" Storyboard.TargetName="AlertBox" Storyboard.TargetProperty="StrokeDashOffset"
Duration="0:3:0" From="1000" To="0"/>
</Storyboard>
</ResourceDictionary>
</UserControl.Resources>
<UserControl.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard Storyboard="{StaticResource MarchingAnts}"/>
</EventTrigger>
</UserControl.Triggers>
<Grid>
<Border BorderThickness="1">
<Border.BorderBrush>
<VisualBrush>
<VisualBrush.Visual>
<Rectangle x:Name="AlertBox" Stroke="Red" StrokeDashOffset="2" StrokeDashArray="5" Margin="5"
Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualWidth}"
Height="{Binding RelativeSource={RelativeSource AncestorType={x:Type Border}}, Path=ActualHeight}"/>
</VisualBrush.Visual>
</VisualBrush>
</Border.BorderBrush>
<Button x:Name="FinishedButton" Padding="0 5" Margin="0" Style="{StaticResource IconButton}" >
<StackPanel Orientation="Horizontal" >
<Label Style="{StaticResource ButtonLabel}" Content="Processing has Finished" />
</StackPanel>
</Button>
</Border>
</Grid>

Resources