0
votes

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!

1

1 Answers

2
votes

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>