953 lines
25 KiB
C#
953 lines
25 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Imaging;
|
|
using Microsoft.Research.DynamicDataDisplay.Common;
|
|
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
|
using System.Windows.Markup;
|
|
using System.ComponentModel;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Collections.Specialized;
|
|
using System.Windows.Input;
|
|
using Microsoft.Research.DynamicDataDisplay.Common.UndoSystem;
|
|
using Microsoft.Research.DynamicDataDisplay.Navigation;
|
|
using System.Windows.Data;
|
|
using Microsoft.Research.DynamicDataDisplay.Charts.Navigation;
|
|
using System.Windows.Automation.Peers;
|
|
using System.Diagnostics;
|
|
|
|
namespace Microsoft.Research.DynamicDataDisplay
|
|
{
|
|
/// <summary>Plotter is a base control for displaying various graphs. It provides
|
|
/// means to draw chart itself and side space for axes, annotations, etc</summary>
|
|
[ContentProperty("Children")]
|
|
[TemplatePart(Name = "PART_HeaderPanel", Type = typeof(StackPanel))]
|
|
[TemplatePart(Name = "PART_FooterPanel", Type = typeof(StackPanel))]
|
|
[TemplatePart(Name = "PART_BottomPanel", Type = typeof(StackPanel))]
|
|
[TemplatePart(Name = "PART_LeftPanel", Type = typeof(StackPanel))]
|
|
[TemplatePart(Name = "PART_RightPanel", Type = typeof(StackPanel))]
|
|
[TemplatePart(Name = "PART_TopPanel", Type = typeof(StackPanel))]
|
|
[TemplatePart(Name = "PART_MainCanvas", Type = typeof(Canvas))]
|
|
[TemplatePart(Name = "PART_CentralGrid", Type = typeof(Grid))]
|
|
[TemplatePart(Name = "PART_MainGrid", Type = typeof(Grid))]
|
|
[TemplatePart(Name = "PART_ContentsGrid", Type = typeof(Grid))]
|
|
[TemplatePart(Name = "PART_ParallelCanvas", Type = typeof(Canvas))]
|
|
public abstract class Plotter : ContentControl
|
|
{
|
|
private PlotterLoadMode loadMode = PlotterLoadMode.Normal;
|
|
protected PlotterLoadMode LoadMode
|
|
{
|
|
get { return loadMode; }
|
|
}
|
|
|
|
protected Plotter() : this(PlotterLoadMode.Normal) { }
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Plotter"/> class.
|
|
/// </summary>
|
|
protected Plotter(PlotterLoadMode loadMode)
|
|
{
|
|
this.loadMode = loadMode;
|
|
|
|
SetPlotter(this, this);
|
|
|
|
if (loadMode == PlotterLoadMode.Normal)
|
|
{
|
|
UpdateUIParts();
|
|
}
|
|
|
|
children = new PlotterChildrenCollection(this);
|
|
children.CollectionChanged += OnChildrenCollectionChanged;
|
|
Loaded += Plotter_Loaded;
|
|
Unloaded += Plotter_Unloaded;
|
|
|
|
genericResources = (ResourceDictionary)Application.LoadComponent(new Uri("/DynamicDataDisplay;component/Themes/Generic.xaml", UriKind.Relative));
|
|
|
|
ContextMenu = null;
|
|
}
|
|
|
|
void Plotter_Unloaded(object sender, RoutedEventArgs e)
|
|
{
|
|
OnUnloaded();
|
|
}
|
|
|
|
protected virtual void OnUnloaded() { }
|
|
|
|
protected override AutomationPeer OnCreateAutomationPeer()
|
|
{
|
|
return new PlotterAutomationPeer(this);
|
|
}
|
|
|
|
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
public override bool ShouldSerializeContent()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
protected override bool ShouldSerializeProperty(DependencyProperty dp)
|
|
{
|
|
// do not serialize context menu if it was created by DefaultContextMenu, because that context menu items contains references of plotter
|
|
if (dp == ContextMenuProperty && children.Any(el => el is DefaultContextMenu)) return false;
|
|
if (dp == TemplateProperty) return false;
|
|
if (dp == ContentProperty) return false;
|
|
|
|
return base.ShouldSerializeProperty(dp);
|
|
}
|
|
|
|
private const string templateKey = "defaultPlotterTemplate";
|
|
private const string styleKey = "defaultPlotterStyle";
|
|
private void UpdateUIParts()
|
|
{
|
|
ResourceDictionary dict = new ResourceDictionary
|
|
{
|
|
Source = new Uri("/DynamicDataDisplay;component/Common/PlotterStyle.xaml", UriKind.Relative)
|
|
};
|
|
|
|
Resources.MergedDictionaries.Add(dict);
|
|
|
|
Style = (Style)dict[styleKey];
|
|
|
|
ControlTemplate template = (ControlTemplate)dict[templateKey];
|
|
Template = template;
|
|
ApplyTemplate();
|
|
}
|
|
|
|
private ResourceDictionary genericResources;
|
|
protected ResourceDictionary GenericResources
|
|
{
|
|
get { return genericResources; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forces plotter to load.
|
|
/// </summary>
|
|
public void PerformLoad()
|
|
{
|
|
isLoadedIntensionally = true;
|
|
ApplyTemplate();
|
|
|
|
Plotter_Loaded(null, null);
|
|
}
|
|
|
|
private bool isLoadedIntensionally = false;
|
|
protected virtual bool IsLoadedInternal
|
|
{
|
|
get { return isLoadedIntensionally || IsLoaded; }
|
|
}
|
|
|
|
private void Plotter_Loaded(object sender, RoutedEventArgs e)
|
|
{
|
|
ExecuteWaitingChildrenAdditions();
|
|
|
|
OnLoaded();
|
|
}
|
|
|
|
protected internal void ExecuteWaitingChildrenAdditions()
|
|
{
|
|
foreach (var action in waitingForExecute)
|
|
{
|
|
action();
|
|
}
|
|
|
|
waitingForExecute.Clear();
|
|
}
|
|
|
|
protected virtual void OnLoaded()
|
|
{
|
|
// this is done to enable keyboard shortcuts
|
|
Focus();
|
|
|
|
//foreach (var plotterElement in elementsWaitingForBeingAttached)
|
|
//{
|
|
// if (Children.Contains(plotterElement))
|
|
// {
|
|
// plotterElement.OnPlotterAttached(this);
|
|
// }
|
|
//}
|
|
//elementsWaitingForBeingAttached.Clear();
|
|
}
|
|
|
|
protected override void OnTemplateChanged(ControlTemplate oldTemplate, ControlTemplate newTemplate)
|
|
{
|
|
base.OnTemplateChanged(oldTemplate, newTemplate);
|
|
}
|
|
|
|
private Grid contentsGrid;
|
|
public override void OnApplyTemplate()
|
|
{
|
|
base.OnApplyTemplate();
|
|
|
|
addedVisualElements.Clear();
|
|
|
|
foreach (var item in GetAllPanels())
|
|
{
|
|
INotifyingPanel panel = item as INotifyingPanel;
|
|
if (panel != null)
|
|
{
|
|
panel.ChildrenCreated -= notifyingItem_ChildrenCreated;
|
|
if (panel.NotifyingChildren != null)
|
|
{
|
|
panel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
|
|
}
|
|
}
|
|
}
|
|
|
|
var headerPanel = GetPart<StackPanel>("PART_HeaderPanel");
|
|
MigrateChildren(this.headerPanel, headerPanel);
|
|
this.headerPanel = headerPanel;
|
|
|
|
var footerPanel = GetPart<StackPanel>("PART_FooterPanel");
|
|
MigrateChildren(this.footerPanel, footerPanel);
|
|
this.footerPanel = footerPanel;
|
|
|
|
var leftPanel = GetPart<StackPanel>("PART_LeftPanel");
|
|
MigrateChildren(this.leftPanel, leftPanel);
|
|
this.leftPanel = leftPanel;
|
|
|
|
var bottomPanel = GetPart<StackPanel>("PART_BottomPanel");
|
|
MigrateChildren(this.bottomPanel, bottomPanel);
|
|
this.bottomPanel = bottomPanel;
|
|
|
|
var rightPanel = GetPart<StackPanel>("PART_RightPanel");
|
|
MigrateChildren(this.rightPanel, rightPanel);
|
|
this.rightPanel = rightPanel;
|
|
|
|
var topPanel = GetPart<StackPanel>("PART_TopPanel");
|
|
MigrateChildren(this.topPanel, topPanel);
|
|
this.topPanel = topPanel;
|
|
|
|
var mainCanvas = GetPart<Canvas>("PART_MainCanvas");
|
|
MigrateChildren(this.mainCanvas, mainCanvas);
|
|
this.mainCanvas = mainCanvas;
|
|
|
|
var centralGrid = GetPart<Grid>("PART_CentralGrid");
|
|
MigrateChildren(this.centralGrid, centralGrid);
|
|
this.centralGrid = centralGrid;
|
|
|
|
var mainGrid = GetPart<Grid>("PART_MainGrid");
|
|
MigrateChildren(this.mainGrid, mainGrid);
|
|
this.mainGrid = mainGrid;
|
|
|
|
var parallelCanvas = GetPart<Canvas>("PART_ParallelCanvas");
|
|
MigrateChildren(this.parallelCanvas, parallelCanvas);
|
|
this.parallelCanvas = parallelCanvas;
|
|
|
|
var contentsGrid = GetPart<Grid>("PART_ContentsGrid");
|
|
MigrateChildren(this.contentsGrid, contentsGrid);
|
|
this.contentsGrid = contentsGrid;
|
|
|
|
Content = contentsGrid;
|
|
AddLogicalChild(contentsGrid);
|
|
|
|
foreach (var notifyingItem in GetAllPanels())
|
|
{
|
|
INotifyingPanel panel = notifyingItem as INotifyingPanel;
|
|
if (panel != null)
|
|
{
|
|
if (panel.NotifyingChildren == null)
|
|
panel.ChildrenCreated += notifyingItem_ChildrenCreated;
|
|
else
|
|
panel.NotifyingChildren.CollectionChanged += OnVisualCollectionChanged;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void MigrateChildren(Panel previousParent, Panel currentParent)
|
|
{
|
|
if (previousParent != null && currentParent != null)
|
|
{
|
|
UIElement[] children = new UIElement[previousParent.Children.Count];
|
|
previousParent.Children.CopyTo(children, 0);
|
|
previousParent.Children.Clear();
|
|
|
|
foreach (var child in children)
|
|
{
|
|
if (!currentParent.Children.Contains(child))
|
|
{
|
|
currentParent.Children.Add(child);
|
|
}
|
|
}
|
|
}
|
|
else if (previousParent != null)
|
|
{
|
|
previousParent.Children.Clear();
|
|
}
|
|
}
|
|
|
|
private void notifyingItem_ChildrenCreated(object sender, EventArgs e)
|
|
{
|
|
INotifyingPanel panel = (INotifyingPanel)sender;
|
|
|
|
SubscribePanelEvents(panel);
|
|
}
|
|
|
|
private void SubscribePanelEvents(INotifyingPanel panel)
|
|
{
|
|
panel.ChildrenCreated -= notifyingItem_ChildrenCreated;
|
|
|
|
panel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
|
|
panel.NotifyingChildren.CollectionChanged += OnVisualCollectionChanged;
|
|
}
|
|
|
|
private void OnVisualCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (e.NewItems != null)
|
|
{
|
|
foreach (var item in e.NewItems)
|
|
{
|
|
INotifyingPanel notifyingPanel = item as INotifyingPanel;
|
|
if (notifyingPanel != null)
|
|
{
|
|
if (notifyingPanel.NotifyingChildren != null)
|
|
{
|
|
notifyingPanel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
|
|
notifyingPanel.NotifyingChildren.CollectionChanged += OnVisualCollectionChanged;
|
|
}
|
|
else
|
|
{
|
|
notifyingPanel.ChildrenCreated += notifyingItem_ChildrenCreated;
|
|
}
|
|
}
|
|
|
|
OnVisualChildAdded((UIElement)item, (UIElementCollection)sender);
|
|
}
|
|
}
|
|
if (e.OldItems != null)
|
|
{
|
|
foreach (var item in e.OldItems)
|
|
{
|
|
INotifyingPanel notifyingPanel = item as INotifyingPanel;
|
|
if (notifyingPanel != null)
|
|
{
|
|
notifyingPanel.ChildrenCreated -= notifyingItem_ChildrenCreated;
|
|
if (notifyingPanel.NotifyingChildren != null)
|
|
{
|
|
notifyingPanel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
|
|
}
|
|
}
|
|
|
|
OnVisualChildRemoved((UIElement)item, (UIElementCollection)sender);
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly VisualBindingCollection visualBindingCollection = new VisualBindingCollection();
|
|
public VisualBindingCollection VisualBindings
|
|
{
|
|
get { return visualBindingCollection; }
|
|
}
|
|
|
|
protected virtual void OnVisualChildAdded(UIElement target, UIElementCollection uIElementCollection)
|
|
{
|
|
IPlotterElement element = null;
|
|
if (addingElements.Count > 0)
|
|
{
|
|
element = addingElements.Peek();
|
|
|
|
var dict = visualBindingCollection.Cache;
|
|
var proxy = dict[element];
|
|
|
|
List<UIElement> visualElements;
|
|
if (!addedVisualElements.ContainsKey(element))
|
|
{
|
|
visualElements = new List<UIElement>();
|
|
addedVisualElements.Add(element, visualElements);
|
|
}
|
|
else
|
|
{
|
|
visualElements = addedVisualElements[element];
|
|
}
|
|
|
|
visualElements.Add(target);
|
|
|
|
SetBindings(proxy, target);
|
|
}
|
|
}
|
|
|
|
private void SetBindings(UIElement proxy, UIElement target)
|
|
{
|
|
if (proxy != target)
|
|
{
|
|
foreach (var property in GetPropertiesToSetBindingOn())
|
|
{
|
|
BindingOperations.SetBinding(target, property, new Binding { Path = new PropertyPath(property.Name), Source = proxy, Mode = BindingMode.TwoWay });
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RemoveBindings(UIElement proxy, UIElement target)
|
|
{
|
|
if (proxy != target)
|
|
{
|
|
foreach (var property in GetPropertiesToSetBindingOn())
|
|
{
|
|
BindingOperations.ClearBinding(target, property);
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerable<DependencyProperty> GetPropertiesToSetBindingOn()
|
|
{
|
|
yield return UIElement.OpacityProperty;
|
|
yield return UIElement.VisibilityProperty;
|
|
yield return UIElement.IsHitTestVisibleProperty;
|
|
//yield return FrameworkElement.DataContextProperty;
|
|
}
|
|
|
|
protected virtual void OnVisualChildRemoved(UIElement target, UIElementCollection uiElementCollection)
|
|
{
|
|
IPlotterElement element = null;
|
|
if (removingElements.Count > 0)
|
|
{
|
|
element = removingElements.Peek();
|
|
|
|
var dict = visualBindingCollection.Cache;
|
|
var proxy = dict[element];
|
|
|
|
if (addedVisualElements.ContainsKey(element))
|
|
{
|
|
var list = addedVisualElements[element];
|
|
list.Remove(target);
|
|
|
|
if (list.Count == 0)
|
|
{
|
|
dict.Remove(element);
|
|
}
|
|
|
|
addedVisualElements.Remove(element);
|
|
}
|
|
|
|
RemoveBindings(proxy, target);
|
|
}
|
|
}
|
|
|
|
internal virtual IEnumerable<Panel> GetAllPanels()
|
|
{
|
|
yield return headerPanel;
|
|
yield return footerPanel;
|
|
|
|
yield return leftPanel;
|
|
yield return bottomPanel;
|
|
yield return rightPanel;
|
|
yield return topPanel;
|
|
|
|
yield return mainCanvas;
|
|
yield return centralGrid;
|
|
yield return mainGrid;
|
|
yield return parallelCanvas;
|
|
yield return contentsGrid;
|
|
}
|
|
|
|
private T GetPart<T>(string name)
|
|
{
|
|
return (T)Template.FindName(name, this);
|
|
}
|
|
|
|
#region Children and add/removed events handling
|
|
|
|
private readonly PlotterChildrenCollection children;
|
|
|
|
/// <summary>
|
|
/// Provides access to Plotter's children charts.
|
|
/// </summary>
|
|
/// <value>The children.</value>
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
|
|
public PlotterChildrenCollection Children
|
|
{
|
|
[DebuggerStepThrough]
|
|
get { return children; }
|
|
}
|
|
|
|
private List<Action> waitingForExecute = new List<Action>();
|
|
|
|
bool executedWaitingChildrenAdding = false;
|
|
private void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
|
{
|
|
if (IsLoadedInternal && !executedWaitingChildrenAdding)
|
|
{
|
|
executedWaitingChildrenAdding = true;
|
|
ExecuteWaitingChildrenAdditions();
|
|
}
|
|
|
|
if (e.NewItems != null)
|
|
{
|
|
foreach (IPlotterElement item in e.NewItems)
|
|
{
|
|
if (IsLoadedInternal)
|
|
{
|
|
OnChildAdded(item);
|
|
}
|
|
else
|
|
{
|
|
waitingForExecute.Add(() => OnChildAdded(item));
|
|
}
|
|
}
|
|
}
|
|
if (e.OldItems != null)
|
|
{
|
|
foreach (IPlotterElement item in e.OldItems)
|
|
{
|
|
if (IsLoadedInternal)
|
|
{
|
|
OnChildRemoving(item);
|
|
}
|
|
else
|
|
{
|
|
waitingForExecute.Add(() => OnChildRemoving(item));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly Stack<IPlotterElement> addingElements = new Stack<IPlotterElement>();
|
|
|
|
bool performChildChecks = true;
|
|
internal bool PerformChildChecks
|
|
{
|
|
get { return performChildChecks; }
|
|
set { performChildChecks = value; }
|
|
}
|
|
|
|
private IPlotterElement currentChild = null;
|
|
protected IPlotterElement CurrentChild
|
|
{
|
|
get { return currentChild; }
|
|
}
|
|
|
|
protected virtual void OnChildAdded(IPlotterElement child)
|
|
{
|
|
if (child != null)
|
|
{
|
|
addingElements.Push(child);
|
|
currentChild = child;
|
|
try
|
|
{
|
|
UIElement visualProxy = CreateVisualProxy(child);
|
|
visualBindingCollection.Cache.Add(child, visualProxy);
|
|
|
|
if (performChildChecks && child.Plotter != null)
|
|
{
|
|
throw new InvalidOperationException(Strings.Exceptions.PlotterElementAddedToAnotherPlotter);
|
|
}
|
|
|
|
FrameworkElement styleableElement = child as FrameworkElement;
|
|
if (styleableElement != null)
|
|
{
|
|
Type key = styleableElement.GetType();
|
|
if (genericResources.Contains(key))
|
|
{
|
|
Style elementStyle = (Style)genericResources[key];
|
|
styleableElement.Style = elementStyle;
|
|
}
|
|
}
|
|
|
|
if (performChildChecks)
|
|
{
|
|
child.OnPlotterAttached(this);
|
|
if (child.Plotter != this)
|
|
{
|
|
throw new InvalidOperationException(Strings.Exceptions.InvalidParentPlotterValue);
|
|
}
|
|
}
|
|
|
|
DependencyObject dependencyObject = child as DependencyObject;
|
|
if (dependencyObject != null)
|
|
{
|
|
SetPlotter(dependencyObject, this);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
addingElements.Pop();
|
|
currentChild = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private UIElement CreateVisualProxy(IPlotterElement child)
|
|
{
|
|
if (visualBindingCollection.Cache.ContainsKey(child))
|
|
throw new InvalidOperationException(Strings.Exceptions.VisualBindingsWrongState);
|
|
|
|
UIElement result = child as UIElement;
|
|
|
|
if (result == null)
|
|
{
|
|
result = new UIElement();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private readonly Stack<IPlotterElement> removingElements = new Stack<IPlotterElement>();
|
|
protected virtual void OnChildRemoving(IPlotterElement child)
|
|
{
|
|
if (child != null)
|
|
{
|
|
currentChild = child;
|
|
removingElements.Push(child);
|
|
try
|
|
{
|
|
// todo probably here child.Plotter can be null.
|
|
if (performChildChecks && child.Plotter != this && child.Plotter != null)
|
|
{
|
|
throw new InvalidOperationException(Strings.Exceptions.InvalidParentPlotterValueRemoving);
|
|
}
|
|
|
|
if (performChildChecks)
|
|
{
|
|
if (child.Plotter != null)
|
|
child.OnPlotterDetaching(this);
|
|
|
|
if (child.Plotter != null)
|
|
{
|
|
throw new InvalidOperationException(Strings.Exceptions.ParentPlotterNotNull);
|
|
}
|
|
}
|
|
|
|
DependencyObject dependencyObject = child as DependencyObject;
|
|
if (dependencyObject != null)
|
|
{
|
|
SetPlotter(dependencyObject, null);
|
|
}
|
|
|
|
visualBindingCollection.Cache.Remove(child);
|
|
|
|
if (addedVisualElements.ContainsKey(child) && addedVisualElements[child].Count > 0)
|
|
{
|
|
throw new InvalidOperationException(String.Format(Strings.Exceptions.PlotterElementDidnotCleanedAfterItself, child.ToString()));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
currentChild = null;
|
|
removingElements.Pop();
|
|
}
|
|
}
|
|
}
|
|
|
|
private readonly Dictionary<IPlotterElement, List<UIElement>> addedVisualElements = new Dictionary<IPlotterElement, List<UIElement>>();
|
|
|
|
#endregion
|
|
|
|
#region Layout zones
|
|
|
|
private Panel parallelCanvas;
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Panel ParallelCanvas
|
|
{
|
|
get { return parallelCanvas; }
|
|
protected set { parallelCanvas = value; }
|
|
}
|
|
|
|
private Panel headerPanel;
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Panel HeaderPanel
|
|
{
|
|
get { return headerPanel; }
|
|
protected set { headerPanel = value; }
|
|
}
|
|
|
|
private Panel footerPanel;
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Panel FooterPanel
|
|
{
|
|
get { return footerPanel; }
|
|
protected set { footerPanel = value; }
|
|
}
|
|
|
|
private Panel leftPanel;
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Panel LeftPanel
|
|
{
|
|
get { return leftPanel; }
|
|
protected set { leftPanel = value; }
|
|
}
|
|
|
|
private Panel rightPanel;
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Panel RightPanel
|
|
{
|
|
get { return rightPanel; }
|
|
protected set { rightPanel = value; }
|
|
}
|
|
|
|
private Panel topPanel;
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Panel TopPanel
|
|
{
|
|
get { return topPanel; }
|
|
protected set { topPanel = value; }
|
|
}
|
|
|
|
private Panel bottomPanel;
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Panel BottomPanel
|
|
{
|
|
get { return bottomPanel; }
|
|
protected set { bottomPanel = value; }
|
|
}
|
|
|
|
private Panel mainCanvas;
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Panel MainCanvas
|
|
{
|
|
get { return mainCanvas; }
|
|
protected set { mainCanvas = value; }
|
|
}
|
|
|
|
private Panel centralGrid;
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Panel CentralGrid
|
|
{
|
|
get { return centralGrid; }
|
|
protected set { centralGrid = value; }
|
|
}
|
|
|
|
private Panel mainGrid;
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public Panel MainGrid
|
|
{
|
|
get { return mainGrid; }
|
|
protected set { mainGrid = value; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Screenshots & copy to clipboard
|
|
|
|
public BitmapSource CreateScreenshot()
|
|
{
|
|
|
|
UIElement parent = (UIElement)Parent;
|
|
|
|
Rect renderBounds = new Rect(RenderSize);
|
|
|
|
Point p1 = renderBounds.TopLeft;
|
|
Point p2 = renderBounds.BottomRight;
|
|
|
|
if (parent != null)
|
|
{
|
|
p1 = TranslatePoint(p1, parent);
|
|
p2 = TranslatePoint(p2, parent);
|
|
}
|
|
|
|
Int32Rect rect = new Rect(p1, p2).ToInt32Rect();
|
|
|
|
return ScreenshotHelper.CreateScreenshot(this, rect);
|
|
}
|
|
|
|
|
|
/// <summary>Saves screenshot to file.</summary>
|
|
/// <param name="filePath">File path.</param>
|
|
public void SaveScreenshot(string filePath)
|
|
{
|
|
ScreenshotHelper.SaveBitmapToFile(CreateScreenshot(), filePath);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves screenshot to stream.
|
|
/// </summary>
|
|
/// <param name="stream">The stream.</param>
|
|
/// <param name="fileExtension">The file type extension.</param>
|
|
public void SaveScreenshotToStream(Stream stream, string fileExtension)
|
|
{
|
|
ScreenshotHelper.SaveBitmapToStream(CreateScreenshot(), stream, fileExtension);
|
|
}
|
|
|
|
/// <summary>Copies the screenshot to clipboard.</summary>
|
|
public void CopyScreenshotToClipboard()
|
|
{
|
|
Clipboard.Clear();
|
|
Clipboard.SetImage(CreateScreenshot());
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsDefaultElement attached property
|
|
|
|
protected void SetAllChildrenAsDefault()
|
|
{
|
|
foreach (var child in Children.OfType<DependencyObject>())
|
|
{
|
|
child.SetValue(IsDefaultElementProperty, true);
|
|
}
|
|
}
|
|
|
|
/// <summary>Gets a value whether specified graphics object is default to this plotter or not</summary>
|
|
/// <param name="obj">Graphics object to check</param>
|
|
/// <returns>True if it is default or false otherwise</returns>
|
|
public static bool GetIsDefaultElement(DependencyObject obj)
|
|
{
|
|
return (bool)obj.GetValue(IsDefaultElementProperty);
|
|
}
|
|
|
|
public static void SetIsDefaultElement(DependencyObject obj, bool value)
|
|
{
|
|
obj.SetValue(IsDefaultElementProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty IsDefaultElementProperty = DependencyProperty.RegisterAttached(
|
|
"IsDefaultElement",
|
|
typeof(bool),
|
|
typeof(Plotter),
|
|
new UIPropertyMetadata(false));
|
|
|
|
/// <summary>Removes all user graphs from given UIElementCollection,
|
|
/// leaving only default graphs</summary>
|
|
protected static void RemoveUserElements(IList<IPlotterElement> elements)
|
|
{
|
|
int index = 0;
|
|
|
|
while (index < elements.Count)
|
|
{
|
|
DependencyObject d = elements[index] as DependencyObject;
|
|
if (d != null && !GetIsDefaultElement(d))
|
|
{
|
|
elements.RemoveAt(index);
|
|
}
|
|
else
|
|
{
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
public void RemoveUserElements()
|
|
{
|
|
RemoveUserElements(Children);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region IsDefaultAxis
|
|
|
|
public static bool GetIsDefaultAxis(DependencyObject obj)
|
|
{
|
|
return (bool)obj.GetValue(IsDefaultAxisProperty);
|
|
}
|
|
|
|
public static void SetIsDefaultAxis(DependencyObject obj, bool value)
|
|
{
|
|
obj.SetValue(IsDefaultAxisProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty IsDefaultAxisProperty = DependencyProperty.RegisterAttached(
|
|
"IsDefaultAxis",
|
|
typeof(bool),
|
|
typeof(Plotter),
|
|
new UIPropertyMetadata(false, OnIsDefaultAxisChanged));
|
|
|
|
private static void OnIsDefaultAxisChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
Plotter parentPlotter = null;
|
|
IPlotterElement plotterElement = d as IPlotterElement;
|
|
if (plotterElement != null)
|
|
{
|
|
parentPlotter = plotterElement.Plotter;
|
|
|
|
if (parentPlotter != null)
|
|
{
|
|
parentPlotter.OnIsDefaultAxisChangedCore(d, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
protected virtual void OnIsDefaultAxisChangedCore(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
|
|
|
|
#endregion
|
|
|
|
#region Undo
|
|
|
|
private readonly UndoProvider undoProvider = new UndoProvider();
|
|
public UndoProvider UndoProvider
|
|
{
|
|
get { return undoProvider; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Plotter attached property
|
|
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
public static Plotter GetPlotter(DependencyObject obj)
|
|
{
|
|
return (Plotter)obj.GetValue(PlotterProperty);
|
|
}
|
|
|
|
public static void SetPlotter(DependencyObject obj, Plotter value)
|
|
{
|
|
obj.SetValue(PlotterProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty PlotterProperty = DependencyProperty.RegisterAttached(
|
|
"Plotter",
|
|
typeof(Plotter),
|
|
typeof(Plotter),
|
|
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, OnPlotterChanged));
|
|
|
|
private static void OnPlotterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
FrameworkElement element = d as FrameworkElement;
|
|
Plotter prevPlotter = (Plotter)e.OldValue;
|
|
Plotter currPlotter = (Plotter)e.NewValue;
|
|
|
|
// raise Plotter[*] events, where * is Attached, Detaching, Changed.
|
|
if (element != null)
|
|
{
|
|
PlotterChangedEventArgs args = new PlotterChangedEventArgs(prevPlotter, currPlotter, PlotterDetachingEvent);
|
|
|
|
if (currPlotter == null && prevPlotter != null)
|
|
{
|
|
RaisePlotterEvent(element, args);
|
|
}
|
|
else if (currPlotter != null)
|
|
{
|
|
args.RoutedEvent = PlotterAttachedEvent;
|
|
RaisePlotterEvent(element, args);
|
|
}
|
|
|
|
args.RoutedEvent = PlotterChangedEvent;
|
|
RaisePlotterEvent(element, args);
|
|
}
|
|
}
|
|
|
|
private static void RaisePlotterEvent(FrameworkElement element, PlotterChangedEventArgs args)
|
|
{
|
|
element.RaiseEvent(args);
|
|
PlotterEvents.Notify(element, args);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Plotter routed events
|
|
|
|
public static readonly RoutedEvent PlotterAttachedEvent = EventManager.RegisterRoutedEvent(
|
|
"PlotterAttached",
|
|
RoutingStrategy.Direct,
|
|
typeof(PlotterChangedEventHandler),
|
|
typeof(Plotter));
|
|
|
|
public static readonly RoutedEvent PlotterDetachingEvent = EventManager.RegisterRoutedEvent(
|
|
"PlotterDetaching",
|
|
RoutingStrategy.Direct,
|
|
typeof(PlotterChangedEventHandler),
|
|
typeof(Plotter));
|
|
|
|
public static readonly RoutedEvent PlotterChangedEvent = EventManager.RegisterRoutedEvent(
|
|
"PlotterChanged",
|
|
RoutingStrategy.Direct,
|
|
typeof(PlotterChangedEventHandler),
|
|
typeof(Plotter));
|
|
|
|
#endregion
|
|
}
|
|
|
|
public delegate void PlotterChangedEventHandler(object sender, PlotterChangedEventArgs e);
|
|
}
|
|
|