aboutsummaryrefslogtreecommitdiffstats
path: root/Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs
diff options
context:
space:
mode:
authorRoy Ben-Shabat <Roy@Twine-s.com>2018-01-16 12:17:10 +0200
committerRoy Ben-Shabat <Roy@Twine-s.com>2018-01-16 12:17:10 +0200
commit0fda2ba3ff49bdc1ffc6833f658e2164af187008 (patch)
tree6f3a24d0671ebda50debb8511ab40e0bda0a0df0 /Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs
parent28103646681686bf1b58275d5dbccb92d2b26f9f (diff)
downloadTango-0fda2ba3ff49bdc1ffc6833f658e2164af187008.tar.gz
Tango-0fda2ba3ff49bdc1ffc6833f658e2164af187008.zip
Embedded RealTimeGraphEx library to solution.
Added graphs to technician view. Implemented simple sensors data test using Machine Emulator.
Diffstat (limited to 'Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs')
-rw-r--r--Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs1346
1 files changed, 1346 insertions, 0 deletions
diff --git a/Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs b/Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs
new file mode 100644
index 000000000..cf8d6c0fe
--- /dev/null
+++ b/Software/Visual_Studio/SideChains/RealTimeGraphEx/RealTimeGraphExBase.cs
@@ -0,0 +1,1346 @@
+using RealTimeGraphEx.Components;
+using RealTimeGraphEx.Enums;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Input;
+using System.Windows.Markup;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+using System.Windows.Threading;
+
+namespace RealTimeGraphEx
+{
+ #region Delegates
+
+ public delegate void ZoomCompleteDelegate(Point transformOrigin, double scaleX, double scaleY);
+ public delegate void PanningCompleteDelegate(Point translate);
+
+ #endregion
+
+ /// <summary>
+ /// Represents an abstract base class for the RealTimeGraphEx.
+ /// </summary>
+ [ContentProperty("Components")]
+ public abstract class RealTimeGraphExBase : UserControl, IDisposable
+ {
+ private Rect lastBounds;
+ private Point lastCenter;
+ private bool _loaded;
+
+ #region Events
+
+ /// <summary>
+ /// Occurs just before rendering the actual visuals on the writeable bitmap.
+ /// </summary>
+ public event Action<WriteableBitmap, double> BeforeRenderVisuals;
+
+ /// <summary>
+ /// Occurs after rendering the actual visuals on the writeable bitmap.
+ /// </summary>
+ public event Action<WriteableBitmap, double> AfterRenderVisuals;
+
+ /// <summary>
+ /// Occurs when [zooming to].
+ /// </summary>
+ public event ZoomCompleteDelegate ZoomComplete;
+
+ /// <summary>
+ /// Occurs when [panning to].
+ /// </summary>
+ public event PanningCompleteDelegate PanningComplete;
+
+ /// <summary>
+ /// Occurs when the mouse pointer moves while over this element.
+ /// </summary>
+ public new event MouseEventHandler MouseMove;
+
+ /// <summary>
+ /// Occurs when the mouse pointer leaves the bounds of this element.
+ /// </summary>
+ public new event MouseEventHandler MouseLeave;
+
+ #endregion
+
+ #region Protected Fields
+
+ //Visuals
+ internal Grid gridMain;
+ internal Grid gridLinesAndImageWrapperGrid;
+ internal Grid gridBack;
+ internal StackPanel stackLeft;
+ internal StackPanel stackRight;
+ internal double virtualMinimum;
+ internal double virtualMaximum;
+ internal double virtualStart;
+ internal double virtualEnd;
+ protected Image img;
+ internal Grid gridInnerContentWrapper;
+ //protected ScaleTransform scaleTransform;
+ //protected TranslateTransform translateTransform;
+ protected Canvas selectionCanvas;
+ protected Rectangle selectionRectangle;
+ protected Thumb moveThumb;
+ protected bool isSelectionMouseDown;
+ protected bool isScaled;
+
+ //Multi threading
+ protected Thread pushThread;
+ protected ManualResetEvent _requestTermination;
+ protected ManualResetEvent _terminated;
+
+ //Plotting
+ protected double xValueCounter;
+
+ //Resizing
+ protected DispatcherTimer resizeTimer;
+ protected bool isAfterResize;
+
+ #endregion
+
+ #region Properties
+
+ /// <summary>
+ /// Gets or sets whether this graph is controlled by a synchronization manager.
+ /// </summary>
+ internal bool IsSynced { get; set; }
+
+ /// <summary>
+ /// Gets or sets an extra content which will be placed on top of the graph control.
+ /// </summary>
+ public FrameworkElement InnerContent
+ {
+ get { return (FrameworkElement)GetValue(InnerContentProperty); }
+ set { SetValue(InnerContentProperty, value); }
+ }
+ public static readonly DependencyProperty InnerContentProperty =
+ DependencyProperty.Register("InnerContent", typeof(FrameworkElement), typeof(RealTimeGraphExBase), new PropertyMetadata(null, new PropertyChangedCallback(InnerContentChanged)));
+ private static void InnerContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ //Add the content control to the inner wrapper grid.
+ (d as RealTimeGraphExBase).gridInnerContentWrapper.Children.Add((d as RealTimeGraphExBase).InnerContent);
+ }
+
+ /// <summary>
+ /// Gets or sets the maximum expected value to be plotted on the graph (default 255).
+ /// </summary>
+ public double Maximum
+ {
+ get { return (double)GetValue(MaximumProperty); }
+ set { SetValue(MaximumProperty, value); }
+ }
+ public static readonly DependencyProperty MaximumProperty =
+ DependencyProperty.Register("Maximum", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(255.0, new PropertyChangedCallback(CrossModelChanged)));
+
+ /// <summary>
+ /// Gets or sets the minimum expected value to be plotted on the graph (default 0).
+ /// </summary>
+ public double Minimum
+ {
+ get { return (double)GetValue(MinimumProperty); }
+ set { SetValue(MinimumProperty, value); }
+ }
+ public static readonly DependencyProperty MinimumProperty =
+ DependencyProperty.Register("Minimum", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(0.0, new PropertyChangedCallback(CrossModelChanged)));
+
+ /// <summary>
+ /// Gets or sets the graph refresh rate in milliseconds (default 30, affects performance).
+ /// </summary>
+ public int RefreshRate
+ {
+ get { return (int)GetValue(RefreshRateProperty); }
+ set { SetValue(RefreshRateProperty, value); }
+ }
+ public static readonly DependencyProperty RefreshRateProperty =
+ DependencyProperty.Register("RefreshRate", typeof(int), typeof(RealTimeGraphExBase), new PropertyMetadata(30, new PropertyChangedCallback(CrossModelChanged)));
+
+ /// <summary>
+ /// Gets or sets whether the graph will be rendered antialiased (affects performance).
+ /// </summary>
+ public bool Antialiased
+ {
+ get { return (bool)GetValue(AntialiasedProperty); }
+ set { SetValue(AntialiasedProperty, value); }
+ }
+ public static readonly DependencyProperty AntialiasedProperty =
+ DependencyProperty.Register("Antialiased", typeof(bool), typeof(RealTimeGraphExBase), new PropertyMetadata(true, new PropertyChangedCallback(CrossModelChanged)));
+
+ /// <summary>
+ /// Gets or sets the collection of graph add-on components.
+ /// </summary>
+ public ObservableCollection<ComponentBase> Components
+ {
+ get { return (ObservableCollection<ComponentBase>)GetValue(ComponentsProperty); }
+ set { SetValue(ComponentsProperty, value); }
+ }
+ public static readonly DependencyProperty ComponentsProperty =
+ DependencyProperty.Register("Components", typeof(ObservableCollection<ComponentBase>), typeof(RealTimeGraphExBase), new PropertyMetadata(null, new PropertyChangedCallback(ComponentsChanged)));
+ private static void ComponentsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ (d as RealTimeGraphExBase).OnRenderComponents();
+ }
+
+ /// <summary>
+ /// Gets or sets a value indicating whether this graph is paused.
+ /// </summary>
+ /// <value>
+ /// <c>true</c> if this instance is paused; otherwise, <c>false</c>.
+ /// </value>
+ public bool IsPaused
+ {
+ get { return (bool)GetValue(IsPausedProperty); }
+ set { SetValue(IsPausedProperty, value); }
+ }
+ public static readonly DependencyProperty IsPausedProperty =
+ DependencyProperty.Register("IsPaused", typeof(bool), typeof(RealTimeGraphExBase), new PropertyMetadata(false, new PropertyChangedCallback(CrossModelChanged)));
+
+ ///// <summary>
+ ///// Gets or sets the zoom level for the graph.
+ ///// </summary>
+ public double Zoom
+ {
+ get { return (double)GetValue(ZoomProperty); }
+ set { SetValue(ZoomProperty, value); }
+ }
+ public static readonly DependencyProperty ZoomProperty =
+ DependencyProperty.Register("Zoom", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(1.0, new PropertyChangedCallback(ZoomChanged)));
+ private static void ZoomChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var control = d as RealTimeGraphExBase;
+
+ if (control.ZoomMode != ZoomModeEnum.Manual && control.ZoomMode != ZoomModeEnum.MouseWheel) return;
+
+ if (control.Zoom < 1)
+ {
+ control.Zoom = 1;
+ return;
+ }
+
+ if (control.Zoom > 1)
+ {
+ control.gridLinesAndImageWrapperGrid.Cursor = Cursors.SizeAll;
+ }
+ else
+ {
+ control.gridLinesAndImageWrapperGrid.Cursor = Cursors.Arrow;
+ }
+
+ control.gridLinesAndImageWrapperGrid.RenderTransformOrigin = new Point(0.5, 0.5);
+ //control.scaleTransform.ScaleX = control.Zoom;
+ //control.scaleTransform.ScaleY = control.Zoom;
+
+ double desiredWidth = control.gridMain.ActualWidth * control.Zoom;
+ double desiredHeight = control.gridMain.ActualHeight * control.Zoom;
+ double desiredLeft = ((desiredWidth - control.gridMain.ActualWidth) / 2) * -1;
+ double desiredTop = ((desiredHeight - control.gridMain.ActualHeight) / 2) * -1;
+
+ control.gridLinesAndImageWrapperGrid.Width = desiredWidth;
+ control.gridLinesAndImageWrapperGrid.Height = desiredHeight;
+ control.gridLinesAndImageWrapperGrid.Margin = new Thickness(desiredLeft, desiredTop, 0, 0);
+
+ Point relativePoint = control.gridLinesAndImageWrapperGrid.TransformToAncestor(control.gridMain).Transform(new Point(0, 0));
+ Rect bounds = control.BoundsRelativeTo(control.gridLinesAndImageWrapperGrid, control.gridMain);
+
+ if (desiredLeft > 0)
+ {
+ control.gridLinesAndImageWrapperGrid.Margin = new Thickness(0, control.gridLinesAndImageWrapperGrid.Margin.Top, 0, 0);
+ }
+
+ if (desiredTop > 0)
+ {
+ control.gridLinesAndImageWrapperGrid.Margin = new Thickness(control.gridLinesAndImageWrapperGrid.Margin.Left, 0, 0, 0);
+ }
+
+ if (desiredWidth < control.gridMain.ActualWidth)
+ {
+ control.gridLinesAndImageWrapperGrid.Margin = new Thickness(0, control.gridLinesAndImageWrapperGrid.Margin.Top, 0, 0);
+ }
+
+ if (desiredHeight < control.gridMain.ActualHeight)
+ {
+ control.gridLinesAndImageWrapperGrid.Margin = new Thickness(control.gridLinesAndImageWrapperGrid.Margin.Left, 0, 0, 0);
+ }
+
+ control.OnZoomingComplete(new Point(0, 0), control.Zoom, control.Zoom);
+ }
+
+ /// <summary>
+ /// Gets or sets the zoom mode.
+ /// </summary>
+ /// <value>
+ /// The zoom mode.
+ /// </value>
+ public ZoomModeEnum ZoomMode
+ {
+ get { return (ZoomModeEnum)GetValue(ZoomModeProperty); }
+ set { SetValue(ZoomModeProperty, value); }
+ }
+ public static readonly DependencyProperty ZoomModeProperty =
+ DependencyProperty.Register("ZoomMode", typeof(ZoomModeEnum), typeof(RealTimeGraphExBase), new PropertyMetadata(ZoomModeEnum.Selection));
+
+ /// <summary>
+ /// Gets or sets the mouse value.
+ /// </summary>
+ /// <value>
+ /// The mouse value.
+ /// </value>
+ public double MouseValue
+ {
+ get { return (double)GetValue(MouseValueProperty); }
+ set { SetValue(MouseValueProperty, value); }
+ }
+ public static readonly DependencyProperty MouseValueProperty =
+ DependencyProperty.Register("MouseValue", typeof(double), typeof(RealTimeGraphExBase), new PropertyMetadata(0.0));
+
+ /// <summary>
+ /// Gets or sets the selection fill.
+ /// </summary>
+ /// <value>
+ /// The selection fill.
+ /// </value>
+ public Brush SelectionFill
+ {
+ get { return (Brush)GetValue(SelectionFillProperty); }
+ set { SetValue(SelectionFillProperty, value); }
+ }
+ public static readonly DependencyProperty SelectionFillProperty =
+ DependencyProperty.Register("SelectionFill", typeof(Brush), typeof(RealTimeGraphExBase), new PropertyMetadata(new SolidColorBrush() { Color = Colors.Black, Opacity = 0.2 }, new PropertyChangedCallback(SelectionFillChanged)));
+ private static void SelectionFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var control = d as RealTimeGraphExBase;
+ control.selectionRectangle.Fill = control.SelectionFill;
+ }
+
+ /// <summary>
+ /// Gets or sets the selection fill.
+ /// </summary>
+ /// <value>
+ /// The selection fill.
+ /// </value>
+ public Brush SelectionStroke
+ {
+ get { return (Brush)GetValue(SelectionStrokeProperty); }
+ set { SetValue(SelectionStrokeProperty, value); }
+ }
+ public static readonly DependencyProperty SelectionStrokeProperty =
+ DependencyProperty.Register("SelectionStroke", typeof(Brush), typeof(RealTimeGraphExBase), new PropertyMetadata(Brushes.White, new PropertyChangedCallback(SelectionStrokeChanged)));
+ private static void SelectionStrokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ var control = d as RealTimeGraphExBase;
+ control.selectionRectangle.Stroke = control.SelectionStroke;
+ }
+
+ public ZoomDirectionEnum ZoomDirection
+ {
+ get { return (ZoomDirectionEnum)GetValue(ZoomDirectionProperty); }
+ set { SetValue(ZoomDirectionProperty, value); }
+ }
+ public static readonly DependencyProperty ZoomDirectionProperty =
+ DependencyProperty.Register("ZoomDirection", typeof(ZoomDirectionEnum), typeof(RealTimeGraphExBase), new PropertyMetadata(ZoomDirectionEnum.Both));
+
+ #endregion
+
+ #region Cross Thread Fields
+
+ protected double _height;
+ protected double _width;
+ protected double _mainHeight;
+ protected double _mainWidth;
+ protected double _offSetX;
+ protected double _offSetY;
+ protected int _refreshRate;
+ protected double _maximum;
+ protected double _minimum;
+ protected double _originalMinimum;
+ protected bool _antialiased;
+ protected bool _isPaused;
+ protected bool _disableRendering;
+ #endregion
+
+ #region Constructors
+
+ /// <summary>
+ /// Initializes a new instance of RealTimeGraphExBase
+ /// </summary>
+ public RealTimeGraphExBase()
+ {
+ this.Loaded += OnGraphLoaded;
+ this.SizeChanged += OnSizeChanged;
+ resizeTimer = new DispatcherTimer();
+ resizeTimer.Interval = TimeSpan.FromMilliseconds(150);
+ resizeTimer.IsEnabled = false;
+ resizeTimer.Tick += OnResizeEnd;
+
+ Components = new ObservableCollection<ComponentBase>();
+
+ //Uri NewTheme = new Uri(@"/RealTimeGraphEx;component/Resources/Resources.xaml", UriKind.Relative);
+ //ResourceDictionary dictionary = (ResourceDictionary)Application.LoadComponent(NewTheme);
+ //Application.Current.Resources.MergedDictionaries.Add(dictionary);
+
+ Initialize();
+ }
+
+ #endregion
+
+ #region Virtual Methods
+
+ /// <summary>
+ /// Enabled/Disabled the rendering of the graph's image.
+ /// </summary>
+ protected virtual void ChangeRenderMode(bool enabled)
+ {
+ _disableRendering = !enabled;
+ }
+
+ /// <summary>
+ /// Raises the BeforeRenderVisuals event.
+ /// </summary>
+ /// <param name="bmp"></param>
+ protected virtual void OnBeforeRenderingVisuals(WriteableBitmap bmp, double scaleFactor)
+ {
+ if (BeforeRenderVisuals != null) BeforeRenderVisuals(bmp, scaleFactor);
+ }
+
+ /// <summary>
+ /// Raises the AfterRenderVisuals event.
+ /// </summary>
+ /// <param name="bmp"></param>
+ protected virtual void OnAfterRenderingVisuals(WriteableBitmap bmp, double scaleFactor)
+ {
+ if (AfterRenderVisuals != null) AfterRenderVisuals(bmp, scaleFactor);
+ }
+
+ /// <summary>
+ /// Override this method to apply logic when graph resize is complete.
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e"></param>
+ protected virtual void OnResizeEnd(object sender, EventArgs e)
+ {
+ resizeTimer.IsEnabled = false;
+ resizeTimer.Stop();
+ OnSetCrossThreadFields();
+ isAfterResize = true;
+ }
+
+ /// <summary>
+ /// Override this method to apply logic while graph is resizing.
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e"></param>
+ protected virtual void OnSizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ resizeTimer.IsEnabled = true;
+ resizeTimer.Stop();
+ resizeTimer.Start();
+ }
+
+ /// <summary>
+ /// Initializes the control.
+ /// </summary>
+ protected virtual void Initialize()
+ {
+ //Initialize the main grid
+ gridMain = new Grid() { ClipToBounds = true };
+
+ gridMain.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
+ gridMain.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(1, GridUnitType.Star) });
+ gridMain.ColumnDefinitions.Add(new ColumnDefinition() { Width = GridLength.Auto });
+
+ //Initialize the grid lines and image wrapper grid.
+ gridLinesAndImageWrapperGrid = new Grid() { Background = Brushes.Transparent };
+
+ //Initialize the grid lines grid.
+ gridBack = new Grid();
+
+ //Initialize the graph image.
+ img = new Image() { Stretch = Stretch.None, HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Stretch };
+
+ //Initialize the Y Axis grid.
+ stackLeft = new StackPanel() { HorizontalAlignment = HorizontalAlignment.Left, Orientation = Orientation.Horizontal, FlowDirection = System.Windows.FlowDirection.LeftToRight };
+
+ //Initializing the X Axis grid.
+ stackRight = new StackPanel() { HorizontalAlignment = HorizontalAlignment.Right, Orientation = Orientation.Horizontal, FlowDirection = System.Windows.FlowDirection.RightToLeft };
+
+ //Initialize the inner content wrapper.
+ gridInnerContentWrapper = new Grid();
+
+ //Add the main grid to the control.
+ this.Content = gridMain;
+
+ //Add the wrapper grid to the main grid.
+ gridMain.Children.Add(gridLinesAndImageWrapperGrid);
+
+ //Add the grid lines grid to the wrapper.
+ gridLinesAndImageWrapperGrid.Children.Add(gridBack);
+
+ //Add the graph image to the wrapper.
+ gridMain.Children.Add(img);
+ img.IsHitTestVisible = false;
+ Grid.SetColumnSpan(img, 3);
+
+ //Add the left grid to the main grid.
+ gridMain.Children.Add(stackLeft);
+
+ //Add the right grid to the main grid.
+ gridMain.Children.Add(stackRight);
+
+ gridMain.Children.Add(gridInnerContentWrapper);
+
+ Grid.SetColumn(stackLeft, 0);
+ Grid.SetColumnSpan(stackLeft, 1);
+ Grid.SetColumn(stackRight, 2);
+ Grid.SetColumnSpan(stackRight, 1);
+ Grid.SetColumn(gridLinesAndImageWrapperGrid, 1);
+ Grid.SetColumnSpan(gridLinesAndImageWrapperGrid, 1);
+ Grid.SetColumn(gridInnerContentWrapper, 0);
+ Grid.SetColumnSpan(gridInnerContentWrapper, 3);
+
+ //Set size changed event to get the exact size of image grid!
+ gridLinesAndImageWrapperGrid.SizeChanged += OnImageGridSizeChanged;
+
+ //Zooming
+ //scaleTransform = new ScaleTransform(1, 1);
+ //translateTransform = new TranslateTransform(0, 0);
+ //TransformGroup group = new TransformGroup();
+ //group.Children.Add(scaleTransform);
+ //group.Children.Add(translateTransform);
+ //gridLinesAndImageWrapperGrid.RenderTransform = group;
+ gridLinesAndImageWrapperGrid.PreviewMouseWheel += gridLinesAndImageWrapperGrid_PreviewMouseWheel;
+ moveThumb = new Thumb() { HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch, Opacity = 0 };
+ if (!gridLinesAndImageWrapperGrid.Children.Contains(moveThumb))
+ {
+ gridLinesAndImageWrapperGrid.Children.Add(moveThumb);
+ moveThumb.DragDelta += moveThumb_DragDelta;
+ }
+
+ selectionCanvas = new Canvas() { Background = null, HorizontalAlignment = System.Windows.HorizontalAlignment.Stretch, VerticalAlignment = System.Windows.VerticalAlignment.Stretch };
+ Panel.SetZIndex(moveThumb, 9999);
+ Grid.SetColumnSpan(selectionCanvas, 3);
+ gridMain.Children.Add(selectionCanvas);
+ selectionRectangle = new Rectangle() { Fill = SelectionFill, Stroke = SelectionStroke };
+
+ gridLinesAndImageWrapperGrid.PreviewMouseDown += gridLinesAndImageWrapperGrid_PreviewMouseDown;
+ selectionCanvas.MouseMove += selectionCanvas_MouseMove;
+ selectionCanvas.PreviewMouseUp += selectionCanvas_PreviewMouseUp;
+ gridLinesAndImageWrapperGrid.PreviewMouseLeftButtonDown += gridLinesAndImageWrapperGrid_PreviewMouseLeftButtonDown;
+ gridLinesAndImageWrapperGrid.MouseMove += OnMouseMove;
+ gridLinesAndImageWrapperGrid.MouseLeave += OnMouseLeave;
+ this.SizeChanged += (x, y) => { if (isScaled) { ResetZoom(); }; };
+ }
+
+ /// <summary>
+ /// Resets the graph zooming.
+ /// </summary>
+ public virtual void ResetZoom()
+ {
+ if (!isScaled) return;
+
+ isScaled = false;
+
+ gridLinesAndImageWrapperGrid.RenderTransformOrigin = new Point(0.5, 0.5);
+
+ DoubleAnimation aniX = new DoubleAnimation();
+ aniX.Duration = new Duration(TimeSpan.FromSeconds(0.1));
+ aniX.To = gridMain.ActualWidth;
+
+ DoubleAnimation aniY = new DoubleAnimation();
+ aniY.Duration = new Duration(TimeSpan.FromSeconds(0.1));
+ aniY.To = gridMain.ActualHeight;
+
+ ThicknessAnimation marginAni = new ThicknessAnimation();
+ marginAni.Duration = new Duration(TimeSpan.FromSeconds(0.1));
+ marginAni.To = new Thickness(0, 0, 0, 0);
+
+ if (!double.IsNaN(gridLinesAndImageWrapperGrid.Width))
+ {
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, aniX);
+ }
+
+ if (!double.IsNaN(gridLinesAndImageWrapperGrid.Height))
+ {
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, aniY);
+ }
+
+ aniY.CurrentTimeInvalidated += (x, y) =>
+ {
+ ApplyVirtualMaximumMinimum();
+ };
+
+ marginAni.Completed += (x, y) =>
+ {
+ if (!double.IsNaN(gridLinesAndImageWrapperGrid.Width))
+ {
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, null);
+ }
+
+ if (!double.IsNaN(gridLinesAndImageWrapperGrid.Height))
+ {
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, null);
+ }
+
+ gridLinesAndImageWrapperGrid.Width = double.NaN;
+ gridLinesAndImageWrapperGrid.Height = double.NaN;
+
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, null);
+ gridLinesAndImageWrapperGrid.Margin = new Thickness(0, 0, 0, 0);
+
+ ApplyVirtualMaximumMinimum();
+
+ Thread t = new Thread(() =>
+ {
+ Thread.Sleep(100);
+
+ this.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ OnZoomingComplete(new Point(0.5, 0.5), gridLinesAndImageWrapperGrid.ActualWidth, gridLinesAndImageWrapperGrid.ActualHeight);
+ }), DispatcherPriority.Background);
+ });
+ t.Start();
+ };
+
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, marginAni);
+ gridLinesAndImageWrapperGrid.Cursor = Cursors.Arrow;
+
+ gridLinesAndImageWrapperGrid.PreviewMouseDown += gridLinesAndImageWrapperGrid_PreviewMouseDown;
+ }
+
+ /// <summary>
+ /// Override this method to apply extra logic when the grid containing the graph and grid lines changes its size.
+ /// </summary>
+ /// <param name="sender"></param>
+ /// <param name="e"></param>
+ protected virtual void OnImageGridSizeChanged(object sender, SizeChangedEventArgs e)
+ {
+ OnSetCrossThreadFields();
+ }
+
+ /// <summary>
+ /// Occurs after the loaded graph event.
+ /// </summary>
+ /// <param name="sender">Graph instance.</param>
+ /// <param name="e">Arguments</param>
+ protected virtual void OnGraphLoaded(object sender, RoutedEventArgs e)
+ {
+ if (!_loaded)
+ {
+ _loaded = true;
+
+ if (InnerContent != null)
+ {
+ gridInnerContentWrapper.Children.Clear();
+ gridInnerContentWrapper.Children.Add(InnerContent);
+ }
+
+ OnRenderComponents();
+
+ OnSetCrossThreadFields();
+ }
+ }
+
+ /// <summary>
+ /// Set the RealTimeGraphCrossThreadModel properties used by the graph thread.
+ /// </summary>
+ protected virtual void OnSetCrossThreadFields()
+ {
+ this.Dispatcher.Invoke(() =>
+ {
+ _antialiased = Antialiased;
+ _height = gridLinesAndImageWrapperGrid.ActualHeight;
+ _width = gridLinesAndImageWrapperGrid.ActualWidth;
+ _mainWidth = gridMain.ActualWidth;
+ _mainHeight = gridMain.ActualHeight;
+ _maximum = Maximum;
+ _minimum = Minimum;
+ _originalMinimum = Minimum;
+ _refreshRate = RefreshRate;
+ _isPaused = IsPaused;
+ virtualMinimum = Minimum;
+ virtualMaximum = Maximum;
+ virtualStart = 0;
+ virtualEnd = gridMain.ActualWidth;
+ RenderOptions.SetEdgeMode(this, _antialiased ? EdgeMode.Unspecified : EdgeMode.Aliased);
+
+ }, DispatcherPriority.Send);
+ }
+
+ /// <summary>
+ /// Override this method to apply extra logic for clearing the graph.
+ /// </summary>
+ protected virtual void OnClearGraph()
+ {
+
+ }
+
+ /// <summary>
+ /// Override this method to render the graph image.
+ /// </summary>
+ protected internal virtual void OnRenderGraph()
+ {
+
+ }
+
+ /// <summary>
+ /// Override this method to set the cross thread model properties.
+ /// </summary>
+ /// <param name="e"></param>
+ protected virtual void OnGraphPropertiesChanged(DependencyPropertyChangedEventArgs e)
+ {
+ OnSetCrossThreadFields();
+ }
+
+ /// <summary>
+ /// Convert the specified integer to graph Y position.
+ /// </summary>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ protected virtual double ConvertYToImageY(double value)
+ {
+ double valuePrecentage = ((((value - _minimum) * 100) / (_maximum - _minimum)) * _height) / 100;
+ return valuePrecentage;
+ }
+
+ /// <summary>
+ /// Convert the specified integer to graph X position.
+ /// </summary>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ protected virtual double ConvertYToImageX(double value)
+ {
+ double valuePrecentage = ((((value - _minimum) * 100) / (_maximum - _minimum)) * _width) / 100;
+ return valuePrecentage;
+ }
+
+ /// <summary>
+ /// Convert the specified integer to graph flipped Y position.
+ /// </summary>
+ /// <param name="value"></param>
+ /// <returns></returns>
+ protected virtual double ConvertYToImageYFliped(double value)
+ {
+ double valuePrecentage = ConvertYToImageY(value);
+ valuePrecentage = _height - valuePrecentage; //Flip
+ return valuePrecentage;
+ }
+
+ /// <summary>
+ /// Renders the collection of graph components.
+ /// </summary>
+ protected virtual void OnRenderComponents()
+ {
+ if (Components != null)
+ {
+ foreach (var component in Components)
+ {
+ component.Graph = this;
+ component.Render(this);
+
+ if (component.Location == ComponentLocationEnum.Left)
+ {
+ if (!stackLeft.Children.Contains(component))
+ {
+ stackLeft.Children.Add(component);
+ }
+ }
+ else if (component.Location == ComponentLocationEnum.Right)
+ {
+ if (!stackRight.Children.Contains(component))
+ {
+ stackRight.Children.Add(component);
+ }
+ }
+ else if (component.Location == ComponentLocationEnum.Back)
+ {
+ if (!gridBack.Children.Contains(component))
+ {
+ gridBack.Children.Add(component);
+ }
+ }
+ else if (component.Location == ComponentLocationEnum.Front)
+ {
+ if (!gridInnerContentWrapper.Children.Contains(component))
+ {
+ gridInnerContentWrapper.Children.Add(component);
+ }
+ }
+
+ }
+ }
+ }
+
+ /// <summary>
+ /// Sets the current state of the graph.
+ /// </summary>
+ /// <param name="paused">if set to <c>true</c> [paused].</param>
+ protected virtual void SetPaused(bool paused)
+ {
+ this.Dispatcher.Invoke(() =>
+ {
+
+ IsPaused = paused;
+
+ });
+ }
+
+ /// <summary>
+ /// Called when [zooming to].
+ /// </summary>
+ /// <param name="transformOrigin">The transform origin.</param>
+ /// <param name="scaleX">The scale markerPosition.</param>
+ /// <param name="scaleY">The scale y.</param>
+ protected virtual void OnZoomingComplete(Point transformOrigin, double scaleX, double scaleY)
+ {
+ if (ZoomComplete != null) ZoomComplete(transformOrigin, scaleX, scaleY);
+ }
+
+ /// <summary>
+ /// Called when [panning to].
+ /// </summary>
+ /// <param name="translate">The translate.</param>
+ protected virtual void OnPanningComplete(Point translate)
+ {
+ if (PanningComplete != null) PanningComplete(translate);
+ }
+
+ /// <summary>
+ /// Converts the y position to value.
+ /// </summary>
+ /// <param name="y">The Y Position.</param>
+ /// <returns>The Y Value</returns>
+ protected virtual double ConvertYPositionToValue(double y)
+ {
+ double valuePrecentage = (y * 100) / gridLinesAndImageWrapperGrid.ActualHeight;
+ valuePrecentage = (valuePrecentage * (_maximum - _minimum)) / 100;
+ valuePrecentage = _minimum + (_maximum - _minimum) - valuePrecentage; //Flip
+ return valuePrecentage;
+ }
+
+ /// <summary>
+ /// Add an IRealTimeGraphComponent instance to the components collection.
+ /// </summary>
+ /// <param name="component"></param>
+ internal virtual void ApplyComponent(FrameworkElement component)
+ {
+ if (!stackLeft.Children.Contains(component) && !stackLeft.Children.Contains(component))
+ {
+ if (component.HorizontalAlignment == System.Windows.HorizontalAlignment.Right)
+ {
+ stackRight.Children.Add(component);
+ }
+ else
+ {
+ stackLeft.Children.Add(component);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Called when [mouse move].
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnMouseMove(object sender, MouseEventArgs e)
+ {
+ MouseValue = ConvertYPositionToValue(e.GetPosition(gridLinesAndImageWrapperGrid).Y);
+ if (MouseMove != null) MouseMove(this, e);
+ }
+
+ /// <summary>
+ /// Called when [mouse leave].
+ /// </summary>
+ /// <param name="sender">The sender.</param>
+ /// <param name="e">The <see cref="MouseEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnMouseLeave(object sender, MouseEventArgs e)
+ {
+ if (MouseLeave != null) MouseLeave(this, e);
+ }
+
+ /// <summary>
+ /// Normalizes the value.
+ /// </summary>
+ /// <param name="value">The value.</param>
+ protected virtual void NormalizeValue(ref double value)
+ {
+ if (value > _maximum)
+ {
+ value = _maximum;
+ }
+ else if (value < _originalMinimum)
+ {
+ value = _originalMinimum;
+ }
+ }
+
+ /// <summary>
+ /// Override to apply extra logic when dragging on zoom mode.
+ /// </summary>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ protected virtual void OnDragging(DragDeltaEventArgs e)
+ {
+
+ }
+
+ #endregion
+
+ #region Protected Methods
+
+ /// <summary>
+ /// Clears the graph.
+ /// </summary>
+ protected void ClearGraph()
+ {
+ StopPushThread();
+ xValueCounter = 0;
+ OnSetCrossThreadFields();
+
+ OnClearGraph();
+
+ if (img != null)
+ {
+ WriteableBitmap bmp = BitmapFactory.New((int)_width, (int)_height);
+ bmp.Clear(Colors.Transparent);
+
+ bmp.Freeze();
+
+ img.Dispatcher.BeginInvoke(new Action(() =>
+ {
+ img.Source = bmp;
+ }), System.Windows.Threading.DispatcherPriority.Send);
+ }
+ }
+
+ /// <summary>
+ /// Starts the "push" thread.
+ /// </summary>
+ protected void StartPushThread()
+ {
+ if (pushThread == null && !IsSynced)
+ {
+ _requestTermination = new ManualResetEvent(false);
+ _terminated = new ManualResetEvent(false);
+ pushThread = new Thread(PushDataThreadMethod);
+ pushThread.IsBackground = true;
+ pushThread.Start();
+ }
+ }
+
+ /// <summary>
+ /// Stops the "push" thread.
+ /// </summary>
+ protected void StopPushThread()
+ {
+ if (pushThread != null)
+ {
+ _requestTermination.Set();
+ _terminated.WaitOne(50);
+ pushThread = null;
+ }
+ }
+
+ /// <summary>
+ /// Draw the specified polygon vectorsCollection on the graph. The drawing will be anti-aliased if specified in the cross model fields.
+ /// </summary>
+ [DebuggerStepThrough]
+ [DebuggerHidden]
+ protected void DrawPolyline(WriteableBitmap bmp, int[] points, Color stroke)
+ {
+ try
+ {
+ if (points.Length > 0)
+ {
+ if (_antialiased)
+ {
+ bmp.DrawPolylineAa(points, stroke);
+ }
+ else
+ {
+ bmp.DrawPolyline(points, stroke);
+ }
+ }
+ }
+ catch
+ {
+ Debug.WriteLine("[RealTimeGraphEx] [DrawPolyline] Error encountered while trying to Draw on bitmap.");
+ }
+ }
+
+ /// <summary>
+ /// Forces events update.
+ /// </summary>
+ public static void DoEvents()
+ {
+ Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
+ new Action(delegate { }));
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ /// <summary>
+ /// The push thread method.
+ /// </summary>
+ private void PushDataThreadMethod()
+ {
+ while (!_requestTermination.WaitOne(0))
+ {
+ OnRenderGraph();
+ Thread.Sleep(_refreshRate);
+ }
+
+ _terminated.Set();
+ }
+
+ private Rect BoundsRelativeTo(FrameworkElement element, Visual relativeTo)
+ {
+ return
+ element.TransformToVisual(relativeTo)
+ .TransformBounds(LayoutInformation.GetLayoutSlot(element));
+ }
+
+ private void ApplyVirtualMaximumMinimum()
+ {
+ var b = new Rect(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top, gridLinesAndImageWrapperGrid.Width, gridLinesAndImageWrapperGrid.Height);
+
+ double maxDecreasePrecentage = (Math.Abs(b.Top) * 100) / gridMain.ActualHeight;
+ double minIncreasePrecentage = ((b.Bottom - gridMain.ActualHeight) * 100) / gridMain.ActualHeight;
+
+
+ virtualMaximum = ConvertYPositionToValue(Math.Abs(b.Top));
+ virtualMinimum = ConvertYPositionToValue(b.Height - Math.Abs(b.Height - (gridMain.ActualHeight - b.Top)));
+ virtualStart = Math.Abs(b.Left);
+ virtualEnd = virtualStart + gridMain.ActualWidth;
+
+ _offSetX = gridLinesAndImageWrapperGrid.Margin.Left;
+ _offSetY = gridLinesAndImageWrapperGrid.Margin.Top;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ /// <summary>
+ /// Gets the size of the graph render.
+ /// </summary>
+ /// <returns></returns>
+ public Rect GetGraphRenderBounds()
+ {
+ var b = BoundsRelativeTo(gridLinesAndImageWrapperGrid, gridMain);
+ return b;
+ }
+
+ #endregion
+
+ #region Static Methods
+
+ protected static void CrossModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ (d as RealTimeGraphExBase).OnGraphPropertiesChanged(e);
+ }
+
+ #endregion
+
+ #region Event Handlers
+
+ /// <summary>
+ /// Handles the PreviewMouseWheel event of the gridLinesAndImageWrapperGrid control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="MouseWheelEventArgs"/> instance containing the event data.</param>
+ protected void gridLinesAndImageWrapperGrid_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
+ {
+ if (ZoomMode == ZoomModeEnum.MouseWheel)
+ {
+ gridLinesAndImageWrapperGrid.PreviewMouseDown -= gridLinesAndImageWrapperGrid_PreviewMouseDown;
+ Zoom += (e.Delta < 0 ? -0.1 : 0.1);
+ }
+ else if (ZoomMode == ZoomModeEnum.Selection)
+ {
+
+ var center = new Point(e.GetPosition(gridLinesAndImageWrapperGrid).X, e.GetPosition(gridLinesAndImageWrapperGrid).Y);
+
+ int zoomFactor = 20;
+
+ double width = gridLinesAndImageWrapperGrid.ActualWidth + (e.Delta < 0 ? zoomFactor * -1 : zoomFactor);
+ double height = gridLinesAndImageWrapperGrid.ActualHeight + (e.Delta < 0 ? zoomFactor * -1 : zoomFactor);
+ double left = gridLinesAndImageWrapperGrid.Margin.Left;
+ double top = gridLinesAndImageWrapperGrid.Margin.Top;
+
+ bool reset = true;
+
+ if (ZoomDirection != ZoomDirectionEnum.Y)
+ {
+ if (width > gridMain.ActualWidth)
+ {
+ gridLinesAndImageWrapperGrid.Width = width;
+ reset = false;
+ }
+ }
+
+ if (ZoomDirection != ZoomDirectionEnum.X)
+ {
+ if (height > gridMain.ActualHeight)
+ {
+ gridLinesAndImageWrapperGrid.Height = height;
+ reset = false;
+ }
+ }
+
+ double factorX = zoomFactor / (gridLinesAndImageWrapperGrid.ActualWidth / center.X);
+ double factorY = zoomFactor / (gridLinesAndImageWrapperGrid.ActualHeight / center.Y);
+
+ if (ZoomDirection != ZoomDirectionEnum.Y)
+ {
+ if (gridLinesAndImageWrapperGrid.Margin.Left + (e.Delta < 0 ? factorX : -factorX) < 0)
+ {
+ left = gridLinesAndImageWrapperGrid.Margin.Left + (e.Delta < 0 ? factorX : -factorX);
+ reset = false;
+ }
+ }
+
+ if (ZoomDirection != ZoomDirectionEnum.X)
+ {
+ if (gridLinesAndImageWrapperGrid.Margin.Top + (e.Delta < 0 ? factorY : -factorY) < 0)
+ {
+ top = gridLinesAndImageWrapperGrid.Margin.Top + (e.Delta < 0 ? factorY : -factorY);
+ reset = false;
+ }
+ }
+
+ gridLinesAndImageWrapperGrid.Margin =
+ new Thickness(
+ left,
+ top,
+ gridLinesAndImageWrapperGrid.Margin.Right,
+ gridLinesAndImageWrapperGrid.Margin.Bottom);
+
+ OnZoomingComplete(lastCenter, gridLinesAndImageWrapperGrid.Width, gridLinesAndImageWrapperGrid.Height);
+ ApplyVirtualMaximumMinimum();
+
+ if (reset)
+ {
+ ResetZoom();
+ gridLinesAndImageWrapperGrid.PreviewMouseDown += gridLinesAndImageWrapperGrid_PreviewMouseDown;
+ isScaled = false;
+ }
+ else
+ {
+ gridLinesAndImageWrapperGrid.PreviewMouseDown -= gridLinesAndImageWrapperGrid_PreviewMouseDown;
+ gridLinesAndImageWrapperGrid.Cursor = Cursors.SizeAll;
+ isScaled = true;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Handles the DragDelta event of the moveThumb control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="DragDeltaEventArgs"/> instance containing the event data.</param>
+ protected void moveThumb_DragDelta(object sender, DragDeltaEventArgs e)
+ {
+ if (ZoomMode == ZoomModeEnum.None) return;
+
+ Point relativePoint = gridLinesAndImageWrapperGrid.TransformToAncestor(gridMain).Transform(new Point(0, 0));
+ Rect bounds = BoundsRelativeTo(gridLinesAndImageWrapperGrid, gridMain);
+
+ double right = (gridLinesAndImageWrapperGrid.ActualWidth + gridLinesAndImageWrapperGrid.Margin.Left);
+ double bottom = (gridLinesAndImageWrapperGrid.ActualHeight + gridLinesAndImageWrapperGrid.Margin.Top);
+
+ if (gridLinesAndImageWrapperGrid.Margin.Left + e.HorizontalChange <= 0 && right + e.HorizontalChange >= gridMain.ActualWidth)
+ {
+ gridLinesAndImageWrapperGrid.Margin = new Thickness(gridLinesAndImageWrapperGrid.Margin.Left + e.HorizontalChange, gridLinesAndImageWrapperGrid.Margin.Top, 0, 0);
+ //translateTransform.X += e.HorizontalChange;
+ OnPanningComplete(new Point(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top));
+ ApplyVirtualMaximumMinimum();
+ }
+
+ if (gridLinesAndImageWrapperGrid.Margin.Top + e.VerticalChange <= 0 && bottom + e.VerticalChange >= gridMain.ActualHeight)
+ {
+ gridLinesAndImageWrapperGrid.Margin = new Thickness(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top + e.VerticalChange, 0, 0);
+ //translateTransform.Y += e.VerticalChange;
+ OnPanningComplete(new Point(gridLinesAndImageWrapperGrid.Margin.Left, gridLinesAndImageWrapperGrid.Margin.Top));
+ ApplyVirtualMaximumMinimum();
+ }
+
+ OnDragging(e);
+ }
+
+ /// <summary>
+ /// Handles the PreviewMouseLeftButtonDown event of the gridLinesAndImageWrapperGrid control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="MouseButtonEventArgs"/> instance containing the event data.</param>
+ protected void gridLinesAndImageWrapperGrid_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ if (ZoomMode != ZoomModeEnum.Selection) return;
+
+ if (e.ClickCount == 2)
+ {
+ ResetZoom();
+ }
+ }
+
+ /// <summary>
+ /// </summary>
+ /// <returns></returns>
+ protected void selectionCanvas_PreviewMouseUp(object sender, MouseButtonEventArgs e)
+ {
+ if (ZoomMode != ZoomModeEnum.Selection) return;
+
+ var bounds = new Rect(Canvas.GetLeft(selectionRectangle), Canvas.GetTop(selectionRectangle), selectionRectangle.Width, selectionRectangle.Height);
+ selectionCanvas.Children.Remove(selectionRectangle);
+ isSelectionMouseDown = false;
+ selectionCanvas.Background = null;
+
+ if (selectionRectangle.Width <= 5 || selectionRectangle.Height <= 5) return;
+
+ Point center = new Point(bounds.Left * (gridLinesAndImageWrapperGrid.ActualWidth / bounds.Width), bounds.Top * (gridLinesAndImageWrapperGrid.ActualHeight / bounds.Height));
+ gridLinesAndImageWrapperGrid.RenderTransformOrigin = center;
+
+ lastBounds = bounds;
+ lastCenter = center;
+
+ double scaleX = gridLinesAndImageWrapperGrid.ActualWidth;
+ double scaleY = gridLinesAndImageWrapperGrid.ActualHeight;
+
+ if (ZoomDirection != ZoomDirectionEnum.Y)
+ {
+ scaleX = gridLinesAndImageWrapperGrid.ActualWidth * (gridLinesAndImageWrapperGrid.ActualWidth / bounds.Width);
+ }
+
+ if (ZoomDirection != ZoomDirectionEnum.X)
+ {
+ scaleY = gridLinesAndImageWrapperGrid.ActualHeight * (gridLinesAndImageWrapperGrid.ActualHeight / bounds.Height);
+ }
+
+ isScaled = true;
+
+ DoubleAnimation aniX = new DoubleAnimation();
+ aniX.Duration = new Duration(TimeSpan.FromSeconds(0.1));
+ aniX.To = scaleX;
+
+ DoubleAnimation aniY = new DoubleAnimation();
+ aniY.Duration = new Duration(TimeSpan.FromSeconds(0.1));
+ aniY.To = scaleY;
+
+ ThicknessAnimation marginAni = new ThicknessAnimation();
+ marginAni.Duration = new Duration(TimeSpan.FromSeconds(0.1));
+ marginAni.To = new Thickness(center.X * -1, center.Y * -1, 0, 0);
+
+ aniY.CurrentTimeInvalidated += (x, y) =>
+ {
+ ApplyVirtualMaximumMinimum();
+ };
+
+ aniY.Completed += (x, y) =>
+ {
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, null);
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, null);
+ gridLinesAndImageWrapperGrid.Width = scaleX;
+ gridLinesAndImageWrapperGrid.Height = scaleY;
+
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, null);
+
+ double marginLeft = 0;
+ double marginTop = 0;
+
+ if (ZoomDirection != ZoomDirectionEnum.Y)
+ {
+ marginLeft = center.X * -1;
+ }
+
+ if (ZoomDirection != ZoomDirectionEnum.X)
+ {
+ marginTop = center.Y * -1;
+ }
+
+ gridLinesAndImageWrapperGrid.Margin = new Thickness(marginLeft, marginTop, 0, 0);
+
+ OnZoomingComplete(center, scaleX, scaleY);
+ ApplyVirtualMaximumMinimum();
+ };
+
+ gridLinesAndImageWrapperGrid.Width = gridLinesAndImageWrapperGrid.ActualWidth;
+ gridLinesAndImageWrapperGrid.Height = gridLinesAndImageWrapperGrid.ActualHeight;
+
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.MarginProperty, marginAni);
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.WidthProperty, aniX);
+ gridLinesAndImageWrapperGrid.BeginAnimation(Grid.HeightProperty, aniY);
+ gridLinesAndImageWrapperGrid.Cursor = Cursors.SizeAll;
+
+ gridLinesAndImageWrapperGrid.PreviewMouseDown -= gridLinesAndImageWrapperGrid_PreviewMouseDown;
+ }
+
+ /// <summary>
+ /// Handles the MouseMove event of the selectionCanvas control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
+ protected void selectionCanvas_MouseMove(object sender, MouseEventArgs e)
+ {
+ if (isSelectionMouseDown)
+ {
+ double width = e.GetPosition(selectionCanvas).X - Canvas.GetLeft(selectionRectangle);
+ double height = e.GetPosition(selectionCanvas).Y - Canvas.GetTop(selectionRectangle);
+
+ if (width > 0)
+ {
+ selectionRectangle.Width = width;
+ }
+
+ if (height > 0)
+ {
+ selectionRectangle.Height = height;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Handles the PreviewMouseDown event of the gridLinesAndImageWrapperGrid control.
+ /// </summary>
+ /// <param name="sender">The source of the event.</param>
+ /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
+ protected void gridLinesAndImageWrapperGrid_PreviewMouseDown(object sender, MouseButtonEventArgs e)
+ {
+ if (ZoomMode != ZoomModeEnum.Selection) return;
+ selectionCanvas.Children.Remove(selectionRectangle);
+ selectionCanvas.Background = Brushes.Transparent;
+ selectionRectangle.Width = 0;
+ selectionRectangle.Height = 0;
+ selectionCanvas.Children.Add(selectionRectangle);
+ Canvas.SetLeft(selectionRectangle, e.GetPosition(selectionCanvas).X);
+ Canvas.SetTop(selectionRectangle, e.GetPosition(selectionCanvas).Y);
+ isSelectionMouseDown = true;
+ DoEvents();
+ }
+
+ #endregion
+
+ #region IDisposable Members
+
+ /// <summary>
+ /// Disposes the current instance.
+ /// </summary>
+ public void Dispose()
+ {
+ if (pushThread != null)
+ {
+ pushThread.Abort();
+ }
+ }
+
+ #endregion
+ }
+}