Help for Tune Smithy Koch snowflake icon.gif

How to let user click and drag anywhere on window or dialog to move it

From Tune Smithy

(Difference between revisions)
Jump to: navigation, search
(New page: This is dead simple when you know how, though you can struggle for ages if you don't know the trick. Windows sends your window or dialog a WM_NCHITTEST to detect the various parts of the ...)
 
(4 intermediate revisions not shown)
Line 1: Line 1:
 +
[[C-code]]
 +
 +
Here the idea is you have a dialog or window with no title bar and want user to be able to move it around on the screen with click and drag. Or maybe it has a title bar but you still want user to be able to use click and drag anywhere on the window to move it.
 +
This is dead simple when you know how, though you can struggle for ages if you don't know the trick.
This is dead simple when you know how, though you can struggle for ages if you don't know the trick.
Windows sends your window or dialog a WM_NCHITTEST to detect the various parts of the window such as the resizing borders, or title bar etc. To let the user move the window with click and drag, just tell Windows that everything is the title bar.
Windows sends your window or dialog a WM_NCHITTEST to detect the various parts of the window such as the resizing borders, or title bar etc. To let the user move the window with click and drag, just tell Windows that everything is the title bar.
 +
 +
===A possible undesirable side effect for resizable windows===
 +
 +
If your window is resizable, then a double click on the window is treated as the same as a double click on the title bar, so you will also let the user maximise the window or dialog by double clicking anywhere on the window.
 +
 +
This can be confusing to the user if not expected.
 +
 +
===How it is done for a normal window===
For a normal window made using e.g. CreateWindow with a DefWindowProc(..) you do it like this:
For a normal window made using e.g. CreateWindow with a DefWindowProc(..) you do it like this:
-
<sourcelang="c">
+
<source lang="c">
  switch (message)
  switch (message)
  {
  {
Line 23: Line 35:
Or alternatively call DefWindowProc for the message, and check to see if its return value is HTCLIENT and if so return HTCAPTION.
Or alternatively call DefWindowProc for the message, and check to see if its return value is HTCLIENT and if so return HTCAPTION.
-
You might think you could just return HTCAPTION whatever happens - but there are many other possible return values to show e.g. that the mouse is in the Close button, or over one of the resizing borders of the dialog etc.
+
You might think you could simplify that code, and just return HTCAPTION whatever happens - but there are many other possible return values needed. E.g. there are return values to show that the mouse is in the Close button, or over one of the resizing borders of the dialog etc.
 +
 
 +
===How to do it with a dialog===
If it is a dialog you need to do it like this:
If it is a dialog you need to do it like this:
Line 51: Line 65:
That's because you just return 1 to show that the message is processed, and with no DefWindowProc you need to use SetWindowLong with DWL_MSGRESULT to supply Windows with the return value.
That's because you just return 1 to show that the message is processed, and with no DefWindowProc you need to use SetWindowLong with DWL_MSGRESULT to supply Windows with the return value.
 +
 +
In this code, the click and drag will only work for static text areas of the window or blank background areas - as you would want it to - you don't want the window to move when the user just wants to click and drag to highlight text for instance.
 +
 +
You don't need to worry about any editable text areas or buttons or other controls e.g. custom controls or whatever in the window - as they are on top of it, then they will catch the WM_NCHITTEST before it gets to the dialog proc.
 +
 +
===Resizable windows and dialogs===
 +
 +
With resizable windows or dialogs, you want to discard double clicks except on the title bar. I do it in the message loop - obviously not going to be a solution for everyone, but maybe you can use this idea in some other way if not?
 +
 +
<source lang="c">
 +
if(click_and_drag_dialogs)
 +
  switch(msg.message)
 +
  {
 +
  case WM_NCLBUTTONDBLCLK:
 +
  if(IsOneOfMyResizableNonClientWindows(msg.hwnd))
 +
  {
 +
    RECT rc;
 +
    POINTS pts=MAKEPOINTS(msg.lParam);
 +
    POINT pt;
 +
    pt.x=pts.x;pt.y=pts.y;
 +
    GetClientRect(msg.hwnd,&rc);
 +
    ClientToScreenRect(msg.hwnd,&rc);
 +
    if(PtInRect(&rc,pt))
 +
    goto endloop;
 +
  }
 +
  }
 +
</source>
 +
 +
There IsOneOfMyAppsDialogs(...) is code you will need to write yourself, to check to see if it is one of the windows it needs to be called for and not e.g. one of its child buttons or text areas etc. The way I do it is I maintain a list of the window handles of all the windows in my program which I use e.g. for Ctrl + tab switching and in many other ways within the program, so was easy to do, but obviously will depend on your situation how you do it.
 +
 +
You could for instance use GetClientRect and GetWindowRect and if they differ in height by at least the size of the windows caption (from GetSystemMetrics(SM_CYCAPTION)) it must be a window with a caption, which might be enough to mean you don't mind disabling double clicks for it.

Latest revision as of 23:44, 28 November 2011

C-code

Here the idea is you have a dialog or window with no title bar and want user to be able to move it around on the screen with click and drag. Or maybe it has a title bar but you still want user to be able to use click and drag anywhere on the window to move it.

This is dead simple when you know how, though you can struggle for ages if you don't know the trick.

Windows sends your window or dialog a WM_NCHITTEST to detect the various parts of the window such as the resizing borders, or title bar etc. To let the user move the window with click and drag, just tell Windows that everything is the title bar.

Contents

A possible undesirable side effect for resizable windows

If your window is resizable, then a double click on the window is treated as the same as a double click on the title bar, so you will also let the user maximise the window or dialog by double clicking anywhere on the window.

This can be confusing to the user if not expected.

How it is done for a normal window

For a normal window made using e.g. CreateWindow with a DefWindowProc(..) you do it like this:

switch (message)
 {
 case WM_NCHITTEST:
  {
    POINT pt;
    RECT rcClient;
    GetCursorPos(&pt);
    GetClientRect(hDlg,&rcClient);
    ClientToScreenRect(hDlg,&rcClient);
    if(PtInRect(&rcClient,pt))
     return HTCAPTION;
  }
  break;
 }

Or alternatively call DefWindowProc for the message, and check to see if its return value is HTCLIENT and if so return HTCAPTION.

You might think you could simplify that code, and just return HTCAPTION whatever happens - but there are many other possible return values needed. E.g. there are return values to show that the mouse is in the Close button, or over one of the resizing borders of the dialog etc.

How to do it with a dialog

If it is a dialog you need to do it like this:

switch(message)
 {
 case WM_NCHITTEST:
  {
   POINT pt;
   RECT rcClient;
   GetCursorPos(&pt);
   GetClientRect(hDlg,&rcClient);
   ClientToScreenRect(hDlg,&rcClient);
   if(PtInRect(&rcClient,pt))
   {
    //Because it is a dialog need to do it this way and return 1
    SetWindowLong(hDlg,DWL_MSGRESULT,HTCAPTION);
    return 1;
    // For a normal window would use:
    // return HTCAPTION;
   }
  }
  break;
 }

That's because you just return 1 to show that the message is processed, and with no DefWindowProc you need to use SetWindowLong with DWL_MSGRESULT to supply Windows with the return value.

In this code, the click and drag will only work for static text areas of the window or blank background areas - as you would want it to - you don't want the window to move when the user just wants to click and drag to highlight text for instance.

You don't need to worry about any editable text areas or buttons or other controls e.g. custom controls or whatever in the window - as they are on top of it, then they will catch the WM_NCHITTEST before it gets to the dialog proc.

Resizable windows and dialogs

With resizable windows or dialogs, you want to discard double clicks except on the title bar. I do it in the message loop - obviously not going to be a solution for everyone, but maybe you can use this idea in some other way if not?

if(click_and_drag_dialogs)
  switch(msg.message)
  {
   case WM_NCLBUTTONDBLCLK:
   if(IsOneOfMyResizableNonClientWindows(msg.hwnd))
   {
    RECT rc;
    POINTS pts=MAKEPOINTS(msg.lParam);
    POINT pt;
    pt.x=pts.x;pt.y=pts.y;
    GetClientRect(msg.hwnd,&rc);
    ClientToScreenRect(msg.hwnd,&rc);
    if(PtInRect(&rc,pt))
     goto endloop;
   }
  }

There IsOneOfMyAppsDialogs(...) is code you will need to write yourself, to check to see if it is one of the windows it needs to be called for and not e.g. one of its child buttons or text areas etc. The way I do it is I maintain a list of the window handles of all the windows in my program which I use e.g. for Ctrl + tab switching and in many other ways within the program, so was easy to do, but obviously will depend on your situation how you do it.

You could for instance use GetClientRect and GetWindowRect and if they differ in height by at least the size of the windows caption (from GetSystemMetrics(SM_CYCAPTION)) it must be a window with a caption, which might be enough to mean you don't mind disabling double clicks for it.

Personal tools
Namespaces
Variants
Actions
Navigation
How to use the wiki
More
Toolbox