|
|
@@ -0,0 +1,469 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
+using System.Text;
|
|
|
+using System.Windows;
|
|
|
+using System.Windows.Controls;
|
|
|
+using System.Windows.Data;
|
|
|
+using System.Windows.Documents;
|
|
|
+using System.Windows.Input;
|
|
|
+using System.Windows.Media;
|
|
|
+using System.Windows.Media.Imaging;
|
|
|
+using System.Windows.Navigation;
|
|
|
+using System.Windows.Shapes;
|
|
|
+
|
|
|
+namespace Controls.BehaviorView
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// 按照步骤 1a 或 1b 操作,然后执行步骤 2 以在 XAML 文件中使用此自定义控件。
|
|
|
+ ///
|
|
|
+ /// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。
|
|
|
+ /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
|
|
|
+ /// 元素中:
|
|
|
+ ///
|
|
|
+ /// xmlns:MyNamespace="clr-namespace:BehaviorView"
|
|
|
+ ///
|
|
|
+ ///
|
|
|
+ /// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。
|
|
|
+ /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
|
|
|
+ /// 元素中:
|
|
|
+ ///
|
|
|
+ /// xmlns:MyNamespace="clr-namespace:BehaviorView;assembly=BehaviorView"
|
|
|
+ ///
|
|
|
+ /// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用,
|
|
|
+ /// 并重新生成以避免编译错误:
|
|
|
+ ///
|
|
|
+ /// 在解决方案资源管理器中右击目标项目,然后依次单击
|
|
|
+ /// “添加引用”->“项目”->[选择此项目]
|
|
|
+ ///
|
|
|
+ ///
|
|
|
+ /// 步骤 2)
|
|
|
+ /// 继续操作并在 XAML 文件中使用控件。
|
|
|
+ ///
|
|
|
+ /// <MyNamespace:CustomControl1/>
|
|
|
+ ///
|
|
|
+ /// </summary>
|
|
|
+ /// <summary>
|
|
|
+ /// A Canvas which manages dragging of the UIElements it contains.
|
|
|
+ /// </summary>
|
|
|
+ public class BehaviorView : Canvas
|
|
|
+ {
|
|
|
+ // Stores a reference to the UIElement currently being dragged by the user.
|
|
|
+ private UIElement elementBeingDragged;
|
|
|
+
|
|
|
+ // Keeps track of where the mouse cursor was when a drag operation began.
|
|
|
+ private Point origCursorLocation;
|
|
|
+
|
|
|
+ // The offsets from the DragCanvas' edges when the drag operation began.
|
|
|
+ private double origHorizOffset, origVertOffset;
|
|
|
+
|
|
|
+ // Keeps track of which horizontal and vertical offset should be modified for the drag element.
|
|
|
+ private bool modifyLeftOffset, modifyTopOffset;
|
|
|
+
|
|
|
+ // True if a drag operation is underway, else false.
|
|
|
+ private bool isDragInProgress;
|
|
|
+
|
|
|
+ public static readonly DependencyProperty CanBeDraggedProperty;
|
|
|
+
|
|
|
+ public static bool GetCanBeDragged(UIElement uiElement)
|
|
|
+ {
|
|
|
+ if (uiElement == null)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ return (bool)uiElement.GetValue(CanBeDraggedProperty);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void SetCanBeDragged(UIElement uiElement, bool value)
|
|
|
+ {
|
|
|
+ if (uiElement != null)
|
|
|
+ uiElement.SetValue(CanBeDraggedProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static readonly DependencyProperty AllowDraggingProperty;
|
|
|
+ public static readonly DependencyProperty AllowDragOutOfViewProperty;
|
|
|
+
|
|
|
+ static BehaviorView()
|
|
|
+ {
|
|
|
+ AllowDraggingProperty = DependencyProperty.Register(
|
|
|
+ "AllowDragging",
|
|
|
+ typeof(bool),
|
|
|
+ typeof(BehaviorView),
|
|
|
+ new PropertyMetadata(true));
|
|
|
+
|
|
|
+ AllowDragOutOfViewProperty = DependencyProperty.Register(
|
|
|
+ "AllowDragOutOfView",
|
|
|
+ typeof(bool),
|
|
|
+ typeof(BehaviorView),
|
|
|
+ new UIPropertyMetadata(false));
|
|
|
+
|
|
|
+ CanBeDraggedProperty = DependencyProperty.RegisterAttached(
|
|
|
+ "CanBeDragged",
|
|
|
+ typeof(bool),
|
|
|
+ typeof(BehaviorView),
|
|
|
+ new UIPropertyMetadata(true));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Initializes a new instance of DragCanvas. UIElements in
|
|
|
+ /// the DragCanvas will immediately be draggable by the user.
|
|
|
+ /// </summary>
|
|
|
+ public BehaviorView()
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets/sets whether elements in the DragCanvas should be draggable by the user.
|
|
|
+ /// The default value is true. This is a dependency property.
|
|
|
+ /// </summary>
|
|
|
+ public bool AllowDragging
|
|
|
+ {
|
|
|
+ get { return (bool)base.GetValue(AllowDraggingProperty); }
|
|
|
+ set { base.SetValue(AllowDraggingProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets/sets whether the user should be able to drag elements in the DragCanvas out of
|
|
|
+ /// the viewable area. The default value is false. This is a dependency property.
|
|
|
+ /// </summary>
|
|
|
+ public bool AllowDragOutOfView
|
|
|
+ {
|
|
|
+ get { return (bool)GetValue(AllowDragOutOfViewProperty); }
|
|
|
+ set { SetValue(AllowDragOutOfViewProperty, value); }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Assigns the element a z-index which will ensure that
|
|
|
+ /// it is in front of every other element in the Canvas.
|
|
|
+ /// The z-index of every element whose z-index is between
|
|
|
+ /// the element's old and new z-index will have its z-index
|
|
|
+ /// decremented by one.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="targetElement">
|
|
|
+ /// The element to be sent to the front of the z-order.
|
|
|
+ /// </param>
|
|
|
+ public void BringToFront(UIElement element)
|
|
|
+ {
|
|
|
+ this.UpdateZOrder(element, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Assigns the element a z-index which will ensure that
|
|
|
+ /// it is behind every other element in the Canvas.
|
|
|
+ /// The z-index of every element whose z-index is between
|
|
|
+ /// the element's old and new z-index will have its z-index
|
|
|
+ /// incremented by one.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="targetElement">
|
|
|
+ /// The element to be sent to the back of the z-order.
|
|
|
+ /// </param>
|
|
|
+ public void SendToBack(UIElement element)
|
|
|
+ {
|
|
|
+ this.UpdateZOrder(element, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Returns the UIElement currently being dragged, or null.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Note to inheritors: This property exposes a protected
|
|
|
+ /// setter which should be used to modify the drag element.
|
|
|
+ /// </remarks>
|
|
|
+ public UIElement ElementBeingDragged
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ if (!this.AllowDragging)
|
|
|
+ return null;
|
|
|
+ else
|
|
|
+ return this.elementBeingDragged;
|
|
|
+ }
|
|
|
+ protected set
|
|
|
+ {
|
|
|
+ if (this.elementBeingDragged != null)
|
|
|
+ this.elementBeingDragged.ReleaseMouseCapture();
|
|
|
+
|
|
|
+ if (!this.AllowDragging)
|
|
|
+ this.elementBeingDragged = null;
|
|
|
+ else
|
|
|
+ {
|
|
|
+ if (BehaviorView.GetCanBeDragged(value))
|
|
|
+ {
|
|
|
+ this.elementBeingDragged = value;
|
|
|
+ this.elementBeingDragged.CaptureMouse();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ this.elementBeingDragged = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Walks up the visual tree starting with the specified DependencyObject,
|
|
|
+ /// looking for a UIElement which is a child of the Canvas. If a suitable
|
|
|
+ /// element is not found, null is returned. If the 'depObj' object is a
|
|
|
+ /// UIElement in the Canvas's Children collection, it will be returned.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="depObj">
|
|
|
+ /// A DependencyObject from which the search begins.
|
|
|
+ /// </param>
|
|
|
+ public UIElement FindCanvasChild(DependencyObject depObj)
|
|
|
+ {
|
|
|
+ while (depObj != null)
|
|
|
+ {
|
|
|
+ // If the current object is a UIElement which is a child of the
|
|
|
+ // Canvas, exit the loop and return it.
|
|
|
+ UIElement elem = depObj as UIElement;
|
|
|
+ if (elem != null && base.Children.Contains(elem))
|
|
|
+ break;
|
|
|
+
|
|
|
+ // VisualTreeHelper works with objects of type Visual or Visual3D.
|
|
|
+ // If the current object is not derived from Visual or Visual3D,
|
|
|
+ // then use the LogicalTreeHelper to find the parent element.
|
|
|
+ if (depObj is Visual || depObj is Visual3D)
|
|
|
+ depObj = VisualTreeHelper.GetParent(depObj);
|
|
|
+ else
|
|
|
+ depObj = LogicalTreeHelper.GetParent(depObj);
|
|
|
+ }
|
|
|
+ return depObj as UIElement;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnPreviewMouseLeftButtonDown(MouseButtonEventArgs e)
|
|
|
+ {
|
|
|
+ base.OnPreviewMouseLeftButtonDown(e);
|
|
|
+
|
|
|
+ this.isDragInProgress = false;
|
|
|
+
|
|
|
+ // Cache the mouse cursor location.
|
|
|
+ this.origCursorLocation = e.GetPosition(this);
|
|
|
+
|
|
|
+ // Walk up the visual tree from the element that was clicked,
|
|
|
+ // looking for an element that is a direct child of the Canvas.
|
|
|
+ this.ElementBeingDragged = this.FindCanvasChild(e.Source as DependencyObject);
|
|
|
+ if (this.ElementBeingDragged == null)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // Get the element's offsets from the four sides of the Canvas.
|
|
|
+ double left = Canvas.GetLeft(this.ElementBeingDragged);
|
|
|
+ double right = Canvas.GetRight(this.ElementBeingDragged);
|
|
|
+ double top = Canvas.GetTop(this.ElementBeingDragged);
|
|
|
+ double bottom = Canvas.GetBottom(this.ElementBeingDragged);
|
|
|
+
|
|
|
+ // Calculate the offset deltas and determine for which sides
|
|
|
+ // of the Canvas to adjust the offsets.
|
|
|
+ this.origHorizOffset = ResolveOffset(left, right, out this.modifyLeftOffset);
|
|
|
+ this.origVertOffset = ResolveOffset(top, bottom, out this.modifyTopOffset);
|
|
|
+
|
|
|
+ // Set the Handled flag so that a control being dragged
|
|
|
+ // does not react to the mouse input.
|
|
|
+ e.Handled = true;
|
|
|
+
|
|
|
+ this.isDragInProgress = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnPreviewMouseMove(MouseEventArgs e)
|
|
|
+ {
|
|
|
+ base.OnPreviewMouseMove(e);
|
|
|
+
|
|
|
+ // If no element is being dragged, there is nothing to do.
|
|
|
+ if (this.ElementBeingDragged == null || !this.isDragInProgress)
|
|
|
+ return;
|
|
|
+
|
|
|
+ // Get the position of the mouse cursor, relative to the Canvas.
|
|
|
+ Point cursorLocation = e.GetPosition(this);
|
|
|
+
|
|
|
+ // These values will store the new offsets of the drag element.
|
|
|
+ double newHorizontalOffset, newVerticalOffset;
|
|
|
+
|
|
|
+ // Determine the horizontal offset.
|
|
|
+ if (this.modifyLeftOffset)
|
|
|
+ newHorizontalOffset = this.origHorizOffset + (cursorLocation.X - this.origCursorLocation.X);
|
|
|
+ else
|
|
|
+ newHorizontalOffset = this.origHorizOffset - (cursorLocation.X - this.origCursorLocation.X);
|
|
|
+
|
|
|
+ // Determine the vertical offset.
|
|
|
+ if (this.modifyTopOffset)
|
|
|
+ newVerticalOffset = this.origVertOffset + (cursorLocation.Y - this.origCursorLocation.Y);
|
|
|
+ else
|
|
|
+ newVerticalOffset = this.origVertOffset - (cursorLocation.Y - this.origCursorLocation.Y);
|
|
|
+
|
|
|
+ if (!this.AllowDragOutOfView)
|
|
|
+ {
|
|
|
+ // Get the bounding rect of the drag element.
|
|
|
+ Rect elemRect = this.CalculateDragElementRect(newHorizontalOffset, newVerticalOffset);
|
|
|
+
|
|
|
+ //
|
|
|
+ // If the element is being dragged out of the viewable area,
|
|
|
+ // determine the ideal rect location, so that the element is
|
|
|
+ // within the edge(s) of the canvas.
|
|
|
+ //
|
|
|
+ bool leftAlign = elemRect.Left < 0;
|
|
|
+ bool rightAlign = elemRect.Right > this.ActualWidth;
|
|
|
+
|
|
|
+ if (leftAlign)
|
|
|
+ newHorizontalOffset = modifyLeftOffset ? 0 : this.ActualWidth - elemRect.Width;
|
|
|
+ else if (rightAlign)
|
|
|
+ newHorizontalOffset = modifyLeftOffset ? this.ActualWidth - elemRect.Width : 0;
|
|
|
+
|
|
|
+ bool topAlign = elemRect.Top < 0;
|
|
|
+ bool bottomAlign = elemRect.Bottom > this.ActualHeight;
|
|
|
+
|
|
|
+ if (topAlign)
|
|
|
+ newVerticalOffset = modifyTopOffset ? 0 : this.ActualHeight - elemRect.Height;
|
|
|
+ else if (bottomAlign)
|
|
|
+ newVerticalOffset = modifyTopOffset ? this.ActualHeight - elemRect.Height : 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this.modifyLeftOffset)
|
|
|
+ Canvas.SetLeft(this.ElementBeingDragged, newHorizontalOffset);
|
|
|
+ else
|
|
|
+ Canvas.SetRight(this.ElementBeingDragged, newHorizontalOffset);
|
|
|
+
|
|
|
+ if (this.modifyTopOffset)
|
|
|
+ Canvas.SetTop(this.ElementBeingDragged, newVerticalOffset);
|
|
|
+ else
|
|
|
+ Canvas.SetBottom(this.ElementBeingDragged, newVerticalOffset);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnPreviewMouseUp(MouseButtonEventArgs e)
|
|
|
+ {
|
|
|
+ base.OnPreviewMouseUp(e);
|
|
|
+
|
|
|
+ // Reset the field whether the left or right mouse button was
|
|
|
+ // released, in case a context menu was opened on the drag element.
|
|
|
+ this.ElementBeingDragged = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Returns a Rect which describes the bounds of the element being dragged.
|
|
|
+ /// </summary>
|
|
|
+ private Rect CalculateDragElementRect(double newHorizOffset, double newVertOffset)
|
|
|
+ {
|
|
|
+ if (this.ElementBeingDragged == null)
|
|
|
+ throw new InvalidOperationException("ElementBeingDragged is null.");
|
|
|
+
|
|
|
+ Size elemSize = this.ElementBeingDragged.RenderSize;
|
|
|
+
|
|
|
+ double x, y;
|
|
|
+
|
|
|
+ if (this.modifyLeftOffset)
|
|
|
+ x = newHorizOffset;
|
|
|
+ else
|
|
|
+ x = this.ActualWidth - newHorizOffset - elemSize.Width;
|
|
|
+
|
|
|
+ if (this.modifyTopOffset)
|
|
|
+ y = newVertOffset;
|
|
|
+ else
|
|
|
+ y = this.ActualHeight - newVertOffset - elemSize.Height;
|
|
|
+
|
|
|
+ Point elemLoc = new Point(x, y);
|
|
|
+
|
|
|
+ return new Rect(elemLoc, elemSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Determines one component of a UIElement's location
|
|
|
+ /// within a Canvas (either the horizontal or vertical offset).
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="side1">
|
|
|
+ /// The value of an offset relative to a default side of the
|
|
|
+ /// Canvas (i.e. top or left).
|
|
|
+ /// </param>
|
|
|
+ /// <param name="side2">
|
|
|
+ /// The value of the offset relative to the other side of the
|
|
|
+ /// Canvas (i.e. bottom or right).
|
|
|
+ /// </param>
|
|
|
+ /// <param name="useSide1">
|
|
|
+ /// Will be set to true if the returned value should be used
|
|
|
+ /// for the offset from the side represented by the 'side1'
|
|
|
+ /// parameter. Otherwise, it will be set to false.
|
|
|
+ /// </param>
|
|
|
+ private static double ResolveOffset(double side1, double side2, out bool useSide1)
|
|
|
+ {
|
|
|
+ // If the Canvas.Left and Canvas.Right attached properties
|
|
|
+ // are specified for an element, the 'Left' value is honored.
|
|
|
+ // The 'Top' value is honored if both Canvas.Top and
|
|
|
+ // Canvas.Bottom are set on the same element. If one
|
|
|
+ // of those attached properties is not set on an element,
|
|
|
+ // the default value is Double.NaN.
|
|
|
+ useSide1 = true;
|
|
|
+ double result;
|
|
|
+ if (Double.IsNaN(side1))
|
|
|
+ {
|
|
|
+ if (Double.IsNaN(side2))
|
|
|
+ {
|
|
|
+ // Both sides have no value, so set the
|
|
|
+ // first side to a value of zero.
|
|
|
+ result = 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ result = side2;
|
|
|
+ useSide1 = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ result = side1;
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Helper method used by the BringToFront and SendToBack methods.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="element">
|
|
|
+ /// The element to bring to the front or send to the back.
|
|
|
+ /// </param>
|
|
|
+ /// <param name="bringToFront">
|
|
|
+ /// Pass true if calling from BringToFront, else false.
|
|
|
+ /// </param>
|
|
|
+ private void UpdateZOrder(UIElement element, bool bringToFront)
|
|
|
+ {
|
|
|
+
|
|
|
+ if (element == null)
|
|
|
+ throw new ArgumentNullException("element");
|
|
|
+
|
|
|
+ if (!base.Children.Contains(element))
|
|
|
+ throw new ArgumentException("Must be a child element of the Canvas.", "element");
|
|
|
+
|
|
|
+ // Determine the Z-Index for the target UIElement.
|
|
|
+ int elementNewZIndex = -1;
|
|
|
+ if (bringToFront)
|
|
|
+ {
|
|
|
+ foreach (UIElement elem in base.Children)
|
|
|
+ if (elem.Visibility != Visibility.Collapsed)
|
|
|
+ ++elementNewZIndex;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ elementNewZIndex = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Determine if the other UIElements' Z-Index
|
|
|
+ // should be raised or lowered by one.
|
|
|
+ int offset = (elementNewZIndex == 0) ? +1 : -1;
|
|
|
+
|
|
|
+ int elementCurrentZIndex = Canvas.GetZIndex(element);
|
|
|
+
|
|
|
+
|
|
|
+ // Update the Z-Index of every UIElement in the Canvas.
|
|
|
+ foreach (UIElement childElement in base.Children)
|
|
|
+ {
|
|
|
+ if (childElement == element)
|
|
|
+ Canvas.SetZIndex(element, elementNewZIndex);
|
|
|
+ else
|
|
|
+ {
|
|
|
+ int zIndex = Canvas.GetZIndex(childElement);
|
|
|
+
|
|
|
+ // Only modify the z-index of an element if it is
|
|
|
+ // in between the target element's old and new z-index.
|
|
|
+ if (bringToFront && elementCurrentZIndex < zIndex ||
|
|
|
+ !bringToFront && zIndex < elementCurrentZIndex)
|
|
|
+ {
|
|
|
+ Canvas.SetZIndex(childElement, zIndex + offset);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|