Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Resizable window border


  • Please log in to reply
20 replies to this topic
bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
I've got completely borderless windows in my current project, SteamLab.

I'd like to make the windows resizable, using Titan's Anchor() function.

The problem is, every time I add +Resize to the gui window, it creates a full window border which does not match my windows at all.

Is there any way I can have a resizable window that allows dragging on its edge to resize it, rather than this unattractive window border on a normally borderless window?[/url]

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Good idea. Here's an example:
OnMessage(0x201, "WM_LBUTTONDOWN")
OnMessage(0x84, "WM_NCHITTEST")
OnMessage(0x83, "WM_NCCALCSIZE")
Gui, +Resize
Gui, Add, Text, Border vBorder
Gui, Show, W100 H100
return

GuiSize:
    GuiControl, Move, Border, X0 Y0 W%A_GuiWidth% H%A_GuiHeight%
return

; Allow moving the GUI by dragging any point in its client area.
WM_LBUTTONDOWN()
{
    if A_Gui
        PostMessage, 0xA1, 2 ; WM_NCLBUTTONDOWN
}

WM_NCCALCSIZE()
{
    if A_Gui
        return 0    ; Sizes the client area to fill the entire window.
}

; Redefine where the sizing borders are.  This is necessary since
; returning 0 for WM_NCCALCSIZE effectively gives borders zero size.
WM_NCHITTEST(wParam, lParam)
{
    static border_size = 6
    
    if !A_Gui
        return
    
    WinGetPos, gX, gY, gW, gH
    
    x := lParam<<48>>48, y := lParam<<32>>48
    
    hit_left    := x <  gX+border_size
    hit_right   := x >= gX+gW-border_size
    hit_top     := y <  gY+border_size
    hit_bottom  := y >= gY+gH-border_size
    
    if hit_top
    {
        if hit_left
            return 0xD
        else if hit_right
            return 0xE
        else
            return 0xC
    }
    else if hit_bottom
    {
        if hit_left
            return 0x10
        else if hit_right
            return 0x11
        else
            return 0xF
    }
    else if hit_left
        return 0xA
    else if hit_right
        return 0xB
    
    ; else let default hit-testing be done
}

GuiClose:
ExitApp
The above script overrides WM_NCCALCSIZE to "cut out" the default non-client area entirely, allowing controls to be at the very edges of the window. The effect is almost identical to -Caption.

Since "cutting out" the default non-client area also disables the default resizing, the script overrides WM_NCHITTEST to define where the borders are. This only works if the GUI has +Resize.

You should even be able to define your own title bar and buttons, from within WM_NCHITTEST. Below is a complete list of return codes. Descriptions can be found at MSDN: WM_NCHITTEST Notification.
#define HTERROR             (-2)
#define HTTRANSPARENT       (-1)
#define HTNOWHERE           0
#define HTCLIENT            1
#define HTCAPTION           2
#define HTSYSMENU           3
#define HTGROWBOX           4
#define HTSIZE              HTGROWBOX
#define HTMENU              5
#define HTHSCROLL           6
#define HTVSCROLL           7
#define HTMINBUTTON         8
#define HTMAXBUTTON         9
#define HTLEFT              10
#define HTRIGHT             11
#define HTTOP               12
#define HTTOPLEFT           13
#define HTTOPRIGHT          14
#define HTBOTTOM            15
#define HTBOTTOMLEFT        16
#define HTBOTTOMRIGHT       17
#define HTBORDER            18
#define HTREDUCE            HTMINBUTTON
#define HTZOOM              HTMAXBUTTON
#define HTSIZEFIRST         HTLEFT
#define HTSIZELAST          HTBOTTOMRIGHT
#define HTOBJECT            19
#define HTCLOSE             20
#define HTHELP              21


bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
This info is pure gold :) Thanks lexikos!

bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
For some reason I can't get this code to work right with my current windows.

I found when I use your code with -Caption but without +Resize (since some of my windows I don't want to resize), I seem to get a default titlebar showing through my Gui image titlebar, like this:
Posted Image


On the other hand, when I add +Resize, the window appears correctly, and resize correctly, but I randomly get resize borders and a titlebar appearing over the top of my Gui. Is there some way to make sure these elements stay hidden?
Posted Image

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Remove -Caption. On Vista, at least, it causes the window to draw a border whenever the window is activated or deactivated. The WM_NCCALCSIZE override removes the need for -Caption.

It seems omitting -Caption makes the GUI taller, and enforces a minimum size. I'm working on an alternate solution.

Edit: The following seems to work well on Vista and XP (with this simple GUI, at least):
OnMessage(0x201, "WM_LBUTTONDOWN")
OnMessage(0x84, "WM_NCHITTEST")
OnMessage(0x83, "WM_NCCALCSIZE")
OnMessage(0x86, "WM_NCACTIVATE")
Gui, -Caption +Resize
Gui, Add, Text, Border vBorder
Gui, Show, W100 H100
; Gui,Show sizes the GUI incorrectly, since it assumes there is a non-client area.
Gui, +LastFound
WinMove,,,,, 100, 100
; Alternatively, you can position, size, and show it all at once:
;   DllCall("SetWindowPos","uint",WinExist(),"uint",0
;       ,"int",A_ScreenWidth/2-50,"int",A_ScreenHeight/2-50,"int",100,"int",100
;       ,"uint",0x40)
return

GuiSize:
    GuiControl, Move, Border, X0 Y0 W%A_GuiWidth% H%A_GuiHeight%
return

; Allow moving the GUI by dragging any point in its client area.
WM_LBUTTONDOWN()
{
    if A_Gui
        PostMessage, 0xA1, 2 ; WM_NCLBUTTONDOWN
}

; Sizes the client area to fill the entire window.
WM_NCCALCSIZE()
{
    if A_Gui
        return 0
}

; Prevents a border from being drawn when the window is activated.
WM_NCACTIVATE()
{
    if A_Gui
        return 1
}

; Redefine where the sizing borders are.  This is necessary since
; returning 0 for WM_NCCALCSIZE effectively gives borders zero size.
WM_NCHITTEST(wParam, lParam)
{
    static border_size = 6
    
    if !A_Gui
        return
    
    WinGetPos, gX, gY, gW, gH
    
    x := lParam<<48>>48, y := lParam<<32>>48
    
    hit_left    := x <  gX+border_size
    hit_right   := x >= gX+gW-border_size
    hit_top     := y <  gY+border_size
    hit_bottom  := y >= gY+gH-border_size
    
    if hit_top
    {
        if hit_left
            return 0xD
        else if hit_right
            return 0xE
        else
            return 0xC
    }
    else if hit_bottom
    {
        if hit_left
            return 0x10
        else if hit_right
            return 0x11
        else
            return 0xF
    }
    else if hit_left
        return 0xA
    else if hit_right
        return 0xB
    
    ; else let default hit-testing be done
}

GuiClose:
ExitApp


bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
Unfortunately when I use your newest code and remove -Caption from my Guis, they turn out like this:
Posted Image

Any idea why the caption would only partially be going away? It even still has Vista's rounded window border, which my window didn't have before. I'm guessing the controlls are like that due to some of them having relative and others having absolute positioning, and the window size being affected by this script. Hmm..

Update: Oh, actually, if I leave -Caption in there, and use your newest code, it works absolutely perfectly! You have been a HUGE help, I never would have figured this out on my own. Thank you.

bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
Problem:

Because of this:
OnMessage(0x86, "IgnoreNCmessage")

I can only have one of my Gui windows activated at a time. When i try to open another window, I cannot activate it or click anything on it because the activation is being blocked.

Can you think of any way around this?

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
Try again now. I've edited it to return 1 for WM_NCACTIVATE. This had me confused:

an application should return TRUE to indicate that the system should proceed with the default processing

That statement is too general. :x Calling DefWindowProc does the "default processing" which we are trying to avoid. Returning 1 (TRUE) indicates that the system should proceed with (de)activating the window.

Returning any value from the OnMessage handler prevents AutoHotkey from calling DefWindowProc.

I'm guessing the controlls are like that due to ... the window size being affected by this script.

Yes, leaving the Caption enabled makes the GUI taller. Gui,Show seems to take into account a non-client area which does not exist, so the GUI ends up too big. The same is true without Caption disabled, which is why I wrote this part of the example:
; Gui,Show sizes the GUI incorrectly, since it assumes there is a non-client area.
Gui, +LastFound
WinMove,,,,, 100, 100
; Alternatively, you can position, size, and show it all at once:
;   DllCall("SetWindowPos","uint",WinExist(),"uint",0
;       ,"int",A_ScreenWidth/2-50,"int",A_ScreenHeight/2-50,"int",100,"int",100
;       ,"uint",0x40)


bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
Thanks, it works well now.

I did use your WinMove code before posting the previous screenshot; that was the result. I did not try the DllCall though. Might that show the window properly?

Either way, even with -Caption, it seems good now :) Unless it could be better, heh.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

I did use your WinMove code before posting the previous screenshot; that was the result. I did not try the DllCall though. Might that show the window properly?

Maybe, since using Gui sets an initial (incorrect) size, and WinMove may then cause auto-position/sizing based on that incorrect size. I'm not sure, since I haven't used Anchor() much.

bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
This introduces one side effect:

When I show a window with +Resize, it initially becomes inactive and you have to click on it to activate it.

Even if I use WinActivate on the window after it's activated, it stays inactive until I click it. I'm guessing because the code catches the activation message, and there wasn't an A_Gui which sent it?

When I show the same window with -Resize, it stays active when it is created. (The normal behavior).

Is this something easily fixable?

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

When I show a window with +Resize, it initially becomes inactive and you have to click on it to activate it.

I don't see this behaviour with either of the examples, on either Vista or XP.

I'm guessing because the code catches the activation message, and there wasn't an A_Gui which sent it?

I don't think so, since in that case the script simply allows the default processing of the message.

bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
Oh, ok. Well, it must just be odd behavior with my script or my system then, as it does occur every time here. And if I disable the onmessage events, it stops happening (but obviously resizable windows look like they used to).

It works fine and activates properly when I click the window, so I guess it's not a big deal; it's just unnatural seeing just-created windows inactive, heh. I'll see what I can figure out. It probably has something to do with something in my SteamWin functions...

Thanks for these functions anyhow, I never would have gotten that far on my own!

bmcclure
  • Members
  • 774 posts
  • Last active: Jan 04 2014 10:44 PM
  • Joined: 24 Nov 2007
Hey lexikos,

Sometimes when the window is activated/deactivated, a sort of border remnant shows up around the inside edges of the GUI (probably because it's made up of Gui, Add, Picture elements). If you move the GUI off the edge of the screen the border goes away. Sometimes it happens frequently, sometimes it doesn't seem to happen at all.

Posted Image

Is there either:
1. A way to prevent that from ever appearing, or
2. A way to get rid of it without having to redraw the window (and make it flash as a result)?

Update: And I just noticed, if you're running an application which forces Vista to use the standard (non-Aero) theme, and then exit that application so it switches back to the Aero theme, a "glass" border is permanently drawn on the inside edge of the window until you close it and open it again.

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006
The WM_NCACTIVATE handler is supposed to prevent that. Maybe the message isn't always handled?

If a monitored message that is numerically less than 0x312 arrives while the script is absolutely uninterruptible -- such as while a menu is displayed, a KeyDelay/MouseDelay is in progress, or the clipboard is being opened -- the message's function will not be called and the message will be treated as unmonitored.

a "glass" border is permanently drawn on the inside edge of the window

Does my example script also do this?

The best way to avoid these problems is probably to implement resizing entirely yourself.
[*:2e4fjeuw]Monitor WM_LBUTTONDOWN (0x201), WM_MOUSEMOVE (0x200) and WM_LBUTTONUP (0x202).
[*:2e4fjeuw]When WM_LBUTTONDOWN is received, check if the mouse is over a resizing border. If it is, call SetCapture. This allows you to track mouse messages even if the mouse moves outside of the GUI. Also set a flag to indicate that you are resizing.
[*:2e4fjeuw]When WM_MOUSEMOVE is received:
[*:2e4fjeuw]If resizing, resize the window based on the difference between the previous and current mouse position.
[*:2e4fjeuw]Otherwise, set the cursor (using LoadCursor and SetCursor, I guess) based on which region the mouse is in.[*:2e4fjeuw]When WM_LBUTTONUP is received, call ReleaseCapture and unset the resizing flag.[/list]Details for these messages and functions can be found on MSDN. Mouse messages receive the mouse position in lParam.
x := lParam & 0xFFFF
y := lParam >> 16