//****************************************************************************************************** // PanAndZoomViewer.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 09/13/2011 - Mehulbhai P Thakkar // Generated original version of source code. // 12/20/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; namespace GSF.TimeSeries.UI { /// /// class which allows deep zoom composoer type functionalities in WPF apps. /// public class PanAndZoom : ContentControl { #region [ Members ] // Fields private readonly double m_defaultZoomFactor; private FrameworkElement m_source; private Point m_screenStartPoint; private TranslateTransform m_translateTransform; private ScaleTransform m_zoomTransform; private TransformGroup m_transformGroup; private Point m_startOffset; #endregion #region [ Constructors ] /// /// Creates an instance of class. /// public PanAndZoom() { m_screenStartPoint = new Point(0, 0); m_defaultZoomFactor = 1.4; } #endregion #region [ Methods ] /// /// Overrides OnApplyTemplate method from class. /// public override void OnApplyTemplate() { base.OnApplyTemplate(); Setup(this); } private void Setup(FrameworkElement control) { m_source = VisualTreeHelper.GetChild(this, 0) as FrameworkElement; m_translateTransform = new TranslateTransform(); m_zoomTransform = new ScaleTransform(); m_transformGroup = new TransformGroup(); m_transformGroup.Children.Add(m_zoomTransform); m_transformGroup.Children.Add(m_translateTransform); m_source.RenderTransform = m_transformGroup; this.Focusable = true; this.KeyDown += source_KeyDown; this.MouseMove += control_MouseMove; this.MouseDown += source_MouseDown; this.MouseUp += source_MouseUp; this.MouseWheel += source_MouseWheel; } private void source_KeyDown(object sender, KeyEventArgs e) { // hit escape to reset everything if (e.Key == Key.Escape) Reset(); } private void source_MouseWheel(object sender, MouseWheelEventArgs e) { // Zoom into the content. Calculate the zoom factor based on the direction of the mouse wheel. double zoomFactor = m_defaultZoomFactor; if (e.Delta <= 0) zoomFactor = 1.0 / m_defaultZoomFactor; // DoZoom requires both the logical and physical location of the mouse pointer Point physicalPoint = e.GetPosition(this); DoZoom(zoomFactor, m_transformGroup.Inverse.Transform(physicalPoint), physicalPoint); } private void source_MouseUp(object sender, MouseButtonEventArgs e) { if (this.IsMouseCaptured) { // We're done. reset the cursor and release the mouse pointer this.Cursor = Cursors.Arrow; this.ReleaseMouseCapture(); } } private void source_MouseDown(object sender, MouseButtonEventArgs e) { // Save starting point, used later when determining how much to scroll. m_screenStartPoint = e.GetPosition(this); m_startOffset = new Point(m_translateTransform.X, m_translateTransform.Y); this.CaptureMouse(); this.Cursor = Cursors.ScrollAll; } private void control_MouseMove(object sender, MouseEventArgs e) { if (this.IsMouseCaptured) { // if the mouse is captured then move the content by changing the translate transform. // use the Pan Animation to animate to the new location based on the delta between the // starting point of the mouse and the current point. Point physicalPoint = e.GetPosition(this); m_translateTransform.BeginAnimation(TranslateTransform.XProperty, CreatePanAnimation(physicalPoint.X - m_screenStartPoint.X + m_startOffset.X), HandoffBehavior.Compose); m_translateTransform.BeginAnimation(TranslateTransform.YProperty, CreatePanAnimation(physicalPoint.Y - m_screenStartPoint.Y + m_startOffset.Y), HandoffBehavior.Compose); } } /// Helper to create the panning animation for x,y coordinates. /// New value of the coordinate. /// Double animation private DoubleAnimation CreatePanAnimation(double toValue) { DoubleAnimation da = new DoubleAnimation(toValue, new Duration(TimeSpan.FromMilliseconds(300))); da.AccelerationRatio = 0.1; da.DecelerationRatio = 0.9; da.FillBehavior = FillBehavior.HoldEnd; da.Freeze(); return da; } /// Helper to create the zoom double animation for scaling. /// Value to animate to. /// Double animation. private DoubleAnimation CreateZoomAnimation(double toValue) { DoubleAnimation da = new DoubleAnimation(toValue, new Duration(TimeSpan.FromMilliseconds(500))); da.AccelerationRatio = 0.1; da.DecelerationRatio = 0.9; da.FillBehavior = FillBehavior.HoldEnd; da.Freeze(); return da; } /// Zoom into or out of the content. /// Factor to mutliply the zoom level by. /// Logical mouse position relative to the original content. /// Actual mouse position on the screen (relative to the parent window) public void DoZoom(double deltaZoom, Point mousePosition, Point physicalPosition) { double currentZoom = m_zoomTransform.ScaleX; currentZoom *= deltaZoom; m_translateTransform.BeginAnimation(TranslateTransform.XProperty, CreateZoomAnimation(-1 * (mousePosition.X * currentZoom - physicalPosition.X))); m_translateTransform.BeginAnimation(TranslateTransform.YProperty, CreateZoomAnimation(-1 * (mousePosition.Y * currentZoom - physicalPosition.Y))); m_zoomTransform.BeginAnimation(ScaleTransform.ScaleXProperty, CreateZoomAnimation(currentZoom)); m_zoomTransform.BeginAnimation(ScaleTransform.ScaleYProperty, CreateZoomAnimation(currentZoom)); } /// Reset to default zoom level and centered content. public void Reset() { m_translateTransform.BeginAnimation(TranslateTransform.XProperty, CreateZoomAnimation(0)); m_translateTransform.BeginAnimation(TranslateTransform.YProperty, CreateZoomAnimation(0)); m_zoomTransform.BeginAnimation(ScaleTransform.ScaleXProperty, CreateZoomAnimation(1)); m_zoomTransform.BeginAnimation(ScaleTransform.ScaleYProperty, CreateZoomAnimation(1)); } #endregion } }