using RealTimeGraphEx.Controllers;
using RealTimeGraphEx.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
namespace RealTimeGraphEx.ReachGraphs
{
public class RealTimeGraphExReachMultiLineScroll : RealTimeGraphExMultiBase
{
#region Cross Thread Fields
protected List<RealTimeGraphExReachPolygon> _graphPolygons;
protected List<Polygon> polygons;
protected Brush _stroke;
protected Brush _fill;
protected bool _fillGraph;
#endregion
#region Constructors
public RealTimeGraphExReachMultiLineScroll()
: base()
{
_graphPolygons = new List<RealTimeGraphExReachPolygon>();
_graphController = new GraphMultiController();
polygons = new List<Polygon>();
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the graph strokes color.
/// </summary>
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
public static readonly DependencyProperty StrokeProperty =
DependencyProperty.Register("Stroke", typeof(Brush), typeof(RealTimeGraphExReachMultiLineScroll), new PropertyMetadata(Brushes.Black, new PropertyChangedCallback(CrossModelChanged)));
/// <summary>
/// Gets or sets the graph fill color.
/// </summary>
public Brush Fill
{
get { return (Brush)GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
}
public static readonly DependencyProperty FillProperty =
DependencyProperty.Register("Fill", typeof(Brush), typeof(RealTimeGraphExReachMultiLineScroll), new PropertyMetadata(null, new PropertyChangedCallback(CrossModelChanged)));
/// <summary>
/// Gets or sets whether the graph will be rendered using the Fill color property.
/// </summary>
public bool FillGraph
{
get { return (bool)GetValue(FillGraphProperty); }
set { SetValue(FillGraphProperty, value); }
}
public static readonly DependencyProperty FillGraphProperty =
DependencyProperty.Register("FillGraph", typeof(bool), typeof(RealTimeGraphExReachMultiLineScroll), new PropertyMetadata(false, new PropertyChangedCallback(CrossModelChanged)));
#endregion
#region Override Methods
protected override void OnControllerChanged()
{
if (_graphController != null)
{
_graphController.RegisterMethods(ClearGraph, StartPushThread, SetPaused, ChangeRenderMode, null);
polygons.Clear();
gridLinesAndImageWrapperGrid.Children.Remove(img);
foreach (var dataSeries in _graphController.DataSeriesCollection)
{
_graphPolygons.Add(new RealTimeGraphExReachPolygon());
Polygon polygon = new Polygon();
polygons.Add(polygon);
gridLinesAndImageWrapperGrid.Children.Add(polygon);
}
}
}
protected override void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
OnSetCrossThreadFields();
}
protected override void OnSetCrossThreadFields()
{
base.OnSetCrossThreadFields();
this.Dispatcher.Invoke(() =>
{
_maxPoints = MaxPoints;
_scaleFactor = _width / _maxPoints;
_graphController = Controller;
_stroke = Stroke;
_fill = Fill;
if (_graphController != null && _graphController.dataSeriesCollection != null && _graphController.dataSeriesCollection.Count > 0)
{
for (int i = 0; i < _graphController.dataSeriesCollection.Count; i++)
{
polygons[i].Fill = _graphController.dataSeriesCollection[i].fill;
polygons[i].Stroke = _graphController.dataSeriesCollection[i].stroke;
polygons[i].Margin = new Thickness(-2, 1, -2, 0);
}
}
}, System.Windows.Threading.DispatcherPriority.Send);
}
protected override void OnClearGraph()
{
_graphPolygons.ForEach(x => x.ClearPoints());
_graphPolygons.ForEach(x => x.ClearActualPoints());
this.Dispatcher.Invoke(() =>
{
foreach (var polygon in polygons)
{
polygon.Points.Clear();
}
});
base.OnClearGraph();
}
protected internal override void OnRenderGraph()
{
if (_graphController != null && _graphController.dataSeriesCollection.Count > 0 && _graphController.TotalPoints > 0 && _width > 1 && _height > 1)
{
for (int i = 0; i < _graphController.dataSeriesCollection.Count; i++)
{
double seriesCounter = xValueCounter;
for (int j = 0; j < _graphController.dataSeriesCollection[i].Points.Count; j++)
{
double value = _graphController.dataSeriesCollection[i].Points[j];
double valuePrecentage = value;
_graphPolygons[i].Add(seriesCounter, valuePrecentage, value);
seriesCounter += _scaleFactor;
}
}
for (int i = 0; i < _graphController.dataSeriesCollection[0].Points.Count; i++)
{
xValueCounter += _scaleFactor;
}
if (_graphPolygons[0].TotalPoints > _maxPoints + _graphController.dataSeriesCollection[0].Points.Count)
{
xValueCounter -= (_scaleFactor * (_graphPolygons[0].TotalPoints - (_maxPoints + _graphController.dataSeriesCollection[0].Points.Count)));
foreach (var polygon in _graphPolygons)
{
polygon.RemoveFromStart(polygon.TotalPoints - (_maxPoints + _graphController.dataSeriesCollection[0].Points.Count));
}
}
updateCounter++;
if (updateCounter >= 1)
{
updateCounter = 0;
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;
/// <summary>
/// Occurs when [minimum maximum changed].
/// </summary>
public event EventHandler MinMaxChanged;
#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, (d, e) =>
{
var control = d as RealTimeGraphExBase;
CrossModelChanged(d, e);
control.MinMaxChanged?.Invoke(control, new EventArgs());
}));
/// <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, (d, e) =>
{
var control = d as RealTimeGraphExBase;
CrossModelChanged(d, e);
control.MinMaxChanged?.Invoke(control, new EventArgs());
}));
/// <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));
/// <summary>
/// Gets or sets the maximum points to display on the graph (default 1000).
/// </summary>
public int MaxPoints
{
get { return (int)GetValue(MaxPointsProperty); }
set { SetValue(MaxPointsProperty, value); }
}
public static readonly DependencyProperty MaxPointsProperty =
DependencyProperty.Register("MaxPoints", typeof(int), typeof(RealTimeGraphExBase), new PropertyMetadata(1000, new PropertyChangedCallback(CrossModelChanged)));
/// <summary>
/// Gets or sets a value indicating whether [use automatic range].
/// </summary>
public bool UseAutoRange
{
get { return (bool)GetValue(UseAutoRangeProperty); }
set { SetValue(UseAutoRangeProperty, value); }
}
public static readonly DependencyProperty UseAutoRangeProperty =
DependencyProperty.Register("UseAutoRange", typeof(bool), typeof(RealTimeGraphExBase), new PropertyMetadata(false, new PropertyChangedCallback(CrossModelChanged)));
#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;
protected bool _useAutoRange;
#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;
_useAutoRange = UseAutoRange;
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;
}
public void Clear()
{
ClearGraph();
}
public void RenderComponents()
{
OnRenderComponents();
}
#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)
{
StopPushThread();
}
}
#endregion
}
}