Deshabilitar el botón cerrar de un formulario

y volver a habilitarlo... usando API de Windows.
Dos modos de hacer una misma cosa.



Publicado: 20/Jun/2001
Actualizado: 20/Jun/2001 



En ocasiones puede ser interesante poder deshabilitar el botón cerrar (el de la X que hay a la derecha de los formularios), sobre todo si lo que mostramos son cuadros de diálogo.
De esta forma, evitamos que el usuario pueda cerrar el formulario tanto desde el botoncito de la equis, como desde el menú "cerrar" del controlbox.

A continuación te voy a mostrar dos formas para hacer lo que indica el título.
Una es usando funciones catalogadas como "obsoletas" y que han sido heredadas del API de Windows de 16 bits, que aunque funcionales, no se recomienda su uso.
La otra es usando las funciones que sustituyen a esas que ya están obsoletas.
Además de ser funciones "actuales", nos dan la posibilidad de más control sobre lo que hacemos.

El código de la primera forma de hacer que el botón cerrar quede deshabilitado (y se pueda volver a habilitar), está basado en un ejemplo de la Knowledge Base y aunque era para la versión 3.0 del Visual Basic, no ha habido problemas para adaptarlo. Aunque he tenido que añadirle algunas cosillas para que sea 100% operativo (lo he probado con Windows 2000 y funciona perfectamente, espero que con versiones anteriores no haya problemas).
El código de la segunda forma, es de mi cosecha, aunque basándome en el código del referido artículo.

Lo que sigue es válido para las dos formas de hacerlo. Seguramente será más fácil de entender y de codificar usando la primera, aunque con la segunda nos permite usar la aplicación sin importar el idioma del sistema operativo.
Como verás dentro de poco, en cuanto acabe esta "verborrea", usando las funciones "obsoletas", tenemos que indicarle el Caption del menú cerrar, sin embargo con la segunda forma, podemos averiguar el Caption que tenía asignado y usar el mismo... si está en castellano, el caption será Cerrar, pero si el idioma del Windows es inglés, será Close.

Vamos con un "poquillo" más de teoría y después veremos el código.

Hay que tener en cuenta que a la hora de deshabilitar el control cerrar, (y poner la "x" en un gris típico de deshabilitado), se puede hacer de dos formas:

1- Mientras se carga el formulario, dentro del evento Form_Load
2- Una vez que está el formulario mostrado.

Ahora veremos cómo hacerlo para las dos ocasiones.

1- En el caso de que la "deshabilitación" del botón se haga en el evento Form_Load, simplemente se le indica al Windows que modifique el menú indicado y lo ponga deshabilitado.

2-Si se quiere deshabilitar cuando el formulario está mostrado, además del código que deshabilita el menú, hay que hacer que se redibujen los menús. Este mismo caso se da cuando se vuelve a habilitar. Si no se dibujan de nuevo los menús, se quedará habilitado/deshabilitado, pero el aspecto no será el que debe ser.

Bueno, dejemos la "cháchara" y vayamos a ver el código que necesitamos.

El formulario de prueba, tiene dos botones: uno para habilitar el menú y la "x" y otro para deshabilitarlo (ponerlo gris) y hacerlo inoperativo.
Copia el siguiente código y pegalo en el formulario.

Las declaraciones de las funciones del API están declaradas como Private para que las puedas poner en el formulario,  en la parte de Declaraciones/General.

Empecemos viendo el código de las funciones obsoletas, (pero, recuerda, que operativas y válidas)
Fíjate en el menú que, aunque tengas el Windows en un idioma diferente al inglés, te muestra la palabra Close.


'------------------------------------------------------------------------------
' Prueba para deshabilitar el botón cerrar de un formulario         (20/Jun/01)
'
' Basado en un artículo de la KB para VB 3.0 (Q82876)
' How to Disable Close Command in VB Control Menu (System Menu)
'
' ©Guillermo 'guille' Som, 2001
'------------------------------------------------------------------------------
Option Explicit

' Para deshabilitar el menú cerrar (controlbox) de un formulario
Private Declare Function GetSystemMenu Lib "user32" _
    (ByVal hWnd As Long, ByVal bRevert As Long) As Long
Private Declare Function ModifyMenu Lib "user32" Alias "ModifyMenuA" _
    (ByVal hMenu As Long, ByVal nPosition As Long, _
    ByVal wFlags As Long, ByVal wIDNewItem As Long, _
    ByVal lpString As Any) As Long
Private Declare Function DrawMenuBar Lib "user32" _
    (ByVal hWnd As Long) As Long
'
Private Const MF_BYCOMMAND = &H0&
Private Const MF_ENABLED = &H0&
Private Const MF_GRAYED = &H1&
'
Private Const SC_CLOSE = &HF060&

Private Sub Form_Load()
    ' Deshabilitar el botón de cerrar el formulario
    Dim hMenu As Long
    '
    hMenu = GetSystemMenu(hWnd, 0)
    ' Deshabilitar el menú cerrar del formulario
    Call ModifyMenu(hMenu, SC_CLOSE, MF_BYCOMMAND Or MF_GRAYED, -10, "Close")
    '
End Sub

Private Sub cmdDeshabilitar_Click()
    ' Deshabilitar el botón de cerrar el formulario
    Dim hMenu As Long
    '
    hMenu = GetSystemMenu(hWnd, 0)
    ' Deshabilitar el menú cerrar del formulario
    Call ModifyMenu(hMenu, SC_CLOSE, MF_BYCOMMAND Or MF_GRAYED, -10, "Close")
    '
    ' Si esta llamada se hace dentro del Form_Load,
    ' no es necesario redibujar los menús
    ' Redibujar los menús, para que se muestre deshabilitado
    Call DrawMenuBar(hWnd)
    '
End Sub

Private Sub cmdHabilitar_Click()
    ' Habilitar el botón de cerrar el formulario
    Dim hMenu As Long
    '
    hMenu = GetSystemMenu(hWnd, 0)
    ' Esto lo habilita, pero sigue en gris...
    Call ModifyMenu(hMenu, -10, MF_BYCOMMAND Or MF_ENABLED, SC_CLOSE, "Close")
    ' Redibujar los menús, para que se muestre habilitado
    Call DrawMenuBar(hWnd)
    '
End Sub

Ahora veremos el código con las funciones que se deberían usar.
Con esta otra forma de hacerlo, el caption del menú se muestra igual que estaba originalmente, aunque también se pierde el gráfico que por defecto muestra.


'------------------------------------------------------------------------------
' Prueba para deshabilitar el botón cerrar de un formulario         (20/Jun/01)
'
' Basado en un artículo de la KB para VB 3.0 (Q82876)
' How to Disable Close Command in VB Control Menu (System Menu)
'
' ©Guillermo 'guille' Som, 2001
'------------------------------------------------------------------------------
Option Explicit

Private sTextClose As String
Private Const hSC_Close As Long = -10&
'
' Para deshabilitar el menú cerrar (controlbox) de un formulario
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 Const MF_BYCOMMAND = &H0&
Private Const MF_STRING = &H0&
Private Const MF_BITMAP = &H4&
Private Const MF_OWNERDRAW = &H100&
Private Const MF_ENABLED = &H0&
Private Const MF_GRAYED = &H1&
Private Const MF_DISABLED = &H2&
' Estos valores son para NT/2000
Private Const MFS_GRAYED = &H3&
Private Const MFS_DISABLED = MFS_GRAYED
Private Const MFT_STRING = MF_STRING
'
Private Const MIIM_STATE = &H1&
Private Const MIIM_ID = &H2&
Private Const MIIM_SUBMENU = &H4&
Private Const MIIM_CHECKMARKS = &H8&
Private Const MIIM_TYPE = &H10&
Private Const MIIM_DATA = &H20&
'
Private Const SC_SIZE = &HF000&
Private Const SC_MOVE = &HF010&
Private Const SC_MINIMIZE = &HF020&
Private Const SC_MAXIMIZE = &HF030&
Private Const SC_NEXTWINDOW = &HF040&
Private Const SC_PREVWINDOW = &HF050&
Private Const SC_CLOSE = &HF060&

Private Type MENUITEMINFO
    cbSize As Long
    fMask As Long
    fType As Long
    fState As Long
    wID As Long
    hSubMenu As Long
    hbmpChecked As Long
    hbmpUnchecked As Long
    dwItemData As Long
    dwTypeData As String
    cch As Long             ' La longitud de dwTypeData
End Type

Private Declare Function GetMenuItemInfo Lib "user32" Alias "GetMenuItemInfoA" _
    (ByVal hMenu As Long, ByVal uItem As Long, _
    ByVal fByPosition As Boolean, lpMenuItemInfo As MENUITEMINFO) As Long

Private Declare Function SetMenuItemInfo Lib "user32" Alias "SetMenuItemInfoA" _
    (ByVal hMenu As Long, ByVal uItem As Long, _
    ByVal fByPosition As Boolean, lpcMenuItemInfo As MENUITEMINFO) As Long

Private Sub Form_Load()
    ' Obtener el texto del menú Cerrar
    Dim hMenu As Long
    Dim tMenuItemInfo As MENUITEMINFO
    '
    ' Obtener el handle del menú del sistema
    hMenu = GetSystemMenu(hWnd, 0)
    With tMenuItemInfo
        .cbSize = Len(tMenuItemInfo)
        .fMask = MIIM_DATA Or MIIM_ID Or MIIM_TYPE
        .fType = MF_STRING
        .cch = 50
        .dwTypeData = String$(.cch, Chr$(0))
        ' Obtener la información del menú cerrar
        Call GetMenuItemInfo(hMenu, SC_CLOSE, False, tMenuItemInfo)
        ' El Caption del menú cerrar (para que sirva para cualquier idioma de Windows)
        sTextClose = Left$(.dwTypeData, .cch)
        '
'        ' Para deshabilitarlo en el Form_Load
'        .fMask = MIIM_DATA Or MIIM_ID Or MIIM_STATE Or MIIM_TYPE
'        .fState = MF_GRAYED
'        .dwItemData = SC_CLOSE
'        .wID = hSC_Close
'        .fType = MF_STRING
'        .dwTypeData = sTextClose & Chr$(0)
'        .cch = Len(sTextClose) + 1
'        '
'        Call SetMenuItemInfo(hMenu, SC_CLOSE, False, tMenuItemInfo)
    End With
    '
End Sub

Private Sub cmdDeshabilitar_Click()
    ' Deshabilitar el botón de cerrar el formulario
    Dim hMenu As Long
    Dim tMenuItemInfo As MENUITEMINFO
    '
    hMenu = GetSystemMenu(hWnd, 0)
    With tMenuItemInfo
        .cbSize = Len(tMenuItemInfo)
        .fMask = MIIM_DATA Or MIIM_ID Or MIIM_STATE Or MIIM_TYPE
        .fState = MF_GRAYED
        .dwItemData = SC_CLOSE
        .wID = hSC_Close
        .fType = MF_STRING
        .dwTypeData = sTextClose & Chr$(0)
        .cch = Len(sTextClose) + 1
        '
        Call SetMenuItemInfo(hMenu, SC_CLOSE, False, tMenuItemInfo)
    End With
    '
    ' Si esta llamada se hace dentro del Form_Load,
    ' no es necesario redibujar los menús
    ' Redibujar los menús, para que se muestre deshabilitado
    Call DrawMenuBar(hWnd)
    '
End Sub

Private Sub cmdHabilitar_Click()
    ' Habilitar el botón de cerrar el formulario
    Dim hMenu As Long
    Dim tMenuItemInfo As MENUITEMINFO
    '
    ' Obtener el handle del menú del sistema
    hMenu = GetSystemMenu(hWnd, 0)
    With tMenuItemInfo
        .cbSize = Len(tMenuItemInfo)
        .fMask = MIIM_DATA Or MIIM_ID Or MIIM_STATE Or MIIM_TYPE
        .fState = MF_ENABLED
        .dwItemData = hSC_Close
        .wID = SC_CLOSE
        .fType = MF_STRING
        .dwTypeData = sTextClose & Chr$(0)
        .cch = Len(sTextClose) + 1
    End With
    Call SetMenuItemInfo(hMenu, hSC_Close, False, tMenuItemInfo)
    '
    ' Redibujar los menús, para que se muestre habilitado
    Call DrawMenuBar(hWnd)
    '
End Sub

Y eso es todo. Prueba a ejecutar el proyecto, verás que el botón se muestra deshabilitado y no permite cerrar el formulario.
Pulsa en el botón Habilitar y ya estará habilitado y operativo, si pulsas en el botón Deshabilitar, se deshabilitará el botón y se mostrará gris.

La explicación de porqué se usa el valor -10, para cambiar de habilitado a deshabilitado, está explicado en el artículo de la KB del que he usado parte de la información aquí mostrada:
(Q82876) How to Disable Close Command in VB Control Menu (System Menu)

Bájate el código de ejemplo de las dos formas mostradas: deshabilitarCerrar.zip 4.54 KB

Nos vemos.
Guillermo


ir al índice