Can't see stroke - wpf

I derive from shape, this is what is in the DefiningGeometry
protected override Geometry DefiningGeometry
{
get
{
topLeft.X = Math.Min(Start.X, End.X);
topLeft.Y = Math.Min(Start.Y, End.Y);
width.X = Math.Abs(Start.X - End.X);
width.Y = Math.Abs(Start.Y - End.Y);
rectBounds.X = topLeft.X;
rectBounds.Y = topLeft.Y;
rectBounds.Width = width.X;
rectBounds.Height = width.Y;
rectGeo.Rect = rectBounds;
return rectGeo;
}
}
I see the fill, but not the stroke, since the sroke is additional to the width and height I tried to make some room for it by setting:
Width = width.X + StrokeThickness;
//same for height.
But then nothing gets drawn, does anyone know what I am doing wrong? By the way the background and the stroke brush are different color.

Stroke is always on top of Fill. Thicknesses below 1.0 are no problem at all, although very thin strokes naturally tend to become invisible.
Fill exactly fills the Shape's geometry. Stroke renders the geometry's outline, half of the stroke lying inside, half outside the shape.
Never add StrokeThickness to your Shape's width (which would only work as you expect on rectangles anyway). See the MSDN for how the Shape's properties behave.
Do not derive from Shape to create simple geometric objects. Use the predefined Rectangle, Ellipse, Line etc. Use Path for more complex geometries and set Path.Data.
Also consult the Shapes and Basic Drawing in WPF Overview and maybe the Geometry Overview in the MSDN.

StrokeThickness for some reason should be larger than 1 (I am guessing the Fill brush is covering it), or don't set the Fill property, and StrokeThickness 1 works.

Related

How can I layout a line with an arrow in a flexible grid?

My WPF window contains a Grid that stretches to the entire window width and layouts some controls along the way. Inmidst of these controls, there should be displayed some lines with arrows on one end, that visualise some kind of flow. The lines span across multiple grid columns or rows and have some margin to each sides, to keep them away from the other controls. Multiple lines have certain connection points where they must touch. For now, I only need horizontal and vertical lines.
I've tried with the Line control, but that seems to require X and Y coordinates for both ends, in pixels. That's not easy because the line may stretch as the user changes the window size.
Next was the Path control, but I don't know how to add arrow ends there. The Path data was "M0,0 L1,0" with Stretch="Fill", so if I draw an arrow in the path, it would stretch as well, which I shouldn't do.
What alternatives are there to stretch a line but not the arrow on it?
Roll your own control
public sealed class Arrow : FrameworkElement
{
protected override void OnRender(DrawingContext drawingContext)
{
var width = ActualWidth;
var height = ActualHeight;
drawingContext.Draw...; // Draw you arrow line here
drawingContext.Draw...; // Draw you arrow head here
}
}
Of course, you can add all sorts of dependency properties to it in order to control how your arrow is drawn.

WPF giving incorrect size values

I have a WPF application in which I am attempting to take a screen grab of a WindowsFormsHost control. To do this I am doing a Graphics.CopyFromScreen. On most boxes we have tested on this works perfect, however we have one machine that is not grabbing the right size values. The width and height that are given by WPF do not match the actual width and height of the control. When snooping, the incorrect values show for the ActualWidth and ActualHeight of the control as well. Even further, when I snoop the main window (which is maximized), I get an ActualWidth of 1550 and and ActualHeight of 840, but my screen resolution is 1920x1080. I would understand being a few pixels off for borders, margins, etc, but to have WPF tell me my maximized window is 370 pixels shorter in width than the actual screen just doesn't make sense. Can anyone explain this behaviour?
The following is the code being used to capture an image of the control:
public static Bitmap CreateBitmapFromVisual(this FrameworkElement target)
{
Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
if (bounds.Width == 0 && bounds.Height == 0)
return null;
System.Windows.Point p0 = target.PointToScreen(bounds.TopLeft);
System.Drawing.Point p1 = new System.Drawing.Point((int) p0.X, (int) p0.Y);
Bitmap image = new Bitmap((int)bounds.Width, (int)bounds.Height);
Graphics imgGraphics = Graphics.FromImage(image);
imgGraphics.CopyFromScreen(p1.X, p1.Y, 0, 0, new System.Drawing.Size((int) bounds.Width, (int) bounds.Height));
return image;
}
Here is an image to better describe what I'm talking about:
As you can see, snoop is saying that the actualwidth and actualheight of the WindowsFormsHost is 486x336. Any debug information I log says the same thing. However, when I Print Screen and crop to the control in paint, the actual size is 608x423, a sizeable difference.
As a further update, it seems like the width given by wpf is roughly 80% of the actual width. This is true for both the control and the window.
Have you looked at your DPI settings for your display?
To do this go to Control Panel - Display and select Set custom text size (DPI). In the dialog that opens you will see a scale to percentage. If it is not set at 100% then try this. This could be your issue.

Increase StrokeThickness but maintain dimensions of Path

I have a Path that normally has a StrokeThickness of 1. Under certain circumstances, it needs to have a StrokeThickness of 10. When I increase the stroke thickness, I don't want the path to take any additional space.
By default, just increasing the StrokeThickness increases the rendered size of the path. So you get something like this (the blue outline extends beyond the black boundary):
This is what I'm trying to achieve (the blue outline stays within the black boundary):
I can think of two mathematical ways to compensate for the increased StrokeWidth:
Manually adjust the points of the triangle inward.
Use a ScaleTransform on the Geometry of the Path.
Both of these would be somewhat problematic/complex. Is there an easier way?
You could clip the path by its own geometry like this:
<Path ... Clip="{Binding Data, RelativeSource={RelativeSource Self}}"/>
but then you would need to double the StrokeThickness, since only half of the stroke is visible.
On a whim I set StrokeThickness = -1 on my Rectangle and it did exactly what I wanted it to: the stroke goes on the inside of the Rectangle rather than on the outside.

Ellipse geometry bound points

Is there an automatic way to get all the points of an ellipse stroke, without the filling points?
In WPF there are no actual "Points" in a geometry - it is infinitely smooth. This can be seen by zooming in on an ellipse. You can go to 1,000,000x zoom and you can still see curvature and no points.
Since WPF shapes aren't composed of points, your question can be interepted in several ways. You may be looking for any of these:
A list of points that approximates the boundary of the ellipse (polyline approximation)
A set of pixels covered by the ellipse including the fill
A set of pixels covered by the edge of the ellipse
Here are the solutions in each case:
If you're looking for an approximation of the ellipse as discrete points (ie. a dotted-line version that looks like an ellipse), use this code:
PolyLineSegment segment =
ellipse.DefiningGeometry
.GetFlattenedPathGeometry(1.0, ToleranceType.Absolute)
.Figures[0].Segments[0] as PolyLineSegment;
foreach(Point p in segment.Points)
...
If you're looking for the pixels affected, you'll need to RenderTargetBitmap:
RenderTargetBitmap rtb =
new RenderTargetBitmap(width, height, 96, 96, PixelFormat.Gray8);
rtb.Render(ellipse);
byte[] pixels = new byte[width*height];
rtb.CopyPixels(pixels, width, 0);
Any nonzero value in pixels[] is partially covered by the ellipse. This will include points interior to the ellipse if the ellipse has a fill.
If you need to get only the pixels along the edge but your ellipse is filled, or vice versa, you can create a new Shape to pass to RenderTargetBitmap:
var newEllipse = new Path
{
Data = ellipse.DefiningGeometry,
Stroke = Brushes.Black,
};
RenderTargetBitmap rtb = ...
[same as before]
By using Reflector I found out that there is a GetPointList() method in the EllipseGeometry class unfortunately it's private. Maybe you can invoke it through reflection but that's sounds like a very bad hack... I'll see if I find another way...

Get the size (after it has been "stretched") of an item in a ViewBox

Consider the following:
Let's say the Window is 1024x768 and the ViewBox fills the entire window,
this means the TextBox is really large on the screen.
I want to get the size of the TextBox as it is currently on the screen.
If I get DesiredSize or ActualSize or even RenderedSize I always get 100.
Any suggestions?
Update: I could probably get the ActualWidth of the ViewBox and divide it by the ActualWidth of it's child which would give me the current scale factor and expose that as a property somehow but I'm not sure that's the best way to do it.
This is how you get the ScaleTransform the ViewBox exerts on its children:
var child = VisualTreeHelper.GetChild(viewBox, 0) as ContainerVisual;
var scale = child.Transform as ScaleTransform;
Here viewBox is the ViewBox that textbox sits in.
Then you can just multiply scale.ScaleX * textBox.ActualWidth and you get the size in Screen coordinates
But it gets even easier! To get that textbox's size directly in Screen Coordinates you do:
textbox.PointToScreen(new Point(textbox.ActualWidth,textbox.ActualHeight)) - textbox.PointToScreen(new Point(0,0))

Resources