1 //----------------------------------------------------------------
2 // <copyright company="Microsoft Corporation">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //----------------------------------------------------------------
7 namespace System.Activities.Presentation.View
10 using System.Activities.Presentation.Internal.PropertyEditing;
11 using System.Activities.Presentation.Model;
12 using System.Collections.Generic;
15 using System.Windows.Documents;
16 using System.Windows.Input;
17 using System.Windows.Media;
18 using System.Windows.Shapes;
20 internal sealed class RubberBandSelector
22 private const double RubberBandThickness = 2;
23 private EditingContext context;
24 private Rectangle rubberBand;
25 private List<WorkflowViewElement> views = new List<WorkflowViewElement>();
27 public RubberBandSelector(EditingContext context)
29 this.context = context;
30 this.InitializeRubberBand();
33 public bool IsSelected
39 // Relative to the scrollable content
40 private Point StartPoint
46 // Relative to the scrollable content
47 private Point EndPoint
53 // Relative to the scrollable content
54 private Point MouseDownPointToScreen { get; set; }
56 private bool IsSelecting
60 return this.ExtenstionSurface.Children.Contains(this.rubberBand);
64 private bool IsReadyForSelecting
70 private ExtensionSurface ExtenstionSurface
74 return this.Designer.wfViewExtensionSurface;
78 private DesignerView Designer
82 return this.context.Services.GetService<DesignerView>();
86 public void RegisterViewElement(WorkflowViewElement view)
88 if (!this.views.Contains(view))
94 public void UnregisterViewElement(WorkflowViewElement view)
96 if (this.views.Contains(view))
98 this.views.Remove(view);
102 public void OnScrollViewerMouseLeftButtonDown(MouseButtonEventArgs e)
104 this.IsSelected = false;
106 // Start rubber band selection if left button down is not handled by UI elements other than WorkflowViewElement.
107 if (!e.Handled || this.Designer.ShouldStillAllowRubberBandEvenIfMouseLeftButtonDownIsHandled)
109 if (DesignerView.IsMouseInViewport(e, this.Designer.scrollViewer) && !this.IsMouseOnDragHandle(e) && !this.IsMouseOverAdorner(e))
111 this.IsReadyForSelecting = true;
112 this.StartPoint = e.GetPosition(this.Designer.scrollableContent);
114 this.MouseDownPointToScreen = this.Designer.scrollableContent.PointToScreen(this.StartPoint);
115 this.EndPoint = this.StartPoint;
120 public void OnScrollViewerMouseMove(MouseEventArgs e)
122 if (e.LeftButton == MouseButtonState.Pressed && this.IsReadyForSelecting)
124 Point position = e.GetPosition(this.Designer.scrollableContent);
125 Point positionToScreen = this.Designer.scrollableContent.PointToScreen(position);
126 if (!this.IsSelecting && (Math.Abs(positionToScreen.X - this.MouseDownPointToScreen.X) > SystemParameters.MinimumHorizontalDragDistance ||
127 Math.Abs(positionToScreen.Y - this.MouseDownPointToScreen.Y) > SystemParameters.MinimumVerticalDragDistance))
129 this.AddRubberBand();
130 if (!this.Designer.scrollableContent.IsMouseCaptured)
132 this.Designer.scrollableContent.CaptureMouse();
138 if (this.IsSelecting)
140 this.EndPoint = position;
141 this.UpdateRubberBand();
142 AutoScrollHelper.AutoScroll(e.GetPosition(this.Designer.scrollViewer), this.Designer.scrollViewer, 0.2);
148 public void OnScrollViewerPreviewMouseLeftButtonUp(MouseEventArgs e)
150 if (this.IsSelecting)
152 this.EndPoint = e.GetPosition(this.Designer.scrollableContent);
153 this.Select(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl));
154 this.IsSelected = true;
157 this.StopSelecting();
160 public void OnScrollViewerMouseLeave()
162 this.StopSelecting();
165 public void OnScrollViewerEscapeKeyDown()
167 this.StopSelecting();
170 private static Point ClipPoint(Point point)
172 // Negative vaule means the top left corner of the rubber band is outside of the viewport.
173 // We need to clip the rubber band since a very negative value will cause some WPF display issues.
174 // But we use -RubberBandThickness instead of 0 to clip so that the border of the rubber band that should be outside of the viewport is still outside.
175 return new Point(point.X < -RubberBandThickness ? -RubberBandThickness : point.X, point.Y < -RubberBandThickness ? -RubberBandThickness : point.Y);
178 private void StopSelecting()
180 this.IsReadyForSelecting = false;
181 if (this.IsSelecting)
183 this.RemoveRubberBand();
184 if (this.Designer.scrollableContent.IsMouseCaptured)
186 this.Designer.scrollableContent.ReleaseMouseCapture();
191 private bool IsMouseOnDragHandle(MouseButtonEventArgs e)
193 HitTestResult result = VisualTreeHelper.HitTest(this.Designer.scrollableContent, e.GetPosition(this.Designer.scrollableContent));
196 WorkflowViewElement view = VisualTreeUtils.FindVisualAncestor<WorkflowViewElement>(result.VisualHit);
197 if (view != null && view.DragHandle != null)
199 GeneralTransform transform = view.DragHandle.TransformToAncestor(this.Designer);
200 Fx.Assert(transform != null, "transform should not be null");
201 Point topLeft = transform.Transform(new Point(0, 0));
202 Point bottomRight = transform.Transform(new Point(view.DragHandle.ActualWidth, view.DragHandle.ActualHeight));
203 Rect dragHandleRect = new Rect(topLeft, bottomRight);
204 if (dragHandleRect.Contains(e.GetPosition(this.Designer)))
214 private bool IsMouseOverAdorner(MouseButtonEventArgs e)
216 HitTestResult result = VisualTreeHelper.HitTest(this.Designer.scrollViewer, e.GetPosition(this.Designer.scrollViewer));
217 return result != null && VisualTreeUtils.FindVisualAncestor<Adorner>(result.VisualHit) != null;
220 private void AddRubberBand()
222 if (!this.ExtenstionSurface.Children.Contains(this.rubberBand))
224 this.ExtenstionSurface.Children.Add(this.rubberBand);
228 Fx.Assert(false, "Old rubber band was not correctly removed.");
232 private void RemoveRubberBand()
234 if (this.ExtenstionSurface.Children.Contains(this.rubberBand))
236 this.ExtenstionSurface.Children.Remove(this.rubberBand);
240 Fx.Assert(false, "Rubber band was not correctly added.");
244 private void UpdateRubberBand()
246 if (this.ExtenstionSurface.Children.Contains(this.rubberBand))
248 // Transform the start and end points to be relative to the extension surface by transforming to the common ancestor (DesignerView) first
249 GeneralTransform transform1 = this.Designer.scrollableContent.TransformToAncestor(this.Designer);
250 GeneralTransform transform2 = this.Designer.TransformToDescendant(this.ExtenstionSurface);
251 Point start = ClipPoint(transform2.Transform(transform1.Transform(this.StartPoint)));
252 Point end = ClipPoint(transform2.Transform(transform1.Transform(this.EndPoint)));
253 Rect rect = new Rect(start, end);
254 this.rubberBand.Width = rect.Width;
255 this.rubberBand.Height = rect.Height;
256 this.rubberBand.InvalidateVisual();
257 ExtensionSurface.SetPosition(this.rubberBand, rect.TopLeft);
261 Fx.Assert(false, "Rubber band was not correctly added.");
265 private void Select(bool isCtrlKeyDown)
267 bool isRubberBandEmpty = true;
268 Rect rubberBandRect = new Rect(this.StartPoint, this.EndPoint);
269 List<ModelItem> selectedModelItems = new List<ModelItem>();
270 foreach (WorkflowViewElement view in this.views)
272 GeneralTransform transform = view.TransformToAncestor(this.Designer.scrollableContent);
273 Point location = transform.Transform(new Point(0, 0));
274 Rect rect = new Rect(location.X, location.Y, view.ActualWidth, view.ActualHeight);
275 if (rubberBandRect.Contains(rect))
277 isRubberBandEmpty = false;
280 Selection.Toggle(this.context, view.ModelItem);
284 // Make sure the rubber-band selection has the same order
285 // and keyboard focus as ctrl+click one by one, which
286 // 1) model item is added in reverse order
287 // 2) last model item, which is the first in selection array,
289 selectedModelItems.Insert(0, view.ModelItem);
294 if (selectedModelItems.Count > 0)
296 Keyboard.Focus(selectedModelItems[0].View as IInputElement);
297 this.context.Items.SetValue(new Selection(selectedModelItems));
300 if (isRubberBandEmpty && !isCtrlKeyDown
301 && this.ShouldClearSelectioinIfNothingSelected())
303 this.context.Items.SetValue(new Selection());
307 private bool ShouldClearSelectioinIfNothingSelected()
309 Selection curSelection = this.context.Items.GetValue<Selection>();
310 if (curSelection == null || curSelection.SelectionCount == 0)
315 // only one ModelItem is selected and the ModelItem is root designer.
316 // do not clear selection
317 if (curSelection.SelectionCount == 1)
319 ModelItem item = curSelection.PrimarySelection;
320 WorkflowViewElement view = item == null ? null : (item.View as WorkflowViewElement);
321 if (view != null && view.IsRootDesigner)
330 private void InitializeRubberBand()
332 this.rubberBand = new Rectangle();
333 this.rubberBand.StrokeThickness = RubberBandThickness;
334 this.rubberBand.Stroke = WorkflowDesignerColors.RubberBandRectangleBrush;
335 this.rubberBand.Fill = WorkflowDesignerColors.RubberBandRectangleBrush.Clone();
336 this.rubberBand.Fill.Opacity = 0.2;