| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469 |
- 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);
- }
- }
- }
- }
- }
- }
|