Aderic
Aderic

Reputation: 23

WPF DragMove() causes issues

I'm trying to figure out if there's an elegant solution to the problem I've been faced with.

So basically, I designed a borderless loading splash screen which is completely movable via dragging. I find that this happens if the splash screen gets hidden via Hide(), then displays a window via ShowDialog() with the owner set to the splash screen. Things get extremely buggy, but only if you're in mid-drag (with left mouse button down). You become unable to click or move anything, even Visual Studio becomes unresponsive unless you explicitly alt-tab out of the application.

Considering I know when I'm going to spawn the window, I was thinking maybe there'd be a way to cancel the DragMove operation, but I'm having no luck. What I've figured out is that DragMove is synchronous, so I'd guess it'd have to be cancelled from a different thread or in an event callback.

Edit:

public partial class Window_Movable : Window
{
    public Window_Movable()
    {
        InitializeComponent();
    }

    public Boolean CanMove { get; set; } = true;

    private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (CanMove == false)
            return;

        Task.Factory.StartNew(() => {
            System.Threading.Thread.Sleep(1000);

            Dispatcher.Invoke(() => {
                Hide();
                new Window_Movable() {
                    Title = "MOVABLE 2",
                    CanMove = false,
                    Owner = this
                }.ShowDialog();
            });
        });

        DragMove();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        Console.WriteLine("DING");
    }
}

Upvotes: 2

Views: 1583

Answers (2)

Alexey Perkin
Alexey Perkin

Reputation: 46

And one more completely different solution. To make a window movable you can use CaptionHeight of WindowChrome. i.e.

   var windowChrome = 
            WindowChrome.GetWindowChrome(appWindow.Window);
            windowChrome.CaptionHeight = MainWindowToolbar.Height;
    WindowChrome.SetWindowChrome(appWindow.Window, windowChrome);

but you should also set attached property WindowChrome.IsHitTestVisibleInChrome="True" for all controls in the window.

Upvotes: 1

Alexey Perkin
Alexey Perkin

Reputation: 46

I had the same problem and found out that DragMove() method is a problem. https://groups.google.com/forum/#!topic/wpf-disciples/7OcuXrf2whc

To solve I decided to refuse from using it and implement moving logic. I've combined some solutions from https://www.codeproject.com/Questions/284995/DragMove-problem-help-pls and C# WPF Move the window

        private bool _inDrag;
        private Point _anchorPoint;
        private bool _iscaptured;

        private void AppWindowWindowOnMouseMove(object sender, MouseEventArgs e)
        {
            if (!_inDrag)
                return;

            if (!_iscaptured)
            {
                CaptureMouse();
                _iscaptured = true;
            }

            var mousePosition = e.GetPosition(this);
            var mousePositionAbs = new Point
            {
                X = Convert.ToInt16(_appWindowWindow.Left) + mousePosition.X,
                Y = Convert.ToInt16(_appWindowWindow.Top) + mousePosition.Y
            };
            _appWindowWindow.Left = _appWindowWindow.Left + (mousePositionAbs.X - _anchorPoint.X);
            _appWindowWindow.Top = _appWindowWindow.Top + (mousePositionAbs.Y - _anchorPoint.Y);
            _anchorPoint = mousePositionAbs;
        }

        private void AppWindowWindowOnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (_inDrag)
            {
                _inDrag = false;
                _iscaptured = false;
                ReleaseMouseCapture();
            }
        }

        private void AppWindowWindowOnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            _anchorPoint = e.GetPosition(this);
            _anchorPoint.Y = Convert.ToInt16(_appWindowWindow.Top) + _anchorPoint.Y;
            _anchorPoint.X = Convert.ToInt16(_appWindowWindow.Left) + _anchorPoint.X;
            _inDrag = true;
        }

I've spend all yesterday evening and find a more hacky but more functional solution. Which support Maximized state and not require manual coordinate calculation.

    private bool _mRestoreForDragMove;
    private void OnAppWindowWindowOnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        if (e.ClickCount == 2)
        {
            if (_appWindowWindow.ResizeMode != ResizeMode.CanResize &&
                _appWindowWindow.ResizeMode != ResizeMode.CanResizeWithGrip)
            {
                return;
            }

            _appWindowWindow.WindowState = _appWindowWindow.WindowState == WindowState.Maximized
                ? WindowState.Normal
                : WindowState.Maximized;
        }
        else
        {
            _mRestoreForDragMove = _appWindowWindow.WindowState == WindowState.Maximized;

            SafeDragMoveCall(e);
        }
    }

    private void SafeDragMoveCall(MouseEventArgs e)
    {
        Task.Delay(100).ContinueWith(_ =>
        {
            Dispatcher.BeginInvoke((Action)
                delegate
                {
                    if (Mouse.LeftButton == MouseButtonState.Pressed)
                    {
                        _appWindowWindow.DragMove();
                        RaiseEvent(new MouseButtonEventArgs(e.MouseDevice, e.Timestamp, MouseButton.Left)
                        {
                            RoutedEvent = MouseLeftButtonUpEvent
                        });
                    }
                });
        });
    }

    private void OnMouseMove(object sender, MouseEventArgs e)
    {
        if (_mRestoreForDragMove)
        {
            _mRestoreForDragMove = false;

            var point = PointToScreen(e.MouseDevice.GetPosition(this));

            _appWindowWindow.Left = point.X - (_appWindowWindow.RestoreBounds.Width * 0.5);
            _appWindowWindow.Top = point.Y;

            _appWindowWindow.WindowState = WindowState.Normal;

            _appWindowWindow.DragMove();

            SafeDragMoveCall(e);
        }
    }

    private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
    {
        _mRestoreForDragMove = false;
    }

The thing is to send LeftMouseButtonUp event after a short delay to avoid blocking from DragMove()

Sources for last solve: DragMove() and Maximize C# WPF - DragMove and click

Upvotes: 2

Related Questions