1
votes

I'm working with WPF and Canvas and TranslateTransform. (Originally I worked with the "manipulation" feature, whereby touch gestures are translated to MatrixTransform transformations but the problem is evident also with a simple TranslateTransform).

I use a TranslateTransform to move the origin (i.e. the (0,0) point) of my canvas into the middle of my window. I can happily draw objects in the negative coordinate space, but I can't receive mouse input for this region...I only receive MouseDown events when the mouse is in the positive quadrant, as confirmed by a breakpoint and by program behaviour.

Why can I draw in an area I can't receive input in? In the positive quadrant, the mouse input and the drawing are perfectly aligned. Is this a bug in WPF? I've tried moving the MouseDown event handler into the parent element, a DockPanel, but this behaves no differently even though the TranslateTransform is applied at the Canvas element. (I have a Canvas inside a DockPanel inside a Window).

Some fragments of my app:

    <Canvas Name="canvas" MouseDown="Canvas_MouseDown" MouseMove="Canvas_MouseMove"
            MouseUp="Canvas_MouseUp" MouseWheel="Canvas_MouseWheel"
            IsManipulationEnabled="true">
        <Canvas.Background>
            <SolidColorBrush Color="Black" />
        </Canvas.Background>
    </Canvas>


    private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
    {
        currentPoint = inverseRenderTransform.Transform(e.GetPosition(this));
        ...
2
"TranslateTransform to move the origin" - is that in RenderTransform? Please show us all relevant details of your code. - Clemens
In case you are setting the Canvas' RenderTransform, you should have noticed that the Background Brush is only drawn in the positive quadrant. You would hence only get input there. One workaround would be a transparent Rectangle child element with negative Canvas.Left and Top value. - Clemens
Also note that you would not need to call inverseRenderTransform.Transform if you would call e.GetPosition((IInputElement)sender) or e.GetPosition(canvas). - Clemens
@Clemens : That solved my problem. Do you want to write it up as an answer, and get the credit? Thanks also for the tip about e.GetPosition(canvas). - Tim Cooper
Not sure what to write exactly. You still didn't tell us if you are setting the RenderTransform property. Besides that, using two nested Canvases might be a better approach, where the outer has the event handlers attached, and the inner is translated. - Clemens

2 Answers

1
votes

A straightforward way to declare a Canvas that has its origin in the center of its parent element could be a 2x2 Grid with the Canvas in the lower right cell.

<Grid Background="Transparent" MouseDown="OnMouseDown">
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>

    <Canvas x:Name="canvas" Grid.Column="1" Grid.Row="1">
        <Path Fill="Red">
            <Path.Data>
                <EllipseGeometry RadiusX="50" RadiusY="50"/>
            </Path.Data>
        </Path>
    </Canvas>
</Grid>

You would attach input event handlers on the outer Grid, and get the mouse position relative to the Canvas.

private void OnMouseDown(object sender, MouseButtonEventArgs e)
{
    Debug.WriteLine(e.GetPosition(canvas));
}

In case you need a specific offset of the child Canvas in the parent's coordinate space, two nested Canvases with an appropriate Margin would also work.

<Canvas Background="Transparent" MouseDown="OnMouseDown">
    <Canvas x:Name="canvas" Margin="200,200,0,0">
        <Path Fill="Red">
            <Path.Data>
                <EllipseGeometry RadiusX="50" RadiusY="50"/>
            </Path.Data>
        </Path>
    </Canvas>
</Canvas>
0
votes

I solved my own problem thanks to a valuable tip from Clemens, who posted the other answer. Ironically his comment on my question is in my opinion a better (simpler) answer than what he subsequently posted as an answer. He said: "In case you are setting the Canvas' RenderTransform, you should have noticed that the Background Brush is only drawn in the positive quadrant. You would hence only get input there. One workaround would be a transparent Rectangle child element with negative Canvas.Left and Top value."

This is exactly what I did and it solved my problem in a way I consider simpler than the canvas-inside-grid or canvas-inside-canvas solutions.

I sort of think this is a design flaw in WPF. They ought to paint the background brush over the full canvas to avoid this counterintuitive behaviour, IMO. The (0,0) point is not considered special in any other way, in WPF canvases.