MouseDragPopupBehavior that is constrained to the window

Viewed 85

The following behavior allows you to drag a popup around with the mouse.

How can it be modified to constrain the element being dragged to some parent element, eg the Application window?

I don't want to allow the popup to be dragged out of the main window.

public class MouseDragPopupBehavior : Behavior<Popup>
{
    private bool mouseDown;
    private Point oldMousePosition;

    protected override void OnAttached()
    {
        AssociatedObject.MouseLeftButtonDown += (s, e) =>
        {
            mouseDown = true;
            oldMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
            AssociatedObject.Child.CaptureMouse();
        };
        AssociatedObject.MouseMove += (s, e) =>
        {
            if (!mouseDown) return;
            var newMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));

            var offset = newMousePosition - oldMousePosition;
            oldMousePosition = newMousePosition;
            AssociatedObject.HorizontalOffset += offset.X;
            AssociatedObject.VerticalOffset += offset.Y;
        };
        AssociatedObject.MouseLeftButtonUp += (s, e) =>
        {
            mouseDown = false;
            AssociatedObject.Child.ReleaseMouseCapture();
        };
    }
}
1 Answers

It's better to constrain popup not to the window itself, but to the root control of the window, because you likely want to restrict it to the client area of the window (without titlebar and such), and the easiest way I'm aware to get that client area is to just work with the root control inside window.

In case of popup, it's also easier to work with root element inside, because interactions between popup itself and visual tree are complicated.

What you need is to get the bounding rectangle of your popup in screen coordinates, the bounding rectange of contraining control (the root of the window) in screen coordinates, and check if one contains the other. Here is sample code:

public class MouseDragPopupBehavior : Behavior<Popup>
{
    private bool mouseDown;
    private Point oldMousePosition;
    private FrameworkElement constraint;

    protected override void OnAttached() {
        // obtain root of the window (might fail in general case, adjust to your specific case if necessary)
        constraint = (FrameworkElement) Application.Current.MainWindow.Content;
        AssociatedObject.MouseLeftButtonDown += (s, e) =>
        {
            mouseDown = true;
            oldMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
            AssociatedObject.Child.CaptureMouse();
        };
        AssociatedObject.MouseMove += (s, e) =>
        {
            if (!mouseDown) return;
            var newMousePosition = AssociatedObject.PointToScreen(e.GetPosition(AssociatedObject));
            var offset = newMousePosition - oldMousePosition;
            // get root control of popup (might fail in general case, adjust if necessary)
            var popupRoot = (FrameworkElement)AssociatedObject.Child;
            // obtain (x,y) of popup in screen coordinates
            var popupLeftTop = popupRoot.PointToScreen(new Point(0, 0));
            // obtain bounds
            var popupBounds = new Rect(popupLeftTop.X + offset.X, popupLeftTop.Y +  offset.Y, popupRoot.ActualWidth, popupRoot.ActualHeight);
            // do the same with contraint control
            var constraintLeftTop = constraint.PointToScreen(new Point(0, 0));
            var constraintBounds = new Rect(constraintLeftTop.X, constraintLeftTop.Y, constraint.ActualWidth, constraint.ActualHeight);
            // if bounding rect of constraint does not contain popup - that means popup is not fully inside the constraint, then do not apply offset
            if (!constraintBounds.Contains(popupBounds))
                return;
            // otherwise continue
            oldMousePosition = newMousePosition;
            AssociatedObject.HorizontalOffset += offset.X;
            AssociatedObject.VerticalOffset += offset.Y;
        };
        AssociatedObject.MouseLeftButtonUp += (s, e) =>
        {
            mouseDown = false;
            AssociatedObject.Child.ReleaseMouseCapture();
        };
    }
}
Related