Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Limit panning amount in order to prevent image from wandering off of the window

Tags:

c#

wpf

xaml

panning

INTRODUCTION:

I have exported SVG as XAML, and it turned into Canvas with enormous amount of Paths.

After making that Canvas main content of the simple, autogenerated window, image was clipped since it was bigger than main window's client rectangle.

I have solved this problem by implementing panning.

Since I plan on fiddling with the zooming later, I have added dummy ScaleTransform in the XAML of the Canvas and have changed its RenderTransformOrigin to 0.5 , 0.5 (so I can zoom image around its center).

I wanted image to be centered when window is loaded, so I placed that Canvas inside Viewbox, which seems to work just fine, after adjusting few properties.

PROBLEM:

Since I have changed the RenderTransformOrigin of the Canvas, I am unable to figure out math that will enable me to limit panning amount.

Even though the Viewbox is the content of the main window, I can not get dimensions of main window's client area (Viewbox resizes to fit its content). This makes my task even more difficult.

MY EFFORTS TO SOLVE THIS:

There seems no other way to get main window's client rectangle, except with the usage of P/Invoke and GetClientRect WinAPI call.

There was another hack with querying SystemParametersInfo for nonclient metrics such as borders and titlebar, but that is an estimation that might fail due to applied themes and similar.

I haven't tried to use P/Invoke because I simply refuse to do so at the moment. There has to be better solution than this! That path should be my last choice, and the one I will make out of pure desperation.

I have tried to find alternative approach on my own but failed.

I have tried to use TransformToAncestor(...).Transformtobounds(..) for my calculations but that failed too.

QUESTION:

How to limit panning so image does not disappear from the main window when user drags too much?

I will accept alternative solutions. Code that was submitted was a try of an inexperienced, self taught beginner, so i accept constructive critique and advice.

RELEVANT INFORMATION:

In order to keep this post as short as possible, I am giving only relevant XAML and "code behind" snippets below.

Full code is pasted on Pastebin, and can be found at the very bottom of this post.

Relevant XAML snippets:

<Window  x:Class="TestZaMapu.MainWindow" 
        Name="GlavniProzor" 
        WindowStartupLocation="CenterScreen"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" 
        Height="350" 
        Width="525" 
        MouseLeftButtonDown="GlavniProzor_MouseLeftButtonDown" 
        MouseLeftButtonUp="GlavniProzor_MouseLeftButtonUp" 
        MouseMove="GlavniProzor_MouseMove" 
        LostMouseCapture="GlavniProzor_LostMouseCapture">
    <Viewbox 
        Name="surface"
        Stretch="UniformToFill" 
        HorizontalAlignment="Center"
        VerticalAlignment="Center">
        <Canvas xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
                Background="Blue" 
                x:Name="svg4306" 
                Width="494.44705" 
                Height="510.55356"
                <!-- needed in the future, for zooming around center-->
                RenderTransformOrigin="0.5, 0.5">
            <!--Unknown tag: metadata-->
            <Canvas.RenderTransform>
                <TransformGroup>
                    <!-- I intend to experiment with scaling in the future, so I added the below part -->
                    <ScaleTransform ScaleX="1" ScaleY="1"/>
                    <!-- I have used dependency properties for translation -->
                    <TranslateTransform X="{Binding TranslationFactorX, ElementName=GlavniProzor, Mode=TwoWay}" Y="{Binding TranslationFactorY, ElementName=GlavniProzor, Mode=TwoWay}"/>
                </TransformGroup>
            </Canvas.RenderTransform>
        <!-- Bunch of Path objects, omitted for brevity-->
        </Canvas>
    </Viewbox>
</Window>

Relevant Code behind snippets:

namespace TestZaMapu
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            TranslationFactorX = 0;
            TranslationFactorY = 0;
            isPanning = false;

        }

        // panning variables
        private Point ptOldPosition;
        private bool isPanning ;

        // dependency properties for translation along X and Y axes

        public static readonly DependencyProperty TranslateX =
            DependencyProperty.Register("TranslationFactorX", typeof(double), typeof(MainWindow));

        public static readonly DependencyProperty TranslateY =
            DependencyProperty.Register("TranslationFactorY", typeof(double), typeof(MainWindow));

        public double TranslationFactorX
        {
            get { return (double)GetValue(TranslateX); }
            set { SetValue(TranslateX, value); }
        }

        public double TranslationFactorY
        {
            get { return (double)GetValue(TranslateY); }
            set { SetValue(TranslateY, value); }
        }

        // mouse event handlers for panning

        private void GlavniProzor_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonDown(e);
            if (isPanning )
                return;
            isPanning = this.CaptureMouse(); 
            ptOldPosition = e.GetPosition(this);
        }

        private void GlavniProzor_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            base.OnMouseLeftButtonUp(e);

            if (this.IsMouseCaptured)
            {
                this.ReleaseMouseCapture();
                isPanning = false;
            }
        }

        private void GlavniProzor_MouseMove(object sender, MouseEventArgs e)
        {
            base.OnMouseMove(e);

            if (isPanning )
            {
                Point ptNewPosition = e.GetPosition(this);

                if (ptNewPosition != ptOldPosition)
                {
                    Vector direction = ptOldPosition - ptNewPosition;

                    direction.X = TranslationFactorX - direction.X;
                    direction.Y = TranslationFactorY - direction.Y;

                    TranslationFactorX = direction.X;
                    TranslationFactorY = direction.Y;

                    ptOldPosition = ptNewPosition;
                }
            }
        }

        private void GlavniProzor_LostMouseCapture(object sender, MouseEventArgs e)
        {
            isPanning = false;
            this.OnLostMouseCapture(e);
        }
    }
}

Here is the full XAML, and here is full "code behind".

like image 243
AlwaysLearningNewStuff Avatar asked Oct 14 '15 20:10

AlwaysLearningNewStuff


2 Answers

I really didnt read the full question (I think no one will), but here is the code I use in a charting libary to limit panning. This is the math. now addapt it to you case. Tip: next time type only relevant code so we can provide a better answer.

I Call this method when panning ends (mouse up event in my case)

If it helps this is the libary (https://github.com/beto-rodriguez/Live-Charts) maybe you it can help you too see the source code. Relax I think you are making it much harder than what it is.

    private void PreventGraphToBeVisible()
    {
        var tt = Canvas.RenderTransform as TranslateTransform;
        if (tt == null) return;
        var eX = tt.X;
        var eY = tt.Y;
        var xOverflow = -tt.X + ActualWidth - Canvas.Width;
        var yOverflow = -tt.Y + ActualHeight - Canvas.Height;

        if (eX > 0)
        {
            tt.X = 0;
        }

        if (eY > 0)
        {
            tt.Y = 0;
        }

        if (xOverflow > 0)
        {
            tt.X = tt.X + xOverflow;
        }

        if (yOverflow > 0)
        {
            tt.Y = tt.Y + yOverflow;
        }
    }
like image 88
bto.rdz Avatar answered Nov 01 '22 06:11

bto.rdz


It sounds criminally simple, but if your goal is to stop your vector from clipping outside of the space assigned to it, why don't you just change the Stretch="UniformToFill" of your Viewbox to Stretch="Uniform"?

like image 23
Logan Avatar answered Nov 01 '22 06:11

Logan