0
votes

I have a question regarding WPF.

I have a Canvas that serves as a visual editor! I have a few 'nodes' positioned in the Canvas using 'X' and 'Y' properties (Canvas.Left and Canvas.Top). Now, I need this Canvas to let the user Zoom (in & out) and Pan around, as he want's to.

I implemented kind of a hack to emulate that behavior. This is the Code that let's the user 'pan' around in the Canvas:

    ///In file MainWindow.xaml.cs
    private void ZoomPanCanvas_MouseMove(object sender, MouseEventArgs e) {
        if (IsMouseDown) {
            ///Change the Cursor to Scroll
            if (mNetworkUI.Cursor != Cursors.ScrollAll)
                mNetworkUI.Cursor = Cursors.ScrollAll;

            var currPosition = e.GetPosition(mNetworkUI);
            var diff = currPosition - MouseLastPosition;
            var p = new Point(diff.X, diff.Y);
            mNetworkUI.ViewModel.Network.SetTransformOffset(p);
            MouseLastPosition = currPosition;
        }
    }
    ///In file NetworkViewModel.cs
    public void SetTransformOffset(Point newOffset) {
        for (int i = 0; i < Nodes.Count; i++) {
            Nodes[i].X += newOffset.X;
            Nodes[i].Y += newOffset.Y;
        }
    }

Where the 'Nodes' are my editor-nodes displayed in the Canvas. The Zooming (with respect to the mouse position works as follows:

    ///File MainWindow.xaml.cs
    private void ZoomPanCanvas_MouseWheel(object sender, MouseWheelEventArgs e) {
        ///Determine the Scaling Factor and Scale the Rule-Editor
        var factor = (e.Delta > 0) ? (1.1) : (1 / 1.1);
        currrentScale = factor * currrentScale;
        ScaleNetwork();
        ///Translate the Nodes to the desired Positions
        var pos = e.GetPosition(mNetworkUI);
        var transform = new ScaleTransform(factor, factor, pos.X, pos.Y);
        var offSet = new Point(transform.Value.OffsetX, transform.Value.OffsetY);
        mNetworkUI.ViewModel.Network.SetTransformOffset(offSet);
    }
    ///Also in MainWindow.xaml.cs
    private void ScaleNetwork() {
        mNetworkUI.RenderTransform = new ScaleTransform(currrentScale, currrentScale);
        mNetworkUI.Width = ZoomPanCanvas.ActualWidth / currrentScale;
        mNetworkUI.Height = ZoomPanCanvas.ActualHeight / currrentScale;
    }

So, in the 'panning' I calculate the difference to the last mouse position and use that vector to manipulate the nodes, not the Canvas itself. When I zoom, I determine the new zoom, set a new RenderTransform, resize the Canvas to again fill the provided space and again re-position the nodes in the Canvas.

It works very well for now. I can 'pan & zoom' around how I want, but I realized, that with many nodes present in my 'network' (connected nodes), things get quite slow.

One reason is, that on every movement of a node some events are raised resulting in a noticable delay when panning.

How is such a thing (without fixed Canvas-size and Scrollbars) possible in a performant manner? Is there a control out there that I can use? Is this possible with the Extended WPF toolkit's ZoomBox control?

Thank you!

1
Perhaps you can put your Canvas in a ScrollViewer, and set the ScrollViewer's PanningMode property? Just a thought.Mike Eason
Thank you for the suggestion, but it seems this PanningMode only defines the behavior for MultiTouch gestures (scroll only horizontal, vertical or both directions).Eru Iluvatar

1 Answers

0
votes

I've written a Viewport control for this exact functionality.

I've also packaged this up on nuget

PM > Install-Package Han.Wpf.ViewportControl

It extends a ContentControl which can contain any FrameworkElement and provides constrained zoom and pan functionality. Just make sure to add Generic.xaml to your app.xaml

<Application.Resources>

    <ResourceDictionary Source="pack://application:,,,/Han.Wpf.ViewportControl;component/Themes/Generic.xaml" />

</Application.Resources>

Usage:

<Grid width="1200" height="1200">

    <Button />

</Grid>

The source code for the control and theme is on my gist and can be found on my github along with a demo application that loads an image into the viewport control.