using RealTimeGraphX.EventArguments; using RealTimeGraphX.WPF.DataSeries; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; namespace RealTimeGraphX.WPF.Surfaces { /// /// Represents a WPF implementation for . /// This surface expects a graph image of type . /// /// /// public class WpfGraphSurface : Control, IGraphSurface { private Rectangle _selection_rectangle; private Canvas _selection_canvas; private bool _is_selection_mouse_down_zoom; private bool _is_selection_mouse_down_pan; private Point _selection_start_point; private bool _is_scaled; private Rect _pan_rect; private Point _current_mouse_position; private Point _last_mouse_position; private Grid _grid; #region Events /// /// Occurs when has changed. /// public event EventHandler>> InputChanged; /// /// Occurs when the surface size has changed. /// public event EventHandler SurfaceSizeChanged; /// /// Occurs when the connected input has changed. /// event EventHandler> IGraphInputComponent.InputChanged { add { InputChanged += new EventHandler>>((sender, e) => { value.Invoke(sender, new InputChangedEventArgs(e.Input)); }); } remove { InputChanged -= new EventHandler>>((sender, e) => { value.Invoke(sender, new InputChangedEventArgs(e.Input)); }); } } #endregion #region Properties /// /// Gets the current graph image. /// public BitmapSource Image { get { return (BitmapSource)GetValue(ImageProperty); } private set { SetValue(ImageProperty, value); } } public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(BitmapSource), typeof(WpfGraphSurface), new PropertyMetadata(null)); private double _surfaceWidth; /// /// Gets the width of the surface. /// public double SurfaceWidth { get { return _surfaceWidth; } private set { _surfaceWidth = value; } } private double _surfaceHeight; /// /// Gets the height of the surface. /// public double SurfaceHeight { get { return _surfaceHeight; } private set { _surfaceHeight = value; } } private IGraphPainter _input; /// /// Gets the connected input . /// public IGraphPainter Input { get { return _input; } private set { _input = value; OnInputChanged(_input); } } /// /// Gets the connected input . /// IGraphPainter IGraphInputComponent.Input { get { return Input; } } #endregion #region Constructors /// /// Initializes the class. /// static WpfGraphSurface() { DefaultStyleKeyProperty.OverrideMetadata(typeof(WpfGraphSurface), new FrameworkPropertyMetadata(typeof(WpfGraphSurface))); } /// /// Initializes a new instance of the class. /// public WpfGraphSurface() { SizeChanged += OnSizeChanged; } #endregion #region Protected Methods /// /// Called when the mouse moves over the zoom/pan selection canvas. /// /// The sender. /// The instance containing the event data. protected virtual void OnSelectionCanvasMouseMove(object sender, MouseEventArgs e) { _current_mouse_position = e.GetPosition(_selection_canvas); if (_is_selection_mouse_down_zoom && Keyboard.IsKeyDown(Key.LeftCtrl)) { Canvas.SetLeft(_selection_rectangle, _selection_start_point.X); Canvas.SetTop(_selection_rectangle, _selection_start_point.Y); Point _selection_current_point = e.GetPosition(_selection_canvas); if (_selection_current_point.X - _selection_start_point.X > 1) { _selection_rectangle.Width = _selection_current_point.X - _selection_start_point.X; } if (_selection_current_point.Y - _selection_start_point.Y > 1) { _selection_rectangle.Height = _selection_current_point.Y - _selection_start_point.Y; } if (_selection_current_point.X < _selection_start_point.X) { Canvas.SetLeft(_selection_rectangle, _selection_current_point.X); _selection_rectangle.Width = _selection_start_point.X - _selection_current_point.X; } if (_selection_current_point.Y < _selection_start_point.Y) { Canvas.SetTop(_selection_rectangle, _selection_current_point.Y); _selection_rectangle.Height = _selection_start_point.Y - _selection_current_point.Y; } } else if (_is_selection_mouse_down_pan && _is_scaled) { Point _selection_current_point = e.GetPosition(_selection_canvas); double delta_x = _current_mouse_position.X - _last_mouse_position.X; double delta_y = _current_mouse_position.Y - _last_mouse_position.Y; var renderer = Input; double x = renderer.ZoomRect.Left - delta_x; double y = renderer.ZoomRect.Top - delta_y; if (x < 0) { x = 0; } if (y < 0) { y = 0; } if (x + renderer.ZoomRect.Width > SurfaceWidth) { x = x - (x + renderer.ZoomRect.Width - SurfaceWidth); } if (y + renderer.ZoomRect.Height > SurfaceHeight) { y = y - (y + renderer.ZoomRect.Height - SurfaceHeight); } Rect target_rect = new Rect(x, y, renderer.ZoomRect.Width, renderer.ZoomRect.Height); renderer.SetZoomRect(target_rect.ToGraphRect()); } _last_mouse_position = _current_mouse_position; } /// /// Called when the mouse released from the zoom/pan selection canvas. /// /// The sender. /// The instance containing the event data. protected virtual void OnSelectionCanvasMouseUp(object sender, MouseButtonEventArgs e) { _selection_canvas.ReleaseMouseCapture(); if (_is_selection_mouse_down_pan) { _is_selection_mouse_down_pan = false; _pan_rect = Input.ZoomRect.ToWpfRect(); } else if (_is_selection_mouse_down_zoom) { _is_selection_mouse_down_zoom = false; Rect target_rect = new Rect(Canvas.GetLeft(_selection_rectangle), Canvas.GetTop(_selection_rectangle), _selection_rectangle.Width, _selection_rectangle.Height); _pan_rect = target_rect; Input.SetZoomRect(target_rect.ToGraphRect()); _selection_rectangle.Visibility = Visibility.Hidden; _is_scaled = true; } } /// /// Called when the mouse pressed on the zoom/pan selection canvas. /// /// The sender. /// The instance containing the event data. protected virtual void OnSelectionCanvasMouseDown(object sender, MouseButtonEventArgs e) { Mouse.Capture(_selection_canvas); _selection_start_point = e.GetPosition(_selection_canvas); _current_mouse_position = _selection_start_point; _last_mouse_position = _current_mouse_position; if (e.ClickCount == 2) { Input.ResetZoomRect(); _is_scaled = false; } else if (Keyboard.IsKeyDown(Key.LeftCtrl)) { _selection_rectangle.Width = 0; _selection_rectangle.Height = 0; _is_selection_mouse_down_zoom = true; _is_selection_mouse_down_pan = false; _selection_rectangle.Visibility = Visibility.Visible; } else { _is_selection_mouse_down_pan = true; } } /// /// Called when the control size changes /// /// The sender. /// The instance containing the event data. protected virtual void OnSizeChanged(object sender, SizeChangedEventArgs e) { OnSurfaceSizeChanged(_grid.ActualWidth, _grid.ActualHeight); } /// /// Called when surface size has changed. /// /// The width. /// The height. protected virtual void OnSurfaceSizeChanged(double width, double height) { SurfaceWidth = width; SurfaceHeight = height; SurfaceSizeChanged?.Invoke(this, new SurfaceSizeChangedEventArgs() { Width = width, Height = height }); if (Input != null) { Input.ResetZoomRect(); } } /// /// Raises the event. /// /// The input. protected virtual void OnInputChanged(IGraphPainter input) { InputChanged?.Invoke(this, new InputChangedEventArgs>(input)); } /// /// Called when the painter has deliverd a new image to display. /// /// The sender. /// The instance containing the event data. protected virtual void OnPaintingCompleted(object sender, PaintingCompletedEventArgs e) { Image = e.Image; } #endregion #region Apply Template /// /// When overridden in a derived class, is invoked whenever application code or internal processes call . /// public override void OnApplyTemplate() { base.OnApplyTemplate(); _selection_rectangle = GetTemplateChild("PART_SelectionRectangle") as Rectangle; _selection_canvas = GetTemplateChild("PART_SelectionCanvas") as Canvas; _grid = GetTemplateChild("PART_Grid") as Grid; _selection_canvas.MouseDown += OnSelectionCanvasMouseDown; _selection_canvas.MouseUp += OnSelectionCanvasMouseUp; _selection_canvas.MouseMove += OnSelectionCanvasMouseMove; } #endregion #region Public Methods /// /// Invokes a method on the surface thread. /// /// The action. public void InvokeOnSurface(Action action) { Dispatcher.BeginInvoke(action); } /// /// Connects this surface to the specified . /// /// The painter. /// Specifies whether this call was made from an input . public void ConnectInput(IGraphPainter painter, bool fromInput = false) { painter.ThrowIfNull("Cannot connect a null painter."); DisconnectInput(); Input = painter; Input.PaintingCompleted += OnPaintingCompleted; if (!fromInput) { Input.ConnectOutput(this, true); } if (_grid != null) { OnSurfaceSizeChanged(_grid.ActualWidth, _grid.ActualHeight); } } /// /// Disconnects the current connected input . /// /// Specifies whether this call was made from an input . public void DisconnectInput(bool fromInput = false) { if (Input != null) { if (!fromInput) { Input.DisconnectOutput(true); } Input.PaintingCompleted -= OnPaintingCompleted; Input = null; } } /// /// Connects this surface to the specified . /// /// The painter. /// Specifies whether this call was made from an input . void IGraphInputComponent.ConnectInput(IGraphPainter painter, bool fromInput) { ConnectInput(painter as IGraphPainter, fromInput); } #endregion } }