I'm developing an application that draws geometry elements like lines, etc. on a FrameworkElement. The lines, etc. are implemented as GeometryDrawing. As I want to preserve the line thickness at any (possible) zoom level, there is a routine that adapts the line thickness to the current zoom level and the size of the visible geometry.
Now I encounter a problem: At high zoom factors (like 490) - and thus resulting very low pen thicknesses like 0.010 - horizontal and vertical lines disappear. All other lines are not affected.
I already tried to set the SnapsToDevicePixels property of the FrameworkElement I'm drawing on to true but with no effect.
After a bit of research I found this thread: Horizontal or vertical WPF Lines limited to 125,000 pixels?
So it seems that it is a bug in WPF if the line gets too long while being too thin...
Related
- Building a CAD program in WPF:
I want to build a CAD program that will have 10000 LINE objects at a time. I'm using LineGeomery class for drawing lines that are added to a Canvas. I have implemented Zoom and Pan and the performance is great so far.
Only one major disappointment:
The Thickness of the lines gets scaled while zooming. I have tried to Bind the Thickness property of the lines to a factor to keep them unchanged, This works but reduces the performance dramatically while zooming. Clearing and drawing new lines with new thickness on MouseWheel is out of the question as well. This one too reduces performance and is not practical in the current method.
- Now what solutions I have?
Stick with the current method and ignore the change in Thickness
Do the whole job in GDI+
Host GDI in WPF
Use WPF Viewport3D (Will the LineThickness be invariant there?)
- Other solutions?
What other paths you would take. I'm new to WPF and programming and I'm eager to learn.
UPDATE:
This is the way I'm doing it right now. I draw 3000 Lines on the Visual Layer using Pen an Brushes. Then on MouseWheel event I redraw all the Lines with the updated Thickness. Also I don't show the rest of the Lines to the user until he zooms so I only create 3000 out of 10000 Lines in each MouseWheel event.
Instead of using Line objects, you could draw your lines by Path objects. Here is an answer https://stackoverflow.com/a/15323221/1305119
Next to hosting a winforms element inside WPF, I would also implement partial rendering on the zooming feature, e.g. when you zoom in everything that is not visible should not be calculated as well!
I'm trying to draw a graph-paper like grid as the background of a Canvas. This grid is different from most explanations of how to do this that I've found because the canvas can be scaled to implement zooming. What I want to do is have a series of scales of grid lines, i.e. at every 10^n units. Then, the grid lines should fade out as they become close together due to zooming. In other words if n is large, the lines associated with that grid should be darker/heavier weight than those for a smaller n.
This was easy to do in WinForms, I implemented it by overriding OnPaint and defining the color of the line to be a function of the distance to the next grid line. Lines far apart were given a heavier weight than lines close together.
I have not figured out how to do this in WPF. I can sort of get this behavior by creating a line that has a StrokeThickness according to the spacing of the grid lines, but this only works for a small range of StrokeThickness and scaling values. It would work if it were possible to define a line as having a very heavy weight, but still a small StrokeThickness.
Even doing this via implementing a custom control with OnRender is difficult because I have not found a reliable way to get the scale of the control while rendering it (the ScaleTransform is part of one of the parent controls, not the immediate parent).
Any thoughts on how to accomplish this goal would be much appreciated!
I solved this by NOT adding the grid to the canvas but by stacking the canvas on top of another control that contains the grid:
<Grid>
<Canvas x:Name="GridLayer"/>
<Canvas x:Name="DrawingLayer" />
</Grid>
When zooming events occur I simply redraw the GridLayer.
This allowed me to only draw the lines that are needed, to draw them exactly how I want them and, in my case very important because I had potentially a gazillion grid lines, I did not need to draw the lines any longer/taller than needed. This way I conserved a lot of CPU time.
Another thing to note is that I implemented my own zoom code. I did not use a RenderTransform or a ViewBox because I wanted the line to stay at the same width. All I did was keep track of the coordinates of the top left corner to support panning and the zoomlevel. As soon as one of these changes I redraw the canvases. I wrote two functions: one transforms a coordinate on the Canvas to a graph coordinate and the other one does the reverse. The first method allows me to translate cursor coordinates to graph coordinates and the second one will turn the coordinates of the graph into points that can be used to draw on the canvas.
Untested code and making a lot of assumptions about the orientation of axis:
Point Graph2Canvas(Point graphPoint)
{
var canvasPoint = new Point(graphPoint);
canvasPoint.X *= zoomLevel;
canvasPoint.Y *= zoomLevel;
canvasPoint.X -= topLeft.X;
canvasPoint.Y -= topLeft.Y;
return canvasPoint;
}
This can be optimized and the truth is I created more functions that do the same thing for collections of points.
Extra:
I ended up with a far more complex setup that looked a bit like this:
<Grid>
<Canvas x:Name="BackgroundLayer"/>
<Canvas x:Name="GridLayer"/>
<Canvas x:Name="AxisLayer"/>
<Canvas x:Name="DrawingLayer" />
<Canvas x:Name="SelectionBoxLayer"/>
<Canvas x:Name="CursorLayer"/>
</Grid>
I'm working on a graphical program and I'd like to get corner points from figures I mentioned in the title. I need them because at those points places, I want to place white, little rectangles that allows me to resize those figures.
If you place all your shapes in a Canvas you should be able to get the x and y axis using the GetLeft and GetTop methods of the canvas class. From there you can use the shape's width and height to place your white little rectangles.
I am looking for options on how to draw 2 rulers at different scales on a canvas (assume a canvas) that will scale based on user-entered data.
Placing the tick marks and text one-time isn't a big deal, it is how to scale the data as the max/min values are changed by the user AND getting the points (ellipses) on the canvas to look correctly.
Foolishly, I set the size of the canvas to the max values of the current data, but as the data changes that won't work... I had hoped for a 1:1 translation...
Something like taking the current canvas size and redrawing the rulers is where I am headed
thanks,
rusty
I was over thinking the issue plus have just started in 'drawing', 'graphics', etc. Have a lot to learn. It was basic logical to physical translation math that most everyone 'knows'.
rusty
I have an image that is 1px wide, and some height. I need to draw this image across the entire width of the control on it's OnPaint event. I get it to draw, however not correctly. It seems like when it stretches it, it doesn't actually fill all the pixels. As if the interpolation is off. Is there a way to say "stop being smart, just draw it already"? I see no InterpolationMode.Off or .None in the options for the graphics object.
I can confirm I actually drawing the full width by using an image of width X where X is the same width as the control. Then when it draws, it covers the full area as normal. However this control is resized all the time, and to save memory and all that jazz using 1px wide images is quite normal in the web world. This is for a desktop C# application though. Any ideas on how to fix this?
Ok I figured out the magic keywords:
g.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.Half;
This coupled with setting the Interpolation Mode to NearestNeighbour allows for a full block to be drawn.
Without setting the Interpolation mode, you get weird blending (expected).
Without setting the PixelOffsetMode, the nearest neighbour algorithm has no neighbour to compare to on a blank paint and therefore only draws half the image, for half the width. Setting it to offset half, moves everything over by -0.5px, and allows this algorithm to work for block textures.
InterpolationMode.NearestNeighbor is what you want to use in this case.