using System; using System.Collections.Generic; 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.Media.Animation; using System.Windows.Media.Imaging; using Tango.Core.Commands; using Tango.Editors; namespace Tango.Editors { /// /// Represents a collection container and editor. The editor supports Undo, Redo, Cut, Copy and Paste operations. /// /// /// /// /// public partial class ElementsEditor : HybridControl, IConfigurable, ISupportEditingOperations, ISupportUndoRedoOperations { private List _copiedElements; private bool _isSelectionMouseDown; //Determines whether the mouse is down for selection by the selection rectangle. private Point _selectionMouseDownPoint; //Holds the originating point to perform the selection. private bool _selectionPerformed; //Determines whether the selection rectangle has moved at least 1 pixel. #region Events /// /// Occurs when attempting to create a new element using the selection rectangle and holding the shift key. /// public event EventHandler ElementCreation; /// /// Occurs before removing selected elements using the DELETE key. /// public event EventHandler RemovingElements; /// /// Occurs when elements selection has changed;. /// public event EventHandler SelectionChanged; /// /// Occurs after pasting the currently copied elements. The collection of elements represents the new cloned elements. /// public event EventHandler AfterPaste; /// /// Occurs when one or many elements were added. /// public event EventHandler ElementsAdded; /// /// Occurs when one or many elements were removed. /// public event EventHandler ElementsRemoved; public event EventHandler ElementDoubleClicked; #endregion #region Constructors /// /// Initializes a new instance of the class. /// public ElementsEditor() { //Initialize Collections //Elements = new ObservableCollection(); SelectedElements = new ObservableCollection(); InitializeComponent(); //Initialize Default Undo/Redo States Provider. UndoRedoStatesProvider = new ElementsEditorUndoRedoStatesProvider(this); //Register Events. Loaded += ElementsEditor_Loaded; //Initialize Commands CopyCommand = new RelayCommand(Copy, (x) => GetSelectedElements().Count > 0); PasteCommand = new RelayCommand(Paste, (x) => _copiedElements != null && _copiedElements.Count > 0); UndoCommand = new RelayCommand(Undo, (x) => UndoRedoStatesProvider.CanUndo); RedoCommand = new RelayCommand(Redo, (x) => UndoRedoStatesProvider.CanRedo); DeleteCommand = new RelayCommand(() => { RemoveSelectedElements(true); }, (x) => GetSelectedElements().Count > 0); CutCommand = new RelayCommand(Cut, (x) => GetSelectedElements().Count > 0); ZoomCommand = new RelayCommand((str) => { Zoom(Convert.ToDouble(str)); }); ResetZoomCommand = new RelayCommand(() => { ScaleFactor = 1; }); SelectAllCommand = new RelayCommand(SelectAll); BringToFrontCommand = new RelayCommand(BringToFront, (x) => GetSelectedElements().Count > 0); SendToBackCommand = new RelayCommand(SendToBack, (x) => GetSelectedElements().Count > 0); } #endregion #region Properties public BitmapSource PreviewImage { get { return (BitmapSource)GetValue(PreviewImageProperty); } set { SetValue(PreviewImageProperty, value); } } public static readonly DependencyProperty PreviewImageProperty = DependencyProperty.Register("PreviewImage", typeof(BitmapSource), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets a value indicating whether to enable undo and redo operations using the keyboard. /// public bool EnableKeyboardUndoRedoOperations { get { return (bool)GetValue(EnableKeyboardUndoRedoOperationsProperty); } set { SetValue(EnableKeyboardUndoRedoOperationsProperty, value); } } public static readonly DependencyProperty EnableKeyboardUndoRedoOperationsProperty = DependencyProperty.Register("EnableKeyboardUndoRedoOperations", typeof(bool), typeof(ElementsEditor), new PropertyMetadata(true)); /// /// Gets or sets a value indicating whether to enable editing operations using the keyboard. /// public bool EnableKeyboardEditingOperations { get { return (bool)GetValue(EnableKeyboardEditingOperationsProperty); } set { SetValue(EnableKeyboardEditingOperationsProperty, value); } } public static readonly DependencyProperty EnableKeyboardEditingOperationsProperty = DependencyProperty.Register("EnableKeyboardEditingOperations", typeof(bool), typeof(ElementsEditor), new PropertyMetadata(true)); /// /// Gets or sets the width of the editor. /// public double EditorWidth { get { return (double)GetValue(EditorWidthProperty); } set { SetValue(EditorWidthProperty, value); } } public static readonly DependencyProperty EditorWidthProperty = DependencyProperty.Register("EditorWidth", typeof(double), typeof(ElementsEditor), new PropertyMetadata(1280.0)); /// /// Gets or sets the height of the editor. /// public double EditorHeight { get { return (double)GetValue(EditorHeightProperty); } set { SetValue(EditorHeightProperty, value); } } public static readonly DependencyProperty EditorHeightProperty = DependencyProperty.Register("EditorHeight", typeof(double), typeof(ElementsEditor), new PropertyMetadata(720.0)); /// /// Gets or sets the editor scale factor. /// public double ScaleFactor { get { return (double)GetValue(ScaleFactorProperty); } set { SetValue(ScaleFactorProperty, value); } } public static readonly DependencyProperty ScaleFactorProperty = DependencyProperty.Register("ScaleFactor", typeof(double), typeof(ElementsEditor), new PropertyMetadata(1.0, null, (d, e) => { return (d as ElementsEditor).OnCoerceScaleFactor(e); })); /// /// Gets or sets the collection of . /// public ObservableCollection Elements { get { return (ObservableCollection)GetValue(ElementsProperty); } set { SetValue(ElementsProperty, value); } } public static readonly DependencyProperty ElementsProperty = DependencyProperty.Register("Elements", typeof(ObservableCollection), typeof(ElementsEditor), new PropertyMetadata(null, (d, e) => { (d as ElementsEditor).OnElementsChanged(); })); /// /// Gets or sets the selected element. /// public IElementEditor SelectedElement { get { return (IElementEditor)GetValue(SelectedElementProperty); } set { SetValue(SelectedElementProperty, value); } } public static readonly DependencyProperty SelectedElementProperty = DependencyProperty.Register("SelectedElement", typeof(IElementEditor), typeof(ElementsEditor), new PropertyMetadata(null, (d, e) => { (d as ElementsEditor).OnSelectedElementChanged(); })); /// /// Gets or sets the selected elements. /// public ObservableCollection SelectedElements { get { return (ObservableCollection)GetValue(SelectedElementsProperty); } set { SetValue(SelectedElementsProperty, value); } } public static readonly DependencyProperty SelectedElementsProperty = DependencyProperty.Register("SelectedElements", typeof(ObservableCollection), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets the height of the ruler. /// public double RulerHeight { get { return (double)GetValue(RulerHeightProperty); } set { SetValue(RulerHeightProperty, value); } } public static readonly DependencyProperty RulerHeightProperty = DependencyProperty.Register("RulerHeight", typeof(double), typeof(ElementsEditor), new PropertyMetadata(22.0)); /// /// Gets or sets the undo redo states provider. /// public IUndoRedoStatesProvider UndoRedoStatesProvider { get { return (IUndoRedoStatesProvider)GetValue(UndoRedoStatesProviderProperty); } set { SetValue(UndoRedoStatesProviderProperty, value); } } public static readonly DependencyProperty UndoRedoStatesProviderProperty = DependencyProperty.Register("UndoRedoStatesProvider", typeof(IUndoRedoStatesProvider), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets a value indicating whether to bring the selected element to the front z index. /// public bool BringToFrontOnSelect { get { return (bool)GetValue(BringToFrontOnSelectProperty); } set { SetValue(BringToFrontOnSelectProperty, value); } } public static readonly DependencyProperty BringToFrontOnSelectProperty = DependencyProperty.Register("BringToFrontOnSelect", typeof(bool), typeof(ElementsEditor), new PropertyMetadata(true)); /// /// Gets or sets the editor mode. /// public ElementsEditorMode EditorMode { get { return (ElementsEditorMode)GetValue(EditorModeProperty); } set { SetValue(EditorModeProperty, value); } } public static readonly DependencyProperty EditorModeProperty = DependencyProperty.Register("EditorMode", typeof(ElementsEditorMode), typeof(ElementsEditor), new PropertyMetadata(ElementsEditorMode.Default)); /// /// Gets or sets a value indicating whether to enable the creation of new elements using the mouse and Shift key. /// public bool EnableElementCreation { get { return (bool)GetValue(EnableElementCreationProperty); } set { SetValue(EnableElementCreationProperty, value); } } public static readonly DependencyProperty EnableElementCreationProperty = DependencyProperty.Register("EnableElementCreation", typeof(bool), typeof(ElementsEditor), new PropertyMetadata(true)); /// /// Gets or sets an optional attached elements editor for editors mirroring mode. /// public ElementsEditor AttachedEditor { get { return (ElementsEditor)GetValue(AttachedEditorProperty); } set { SetValue(AttachedEditorProperty, value); } } public static readonly DependencyProperty AttachedEditorProperty = DependencyProperty.Register("AttachedEditor", typeof(ElementsEditor), typeof(ElementsEditor), new PropertyMetadata(null, (d, e) => (d as ElementsEditor).OnAttachedEditorChanged())); /// /// Gets or sets the preview visual source opacity. /// public double PreviewVisualSourceOpacity { get { return (double)GetValue(PreviewVisualSourceOpacityProperty); } set { SetValue(PreviewVisualSourceOpacityProperty, value); } } public static readonly DependencyProperty PreviewVisualSourceOpacityProperty = DependencyProperty.Register("PreviewVisualSourceOpacity", typeof(double), typeof(ElementsEditor), new PropertyMetadata(1.0)); /// /// Gets or sets a value indicating whether this instance is editable. /// public bool IsEditable { get { return (bool)GetValue(IsEditableProperty); } set { SetValue(IsEditableProperty, value); } } public static readonly DependencyProperty IsEditableProperty = DependencyProperty.Register("IsEditable", typeof(bool), typeof(ElementsEditor), new PropertyMetadata(true)); #endregion #region Attached Properties #region IsSelected /// /// Determines whether the element is currently selected. /// public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.RegisterAttached("IsSelected", typeof(bool), typeof(ElementsEditor), new FrameworkPropertyMetadata(false)); /// /// Sets the IsSelected attached property. /// /// The element. /// if set to true selected. public static void SetIsSelected(IElementEditor element, bool value) { (element as DependencyObject).SetValue(IsSelectedProperty, value); } /// /// Gets the is IsSelected attached property. /// /// The element. /// public static bool GetIsSelected(IElementEditor element) { return (bool)(element as DependencyObject).GetValue(IsSelectedProperty); } #endregion #endregion #region Theme Properties /// /// Gets or sets the ruler background. /// public Brush RulerBackground { get { return (Brush)GetValue(RulerBackgroundProperty); } set { SetValue(RulerBackgroundProperty, value); } } public static readonly DependencyProperty RulerBackgroundProperty = DependencyProperty.Register("RulerBackground", typeof(Brush), typeof(ElementsEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets or sets the editor background. /// public Brush EditorBackground { get { return (Brush)GetValue(EditorBackgroundProperty); } set { SetValue(EditorBackgroundProperty, value); } } public static readonly DependencyProperty EditorBackgroundProperty = DependencyProperty.Register("EditorBackground", typeof(Brush), typeof(ElementsEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets or sets the selection fill brush. /// public Brush SelectionFillBrush { get { return (Brush)GetValue(SelectionFillBrushProperty); } set { SetValue(SelectionFillBrushProperty, value); } } public static readonly DependencyProperty SelectionFillBrushProperty = DependencyProperty.Register("SelectionFillBrush", typeof(Brush), typeof(ElementsEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); /// /// Gets or sets the selection stroke brush. /// public Brush SelectionStrokeBrush { get { return (Brush)GetValue(SelectionStrokeBrushProperty); } set { SetValue(SelectionStrokeBrushProperty, value); } } public static readonly DependencyProperty SelectionStrokeBrushProperty = DependencyProperty.Register("SelectionStrokeBrush", typeof(Brush), typeof(ElementsEditor), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsRender)); #endregion #region Virtual Methods /// /// Invoked when the attached editor has changed. /// protected virtual void OnAttachedEditorChanged() { if (AttachedEditor != null) { UndoRedoStatesProvider = new AttachedElementsEditorsUndoRedoStatesProvider(this, AttachedEditor); AttachedEditor.UndoRedoStatesProvider = UndoRedoStatesProvider; } } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnElementsAdded(ElementsEventArgs e) { if (ElementsAdded != null) ElementsAdded(this, e); } /// /// Raises the event. /// /// The instance containing the event data. protected virtual void OnElementsRemoved(ElementsEventArgs e) { if (ElementsRemoved != null) ElementsRemoved(this, e); } /// /// Called when the reset scale factor button was clicked. /// /// The sender. /// The instance containing the event data. protected virtual void OnResetScaleFactor(object sender, MouseButtonEventArgs e) { ScaleFactor = 1; } /// /// Called when the selected element has changed. /// protected virtual void OnSelectedElementChanged() { if (SelectedElement != null) { if (BringToFrontOnSelect) { if (Elements.Count > 0) { Canvas.SetZIndex(SelectedElement as UIElement, Elements.Max(x => Canvas.GetZIndex(x as UIElement) + 1)); } } SetElementSelection(SelectedElement, true); if (AttachedEditor != null && AttachedEditor.SelectedElement != SelectedElement.AttachedEditor) { AttachedEditor.SelectedElement = SelectedElement.AttachedEditor; } } else { if (AttachedEditor != null) { AttachedEditor.SelectedElement = null; } } if (!IsCtrlDown()) { foreach (var element in Elements.Where(x => x != SelectedElement)) { SetElementSelection(element, false); } } OnSelectionChanged(); InvalidateRelayCommands(); } /// /// Raises the event. /// protected void OnSelectionChanged() { if (SelectionChanged != null) SelectionChanged(this, new ElementsEventArgs(GetSelectedElements())); } /// /// Called when the elements collection has changed. /// protected virtual void OnElementsChanged() { if (Elements != null) { RegisterElementsEvents(); Elements.CollectionChanged -= Elements_CollectionChanged; Elements.CollectionChanged += Elements_CollectionChanged; SetCanvasElements(); } InvalidateRelayCommands(); } /// /// Called when the hosting canvas has captured a mouse down event. /// /// The sender. /// The instance containing the event data. protected virtual void OnCanvasMouseDown(object sender, MouseButtonEventArgs e) { DeselectElements(); _selectionMouseDownPoint = e.GetPosition(selectionCanvas); _selectionPerformed = false; _isSelectionMouseDown = true; selectionRec.Width = 0; selectionRec.Height = 0; selectionCanvas.Visibility = System.Windows.Visibility.Visible; } /// /// Handles the canvas mouse up event. /// /// The sender. /// The instance containing the event data. protected virtual void OnCanvasMouseUp(object sender, MouseButtonEventArgs e) { if (_selectionPerformed) { _selectionPerformed = false; selectionCanvas.Visibility = System.Windows.Visibility.Hidden; e.Handled = true; if (IsShiftDown() && EnableElementCreation) { OnElementCreation(); } } _isSelectionMouseDown = false; } /// /// Raises the event. /// protected virtual void OnElementCreation() { PrepareUndoState(); var args = new ElementCreationEventArgs(Canvas.GetLeft(selectionRec), Canvas.GetTop(selectionRec), selectionRec.Width, selectionRec.Height); if (ElementCreation != null) ElementCreation(this, args); if (args.AppendUndoState) { CommitUndoState(); } } /// /// Handles the canvas mouse move event. /// /// The sender. /// The instance containing the event data. protected virtual void OnCanvasMouseMove(object sender, MouseEventArgs e) { if (_isSelectionMouseDown) { Point currentMousePoint = e.GetPosition(selectionCanvas); _selectionPerformed = currentMousePoint.X != _selectionMouseDownPoint.X || currentMousePoint.Y != _selectionMouseDownPoint.Y; Canvas.SetLeft(selectionRec, _selectionMouseDownPoint.X); Canvas.SetTop(selectionRec, _selectionMouseDownPoint.Y); if (currentMousePoint.X - _selectionMouseDownPoint.X > 1) { selectionRec.Width = currentMousePoint.X - _selectionMouseDownPoint.X; } if (currentMousePoint.Y - _selectionMouseDownPoint.Y > 1) { selectionRec.Height = currentMousePoint.Y - _selectionMouseDownPoint.Y; } if (currentMousePoint.X < _selectionMouseDownPoint.X) { Canvas.SetLeft(selectionRec, currentMousePoint.X); selectionRec.Width = _selectionMouseDownPoint.X - currentMousePoint.X; } if (currentMousePoint.Y < _selectionMouseDownPoint.Y) { Canvas.SetTop(selectionRec, currentMousePoint.Y); selectionRec.Height = _selectionMouseDownPoint.Y - currentMousePoint.Y; } if (!IsShiftDown()) { //Select intersecting objects Rect selectRect = new Rect(Canvas.GetLeft(selectionRec), Canvas.GetTop(selectionRec), selectionRec.Width, selectionRec.Height); var allElements = GetAllElements(); foreach (var element in allElements) { SetElementSelection(element, GetElementBounds(element).IntersectsWith(selectRect)); } var selectedElements = GetSelectedElements(); if (selectedElements.Count == 1) { SelectedElement = selectedElements.FirstOrDefault(); } } } } /// /// Called when coercing the scale factor. /// /// The value. /// protected virtual object OnCoerceScaleFactor(object value) { if ((double)value < 0.1) { return 0.1; } else { return value; } } /// /// Called when zooming with mouse wheel. /// /// The sender. /// The instance containing the event data. protected virtual void OnMouseWheelZooming(object sender, MouseWheelEventArgs e) { if (e.Delta > 0) //Ticks up { ScaleFactor += 0.1; } else //Ticks Down { ScaleFactor -= 0.1; } Point pointAbsolute = Mouse.GetPosition(gridCanvas); Point pointRelative = Mouse.GetPosition(scrollViewer); scrollViewer.ScrollToHorizontalOffset((pointAbsolute.X * ScaleFactor) - pointRelative.X); scrollViewer.ScrollToVerticalOffset((pointAbsolute.Y * ScaleFactor) - pointRelative.Y); } #endregion #region Event Handlers /// /// Handles the Loaded event of the ElementsEditor control. /// /// The source of the event. /// The instance containing the event data. private void ElementsEditor_Loaded(object sender, RoutedEventArgs e) { SetCanvasElements(); } /// /// Handles the CollectionChanged event of the Elements control. /// /// The source of the event. /// The instance containing the event data. private void Elements_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) { RegisterElementsEvents(); SetCanvasElements(); if (e.NewItems != null && e.NewItems.Count > 0) { OnElementsAdded(new ElementsEventArgs(e.NewItems.Cast().ToList())); } if (e.OldItems != null && e.OldItems.Count > 0) { OnElementsRemoved(new ElementsEventArgs(e.OldItems.Cast().ToList())); } } /// /// Handles the SelectionChanged event of the Element control. /// /// The source of the event. /// The instance containing the event data. /// private void Element_SelectionChanged(object sender, EventArgs e) { IElementEditor element = sender as IElementEditor; SelectedElement = element; } /// /// Handles the Moving event of the Element control. /// /// The source of the event. /// The instance containing the event data. /// private void Element_Moving(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e) { if (IsCtrlDown()) { GetSelectedElements().Where(x => x != sender).ToList().ForEach(x => x.PushMove(e)); } } /// /// Handles the AfterBoundsChange event of the Element control. /// /// The source of the event. /// The instance containing the event data. private void Element_AfterBoundsChange(object sender, EventArgs e) { CommitUndoState(); } /// /// Handles the BeforeBoundsChange event of the Element control. /// /// The source of the event. /// The instance containing the event data. private void Element_BeforeBoundsChange(object sender, EventArgs e) { PrepareUndoState(); } /// /// Handles the PreviewMouseDown event of the Element control. /// /// The source of the event. /// The instance containing the event data. private void Element_PreviewMouseDown(object sender, MouseButtonEventArgs e) { if (e.ChangedButton == MouseButton.Left && !Keyboard.IsKeyDown(Key.LeftShift)) { SelectedElement = sender as IElementEditor; } } #endregion #region Private Methods private void SetCanvasElements() { if (!this.IsInDesignMode()) { if (canvas != null && Elements != null) { foreach (UIElement element in Elements) if (!canvas.Children.Contains(element)) canvas.Children.Add(element); List removeList = new List(); foreach (UIElement element in canvas.Children) if (!Elements.Contains(element as IElementEditor)) removeList.Add(element); foreach (UIElement element in removeList) canvas.Children.Remove(element); } } } /// /// Determines whether the control key is down. /// private bool IsCtrlDown() { return Keyboard.IsKeyDown(Key.LeftCtrl); } /// /// Determines whether the shift control is down. /// private bool IsShiftDown() { return Keyboard.IsKeyDown(Key.LeftShift); } /// /// Registers the elements events. /// private void RegisterElementsEvents() { foreach (var element in Elements) { element.Moving -= Element_Moving; element.Moving += Element_Moving; element.BeforeBoundsChange -= Element_BeforeBoundsChange; element.BeforeBoundsChange += Element_BeforeBoundsChange; element.AfterBoundsChange -= Element_AfterBoundsChange; element.AfterBoundsChange += Element_AfterBoundsChange; if (element is FrameworkElement) { (element as FrameworkElement).PreviewMouseDown -= Element_PreviewMouseDown; (element as FrameworkElement).PreviewMouseDown += Element_PreviewMouseDown; (element as FrameworkElement).PreviewMouseDown -= ElementsEditor_PreviewMouseDown; (element as FrameworkElement).PreviewMouseDown += ElementsEditor_PreviewMouseDown; } } } private void ElementsEditor_PreviewMouseDown(object sender, MouseButtonEventArgs e) { if (e.ClickCount == 2) { ElementDoubleClicked?.Invoke(this, sender as IElementEditor); } } /// /// Prepares the state of the undo. /// private void PrepareUndoState() { UndoRedoStatesProvider.PrepareUndoState(); } /// /// Commits the state of the undo. /// private void CommitUndoState() { UndoRedoStatesProvider.CommitUndoState(); } /// /// Executes the undo/redo state. /// /// The state. private void ExecuteUndoRedoState(List state) { foreach (var setup in state) { setup.Animation.Completed += (x, y) => { var aniValue = setup.DependencyObject.GetValue(setup.DependencyProperty); (setup.DependencyObject as IAnimatable).BeginAnimation(setup.DependencyProperty, null); setup.DependencyObject.SetCurrentValue(setup.DependencyProperty, aniValue); }; (setup.DependencyObject as IAnimatable).BeginAnimation(setup.DependencyProperty, setup.Animation); } } /// /// Creates an undo/redo state. /// /// private List CreateUndoRedoState() { List all = new List(); foreach (var element in Elements) { var setups = element.GetAnimationSetups(TimeSpan.FromSeconds(0), AnimationSetupMode.Discrete); all.AddRange(setups); } return all; } /// /// Gets the element bounds. /// /// The element. /// private Rect GetElementBounds(IElementEditor element) { var visual = element as FrameworkElement; if (visual != null) { var position = visual.TranslatePoint(new Point(0, 0), selectionCanvas); return new Rect(position.X, position.Y, visual.ActualWidth, visual.ActualHeight); } else { return new Rect(); } } /// /// Gets the visual child. /// /// /// The parent. /// private static T GetVisualChild(DependencyObject parent) where T : Visual { T child = default(T); int numVisuals = VisualTreeHelper.GetChildrenCount(parent); for (int i = 0; i < numVisuals; i++) { Visual v = (Visual)VisualTreeHelper.GetChild(parent, i); child = v as T; if (child == null) { child = GetVisualChild(v); } if (child != null) { break; } } return child; } #endregion #region Public Methods /// /// De-selects all elements. /// public void DeselectElements() { Elements.ToList().ForEach(x => SetElementSelection(x, false)); SelectedElement = null; OnSelectionChanged(); InvalidateRelayCommands(); } /// /// Selects all elements. /// public void SelectAll() { Elements.ToList().ForEach(x => SetElementSelection(x, true)); OnSelectionChanged(); InvalidateRelayCommands(); } /// /// Increase or decrease the by the specified factor. /// /// The factor (e.g 0.2 or -0.5). public void Zoom(double factor) { ScaleFactor += factor; } /// /// Gets the selected elements. /// public List GetSelectedElements() { return GetAllElements().Where(x => GetIsSelected(x)).ToList(); } /// /// Gets all elements. /// /// public List GetAllElements() { return Elements.ToList(); } /// /// Removes the selected elements. /// public void RemoveSelectedElements(bool raiseRemoveEvent = false) { ElementsEventArgs args = new ElementsEventArgs(); if (raiseRemoveEvent) { args.Elements = GetSelectedElements(); if (RemovingElements != null) RemovingElements(this, args); } if (!raiseRemoveEvent || !args.Cancel) { PrepareUndoState(); var selectedElements = GetSelectedElements(); selectedElements.ForEach(x => RemoveElement(x)); if (AttachedEditor != null) { foreach (var element in selectedElements) { if (element.AttachedEditor != null) { AttachedEditor.RemoveElement(element.AttachedEditor); } } } CommitUndoState(); OnSelectionChanged(); InvalidateRelayCommands(); } } /// /// Removes the element. /// /// The element. public void RemoveElement(IElementEditor element) { SetElementSelection(element, false); Elements.Remove(element); if (SelectedElement == element) SelectedElement = null; OnSelectionChanged(); InvalidateRelayCommands(); } /// /// Gets the element by the hosted element. /// /// The hosted element. public IElementEditor GetElementByHostedElement(object hostedElement) { return Elements.SingleOrDefault(x => x.HostedElement == hostedElement); } /// /// Undoes the current state of the elements collection. /// public void Undo() { UndoRedoStatesProvider.Undo(); InvalidateRelayCommands(); } /// /// Redoes the current state of the elements collection. /// public void Redo() { UndoRedoStatesProvider.Redo(); InvalidateRelayCommands(); } /// /// Performs copy operation on the selected elements. /// public void Copy() { Copy(false); } private void Copy(bool fromAttached) { _copiedElements = GetSelectedElements(); InvalidateRelayCommands(); if (AttachedEditor != null && !fromAttached) { AttachedEditor.Copy(true); } } /// /// Performs a Cut operation over the selected elements. /// public void Cut() { Copy(); RemoveSelectedElements(); InvalidateRelayCommands(); } /// /// Pastes the last copied elements. /// public void Paste() { Paste(false, Mouse.GetPosition(gridCanvas), null); } private void Paste(bool fromAttached, Point point, List fromAttachedElements) { if (_copiedElements != null) { PrepareUndoState(); List elementsToAdd = new List(); var clonedElements = _copiedElements.Select(x => x.Clone()).ToList(); if (clonedElements.Count == 0) return; var mostLeft = clonedElements.OrderBy(x => x.Left).First().Left; var mostTop = clonedElements.OrderBy(x => x.Top).First().Top; int count = 0; foreach (var element in clonedElements) { element.Left += point.X - mostLeft; element.Top += point.Y - mostTop; elementsToAdd.Add(element); if (fromAttachedElements != null && fromAttachedElements.Count > 0) { fromAttachedElements[count].AttachedEditor = element; element.AttachedEditor = fromAttachedElements[count++]; } } if (AttachedEditor != null && !fromAttached) { AttachedEditor.Paste(true, point, clonedElements); } if (!fromAttached) { if (AfterPaste != null) AfterPaste(this, new ElementsEventArgs(clonedElements)); } foreach (var element in elementsToAdd) { Elements.Add(element); } CommitUndoState(); } InvalidateRelayCommands(); } /// /// Brings the new element Z-index in front of the old element. /// /// The old element. /// The new element. public void BringInFront(IElementEditor oldElement, IElementEditor newElement) { Canvas.SetZIndex(newElement as UIElement, Canvas.GetZIndex(oldElement as UIElement) + 1); } /// /// Brings the specified element to the Z-index front. /// /// The element. public void BringToFront(IElementEditor element) { element.ZIndex = Elements.Max(x => Canvas.GetZIndex(x as UIElement) + 1); } /// /// Brings all the selected items to the front. /// public void BringToFront() { foreach (var element in GetSelectedElements()) { BringToFront(element); } } /// /// Sends all the selected items to the back. /// public void SendToBack() { foreach (var element in GetSelectedElements()) { SendToBack(element); } } /// /// Sets the element Z-index. /// /// The element. /// The index. public void SetZIndex(IElementEditor element, int index) { Canvas.SetZIndex(element as UIElement, index); } /// /// Gets the element Z-index. /// /// The element. /// public int GetZIndex(IElementEditor element) { return Canvas.GetZIndex(element as UIElement); } /// /// Sends the specified element to the Z-index back. /// /// The element. public void SendToBack(IElementEditor element) { element.ZIndex = Elements.Min(x => Canvas.GetZIndex(x as UIElement) - 1); } #endregion #region Keyboard /// /// Invoked when an unhandled  attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event. /// /// The that contains the event data. protected override void OnPreviewKeyDown(KeyEventArgs e) { base.OnPreviewKeyDown(e); if (!IsEditable) return; if (e.Key == Key.Right) { GetSelectedElements().ForEach(x => x.PushMove(new DragDeltaEventArgs(1, 0))); } else if (e.Key == Key.Left) { GetSelectedElements().ForEach(x => x.PushMove(new DragDeltaEventArgs(-1, 0))); } else if (e.Key == Key.Up) { GetSelectedElements().ForEach(x => x.PushMove(new DragDeltaEventArgs(0, -1))); } else if (e.Key == Key.Down) { GetSelectedElements().ForEach(x => x.PushMove(new DragDeltaEventArgs(0, 1))); } else if (e.Key == Key.Z && IsCtrlDown() && EnableKeyboardUndoRedoOperations) { Undo(); } else if (e.Key == Key.Y && IsCtrlDown() && EnableKeyboardUndoRedoOperations) { Redo(); } else if (e.Key == Key.Delete) { RemoveSelectedElements(true); } else if (e.Key == Key.C && IsCtrlDown() && EnableKeyboardEditingOperations) { Copy(); } else if (e.Key == Key.V && IsCtrlDown() && EnableKeyboardEditingOperations) { Paste(); } else if (e.Key == Key.X && IsCtrlDown() && EnableKeyboardEditingOperations) { Cut(); } else if (e.Key == Key.A && IsCtrlDown()) { SelectAll(); } else if (e.Key == Key.F && IsCtrlDown()) { BringToFront(); } else if (e.Key == Key.B && IsCtrlDown()) { SendToBack(); } } #endregion #region Override Methods /// /// Invoked when an unhandled  attached event is raised on this element. Implement this method to add class handling for this event. /// /// The that contains the event data. protected override void OnMouseEnter(MouseEventArgs e) { base.OnMouseEnter(e); //scrollViewer.Focus(); } #endregion #region Commands /// /// Gets or sets the copy command. /// public RelayCommand CopyCommand { get { return (RelayCommand)GetValue(CopyCommandProperty); } set { SetValue(CopyCommandProperty, value); } } public static readonly DependencyProperty CopyCommandProperty = DependencyProperty.Register("CopyCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets the paste command. /// public RelayCommand PasteCommand { get { return (RelayCommand)GetValue(PasteCommandProperty); } set { SetValue(PasteCommandProperty, value); } } public static readonly DependencyProperty PasteCommandProperty = DependencyProperty.Register("PasteCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets the undo command. /// public RelayCommand UndoCommand { get { return (RelayCommand)GetValue(UndoCommandProperty); } set { SetValue(UndoCommandProperty, value); } } public static readonly DependencyProperty UndoCommandProperty = DependencyProperty.Register("UndoCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets the redo command. /// public RelayCommand RedoCommand { get { return (RelayCommand)GetValue(RedoCommandProperty); } set { SetValue(RedoCommandProperty, value); } } public static readonly DependencyProperty RedoCommandProperty = DependencyProperty.Register("RedoCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets the delete command. /// public RelayCommand DeleteCommand { get { return (RelayCommand)GetValue(DeleteCommandProperty); } set { SetValue(DeleteCommandProperty, value); } } public static readonly DependencyProperty DeleteCommandProperty = DependencyProperty.Register("DeleteCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets the cut command. /// public RelayCommand CutCommand { get { return (RelayCommand)GetValue(CutCommandProperty); } set { SetValue(CutCommandProperty, value); } } public static readonly DependencyProperty CutCommandProperty = DependencyProperty.Register("CutCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets the select all command. /// public RelayCommand SelectAllCommand { get { return (RelayCommand)GetValue(SelectAllCommandProperty); } set { SetValue(SelectAllCommandProperty, value); } } public static readonly DependencyProperty SelectAllCommandProperty = DependencyProperty.Register("SelectAllCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets the zoom command. /// public RelayCommand ZoomCommand { get { return (RelayCommand)GetValue(ZoomCommandProperty); } set { SetValue(ZoomCommandProperty, value); } } public static readonly DependencyProperty ZoomCommandProperty = DependencyProperty.Register("ZoomCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Gets or sets the reset zoom command. /// public RelayCommand ResetZoomCommand { get { return (RelayCommand)GetValue(ResetZoomCommandProperty); } set { SetValue(ResetZoomCommandProperty, value); } } public static readonly DependencyProperty ResetZoomCommandProperty = DependencyProperty.Register("ResetZoomCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Invokes bring to front on the selected item. /// public RelayCommand BringToFrontCommand { get { return (RelayCommand)GetValue(BringToFrontCommandProperty); } set { SetValue(BringToFrontCommandProperty, value); } } public static readonly DependencyProperty BringToFrontCommandProperty = DependencyProperty.Register("BringToFrontCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); /// /// Invokes send to back on the selected item. /// public RelayCommand SendToBackCommand { get { return (RelayCommand)GetValue(SendToBackCommandProperty); } set { SetValue(SendToBackCommandProperty, value); } } public static readonly DependencyProperty SendToBackCommandProperty = DependencyProperty.Register("SendToBackCommand", typeof(RelayCommand), typeof(ElementsEditor), new PropertyMetadata(null)); #endregion #region IConfigurable Members /// /// Gets a configuration object representing the configurable properties. This configuration can be serialized to a stream or file, and later be loaded and applied to the configurable object. /// /// public virtual IConfiguration GetConfiguration() { return GetConfiguration(); } /// /// Gets the configuration. /// /// Name of the configuration. /// public virtual ElementsEditorConfiguration GetConfiguration(String configurationName) { ElementsEditorConfiguration config = new ElementsEditorConfiguration(); config.Name = configurationName; config.Date = DateTime.Now; config.ElementsConfigurations = new ObservableCollection(Elements.Select(x => x.GetConfiguration() as ElementEditorConfiguration).ToList()); return config; } /// /// Applies the specified configuration with an optional animation if supported by the configurable type. /// /// The configuration. /// The animation. /// public virtual void SetConfiguration(IConfiguration configuration, ConfigurationAnimation animation) { if (!(configuration is ElementsEditorConfiguration)) { throw new InvalidConfigurationException(); } UndoRedoStatesProvider.Reset(); var config = configuration as ElementsEditorConfiguration; foreach (var elementConfig in config.ElementsConfigurations) { var existingElement = Elements.SingleOrDefault(x => x.ID == elementConfig.ID); if (existingElement != null) { existingElement.SetConfiguration(elementConfig, animation); } else { var newElement = elementConfig.CreateConfigurable(); Elements.Add(newElement); } } var elementsToRemove = Elements.Where(x => !config.ElementsConfigurations.ToList().Exists(y => y.ID == x.ID)).ToList(); elementsToRemove.ForEach(x => RemoveElement(x)); InvalidateRelayCommands(); } #endregion private void SetElementSelection(IElementEditor element, bool selected) { SetIsSelected(element, selected); if (element.AttachedEditor != null && GetIsSelected(element.AttachedEditor) != selected) { ElementsEditor.SetIsSelected(element.AttachedEditor, selected); } } } }