I have a PathGeometry that I want to flip vertically. I have tried the following but it is not working, am I missing something?
PathGeometry myPathGeometry = new PathGeometry();
myPathGeometry.Figures.Add(myPathFigure);
PathGeometry flipMyPathGeometry = new PathGeometry();
ScaleTransform transform = new ScaleTransform(0, -1);
flipMyPathGeometry = Geometry.Combine(Geometry.Empty, myPathGeometry, GeometryCombineMode.Union, transform);
A big problem there is that your width will be zero.
The X and Y scales are factors. As in multipliers. Anything times Zero is zero.
Hence
ScaleTransform(0, -1);
Will give you something with no width.
You presumably want the same width and hence:
ScaleTransform(1, -1);
That might still have another problem if you want the thing to be flipped about it's centre but at least it ought to show up when you use it.
The CenterY calculation is perhaps less than obvious. You can work out the height of a geometry using it's bounds.
Since you're creating a new pathgeometry, maybe you want to retain the original without any transform.
I put some code together that manipulates a geometry from resources and uses it to add a path to a canvas.
Markup:
<Window.Resources>
<Geometry x:Key="Star">
M16.001007,0L20.944,10.533997 32,12.223022 23.998993,20.421997 25.889008,32 16.001007,26.533997 6.1109924,32 8,20.421997 0,12.223022 11.057007,10.533997z
</Geometry>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="myButton" Click="MyButton_Click">
</Button>
<Canvas Grid.Column="1" Name="myCanvas"/>
</Grid>
Code
private void MyButton_Click(object sender, RoutedEventArgs e)
{
Geometry geom = this.Resources["Star"] as Geometry;
Geometry flipped = geom.Clone();
var bounds = geom.Bounds;
double halfY = (bounds.Bottom - bounds.Top) / 2.0;
flipped.Transform = new ScaleTransform(1, -1, 0, halfY );
PathGeometry pg = PathGeometry.CreateFromGeometry(flipped);
var path = new System.Windows.Shapes.Path {Data=pg, Fill= System.Windows.Media.Brushes.Red };
this.myCanvas.Children.Add(path);
}
Just set the PathGeometry's Transform property:
var myPathGeometry = new PathGeometry();
myPathGeometry.Figures.Add(myPathFigure);
myPathGeometry.Transform = new ScaleTransform(1, -1);
Note that you may also need to set the ScaleTransform's CenterY property for a correct vertical alignment.
Both #Andy and #Clemens gave right answers. The reason why I didn't get the expected shape is because I didn't notice that the shape is outside the screen region. However, I used Andy's solution because I need to keep the original shape. Also, he notified me about creating new bounds. The only thing I changed in his answer is the value of the new bounds because with the one that he used, the shape was still outside the screen region.
double newY = (bounds.Bottom - bounds.Top);
Related
What I really want is a way to have a negative stroke Thickness value on a WPF shape such as an ellipse, so that the stoke outline paints outwards towards LEFT and TOP of Shape, rather than inside of the shape, over writing my text when I make the thinkness of the stroke too thick... I want the radius of my ellipse to stay constant, but the stroke to grow outwards with increased thinkness, and the LEFT, TOP placement of the shape to remain contant with the inner fill staying the same size and not getting covered up by stroke as it is increased in size.
I tried DropShadowEffect, but its kind of too blurry and not well defined enough...and looks kind of messy... really I just want a solid line going around the outside of the shape...
As you can see from attached picture above, I tried to put shadow around two the ellipses using this code below. the problem is that I want it to be a solid color around the outside like a scaletransform of another ellipse of a different color.
var e = new Ellipse();
DropShadowEffect effect = new DropShadowEffect();
effect.Color =Colors.Orange;
effect.Direction = 0;
effect.BlurRadius = 30;
effect.ShadowDepth = 4;
effect.Opacity=0;
e.Effect = effect;
t.Text = string.Format("abc");
t.Measure(new Size(gwin.XStep, gwin.YStep));
t.Arrange(new Rect(t.DesiredSize));
e.StrokeThickness = 2;
e.Stroke = Brushes.Black;
canvas.Children.Add(e);
canvas.Children.Add(t);
Another possible direction towards solving the problem:
<Ellipse RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
Convert to c# code and place one scaletransform ellipse centered inside another scaled transform ellipse of different colors... not sure how to set it up though.
Solution:
Based on suggestion below. I tried creating a grid, setting the width and height of the grid to the size of my ellipse, then adding two ellipses to the grid with different colors and one with a margin set to -10. and it works perfectly ... just need to place the larger ellipse with margin -10 behind the other ellipse when adding it to the grid...here's what it looks like now..
Solution is in here somewhere:
g = new Grid();
e = new Ellipse();
h = new Ellipse();
t = new TextBlock();
t.HorizontalAlignment = HorizontalAlignment.Center;
t.VerticalAlignment = VerticalAlignment.Center;
t.FontWeight = FontWeights.ExtraBold;
g.Children.Add(h);
g.Children.Add(e);
g.Children.Add(t);
gwin.canvas.Children.Add(g);
t.Text = String.Format("{0}.{1}", x, y);
g.Width = gwin.XStep;
g.Height = gwin.YStep;
Canvas.SetLeft (g, gwin.X1 + gwin.XStep*x*2);
Canvas.SetTop (g, gwin.Y1 + gwin.YStep*y*2);
e.StrokeThickness = 2;
e.Stroke = Brushes.Black;
h.Margin = new Thickness(-10);
You can use double ellipses inside a grid overlaying each other like this:
<Grid Width="100" Height="100">
<Ellipse Fill="Black" Margin="-10"/>
<Ellipse Fill="Red" />
</Grid>
The size of this compound is still 100x100 even though the first ellipse is bigger and rendered out of its boundaries.
You may also use a Path and then do this
I think there is something like border. Or you can draw one elipse and then a second one in smaller that has the background color.
I'm having issues trying to not only clip, but to "extract" a portion of a WPF Canvas. So basically I would like the "Clip" to expand to the full size of the window, or convert the clipped item to separate UI Element for exporting to PNG. I write pseudocode because the real code comes from an Autocad model.
double oPrintWidth=1169;
Canvas c = new Canvas();
c.Width = oPrintWidth * 2.54;
c.Height = c.Width * ratio;
// Define the path to clip
string thisPathData = "M12233 M222333 M3443" // fake
c.Clip = Geometry.Parse(thisPathData);
At this point I have the same size canvas but everything other than my path is now black. And the path is still in the original position. I need to now make the clip the entire canvas.
I have played with RenderTransform but I'm lost as what to do next, I'm not so good with matrix calculations.
Original Canvas (Collection of UI Elements)
AFTER CLIP
DESIRED RESULT
Ultimately this would be printed but would prefer to keep it in WPF until last minute to retain VECTOR properties for translating to SVG/XPS/ETC
To make a Clip of the entire Canvas and then apply that Clip to the Canvas I recommend you let WPF do it for you be setting the ClipToBounds property:
Canvas c = new Canvas();
c.ClipToBounds = true;
If that doesn't suit your needs, I would look at the Margin, ActualWidth, and ActualHeight properties to determine the clip region. Then create a RectangleGeometry that matches the size of your Canvas.
EDIT in response to your comments.
Well, I've had some time to work at it some more. What I have been able to do is create a clip region, then I transformed the canvas so that the clip region filled the canvas as much as possible. I think this is what you are after...
First of all I needed to measure the clipped region:
Rect bounds = canvas.Clip.Bounds;
double scaleX = c.Width / (bounds.Right - bounds.Left);
double scaleY = c.Height / (bounds.Bottom - bounds.Top);
This scaling information is used to make the clipped region fit exactly to the size of the canvas.
Now, we need to apply transformations to the canvas:
TransformGroup group = new TransformGroup();
TranslateTransform move = new TranslateTransform(-bounds.Left, -bounds.Top);
ScaleTransform scale = new ScaleTransform(scaleX, scaleY);
group.Children.Add(move);
group.Children.Add(scale);
canvas.RenderTransform = group;
So what is happening here? First of all, the objective is to apply a couple transformations. We need to center the clipped region (translation) and we need to make the clipped region larger (scale). Now, when I say clipped region, I mean the contents of that region. In actuality, we are moving the canvas's rendered output. Moving the region bounds is not what we want to do.
To do this in WPF, we need to add each transformation we want to a child of a TransformGroup.
In this case, we are translating the canvas's output so that its top-left corner is (0, 0) This is necessary because afterwards we will scale the rendered output. So, now, we need to scale the canvas's output so that the image fits as large as it can. To do this, we need to create a ratio that compares the canvas size to the clipped region size.
Here is the formula for scaling the output:
ratio = canvasSize / clippedSize
scaledSize = clippsedSize * ratio
Now, scaling the canvas's output will allow the clipped region to appear as large as possible.
Take a look at the results. Here are images demonstrating the canvas's output before and after the transformations are applied:
Before
After
I figure I might as well give you all the code I used:
Canvas c = new Canvas();
double oPrintWidth=100;
double ratio = .89;
c.Width = oPrintWidth * 2.54;
c.Height = c.Width * ratio;
c.Background = new ImageBrush((ImageSource)FindResource("TestImage")) { Stretch = Stretch.UniformToFill };
// Define the path to clip
string newPath = "M 64,64 L 64,128 128,128, 128,64 Z";
c.Clip = Geometry.Parse(newPath);
Rect bounds = c.Clip.Bounds;
double scaleX = c.Width / (bounds.Right - bounds.Left);
double scaleY = c.Height / (bounds.Bottom - bounds.Top);
TransformGroup group = new TransformGroup();
TranslateTransform move = new TranslateTransform(-bounds.Left, -bounds.Top);
ScaleTransform scale = new ScaleTransform(scaleX, scaleY);
group.Children.Add(move);
group.Children.Add(scale);
c.RenderTransform = group;
MyBorder.Child = c;
And the XAML:
<Window.Resources>
<BitmapImage UriSource="uvtest.jpg" x:Key="TestImage"/>
</Window.Resources>
<Grid Background="Gray">
<Border x:Name="MyBorder" Background="White" BorderBrush="Black" BorderThickness="2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
I have a Canvas in my MainWindow and I draw a line there. When it draws over the width/height of my Canvas, the drawing continues in my MainWindow. Is there a mistake in my code or is that normal?
<Canvas x:Name="coordinateSystem" HorizontalAlignment="Right" Height="580" Margin="0,10,283,0" VerticalAlignment="Top" Width="1024" Cursor="Cross" UseLayoutRounding="False"/>
Here is my function I call everytime when I get a new coordinate for my line:
// xOld, yOld and t are static
// t represents the time
private void drawPoly(double value)
{
t++;
Point pOne = new Point(xOld, yOld);
Point pTwo = new Point(t, value);
GeometryGroup lineGroup = new GeometryGroup();
LineGeometry connectorGeometry = new LineGeometry();
connectorGeometry.StartPoint = pOne;
connectorGeometry.EndPoint = pTwo;
lineGroup.Children.Add(connectorGeometry);
System.Windows.Shapes.Path path = new System.Windows.Shapes.Path();
path.Data = lineGroup;
path.StrokeThickness = 1;
path.Stroke = path.Fill = Brushes.Red;
coordinateSystem.Children.Add(path);
xOld = t;
yOld = value;
}
thx
PS: Is there a way to save all drawn points? I want later resize my canvas (zoom out/zoom in) or if the time going to big move my painted line in my canvas and then I need to draw all points again.
Canvases do not clip child elements. If you want to stop the child elements from being drawn outside of the Canvas, you'll need to set ClipToBounds to true or set the Clip of the Canvas.
I have an image in a scrollviewer.The image has Pinch in and out feature implemented on it.
But while scrolling the zoomed image,the aspect ratio changes and images becomes distorted.
Following the xaml:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" Name="scroller" >
<Image Name="image_new" Visibility="Visible" CacheMode="BitmapCache" >
<Image.RenderTransform >
<CompositeTransform x:Name="transform"/>
</Image.RenderTransform >
<toolkit:GestureService.GestureListener>
<toolkit:GestureListener Flick="OnFlick" PinchStarted="OnPinchStarted" PinchDelta="OnPinchDelta" DoubleTap="Onimage_doubletap" Tap="Onimage_singletap" />
</toolkit:GestureService.GestureListener>
</Image>
</ScrollViewer>
And in the .cs file the methods are :
private void OnPinchStarted(object sender, PinchStartedGestureEventArgs e)
{
Point point0 = e.GetPosition(image_new, 0);
Point point1 = e.GetPosition(image_new, 1);
Point midpoint = new Point((point0.X + point1.X) / 2, (point0.Y + point1.Y) / 2);
image_new.RenderTransformOrigin = new Point(midpoint.X / image_new.ActualWidth, midpoint.Y / image_new.ActualHeight);
initialScale = transform.ScaleX;
}
private void OnPinchDelta(object sender, PinchGestureEventArgs e)
{
transform.ScaleX = Math.Max(Math.Min(initialScale * e.DistanceRatio, 3.0), 0.5);
transform.ScaleY = Math.Max(Math.Min(initialScale * e.DistanceRatio, 3.0), 0.5);
}
I think the problem here is that you are changing the RenderTransformOrigin for each pinch gesture, which is resulting in the distortion. I would try leaving the RenderTransformOrigin fixed at 0.5,0.5 to ensure that you get an even scale.
I assume you were moving the origin to try to zoom into/out of the part of the image that the user had started the gesture on. To achieve this, I think you will need to enable the user to pan around the image once zoomed in.
One other point, the scale factor is always the same, so you shoudl just calculate it once, and then assign it to both ScaleX and ScaleY.
I have a path (looks like an oval):
<Path Data="Bla Bla"/>
Now I want to scale the path's width and height to whatever I like. I found a way:
<Grid Width="400" Height="50">
<Viewbox Stretch="Fill">
<Path Data="Bla Bla"/>
</Viewbox>
</Grid>
And this works, but I'm wondering if this is the most efficient way to do this? (I had to introduce a grid and viewbox to do this)
Another way to Scale a Path is to use RenderTransform or LayoutTransform
<Path Data="Bla Bla"
RenderTransformOrigin="0.5, 0.5">
<Path.RenderTransform>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"/>
</Path.RenderTransform>
</Path>
just FYI, since ViewBox uses ScaleTransform inside it it's basically just as good performance-wise.
You basically have 3 ways to scale a Path:
Wrap it into a ViewBox
Apply a ScaleTransform
Explicitly set a Width and a Height
Method 1. and 2. will yield the same result, while 3. is slightly different because the shape will change size, but the stroke will keep the original Thickness (so it's not really a zoom).
Method 1. would be appropriate when you have an area of a given size that you want to fill. On the other hand method 2. will be useful to enlarge (or reduce) the path by a given amount, for ex. two times the original size.
You could do it programmaticaly, like
http://social.msdn.microsoft.com/Forums/vstudio/en-US/a0d473fe-3235-4725-aa24-1ea9307752d3/how-to-rendertransform-in-code-behind-c?forum=wpf
kUIWEB:kArrow mArrow = new kUIWEB:kArrow();
mArrow.Width=30;
mArrow.Height=30;
mArrow.RenderTransformOrigin=new Point(0.5, 0.5);
ScaleTransform myScaleTransform = new ScaleTransform();
myScaleTransform.ScaleY = 1;
myScaleTransform.ScaleX = 1;
RotateTransform myRotateTransform = new RotateTransform();
myRotateTransform.Angle = 0;
TranslateTransform myTranslate = new TranslateTransform ();
myTranslate.X = 12;
myTranslate.X = 15;
SkewTransform mySkew = new SkewTransform ();
mySkew.AngleX=0;
mySkew.AngleY=0;
// Create a TransformGroup to contain the transforms
// and add the transforms to it.
TransformGroup myTransformGroup = new TransformGroup();
myTransformGroup.Children.Add(myScaleTransform);
myTransformGroup.Children.Add(myRotateTransform);
myTransformGroup.Children.Add(myTranslate);
myTransformGroup.Children.Add(mySkew);
// Associate the transforms to the object
mArrow.RenderTransform = myTransformGroup;