private void CalculateLimits(CustomCanvas canvas, IEnumerable <ContentPresenter> items, out double minLeft, out double minTop, out double minDeltaHorizontal, out double minDeltaVertical, out double maxDeltaHorizontal, out double maxDeltaVertical) { minLeft = double.MaxValue; minTop = double.MaxValue; minDeltaHorizontal = double.MaxValue; minDeltaVertical = double.MaxValue; maxDeltaHorizontal = double.MaxValue; maxDeltaVertical = double.MaxValue; foreach (ContentPresenter item in items) { // resize delta should not go out of canvas bounds (top and left) // min distance to top and left among all elements double left = Canvas.GetLeft(item) - BorderThreshold; double top = Canvas.GetTop(item) - BorderThreshold; minLeft = double.IsNaN(left) ? 0 : Math.Min(left, minLeft); minTop = double.IsNaN(top) ? 0 : Math.Min(top, minTop); // resize delta should not go over item minimum height or width // min distance to element minimum heigth and width minDeltaVertical = Math.Min(minDeltaVertical, item.ActualHeight - item.MinHeight); minDeltaHorizontal = Math.Min(minDeltaHorizontal, item.ActualWidth - item.MinWidth); // resize delta should not go out of canvas bounds (bottom and right) maxDeltaVertical = Math.Min(maxDeltaVertical, canvas.ActualHeight - Canvas.GetTop(item) - item.ActualHeight - BorderThreshold); maxDeltaHorizontal = Math.Min(maxDeltaHorizontal, canvas.ActualWidth - Canvas.GetLeft(item) - item.ActualWidth - BorderThreshold); } }
private List <ContentPresenter> GetChildElements(ContentPresenter element) { CustomCanvas elementCanvas = element.FindChild <CustomCanvas>(string.Empty); var childs = elementCanvas?.Children.Cast <ContentPresenter>().ToList(); return(childs); }
/// <summary> /// Initializes a new instance of the <see cref="RubberbandAdorner"/> class /// </summary> /// <param name="canvas">Parent canvas</param> /// <param name="dragStartPoint">Dragging start point</param> /// <param name="mode">Mode of selection rectangle</param> public RubberbandAdorner(CustomCanvas canvas, Point?dragStartPoint, SelectionRectnagleModes mode) : base(canvas) { this.canvas = canvas; this.startPoint = dragStartPoint; // choose color and selection behaviour based on mode switch (mode) { case SelectionRectnagleModes.Selection: { this.rubberandBrush = (Brush)Application.Current.FindResource("SelectionBrush"); this.toSelect = true; break; } case SelectionRectnagleModes.ChoiceBox: { this.rubberandBrush = (Brush)Application.Current.FindResource("MainItemsBrush"); this.toSelect = false; break; } case SelectionRectnagleModes.Grid: { this.rubberandBrush = (Brush)Application.Current.FindResource("MainItemsBrush"); this.toSelect = false; break; } } this.rubberbandPen = new Pen(this.rubberandBrush, 1); this.rubberbandPen.DashStyle = new DashStyle(new double[] { 2 }, 1); }
/// <summary> /// Handle thumb drag /// </summary> private void MoveThumbDragDelta(object sender, DragDeltaEventArgs e) { // dragging content - question or bubble DependencyObject content; // find dragging content - layout different for question and bubbles Adorner adorner = VisualTreeHelper.GetParent(this) as Adorner; if (adorner != null) { content = adorner.AdornedElement; } else { var grid = VisualTreeHelper.GetParent(this); content = VisualTreeHelper.GetParent(grid); } // presenter that holds omr item ContentPresenter presenter = (ContentPresenter)VisualTreeHelper.GetParent(content); // parent canvas CustomCanvas canvas = (CustomCanvas)VisualTreeHelper.GetParent(presenter); // get all selected elements List <ContentPresenter> presenters = ControlHelper.GetSelectedChildPresenters(canvas); double deltaHorizontal = e.HorizontalChange; double deltaVertical = e.VerticalChange; // check out of bounds for each item for (int i = 0; i < presenters.Count; i++) { if (Canvas.GetLeft(presenters[i]) + deltaHorizontal > canvas.ActualWidth - presenters[i].ActualWidth - BorderThreshold || Canvas.GetLeft(presenters[i]) + deltaHorizontal < BorderThreshold) { deltaHorizontal = 0; } if (Canvas.GetTop(presenters[i]) + deltaVertical > canvas.ActualHeight - presenters[i].ActualHeight - BorderThreshold || Canvas.GetTop(presenters[i]) + deltaVertical < BorderThreshold) { deltaVertical = 0; } } // apply change for each item for (int i = 0; i < presenters.Count; i++) { Canvas.SetLeft(presenters[i], Canvas.GetLeft(presenters[i]) + deltaHorizontal); Canvas.SetTop(presenters[i], Canvas.GetTop(presenters[i]) + deltaVertical); } // canvas.InvalidateMeasure(); e.Handled = true; }
/// <summary> /// Find canvas childs as content presenters list /// </summary> /// <param name="canvas">Searched canvas</param> /// <returns>List of content presenters</returns> public static List <ContentPresenter> GetChildPresenters(CustomCanvas canvas) { List <ContentPresenter> presenters = new List <ContentPresenter>(); int childsCount = VisualTreeHelper.GetChildrenCount(canvas); for (int i = 0; i < childsCount; i++) { presenters.Add(VisualTreeHelper.GetChild(canvas, i) as ContentPresenter); } return(presenters); }
/// <summary> /// Finds all selected base omr elements inside canvas /// </summary> /// <param name="canvas">Canvas containing searched items</param> /// <returns>List of content presenters with selected items</returns> public static List <ContentPresenter> GetSelectedChildPresenters(CustomCanvas canvas) { List <ContentPresenter> presenters = new List <ContentPresenter>(); int childsCount = VisualTreeHelper.GetChildrenCount(canvas); for (int i = 0; i < childsCount; i++) { ContentPresenter childPresenter = (ContentPresenter)VisualTreeHelper.GetChild(canvas, i); BaseOmrElement childOmrItem = (BaseOmrElement)VisualTreeHelper.GetChild(childPresenter, 0); if (childOmrItem.IsSelected) { presenters.Add(childPresenter); } } return(presenters); }
/// <summary> /// Snap single element to close elements on canvas /// </summary> /// <param name="movedItem">Item to snap</param> /// <param name="canvas">Main canvas holding all items</param> private void SnapElement(ContentPresenter movedItem, CustomCanvas canvas) { double itemLeft = Canvas.GetLeft(movedItem); double itemTop = Canvas.GetTop(movedItem); TemplateViewModel context = (TemplateViewModel)canvas.DataContext; // get list of close items according to snapDeltaPixels value List <ContentPresenter> presenters = ControlHelper.GetUnselectedChildPresenters(canvas).ToList(); List <ContentPresenter> closeByLeft = presenters.Where(x => Math.Abs(Canvas.GetLeft(x) - itemLeft) <= snapDeltaPixels).ToList(); List <ContentPresenter> closeByTop = presenters.Where(x => Math.Abs(Canvas.GetTop(x) - itemTop) <= snapDeltaPixels).ToList(); List <Line> resultingSnapLines = new List <Line>(); if (closeByLeft.Count > 0) { // closest by left position and then by top ContentPresenter closest = closeByLeft.OrderBy(x => Math.Abs(Canvas.GetLeft(x) - itemLeft)) .ThenBy(x => Math.Abs(Canvas.GetTop(x) - itemTop)).First(); // move snapped item to desired position double targetLeft = Canvas.GetLeft(closest); double targetTop = Canvas.GetTop(closest); Canvas.SetLeft(movedItem, targetLeft); // add lowest item height to the line length double itemHeight = targetTop > itemTop ? closest.Height : movedItem.Height; // draw snap line and add to the list Line line = new Line(); line.X1 = line.X2 = targetLeft; line.Y1 = Math.Max(Math.Min(itemTop, targetTop) - snapLineTail, 0); line.Y2 = Math.Max(itemTop, targetTop) + itemHeight + snapLineTail; resultingSnapLines.Add(line); } if (closeByTop.Count > 0) { // closes by top position and then by left ContentPresenter closest = closeByTop.OrderBy(x => Math.Abs(Canvas.GetTop(x) - itemTop)) .ThenBy(x => Math.Abs(Canvas.GetLeft(x) - itemLeft)).First(); // move snapped item to desired position double targetLeft = Canvas.GetLeft(closest); double targetTop = Canvas.GetTop(closest); Canvas.SetTop(movedItem, targetTop); double itemWidth = targetLeft > itemLeft ? closest.Width : movedItem.Width; // draw snap line and add to the list Line line = new Line(); line.Y1 = line.Y2 = targetTop; line.X1 = Math.Max(Math.Min(itemLeft, targetLeft) - snapLineTail, 0); line.X2 = Math.Max(itemLeft, targetLeft) + itemWidth + snapLineTail; resultingSnapLines.Add(line); } if (context.GotSnapLines) { context.CleanSnapLines(); } // draw snap lines foreach (Line snapLine in resultingSnapLines) { context.AddLine(snapLine); } }
/// <summary> /// Snap group of elements /// </summary> /// <param name="movedGroup">Group of elements to snap</param> /// <param name="canvas">Main canvas holding all items</param> private void SnapGroup(List <ContentPresenter> movedGroup, CustomCanvas canvas) { // get bounding box Rect of snap group Rect boundingBox = GetBoundingBox(movedGroup); TemplateViewModel context = (TemplateViewModel)canvas.DataContext; List <ContentPresenter> canvasItems = ControlHelper.GetUnselectedChildPresenters(canvas).ToList(); // get list of close items according to snapDeltaPixels value List <Line> resultingSnapLines = new List <Line>(); List <ContentPresenter> closeByLeft = canvasItems.Where(x => Math.Abs(Canvas.GetLeft(x) - boundingBox.Left) <= snapDeltaPixels).ToList(); List <ContentPresenter> closeByTop = canvasItems.Where(x => Math.Abs(Canvas.GetTop(x) - boundingBox.Top) <= snapDeltaPixels).ToList(); if (closeByLeft.Count > 0) { // closes by left position and then by top ContentPresenter closest = closeByLeft.OrderBy(x => Math.Abs(Canvas.GetLeft(x) - boundingBox.Left)) .ThenBy(x => Math.Abs(Canvas.GetTop(x) - boundingBox.Top)).First(); double targetLeft = Canvas.GetLeft(closest); double targetTop = Canvas.GetTop(closest); // snap each item of the group according to calculated distance to the target double moveDelta = boundingBox.Left - targetLeft; movedGroup.ForEach(x => Canvas.SetLeft(x, Canvas.GetLeft(x) - moveDelta)); // add lowest item height to the line length double itemHeight = targetTop > boundingBox.Top ? closest.Height : boundingBox.Height; // draw snap line and add to the list Line line = new Line(); line.X1 = line.X2 = targetLeft; line.Y1 = Math.Max(Math.Min(boundingBox.Top, targetTop) - snapLineTail, 0); line.Y2 = Math.Max(boundingBox.Top, targetTop) + itemHeight + snapLineTail; resultingSnapLines.Add(line); } if (closeByTop.Count > 0) { // closes by top position and then by left ContentPresenter closest = closeByTop.OrderBy(x => Math.Abs(Canvas.GetTop(x) - boundingBox.Top)) .ThenBy(x => Math.Abs(Canvas.GetLeft(x) - boundingBox.Left)).First(); double targetLeft = Canvas.GetLeft(closest); double targetTop = Canvas.GetTop(closest); // snap each item of the group according to calculated distance to the target double moveDelta = boundingBox.Top - targetTop; movedGroup.ForEach(x => Canvas.SetTop(x, Canvas.GetTop(x) - moveDelta)); double itemWidth = targetLeft > boundingBox.Left ? closest.Width : boundingBox.Width; // draw snap line and add to the list Line line = new Line(); line.Y1 = line.Y2 = targetTop; line.X1 = Math.Max(Math.Min(boundingBox.Left, targetLeft) - snapLineTail, 0); line.X2 = Math.Max(boundingBox.Left, targetLeft) + itemWidth + snapLineTail; resultingSnapLines.Add(line); } if (context.GotSnapLines) { context.CleanSnapLines(); } // draw snap lines foreach (Line snapLine in resultingSnapLines) { context.AddLine(snapLine); } }
/// <summary> /// Occurs as the mouse changes position when a Thumb control has logical focus and mouse capture /// </summary> /// <param name="sender">The sender</param> /// <param name="e">The event args</param> private void ResizeDragDelta(object sender, DragDeltaEventArgs e) { // get to resized element Grid thumbsGrid = (Grid)VisualTreeHelper.GetParent(this); ResizeThumb parentThumb = (ResizeThumb)VisualTreeHelper.GetParent(thumbsGrid); Adorner adorner = (Adorner)VisualTreeHelper.GetParent(parentThumb); // presenter that holds dragged item ContentPresenter presenter = (ContentPresenter)VisualTreeHelper.GetParent(adorner.AdornedElement); // parent canvas CustomCanvas canvas = (CustomCanvas)VisualTreeHelper.GetParent(presenter); // prepare items for resize depending on item level (question or bubble) List <ContentPresenter> presenters; if (canvas.Name.Equals(Properties.Resources.RootCanvasName)) { // here we resize questions (e.g. choicebox), so take selected items presenters = ControlHelper.GetSelectedChildPresenters(canvas); } else { // here we resize bubbles, so get neighbor bubbles inside question (always resize bubbles equally) presenters = ControlHelper.GetChildPresenters(canvas); } double minLeft, minTop, minDeltaHorizontal, minDeltaVertical; double maxDeltaVertical, maxDeltaHorizontal; double maxHorizontalDeltaToChild = double.MaxValue; double maxVerticalDeltaToChild = double.MaxValue; this.CalculateLimits(canvas, presenters, out minLeft, out minTop, out minDeltaHorizontal, out minDeltaVertical, out maxDeltaHorizontal, out maxDeltaVertical); minDeltaVertical = Math.Min(minDeltaVertical, maxVerticalDeltaToChild); minDeltaHorizontal = Math.Min(minDeltaHorizontal, maxHorizontalDeltaToChild); foreach (ContentPresenter element in presenters) { double newHeight = element.ActualHeight; double newWidth = element.ActualWidth; double newTop = Canvas.GetTop(element); double newLeft = Canvas.GetLeft(element); double scale, dragDeltaVertical; switch (this.VerticalAlignment) { case VerticalAlignment.Bottom: dragDeltaVertical = Math.Max(Math.Min(-e.VerticalChange, minDeltaVertical), -maxDeltaVertical); newHeight = element.ActualHeight - dragDeltaVertical; break; case VerticalAlignment.Top: dragDeltaVertical = Math.Min(Math.Max(-minTop, e.VerticalChange), minDeltaVertical); scale = (element.ActualHeight - dragDeltaVertical) / element.ActualHeight; newTop = newTop - element.Height * (scale - 1); newHeight = element.ActualHeight - dragDeltaVertical; break; } double dragDeltaHorizontal; switch (this.HorizontalAlignment) { case HorizontalAlignment.Left: dragDeltaHorizontal = Math.Min(Math.Max(-minLeft, e.HorizontalChange), minDeltaHorizontal); scale = (element.ActualWidth - dragDeltaHorizontal) / element.ActualWidth; newLeft = Canvas.GetLeft(element) - element.Width * (scale - 1); newWidth = element.ActualWidth * scale; break; case HorizontalAlignment.Right: dragDeltaHorizontal = Math.Max(Math.Min(-e.HorizontalChange, minDeltaHorizontal), -maxDeltaHorizontal); newWidth = element.ActualWidth - dragDeltaHorizontal; break; } var childs = this.GetChildElements(element); if (childs == null) { element.Width = newWidth; element.Height = newHeight; Canvas.SetTop(element, newTop); Canvas.SetLeft(element, newLeft); } else { double scaleH = newHeight / element.ActualHeight; double scaleW = newWidth / element.ActualWidth; if (this.CanTransformChildsRecursive(childs, scaleH, scaleW)) { this.TransformChildsRecursive(childs, scaleH, scaleW); element.Width = newWidth; element.Height = newHeight; Canvas.SetTop(element, newTop); Canvas.SetLeft(element, newLeft); } } } e.Handled = true; }