1 //----------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation. All rights reserved.
3 //----------------------------------------------------------------
4 namespace System.Activities.Presentation.View
7 using System.Collections.Generic;
8 using System.ComponentModel;
10 using System.Windows.Controls;
11 using System.Windows.Media;
12 using System.Globalization;
14 using System.Diagnostics.CodeAnalysis;
16 //This class is responsible for providing functionality to display additional information in context of
17 //the designer view in a popup-like manner. It is basically the canvas control, which is placed on top of
18 //the other visual elements. It provides functionality to add and remove extension windows, as well as manipulating
19 //their position and size
20 sealed class ExtensionSurface : Panel
23 public static readonly DependencyProperty DesignerProperty = DependencyProperty.Register(
26 typeof(ExtensionSurface),
27 new PropertyMetadata(OnDesignerChanged));
29 public static readonly DependencyProperty AutoExpandCanvasProperty = DependencyProperty.Register(
32 typeof(ExtensionSurface),
33 new UIPropertyMetadata(false));
35 public static readonly DependencyProperty PlacementTargetProperty = DependencyProperty.RegisterAttached(
37 typeof(FrameworkElement),
38 typeof(ExtensionSurface),
39 new UIPropertyMetadata(null, OnPlacementTargetChanged));
41 public static readonly DependencyProperty AlignmentProperty = DependencyProperty.RegisterAttached(
43 typeof(PositionAlignment),
44 typeof(ExtensionSurface),
45 new UIPropertyMetadata(PositionAlignment.LeftTop));
47 public static readonly DependencyProperty ModeProperty = DependencyProperty.RegisterAttached(
49 typeof(PlacementMode),
50 typeof(ExtensionSurface),
51 new UIPropertyMetadata(PlacementMode.Absolute, OnPlacementModeChanged));
53 public static readonly DependencyProperty PositionProperty = DependencyProperty.RegisterAttached(
56 typeof(ExtensionSurface),
57 new UIPropertyMetadata(new Point()));
60 Func<double, double, double, bool> IsGreater;
62 KeyValuePair<FrameworkElement, Point> selectedChild;
63 Size rearangeStartSize = new Size();
64 Rect actualPanelRect = new Rect(0, 0, 0, 0);
65 Point canvasOffset = new Point();
66 int currentZIndex = 1000;
68 public ExtensionSurface()
70 //add global handled for ExtensionWindow's CloseEvent
71 this.AddHandler(ExtensionWindow.CloseEvent, new RoutedEventHandler(OnExtensionWindowClosed));
72 this.ClipToBounds = true;
73 this.IsGreater = (v1, v2, v3) => (v1 + v2 > v3);
76 [Fx.Tag.KnownXamlExternal]
77 public DesignerView Designer
79 get { return (DesignerView)GetValue(DesignerProperty); }
80 set { SetValue(DesignerProperty, value); }
83 public bool AutoExpandCanvas
85 get { return (bool)GetValue(AutoExpandCanvasProperty); }
86 set { SetValue(AutoExpandCanvasProperty, value); }
89 static void OnPlacementModeChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
91 ExtensionWindow window = sender as ExtensionWindow;
92 if (null != window && null != window.Surface && window.Visibility == Visibility.Visible)
94 window.Surface.PlaceWindow(window);
98 static void OnPlacementTargetChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
103 //hook for designer mouse events - they are required to handle positioning and resizing
104 static void OnDesignerChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
106 ExtensionSurface ctrl = (ExtensionSurface)sender;
107 DesignerView designer;
108 if (null != args.OldValue)
110 designer = (DesignerView)args.OldValue;
112 if (null != args.NewValue)
114 designer = (DesignerView)args.NewValue;
118 protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
120 ExtensionWindow window = visualRemoved as ExtensionWindow;
123 window.VisibilityChanged -= OnWindowVisibilityChanged;
124 // window.SizeChanged -= OnWindowSizeChanged;
125 this.rearangeStartSize.Width = 0;
126 this.rearangeStartSize.Height = 0;
129 base.OnVisualChildrenChanged(visualAdded, visualRemoved);
131 window = visualAdded as ExtensionWindow;
134 window.VisibilityChanged += OnWindowVisibilityChanged;
135 // window.SizeChanged += OnWindowSizeChanged;
136 if (!window.IsLoaded)
138 window.Loaded += OnChildWindowLoaded;
143 protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
145 base.OnRenderSizeChanged(sizeInfo);
146 foreach (FrameworkElement child in this.Children)
148 ExtensionWindow window = child as ExtensionWindow;
151 if (PlacementMode.Relative == GetMode(window) && null != GetPlacementTarget(window))
153 this.PlaceWindow(window);
156 if (!this.AutoExpandCanvas)
158 this.EnsureWindowIsVisible(window);
164 void OnChildWindowLoaded(object sender, EventArgs e)
166 ExtensionWindow window = (ExtensionWindow)sender;
167 this.OnWindowVisibilityChanged(window, null);
168 window.Loaded -= OnChildWindowLoaded;
171 //void OnWindowSizeChanged(object sender, SizeChangedEventArgs e)
173 // ExtensionWindow window = (ExtensionWindow)sender;
174 // // EnsureWindowIsVisible(window);
177 void OnWindowVisibilityChanged(object sender, RoutedEventArgs args)
179 ExtensionWindow window = (ExtensionWindow)sender;
180 if (window.IsVisible)
182 Func<double, bool> IsInvalid = x => (double.IsInfinity(x) || double.IsNaN(x) || double.Epsilon > x);
184 if (IsInvalid(window.ActualWidth) || IsInvalid(window.ActualWidth) || IsInvalid(window.DesiredSize.Width) || IsInvalid(window.DesiredSize.Height))
186 window.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
192 void PlaceWindow(ExtensionWindow window)
196 FrameworkElement target = ExtensionSurface.GetPlacementTarget(window);
197 PositionAlignment alignment = ExtensionSurface.GetAlignment(window);
198 PlacementMode mode = ExtensionSurface.GetMode(window);
199 Point position = ExtensionSurface.GetPosition(window);
201 Point calculatedPosition = new Point();
202 FrameworkElement commonRoot = null;
203 MatrixTransform transform = null;
207 case PlacementMode.Relative:
210 commonRoot = target.FindCommonVisualAncestor(this) as FrameworkElement;
211 if (null == commonRoot)
215 transform = (MatrixTransform)target.TransformToAncestor(commonRoot);
219 if (!DesignerProperties.GetIsInDesignMode(this))
221 Fx.Assert(string.Format(CultureInfo.InvariantCulture, "PlacementTarget must be set in RelativeMode on ExtensionSurface '{0}'", this.Name));
226 case PlacementMode.Absolute:
227 calculatedPosition = position;
231 Fx.Assert(string.Format(CultureInfo.CurrentCulture, "ExtensionWindowPlacement.Mode {0} specified in ExtensionWindow '{1}' is not supported for ExtensionSurface", mode, window.Name));
235 if (PlacementMode.Relative == mode)
243 case PositionAlignment.LeftTop:
244 calculatedPosition = transform.Transform(calculatedPosition);
247 case PositionAlignment.LeftBottom:
248 calculatedPosition = transform.Transform(new Point(0.0, target.ActualHeight));
251 case PositionAlignment.RightTop:
252 calculatedPosition = transform.Transform(new Point(target.ActualWidth, 0.0));
255 case PositionAlignment.RightBottom:
256 calculatedPosition = transform.Transform(new Point(target.ActualWidth, target.ActualHeight));
259 case PositionAlignment.Center:
260 calculatedPosition = transform.Transform(calculatedPosition);
261 x = ((target.ActualWidth * transform.Matrix.M11) - window.Width) / 2.0;
262 y = ((target.ActualHeight * transform.Matrix.M22) - window.Height) / 2.0;
263 calculatedPosition.Offset(x, y);
266 case PositionAlignment.CenterHorizontal:
267 calculatedPosition = transform.Transform(calculatedPosition);
268 x = ((target.ActualWidth * transform.Matrix.M11) - window.Width) / 2.0;
269 calculatedPosition.Offset(x, 0.0);
272 case PositionAlignment.CenterVertical:
273 calculatedPosition = transform.Transform(calculatedPosition);
274 y = ((target.ActualHeight * transform.Matrix.M22) - window.Height) / 2.0;
275 calculatedPosition.Offset(0.0, y);
279 Fx.Assert(string.Format(CultureInfo.CurrentCulture, "ExtensionWindowPlacement.Position = '{0}' is not supported", alignment));
284 SetWindowPosition(window, calculatedPosition);
288 internal void SetWindowPosition(ExtensionWindow window, Point position)
290 Func<double, double, double, double, double> CalculateInBoundsValue =
291 (pos, size, limit, modifier) =>
293 if (this.AutoExpandCanvas)
295 return pos - modifier;
299 pos = Math.Max(0.0, pos);
300 return pos + size > limit ? limit - size : pos;
304 //in case of AutoExpandCanvas == false:
305 // - do not allow placing window outside surface bounds
306 //in case of AutoExpandCanvas == true:
307 // - include possible negative canvas offset
308 position.X = CalculateInBoundsValue(position.X, window.DesiredSize.Width, this.ActualWidth, this.selectedChild.Value.X);
309 position.Y = CalculateInBoundsValue(position.Y, window.DesiredSize.Height, this.ActualHeight, this.selectedChild.Value.Y);
311 //update its position on canvas
312 ExtensionSurface.SetPosition(window, position);
314 bool requiresMeasure = false;
315 if (this.AutoExpandCanvas)
317 requiresMeasure = true;
318 this.canvasOffset.X = 0;
319 this.canvasOffset.Y = 0;
321 foreach (UIElement item in this.Children)
323 FrameworkElement child = item as FrameworkElement;
326 Point p = ExtensionSurface.GetPosition(child);
327 this.canvasOffset.X = Math.Min(this.canvasOffset.X, p.X);
328 this.canvasOffset.Y = Math.Min(this.canvasOffset.Y, p.Y);
331 this.canvasOffset.X = Math.Abs(this.canvasOffset.X);
332 this.canvasOffset.Y = Math.Abs(this.canvasOffset.Y);
336 this.InvalidateMeasure();
340 this.InvalidateArrange();
344 void EnsureWindowIsVisible(ExtensionWindow window)
346 SetWindowPosition(window, ExtensionSurface.GetPosition(window));
349 internal void SetSize(ExtensionWindow window, Size size)
351 Point pos = ExtensionSurface.GetPosition(window);
352 if (!this.AutoExpandCanvas)
354 if (IsGreater(pos.X, size.Width, this.ActualWidth))
356 size.Width = this.ActualWidth - pos.X;
358 if (IsGreater(pos.Y, size.Height, this.ActualHeight))
360 size.Height = this.ActualHeight - pos.Y;
363 System.Diagnostics.Debug.WriteLine("SetSize oldSize (" + window.Width + "," + window.Height + ") newSize (" + size.Width + "," + size.Height + ")");
364 window.Width = size.Width;
365 window.Height = size.Height;
366 if (this.AutoExpandCanvas)
368 // this.InvalidateMeasure();
372 protected override Size ArrangeOverride(Size arrangeSize)
374 foreach (UIElement child in this.Children)
376 //get (left, top) coorinates
377 Point pos = ExtensionSurface.GetPosition(child);
378 //include eventual negative offset (panel wouldn't display elements with negative coorinates by default)
379 pos.Offset(this.canvasOffset.X, this.canvasOffset.Y);
380 //request child to rearange itself in given rectangle
381 child.Arrange(new Rect(pos, child.DesiredSize));
383 System.Diagnostics.Debug.WriteLine(string.Format(CultureInfo.InvariantCulture, "ArrangeOverride Size({0},{1})", arrangeSize.Width, arrangeSize.Height));
389 protected override Size MeasureOverride(Size constraint)
393 if (this.AutoExpandCanvas)
395 double panelWidth = 0.0;
396 double panelHeight = 0.0;
398 //initially assume that whole content fits in rectangle with coordinates (0,0, ActualWidth, ActualHeight)
399 double offsetMinusX = 0.0;
400 double offsetMinusY = 0.0;
401 double offsetPlusX = this.rearangeStartSize.Width;
402 double offsetPlusY = this.rearangeStartSize.Height;
404 foreach (UIElement item in this.Children)
406 FrameworkElement child = item as FrameworkElement;
409 child.Measure(constraint);
411 //get child's position
412 Point pos = ExtensionSurface.GetPosition(child);
414 //calculate the minimum value of panel's (left,top) corner
415 offsetMinusX = Math.Min(offsetMinusX, pos.X);
416 offsetMinusY = Math.Min(offsetMinusY, pos.Y);
418 //calculate the maximum value of panel's (right, bottom) corner
419 offsetPlusX = Math.Max(offsetPlusX, pos.X + child.DesiredSize.Width);
420 offsetPlusY = Math.Max(offsetPlusY, pos.Y + child.DesiredSize.Height);
424 //get required panel's width and height
425 panelWidth = Math.Abs(offsetPlusX - offsetMinusX);
426 panelHeight = Math.Abs(offsetPlusY - offsetMinusY);
428 this.actualPanelRect.Location = new Point(offsetMinusX, offsetMinusY);
429 this.actualPanelRect.Size = new Size(panelWidth, panelHeight);
431 //return it as result
432 result = new Size(panelWidth, panelHeight);
436 result = base.MeasureOverride(constraint);
438 System.Diagnostics.Debug.WriteLine("MO constraint:" + constraint.Width + "," + constraint.Height + " new: " + result.Width + "," + result.Height);
442 public void SelectWindow(ExtensionWindow window)
444 if (null != window && this.Children.Contains(window))
446 this.selectedChild = new KeyValuePair<FrameworkElement, Point>(window, this.canvasOffset);
447 this.rearangeStartSize.Width = this.ActualWidth;
448 this.rearangeStartSize.Height = this.ActualHeight;
449 Panel.SetZIndex(window, ++this.currentZIndex);
453 void OnExtensionWindowClosed(object sender, RoutedEventArgs args)
455 ExtensionWindow window = args.Source as ExtensionWindow;
459 //remove window from children collection
460 this.Children.Remove(window);
464 public static void SetPlacementTarget(DependencyObject container, FrameworkElement value)
466 container.SetValue(PlacementTargetProperty, value);
469 public static FrameworkElement GetPlacementTarget(DependencyObject container)
471 return (FrameworkElement)container.GetValue(PlacementTargetProperty);
474 public static void SetAlignment(DependencyObject container, PositionAlignment value)
476 container.SetValue(AlignmentProperty, value);
479 public static PositionAlignment GetAlignment(DependencyObject container)
481 return (PositionAlignment)container.GetValue(AlignmentProperty);
484 public static void SetMode(DependencyObject container, PlacementMode value)
486 container.SetValue(ModeProperty, value);
489 public static PlacementMode GetMode(DependencyObject container)
491 return (PlacementMode)container.GetValue(ModeProperty);
494 public static void SetPosition(DependencyObject container, Point value)
496 container.SetValue(PositionProperty, value);
499 public static Point GetPosition(DependencyObject container)
501 return (Point)container.GetValue(PositionProperty);
503 [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Suppress to avoid unnecessary changes.")]
504 public enum PlacementMode
508 [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Suppress to avoid unnecessary changes.")]
509 public enum PositionAlignment
511 LeftTop, LeftBottom, RightTop, RightBottom, Center, CenterHorizontal, CenterVertical