123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489 |
- // --------------------------------------------------------------------------------------------------------------------
- // <copyright file="PlotViewBase.cs" company="OxyPlot">
- // Copyright (c) 2020 OxyPlot contributors
- // </copyright>
- // --------------------------------------------------------------------------------------------------------------------
- namespace OxyPlot.Wpf
- {
- using OxyPlot;
- using System;
- using System.Collections.ObjectModel;
- using System.Linq;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Controls.Primitives;
- using System.Windows.Input;
- using System.Windows.Media;
- using System.Windows.Threading;
- using System.Windows.Documents;
- using CursorType = OxyPlot.CursorType;
- /// <summary>
- /// Base class for WPF PlotView implementations.
- /// </summary>
- [TemplatePart(Name = PartGrid, Type = typeof(Grid))]
- public abstract partial class PlotViewBase : Control, IPlotView
- {
- /// <summary>
- /// The Grid PART constant.
- /// </summary>
- protected const string PartGrid = "PART_Grid";
- /// <summary>
- /// The grid.
- /// </summary>
- protected Grid grid;
- /// <summary>
- /// The plot presenter.
- /// </summary>
- protected FrameworkElement plotPresenter;
- /// <summary>
- /// The render context
- /// </summary>
- protected IRenderContext renderContext;
- /// <summary>
- /// The model lock.
- /// </summary>
- private readonly object modelLock = new object();
- /// <summary>
- /// The current tracker.
- /// </summary>
- private FrameworkElement currentTracker;
- /// <summary>
- /// The current tracker template.
- /// </summary>
- private ControlTemplate currentTrackerTemplate;
- /// <summary>
- /// The default plot controller.
- /// </summary>
- private IPlotController defaultController;
- /// <summary>
- /// Indicates whether the <see cref="PlotViewBase"/> was in the visual tree the last time <see cref="Render"/> was called.
- /// </summary>
- private bool isInVisualTree;
- /// <summary>
- /// The mouse down point.
- /// </summary>
- private ScreenPoint mouseDownPoint;
- /// <summary>
- /// The overlays.
- /// </summary>
- private Canvas overlays;
- /// <summary>
- /// The zoom control.
- /// </summary>
- private ContentControl zoomControl;
- /// <summary>
- /// Initializes static members of the <see cref="PlotViewBase" /> class.
- /// </summary>
- static PlotViewBase()
- {
- DefaultStyleKeyProperty.OverrideMetadata(typeof(PlotViewBase), new FrameworkPropertyMetadata(typeof(PlotViewBase)));
- PaddingProperty.OverrideMetadata(typeof(PlotViewBase), new FrameworkPropertyMetadata(new Thickness(8)));
- }
- /// <summary>
- /// Initializes a new instance of the <see cref="PlotViewBase" /> class.
- /// </summary>
- protected PlotViewBase()
- {
- this.TrackerDefinitions = new ObservableCollection<TrackerDefinition>();
- this.CommandBindings.Add(new CommandBinding(PlotCommands.ResetAxes, (s, e) => this.ResetAllAxes()));
- this.IsManipulationEnabled = true;
- this.LayoutUpdated += this.OnLayoutUpdated;
- }
- /// <summary>
- /// Gets the actual PlotView controller.
- /// </summary>
- /// <value>The actual PlotView controller.</value>
- public IPlotController ActualController => this.Controller ?? (this.defaultController ??= new PlotController());
- /// <inheritdoc/>
- IController IView.ActualController => this.ActualController;
- /// <summary>
- /// Gets the actual model.
- /// </summary>
- /// <value>The actual model.</value>
- public PlotModel ActualModel { get; private set; }
- /// <inheritdoc/>
- Model IView.ActualModel => this.ActualModel;
- /// <summary>
- /// Gets the coordinates of the client area of the view.
- /// </summary>
- public OxyRect ClientArea => new OxyRect(0, 0, this.ActualWidth, this.ActualHeight);
- /// <summary>
- /// Gets the tracker definitions.
- /// </summary>
- /// <value>The tracker definitions.</value>
- public ObservableCollection<TrackerDefinition> TrackerDefinitions { get; }
- /// <summary>
- /// Hides the tracker.
- /// </summary>
- public void HideTracker()
- {
- if (this.currentTracker != null)
- {
- this.overlays.Children.Remove(this.currentTracker);
- this.currentTracker = null;
- this.currentTrackerTemplate = null;
- }
- }
- /// <summary>
- /// Hides the zoom rectangle.
- /// </summary>
- public void HideZoomRectangle()
- {
- this.zoomControl.Visibility = Visibility.Collapsed;
- }
- /// <summary>
- /// Invalidate the PlotView (not blocking the UI thread)
- /// </summary>
- /// <param name="updateData">The update Data.</param>
- public void InvalidatePlot(bool updateData = true)
- {
- if (this.ActualModel == null)
- {
- return;
- }
- lock (this.ActualModel.SyncRoot)
- {
- ((IPlotModel)this.ActualModel).Update(updateData);
- }
- this.BeginInvoke(this.Render);
- }
- /// <inheritdoc/>
- public override void OnApplyTemplate()
- {
- base.OnApplyTemplate();
- this.grid = this.GetTemplateChild(PartGrid) as Grid;
- if (this.grid == null)
- {
- return;
- }
- this.plotPresenter = this.CreatePlotPresenter();
- this.grid.Children.Add(this.plotPresenter);
- this.plotPresenter.UpdateLayout();
- this.renderContext = this.CreateRenderContext();
- this.overlays = new Canvas();
- this.grid.Children.Add(this.overlays);
- this.zoomControl = new ContentControl();
- this.zoomControl.Focusable = false;
- this.overlays.Children.Add(this.zoomControl);
- // add additional grid on top of everthing else to fix issue of mouse events getting lost
- // it must be added last so it covers all other controls
- var mouseGrid = new Grid
- {
- Background = Brushes.Transparent // background must be set for hit test to work
- };
- this.grid.Children.Add(mouseGrid);
- }
- /// <summary>
- /// Pans all axes.
- /// </summary>
- /// <param name="delta">The delta.</param>
- public void PanAllAxes(Vector delta)
- {
- if (this.ActualModel != null)
- {
- this.ActualModel.PanAllAxes(delta.X, delta.Y);
- }
- this.InvalidatePlot(false);
- }
- /// <summary>
- /// Resets all axes.
- /// </summary>
- public void ResetAllAxes()
- {
- if (this.ActualModel != null)
- {
- this.ActualModel.ResetAllAxes();
- }
- this.InvalidatePlot(false);
- }
- /// <summary>
- /// Stores text on the clipboard.
- /// </summary>
- /// <param name="text">The text.</param>
- public void SetClipboardText(string text)
- {
- Clipboard.SetText(text);
- }
- /// <summary>
- /// Sets the cursor type.
- /// </summary>
- /// <param name="cursorType">The cursor type.</param>
- public void SetCursorType(CursorType cursorType)
- {
- this.Cursor = cursorType switch
- {
- CursorType.Pan => this.PanCursor,
- CursorType.ZoomRectangle => this.ZoomRectangleCursor,
- CursorType.ZoomHorizontal => this.ZoomHorizontalCursor,
- CursorType.ZoomVertical => this.ZoomVerticalCursor,
- _ => Cursors.Arrow,
- };
- }
- /// <summary>
- /// Shows the tracker.
- /// </summary>
- /// <param name="trackerHitResult">The tracker data.</param>
- public void ShowTracker(TrackerHitResult trackerHitResult)
- {
- if (trackerHitResult == null)
- {
- this.HideTracker();
- return;
- }
- var trackerTemplate = this.DefaultTrackerTemplate;
- if (trackerHitResult.Series != null && !string.IsNullOrEmpty(trackerHitResult.Series.TrackerKey))
- {
- var match = this.TrackerDefinitions.FirstOrDefault(t => t.TrackerKey == trackerHitResult.Series.TrackerKey);
- if (match != null)
- {
- trackerTemplate = match.TrackerTemplate;
- }
- }
- if (trackerTemplate == null)
- {
- this.HideTracker();
- return;
- }
- if (!ReferenceEquals(trackerTemplate, this.currentTrackerTemplate))
- {
- this.HideTracker();
- var tracker = new ContentControl { Template = trackerTemplate };
- this.overlays.Children.Add(tracker);
- this.currentTracker = tracker;
- this.currentTrackerTemplate = trackerTemplate;
- }
- if (this.currentTracker != null)
- {
- this.currentTracker.DataContext = trackerHitResult;
- }
- }
- /// <summary>
- /// Shows the zoom rectangle.
- /// </summary>
- /// <param name="r">The rectangle.</param>
- public void ShowZoomRectangle(OxyRect r)
- {
- this.zoomControl.Width = r.Width;
- this.zoomControl.Height = r.Height;
- Canvas.SetLeft(this.zoomControl, r.Left);
- Canvas.SetTop(this.zoomControl, r.Top);
- this.zoomControl.Template = this.ZoomRectangleTemplate;
- this.zoomControl.Visibility = Visibility.Visible;
- }
- /// <summary>
- /// Zooms all axes.
- /// </summary>
- /// <param name="factor">The zoom factor.</param>
- public void ZoomAllAxes(double factor)
- {
- if (this.ActualModel != null)
- {
- this.ActualModel.ZoomAllAxes(factor);
- }
- this.InvalidatePlot(false);
- }
- /// <summary>
- /// Clears the background of the plot presenter.
- /// </summary>
- protected abstract void ClearBackground();
- /// <summary>
- /// Creates the plot presenter.
- /// </summary>
- /// <returns>The plot presenter.</returns>
- protected abstract FrameworkElement CreatePlotPresenter();
- /// <summary>
- /// Creates the render context.
- /// </summary>
- /// <returns>The render context.</returns>
- protected abstract IRenderContext CreateRenderContext();
- /// <summary>
- /// Called when the model is changed.
- /// </summary>
- protected void OnModelChanged()
- {
- lock (this.modelLock)
- {
- if (this.ActualModel != null)
- {
- ((IPlotModel)this.ActualModel).AttachPlotView(null);
- this.ActualModel = null;
- }
- if (this.Model != null)
- {
- ((IPlotModel)this.Model).AttachPlotView(this);
- this.ActualModel = this.Model;
- }
- }
- this.InvalidatePlot();
- }
- /// <summary>
- /// Renders the plot model to the plot presenter.
- /// </summary>
- protected void Render()
- {
- if (this.plotPresenter == null || this.renderContext == null)
- {
- return;
- }
- this.isInVisualTree = this.IsInVisualTree();
- this.RenderOverride();
- }
- /// <summary>
- /// Renders the plot model to the plot presenter.
- /// </summary>
- protected virtual void RenderOverride()
- {
- var dpiScale = this.UpdateDpi();
- this.ClearBackground();
- if (this.ActualModel != null)
- {
- // round width and height to full device pixels
- var width = ((int)(this.plotPresenter.ActualWidth * dpiScale)) / dpiScale;
- var height = ((int)(this.plotPresenter.ActualHeight * dpiScale)) / dpiScale;
- lock (this.ActualModel.SyncRoot)
- {
- ((IPlotModel)this.ActualModel).Render(this.renderContext, new OxyRect(0, 0, width, height));
- }
- }
- }
- /// <summary>
- /// Updates the DPI scale of the render context.
- /// </summary>
- /// <returns>The DPI scale.</returns>
- protected virtual double UpdateDpi()
- {
- var transformMatrix = PresentationSource.FromVisual(this)?.CompositionTarget?.TransformToDevice;
- var scale = transformMatrix == null ? 1 : (transformMatrix.Value.M11 + transformMatrix.Value.M22) / 2;
- return scale;
- }
- /// <summary>
- /// Called when the model is changed.
- /// </summary>
- /// <param name="d">The sender.</param>
- /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs" /> instance containing the event data.</param>
- private static void ModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
- {
- ((PlotViewBase)d).OnModelChanged();
- }
- /// <summary>
- /// Invokes the specified action on the dispatcher, if necessary.
- /// </summary>
- /// <param name="action">The action.</param>
- private void BeginInvoke(Action action)
- {
- if (!this.Dispatcher.CheckAccess())
- {
- this.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, action);
- }
- else
- {
- action();
- }
- }
- /// <summary>
- /// Gets a value indicating whether the <see cref="PlotViewBase"/> is connected to the visual tree.
- /// </summary>
- /// <returns><c>true</c> if the PlotViewBase is connected to the visual tree; <c>false</c> otherwise.</returns>
- private bool IsInVisualTree()
- {
- DependencyObject dpObject = this;
- while ((dpObject = VisualTreeHelper.GetParent(dpObject)) != null)
- {
- if (dpObject is Window)
- {
- return true;
- }
- //Check if the parent is an AdornerDecorator like in an ElementHost
- if (dpObject is AdornerDecorator)
- {
- return true;
- }
- //Check if the logical parent is a popup. If so, we found the popuproot
- var logicalRoot = LogicalTreeHelper.GetParent(dpObject);
- if (logicalRoot is Popup)
- {
- return true;
- }
- }
- return false;
- }
- /// <summary>
- /// This event fires every time Layout updates the layout of the trees associated with current Dispatcher.
- /// </summary>
- /// <param name="sender">The sender.</param>
- /// <param name="e">The event args.</param>
- private void OnLayoutUpdated(object sender, EventArgs e)
- {
- // if we were not in the visual tree the last time we tried to render but are now, we have to render
- if (!this.isInVisualTree && this.IsInVisualTree())
- {
- this.Render();
- }
- }
- }
- }
|