views:

705

answers:

2

Hi!

I'm currently creating a MSPaint-like WPF-application and struggling with the implementation of a snappable grid.

The painting of the grid is no problem with a VisualBrush and a Rectangle but the problem is that these lines are then purely for looks and can't be easily changed (for example highlighted when the snapping to a specific line triggered).

My other idea was to have a 2 Canvas solution where 1 Canvas is used for the elements and one Canvas (who is positioned above the other) contains all the grid lines. However I have the feeling that this would mean quite a performance hit.

Are there any other possible ways to implement this kind of functionality?

A: 

You might consider drawing your grid using the DrawingContext inside of OnRender. Drawing this way does not introduce new UIElements into the visual tree, which helps to keep performance up. In some ways, it is similar to what you are currently doing with the VisualBrush, which also does not create new UI elements per copy.

However, since you will actually be individually drawing each line instead of copying the look of a single line, you'll be able to highlight the grid line(s) that participate in snapping without changing the colors of those that do not.

If you are going to go down this route, make sure to have a look into GuidelineSets for positioning your guide lines (more details here), since you'll probably want to have your guide lines snap to the device's pixels so that they draw sharply.

Nicholas Armstrong
+1  A: 

Efficiency considerations of a two-panel approach vs DrawingContext

I have good news for you: You are wrong about the significant performance hit. Your two-canvas idea is nearly optimal, even if you use individual objects for the grid lines. This is because WPF uses retained-mode rendering: When you create the canvas, everything on it is serialized into a compact structure at native level. This only changes when you change the grid lines in some way, such as changing the grid spacing. At all other times the performance will be indistinguishable from the very fastest possible managed-code methods.

A slight performance increase could be had by using DrawingContext as Nicholas describes.

A simpler and more efficient solution

Perhaps a better way then drawing individual lines on the grid canvas is to use two tiled visual brushes (one horizontal, one vertical) to draw all unhilighted lines, then use Rectangle(s) added in code-behind to hilight the line(s) you are snapping to.

The main advantage of this technique is that your grid can be effectively infinite, so there is no need to calculate the right number of grid lines to draw and then update this every time the window resizes or the zoom changes. You also only have three UIElements involved, plus one more for each grid line that is currently hilighted. It also seems cleaner to me than tracking collections of grid lines.

The reason you want to use two visual brushes is that drawing is more efficient: The brush drawing the vertical lines is stretched to a huge distance (eg double.MaxValue/2) in the vertical direction so the GPU gets only one drawing call per vertical line, the same for the horizontal. Doing a two-way tiling is much less efficient.

Adorner layer

Since you asked about alternatives, another possibility is to use Adorner and AdornerLayer with any of the solutions above rather than stacking your canvas using eg a Grid or containing Canvas. For a Paint-like application this is nice because the adorner layer can be above your graphic layer(s) yet the adorners can still attach to individual items that are being displayed.

Ray Burns
I don't really understand your idea with 2 visual brushes. Can you be more specific? I didn't even know there was a way to use 2 different visual brushes on one pane.
chrischu
If you're using `<Rectangle ... Fill="{StaticResource GridBrush}" />` you can replace it with `<Grid><Rectangle ... Fill="{StaticResource HorizBrush}"/><Rectangle ... Fill="{StaticResource VertBrush}"/></Grid>` to draw two rectangles each with its own Visual Brush. HorizBrush will be transparent except for a gridline-width area at the top and VertBrush will be transparent except for a gridline-width area at the left. Viewport/Viewbox and tiling will be set so that each brush is repeated at the appropriate gridline interval in the given direction, creating a grid.
Ray Burns
Note that there may be more efficient ways to do the layout than using a separate `<Grid>`. For example, there may be an existing `<Grid>` you can share, or a `<Canvas>`.
Ray Burns