Draw relative to center with DrawingContext

1.6k views Asked by At

I've been wondering how to draw stuff in WPF with a DrawingContext relative to something that's not the top left corner of the control. My problem is that I want to draw some shapes by connecting various dots, and those dots have to be positionned relative to the center of the host control, with Y pointing upwards.

My elements are rendered using a tree of custom DrawingVisual subclasses, with the root being a Border subclass that contains a VisualCollection. I solved the Y direction problem by specifying a ScaleTransform as the RenderTransform of that Border, essentially flipping the whole control vertically.

No such luck for the other issue, though. Any idea for how to center my origin?

3

There are 3 answers

0
Etienne de Martel On BEST ANSWER

Alright, I got it. Here's how I've done it.

First, I defined a GroupTransform and assigned to a WorldTransform property on my Border subclass.

var trans = new TranslateTransform();
var conv = new HalfConverter(); // a custom converter that halves whatever you pass to it
BindingOperations.SetBinding(trans, TranslateTransform.XProperty, new Binding { Path = new PropertyPath(ActualWidthProperty), Source = this, Converter = conv });
BindingOperations.SetBinding(trans, TranslateTransform.YProperty, new Binding { Path = new PropertyPath(ActualHeightProperty), Source = this, Converter = conv });

WorldTransform = new TransformGroup();
WorldTransform.Children.Add(new ScaleTransform { ScaleY = -1.0 });
WorldTransform.Children.Add(trans);
VisualTransform = WorldTransform;

Then, when I create a new instance of my custom DrawingVisual, I assign its Transform property to my WorldTransform.

// ZoneVisual is my DrawingVisual subclass
var vis = new ZoneVisual(zone) { Transform = WorldTransform };

When I add a new element (a Node, by the way), I simply need to transform it by the inverse of my WorldTransform.

Voila. This might not be the best way, but it seems to work pretty well. I don't have remarkably high performance needs, so it will probably do the job.

0
Jerry Coffin On

Have you tried TranslateTransform?

5
Clemens On

Instead of the ScaleTransform you could use a MatrixTransform that scales by -1 in y direction and translates the coordinate origin to the control's center. This transform must however be updated whenever the control's size changes. Hence you would override OnRenderSizeChanged like below (assuming that you set the RenderTransform property of your control):

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    base.OnRenderSizeChanged(sizeInfo);

    RenderTransform = new MatrixTransform(1d, 0d, 0d, -1d,
        sizeInfo.NewSize.Width / 2d, sizeInfo.NewSize.Height / 2d);
}

EDIT: In case you don't want to transform the whole controls, you could also define a MatrixTransform as class member and apply it to every Visual in the visual children collection.

private MatrixTransform transform = new MatrixTransform();

Assign to the Transform property of every new Visual:

ContainerVisual visual = ...
visual.Transform = transform;

On size changes you would simply update that MatrixTransform:

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
{
    base.OnRenderSizeChanged(sizeInfo);

    transform.Matrix = new Matrix(1d, 0d, 0d, -1d,
        sizeInfo.NewSize.Width / 2d, sizeInfo.NewSize.Height / 2d);
}

Of course you only need to apply the Transform to "top-level" visuals. Children of those visuals will be transformed by the Transform of their parents. I do not exactly understand how you manage your visuals by a "Border subclass that contains a VisualCollection". The typical approach would be to have one parent ContainerVisual as root of the visual tree. Transforms would then be applied to this root visual only.