Appropreate message to initiate window move with the mouse button up

adamstyl 1 Reputation point
2022-12-17T11:19:29.73+00:00

Unfortunately I was asking the wrong way. I know how to move the form by setting its Location property. I have a working solution without p/invoke on github although quite convoluted for my taste. My question is "can I avoid this convolution by leveraging the OS mechanism to do a chore that the OS already knows about?"

Calling SendMessage(Handle, WM_SYSCOMMAND, 0xF102, 0); works as long as the primary mouse button is down. Same goes for SendMessage(Handle, WM_NCLBUTTONDOWN, HT_CAPTION, 0);. Though there is a way to move a window with the mouse up is by calling SendMessage(Handle, WM_SYSCOMMAND, SC_MOVE, 0);. This unfortunately involves an intermediate step of pressing one of the arrow keys (no idea why). The question is: can I combine both? Is there a message sequence I could send in order to initiate a window move without having to hold the mouse down?

Original question follows:

--
Move window without a mouse drag gesture

I have a window (subclass of System.Windows.Forms.Form) without a border in order to apply a custom style to it. Moving the window when the mouse button is down seems to be easy. Either by sending a WM_NCLBUTTONDOWN HT_CAPTION or a WM_SYSCOMMAND 0xF102 message the window can be dragged to a new location. As soon as the mouse button is up though, it seems to be impossible to move the window.

One could send WM_SYSCOMMAND SC_MOVE message but then the cursor moves at the top center of the window and awaits for the user to press any arrow key in order to hook the window for move -which is awkward at the least. I tried to fake a key press/release sequence but that didn't work either with SendMessage or SendInput. I tried to read and print the winapi error code with Marshal.GetLastWin32Error() and I got a 5 which is access denied. The curious thing was that I received the messages after the move sequence ended (ie I manually pressed a key or mouse button). No idea how to work around this.

A solution proposed in SO where I first asked the question is to use IMessageFilter. This is what I ended up doing before even the answer was posted but that has 2 issues one of them being a show stopper: moving the window with its Location property has lag compared to the native way (no big deal for now) and also it doesn't receive mouse messages that occur outside the application forms. That means it won't work a. for multiscreen environments b. if the cursor moves outside the forms of the application. I could create full screen transparent forms for every monitor that will serve as an "message canvas" but still... why not give the OS way a chance.

The desired behavior is: click a button (ie the mouse button is released) move the form where cursor goes, click again to release the form. Is that possible with winapi? Unfortunately I am not familiar with it.

Windows Forms
Windows Forms
A set of .NET Framework managed libraries for developing graphical user interfaces.
1,903 questions
Windows API - Win32
Windows API - Win32
A core set of Windows application programming interfaces (APIs) for desktop and server applications. Previously known as Win32 API.
2,652 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
11,011 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Castorix31 85,881 Reputation points
    2022-12-17T12:53:18.107+00:00

    You can do something like this (I used ClipCursor when the cursor can go outside of the window, but I think it could be better with a Low Level Mouse Hook (WH_MOUSE_LL )...)

    271655-movewindow.gif

    public partial class Form1 : Form  
    {  
        [DllImport("User32.dll", SetLastError = true)]  
        public static extern bool MoveWindow(IntPtr hWnd, int x, int y, int cx, int cy, bool repaint);  
    
        [DllImport("User32.dll", SetLastError = true, CharSet = CharSet.Unicode)]  
        public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);  
    
        public const int SWP_NOSIZE = 0x0001;  
        public const int SWP_NOMOVE = 0x0002;  
        public const int SWP_NOZORDER = 0x0004;  
        public const int SWP_NOREDRAW = 0x0008;  
        public const int SWP_NOACTIVATE = 0x0010;  
        public const int SWP_FRAMECHANGED = 0x0020;  /* The frame changed: send WM_NCCALCSIZE */  
        public const int SWP_SHOWWINDOW = 0x0040;  
        public const int SWP_HIDEWINDOW = 0x0080;  
        public const int SWP_NOCOPYBITS = 0x0100;  
        public const int SWP_NOOWNERZORDER = 0x0200;  /* Don't do owner Z ordering */  
        public const int SWP_NOSENDCHANGING = 0x0400;  /* Don't send WM_WINDOWPOSCHANGING */  
        public const int SWP_DRAWFRAME = SWP_FRAMECHANGED;  
        public const int SWP_NOREPOSITION = SWP_NOOWNERZORDER;  
        public const int SWP_DEFERERASE = 0x2000;  
        public const int SWP_ASYNCWINDOWPOS = 0x4000;  
    
        [DllImport("User32.dll", SetLastError = true)]  
        static extern bool GetCursorPos(out POINT lpPoint);  
    
        [StructLayout(LayoutKind.Sequential)]  
        public struct POINT  
        {  
            public int x;  
            public int y;  
    
            public POINT(int X, int Y)  
            {  
                this.x = X;  
                this.y = Y;  
            }  
        }     
    
        [DllImport("User32.dll", SetLastError = true)]  
        public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);  
    
        [StructLayout(LayoutKind.Sequential)]  
        public struct RECT  
        {  
            public int left;  
            public int top;  
            public int right;  
            public int bottom;  
            public RECT(int Left, int Top, int Right, int Bottom)  
            {  
                left = Left;  
                top = Top;  
                right = Right;  
                bottom = Bottom;  
            }  
        }  
    
        [DllImport("User32.dll", SetLastError = true)]  
        public static extern IntPtr SetCapture(IntPtr hWnd);  
    
        [DllImport("User32.dll", SetLastError = true)]  
        public static extern bool ReleaseCapture();  
    
        [DllImport("User32.dll", SetLastError = true)]  
        public static extern bool ClipCursor(ref RECT lpRect);  
    
        [DllImport("User32.dll", SetLastError = true)]  
        public static extern bool ClipCursor(IntPtr lpRect);  
    
    
        public const int WM_MOUSEMOVE = 0x0200;  
        public const int WM_LBUTTONDOWN = 0x0201;  
        public const int WM_LBUTTONUP = 0x0202;  
        public const int WM_LBUTTONDBLCLK = 0x0203;  
        public const int WM_RBUTTONDOWN = 0x0204;  
        public const int WM_RBUTTONUP = 0x0205;  
    
    
        public Form1()  
        {  
            InitializeComponent();  
            this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;  
        }  
    
        bool bMoving = false;  
        int nX = 0, nY = 0, nXWindow = 0, nYWindow = 0;  
    
        protected override void WndProc(ref Message m)  
        {             
            if (m.Msg == WM_MOUSEMOVE)  
            {  
                if (bMoving)  
                {  
                    POINT pt = new POINT();  
                    GetCursorPos(out pt);  
                    SetWindowPos(m.HWnd, IntPtr.Zero, nXWindow + (pt.x - nX), nYWindow + (pt.y - nY), 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOREDRAW);  
                    //SetWindowPos(m.HWnd, IntPtr.Zero, nXWindow + (pt.x - nX), nYWindow + (pt.y - nY), 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_ASYNCWINDOWPOS);  
                    RECT rc = new RECT();  
                    GetWindowRect(this.Handle, out rc);  
                    ClipCursor(ref rc);  
                }  
                else  
                    ClipCursor(IntPtr.Zero);  
            }  
            else if(m.Msg == WM_LBUTTONDOWN)  
            {  
                bMoving = !bMoving;  
                if (bMoving)  
                {  
                    SetCapture(this.Handle);  
                    RECT rc = new RECT();  
                    GetWindowRect(this.Handle, out rc);  
                    nXWindow = rc.left;  
                    nYWindow = rc.top;  
                    POINT pt = new POINT();  
                    GetCursorPos(out pt);  
                    nX = pt.x;  
                    nY = pt.y;                     
                }  
                else  
                    ReleaseCapture();  
            }  
            else if(m.Msg == WM_RBUTTONUP)  
            {  
                System.Windows.Forms.Application.Exit();  
            }  
            else  
            {  
                base.WndProc(ref m);  
            }  
        }  
    }  
    

  2. Gavin Landon 0 Reputation points
    2024-04-18T22:01:29.68+00:00

    delete, since paste doesn't want to paste correctly.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.