Link to home
Start Free TrialLog in
Avatar of r1gga
r1gga

asked on

BIZARE: ***MDI Maximize button disabled but not for long***

Hello,
I have an MDI with many MDI children, I can disable the maximize button and several other of the MDI's features using the following code:

Option Explicit
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function DeleteMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, _
ByVal wFlags As Long) As Long
Private Declare Function GetSystemMenu Lib "user32" (ByVal hWnd As Long, ByVal bRevert As Long) As Long

Const MF_BYPOSITION = &H400&
Const GWL_STYLE = (-16)
Const WS_THICKFRAME = &H40000
Const WS_MINIMIZEBOX = &H20000
Const WS_MAXIMIZEBOX = &H10000
Const WS_SYSMENU = &H80000

Private Sub RemoveMenus()
Dim hMenu As Long
' Get the form's system menu handle.
hMenu = GetSystemMenu(hWnd, False)
DeleteMenu hMenu, 6, MF_BYPOSITION 'Disables X Button
DeleteMenu hMenu, 5, MF_BYPOSITION 'Removes Form Seperator
DeleteMenu hMenu, 4, MF_BYPOSITION 'Disables Maximize Button
DeleteMenu hMenu, 2, MF_BYPOSITION 'Disables Resizing
' Grey out form's Maximize Button and use thin frame
Dim L As Long
L = GetWindowLong(Me.hWnd, GWL_STYLE)
L = L And Not (WS_MAXIMIZEBOX)
L = L And Not (WS_THICKFRAME)
L = SetWindowLong(Me.hWnd, GWL_STYLE, L)
End Sub

Private Sub MDIForm_Load()
RemoveMenus
formLogin.Show
End Sub

If I comment out the loading of the first form "formLogin" which is an MDIchild then the code works perfectly with maximize greyed out and a thin border on the MDI.

If formLogin.show is not commented out then the sysmenu changes remain i.e. they do not appear in the sysmenu, however the maximize button is not greyed and under windows XP is usable and the border of the MDI becomes thick. How do I fix this.

Thanks in Advance
r1gga
Avatar of JohnMcCann
JohnMcCann

Just a though have you tried placing a refresh statment betwwen these two lines

RemoveMenus
formLogin.Show

Also does the MDI form draw properly after the formLogin has been displayed?

And finally does formLogin need to be an MDI child.
Soory just noticed

but not for long

So it does work for a short period
could try simply allowing the form to load and then using a timer to remove the frmmenus and then show frm login, that way the form gets drawn.

e.g.

Private Sub MDIForm_Load()
   Timer1.Interval = 100
   Timer1.enabled = true
End Sub

Private Sub Timer1_Timer()
   
   timer1.enabled = false

   RemoveMenus
   formLogin.Show
end sub
Avatar of r1gga

ASKER

Sorry I forgot to mention, I can achieve the desired effect through use of a timer, however I seek something more reliable as the timer can be beaten if the user clicks fast enough leaving the user with a maximized window that can then not be resized as the timer has occured. My timer code is:

Private Sub Timer1_Timer()
RemoveMenus
End Sub
Can you load the MDIForm with its .Visible = false, then in the timer you can show it, that way the user can never beat your timer, as they cannot see the form.
Avatar of r1gga

ASKER

The thing is I am looking for a more gneric solution, as this method method may allow formLogin to be displayed but then after that when formMain is displayed the same problem occurs.
Thanks r1gga
Hi okay, i think I figured it out, justt re-read your code

' Grey out form's Maximize Button and use thin frame
Dim L As Long
L = GetWindowLong(Me.hWnd, GWL_STYLE)
L = L And Not (WS_MAXIMIZEBOX)
L = L And Not (WS_THICKFRAME)
L = SetWindowLong(Me.hWnd, GWL_STYLE, L)

After calling SetWindowLong, you need to redraw the window, so add these lines:

swpFlags = SWP_FRAMECHANGED or SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER
SetWindowPos(Me.Hwnd,0,0,0,0,0,swpFlags)

Now you will notice that your remove menu function call will work properly.
Avatar of r1gga

ASKER

The thing is I am looking for a more gneric solution, as this method method may allow formLogin to be displayed but then after that when formMain is displayed the same problem occurs.
Thanks r1gga
Avatar of r1gga

ASKER

I get variable not defined SWP_Framechanged
Thanks
r1gga
Private Const SWP_FRAMCHANGED = &H20
SWP_NOMOVE = &H2
SWP_NOSIZE = &H1
SWP_NOZORDER = &H4

And the SetWindowPos is defined in user32
Avatar of r1gga

ASKER

I get variable not defined SWP_Framechanged
Thanks
r1gga
Avatar of r1gga

ASKER

sorry I pressed refresh on an old window ignor ethe last post
Avatar of r1gga

ASKER

setwindowpos is not a valid function i.e. it appears in red.
Have u tried this out?
err its an API  u have to declare it as :

Private Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long
Avatar of r1gga

ASKER

The only effect your code has is to refresh the image of the maximize button, which is useful but only in conjunction with a timer that executes the function removemenus(). Here is the code as it stands with your modification:

Option Explicit
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" _
(ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function DeleteMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, _
ByVal wFlags As Long) As Long
Private Declare Function GetSystemMenu Lib "user32" (ByVal hWnd As Long, ByVal bRevert As Long) As Long
Private Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long

Const MF_BYPOSITION = &H400&
Const GWL_STYLE = (-16)
Const WS_THICKFRAME = &H40000
Const WS_MINIMIZEBOX = &H20000
Const WS_MAXIMIZEBOX = &H10000
Const WS_SYSMENU = &H80000
Const SWP_FRAMECHANGED = &H20
Const SWP_NOMOVE = &H2
Const SWP_NOSIZE = &H1
Const SWP_NOZORDER = &H4

Private Sub RemoveMenus()
Dim hMenu As Long
' Get the form's system menu handle.
hMenu = GetSystemMenu(hWnd, False)
DeleteMenu hMenu, 6, MF_BYPOSITION 'Disables X Button
DeleteMenu hMenu, 5, MF_BYPOSITION 'Removes Form Seperator
DeleteMenu hMenu, 4, MF_BYPOSITION 'Disables Maximize Button
DeleteMenu hMenu, 2, MF_BYPOSITION 'Disables Resizing
' Grey out form's Maximize Button and use thin frame
Dim L As Long
L = GetWindowLong(Me.hWnd, GWL_STYLE)
L = L And Not (WS_MAXIMIZEBOX)
L = L And Not (WS_THICKFRAME)
L = SetWindowLong(Me.hWnd, GWL_STYLE, L)

L = SWP_FRAMECHANGED Or SWP_NOMOVE Or SWP_NOSIZE Or SWP_NOZORDER
L = SetWindowPos(Me.hWnd, 0, 0, 0, 0, 0, L)
End Sub

Private Sub MDIForm_Load()

formLogin.Show
RemoveMenus

End Sub

If you create a mockup mdi which has an intial mdi child that is replaced post-loading by on screen button clicks then you will better be able to visualise the problem. As the code stands the maximize button is ok initially but as soon as another form is loaded it becomes usable again.

Thanks for your help
r1gga
Avatar of r1gga

ASKER

So a simplification of my problem is that I need the changes to the MDI window to remain when MDI children are loaded
Hi, what I suggest you do is rather than use the DeleteMenu api is use the following code to handle your management of the form buttons. For a demo, simply create a new project add a form1 and MDIForm. Now in the MDI form paste the following code:

Option Explicit

Private Const SWP_HIDEWINDOW = &H80
Private Const SWP_NOACTIVATE = &H10
Private Const SWP_NOCOPYBITS = &H100
Private Const SWP_NOMOVE = &H2
Private Const SWP_NOOWNERZORDER = &H200      '  Don't do owner Z ordering
Private Const SWP_NOREDRAW = &H8
Private Const SWP_NOREPOSITION = SWP_NOOWNERZORDER
Private Const SWP_NOSIZE = &H1
Private Const SWP_NOZORDER = &H4
Private Const SWP_SHOWWINDOW = &H40
Private Const SWP_NOSENDCHANGING = &H400
Private Const SWP_FRAMECHANGED = &H20
Private swpFlags As Long
Private Const HWND_TOPMOST = -1
Private Const HWND_NOTOPMOST = -2
Private Const HWND_BOTTOM = 1
Private Const HWND_TOP = 0
Private Declare Function SetWindowPos Lib "user32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long

Private Const GWL_HWNDPARENT = -8
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long

' Used to get window style bits.
Private Const GWL_STYLE = (-16)
Private Const GWL_EXSTYLE = (-20)

' Style bits.
Public Enum WindowStyleBits
    WS_MAXIMIZEBOX = &H10000
    WS_MINIMIZEBOX = &H20000
    WS_THICKFRAME = &H40000
    WS_SYSMENU = &H80000
    WS_CAPTION = &HC00000
End Enum

Public Sub FormAPIs_ChangeWindowAppearance(hWnd As Long, WindowAppearanceProperty As WindowStyleBits, IsVisible As Boolean)

10      Dim lStyle As Long
   
            ' Retrieve current style bits.
20          lStyle = GetWindowLong(hWnd, GWL_STYLE)

            ' Set requested bit On or Off and Redraw.
60          If IsVisible = True Then
70              lStyle = lStyle Or WindowAppearanceProperty
80          Else
90              lStyle = lStyle And Not WindowAppearanceProperty
100         End If
            Call SetWindowLong(hWnd, GWL_STYLE, lStyle)
   
            ' Redraw window with new style.
140         swpFlags = SWP_FRAMECHANGED Or SWP_NOMOVE Or SWP_NOZORDER Or SWP_NOSIZE
            Call SetWindowPos(hWnd, 0, 0, 0, 0, 0, swpFlags)


End Sub

Private Sub MDIForm_Load()

    Call FormAPIs_ChangeWindowAppearance(Me.hWnd, WS_MAXIMIZEBOX, False)
   
End Sub

Private Sub mnuNewForm_Click()
    Form1.Show
End Sub

You can ignore the line numbers, the code is cut from another project that uses line number error handling. Okay after you paste the code, use the Menu Editor and create a menu option called mnuNewForm. So when you click it it will launch the Form1. You will notice that the MDIForm does not change the maximise enabled.

The only way that the Maximise button would get re-enabled, is if somewhere in your code, it is redrawing the menu from another API call you are using. I have been using this method to block form button user menus for ages and never had the problem you are describing.

You can also use the FormAPIs_ChangeWindowAppearance function to change the style of the other buttons. Give it a try and let me know.

Also just read in MSDN that you have to use DrawMenuBar API after calling DeleteMenu, to see the changes, so I am assuming that when you call SetWindowLong, it is redrawing the window, but not permanently setting the buttons. So when you load the other form, the MDI is being told to refresh, and it just goes back to the original menu setup as you never called DrawMenuBar?

Well thats just a guess.
Avatar of r1gga

ASKER

You're a great help rainUK, I have discovered why the problem I am experiencing is occuring. On every single one of my MDI Child forms I have the following code:

Private Sub Form_Load()
    ' Set Window Caption
    mdiMain.Caption = Me.Caption
End Sub

And this is what is refreshing the window. So my code should have worked from the outset if only I had omitted the reference to the MDI's caption. This said how do I go about changing the MDI's caption without experiencing the same problem?

Thanks r1gga
Avatar of r1gga

ASKER

Sorry just read your post on MSDN, so could you please give me the code to define the API Draw Menubar and also call it. This should enable me to alter the caption without re-enabling the maximize button right?
Thanks
r1gga
Hi R1gga,

Just read MSDN again and I am sure that you not calling DrawMenuBar is not persisting the changes, so here is a proper example of using the APIs. So after you call DrawMenuBar you should not need to change any other code, as the change in caption should not affect the new window style.


Private Declare Function GetSystemMenu Lib "user32" (ByVal hwnd As Long, ByVal bRevert As Long) As Long
Private Declare Function GetMenuItemCount Lib "user32" (ByVal hMenu As Long) As Long
Private Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function RemoveMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long
Const MF_BYPOSITION = &H400&
Const MF_REMOVE = &H1000&
Private Sub Form_Load()
    Dim hSysMenu As Long, nCnt As Long
    ' Get handle to our form's system menu
    ' (Restore, Maximize, Move, close etc.)
    hSysMenu = GetSystemMenu(Me.hwnd, False)

    If hSysMenu Then
        ' Get System menu's menu count
        nCnt = GetMenuItemCount(hSysMenu)
        If nCnt Then
            ' Menu count is based on 0 (0, 1, 2, 3...)
            RemoveMenu hSysMenu, nCnt - 1, MF_BYPOSITION Or MF_REMOVE
            RemoveMenu hSysMenu, nCnt - 2, MF_BYPOSITION Or MF_REMOVE ' Remove the seperator
            DrawMenuBar Me.hwnd
            ' Force caption bar's refresh. Disabling X button
            Me.Caption = "Try to close me!"
        End If
    End If
End Sub
Avatar of r1gga

ASKER

Okay I have implemented draw menu bar as follows but the problem is still there, it only occurs when the mdiMain.Caption command is used, without this command it is okay:

Option Explicit
Private Declare Function GetSystemMenu Lib "user32" (ByVal hWnd As Long, ByVal bRevert As Long) As Long
Private Declare Function DrawMenuBar Lib "user32" (ByVal hWnd As Long) As Long
Private Declare Function RemoveMenu Lib "user32" (ByVal hMenu As Long, ByVal nPosition As Long, ByVal wFlags As Long) As Long
Private Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long) As Long
Private Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Const MF_BYPOSITION = &H400&
Const MF_REMOVE = &H1000&
Const GWL_STYLE = (-16)
Const WS_THICKFRAME = &H40000
Const WS_MAXIMIZEBOX = &H10000

Private Sub RemoveMenus()
    Dim hSysMenu As Long
    ' Get handle to our form's system menu
    ' (Restore, Maximize, Move, close etc.)
    hSysMenu = GetSystemMenu(Me.hWnd, False)
    ' Menu count is based on 0 (0, 1, 2, 3...)
    RemoveMenu hSysMenu, 6, MF_BYPOSITION Or MF_REMOVE ' Removes Close
    RemoveMenu hSysMenu, 5, MF_BYPOSITION Or MF_REMOVE ' Remove the seperator
    RemoveMenu hSysMenu, 4, MF_BYPOSITION Or MF_REMOVE ' Removes Maximize
    RemoveMenu hSysMenu, 2, MF_BYPOSITION Or MF_REMOVE ' Removes Resize
   
    ' Grey out form's Maximize Button and use thin frame
    Dim L As Long
    L = GetWindowLong(Me.hWnd, GWL_STYLE)
    L = L And Not (WS_MAXIMIZEBOX)
    L = L And Not (WS_THICKFRAME)
    L = SetWindowLong(Me.hWnd, GWL_STYLE, L)
   
    ' Force caption bar's refresh. Disabling X button
    DrawMenuBar Me.hWnd
    Me.Caption = "Try to close me!"
End Sub

Private Sub MDIForm_Load()
RemoveMenus
End Sub

If you have access to VB could u try this out?
Avatar of r1gga

ASKER

so in short calling:
Me.Caption = "Try to Close me!"
Undoes the greying out of the maximise button (Not the close button) and also undoes the thick border.

None of the SysMenu changes are lost so with or without drawmenubar follow the removemenu API, the changes that are lost are from the following code:
   Dim L As Long
   L = GetWindowLong(Me.hWnd, GWL_STYLE)
   L = L And Not (WS_MAXIMIZEBOX)
   L = L And Not (WS_THICKFRAME)
   L = SetWindowLong(Me.hWnd, GWL_STYLE, L)
Any clues?
Thanks r1gga
give me 30 mins, I'll just try your code
Avatar of r1gga

ASKER

MSDN says:
"IF using SetWindowLong then SetWindPos must be used after this"
yes thats right, but I just tried your code, the thing is, that you set the caption while the form is in its form loading routine, this is causing it to redraw for some reason, because if I take that out and change the caption from any other sub or form it is okay.
actually thats a lie, sorry lol
I just tried it and you are right it redraws
Avatar of r1gga

ASKER

yeah I tried implementing the Set window Pos as follows but to no avail! Is it just me or does this not make sense?

    L = SetWindowLong(Me.hWnd, GWL_STYLE, L)
    ' Force caption bar's refresh. Disabling X button
    DrawMenuBar Me.hWnd
    L = SWP_FRAMECHANGED Or SWP_NOMOVE Or SWP_NOSIZE Or SWP_NOZORDER
    L = SetWindowPos(Me.hWnd, 0, 0, 0, 0, 0, L)
actually thats a lie, sorry lol
I just tried it and you are right it redraws
actually thats a lie, sorry lol
I just tried it and you are right it redraws
ASKER CERTIFIED SOLUTION
Avatar of RainUK
RainUK

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of r1gga

ASKER

I'm going to remove the reference to the MDI's caption, but hey thats life having to work around visual basics problems rather than visual basic working around my problems.
Anyway thanks for your help and your quick posts.
r1gga
Avatar of r1gga

ASKER

SOrry about the C but there was no real solution reached just a compromise
glad to help, its not the grade or points, but getting your code to work the way you need it to