Botón Cerrar desactivado
[Leyendo procedimientos externos almacenados en la librería de Windows USER32.DLL
]

[Ejercicio en que se desactiva el botón de cierre de la barra de título del formulario, así como la función de las teclas ALT+F4]

Fecha: 05/Octubre/2003 (11/Octubre/2003)
Autor: Miliuco (
Emilio Pérez Egido) - [email protected]

 
.

A raíz de la petición de ayuda de un amigo, que desea que una aplicación arranque sin que pueda ser terminada por el usuario pulsando el botón Cerrar (el aspa de la esquina derecha) de la Barra de título del formulario ni mediante la pulsación de las teclas ALT+F4, he encontrado muchos códigos en Internet, unos para C#, otros para C++ y otros para Visual Basic. Todos ellos tienen en común la necesidad de leer procedimientos externos, almacenados en librerías de Windows, User32.dll en este caso concreto. Pero así como los ejemplos encontrados para C#, por lo general, funcionan bien y rápido (los de C++ los dejo a un lado por mi ignorancia), los que he visto para Visual Basic .NET me han dado muchos problemas: algunos no funcionan bien y, los que sí lo hacen, tardan bastante en desactivar el botón de cierre, lo que causa mal efecto.

Incluso Microsoft tiene un ejemplo de este tipo, pero referido a una aplicación de consola, que puede ser consultado aquí.
Así que he realizado en Visual Basic .NET un código que desactiva ese botón y el efecto de las teclas ALT+F4 de manera rápida, lo que causa mejor impresión al usuario.

Si alguien desea profundizar sobre este asunto, ha de buscar información sobre las API de Windows, pues en realidad éso es lo que hacemos, utilizar procedimientos externos a Visual Basic, existentes en librerías del sistema. En este ejercicio leemos procedimientos de la librería USER32.DLL, y con ellos actuamos sobre el llamado Menú de sistema de las ventanas, el que provee de funcionalidad tanto a los botones de Maximizar / Minimizar / Cerrar como a los elementos que cuelgan del icono de la Barra de título: Restaurar / Mover / Tamaño / Minimizar / Maximizar / Separador / Cerrar.

A continuación se muestra el código en Visual Basic .NET:



'DESACTIVAR EL BOTÓN CERRAR Y LA FUNCIÓN DE CIERRE CON ALT+F4

'NOTA de Miliuco:
'en esta aplicación se usa el tipo de dato IntPtr. Se ha diseñado
'el tipo IntPtr para que sea un número entero cuyo tamaño sea específico
'de la plataforma. Es decir, se espera que una instancia de este tipo tenga
'lugar en sistemas operativos y hardware de 32 bits, y en sistemas
'operativos y hardware de 64 bits. El tipo IntPtr se puede utilizar por idiomas
'que admiten punteros, y como un medio común para hacer referencia a los
'datos entre idiomas que admiten o no punteros. El tipo IntPtr es compatible
'con CLS (Common Language Specification) -> conjunto de características
'básicas de lenguaje englobadas en .NET Framework. El tipo IntPtr pertenece
'al espacio de nombres System

'Importar espacios de nombres necesarios para la aplicación
Imports System
Imports System.Drawing
Imports System.Collections
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Data

'Para leer procedimientos externos radicados en librerías de windows
'Imports System.Runtime.InteropServices

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Código generado por el Diseñador de Windows Forms "

    Public Sub New()
        MyBase.New()

        'El Diseñador de Windows Forms requiere esta llamada.
        InitializeComponent()

        'Agregar cualquier inicialización después de la llamada a InitializeComponent()

    End Sub

    'Form reemplaza a Dispose para limpiar la lista de componentes.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Requerido por el Diseñador de Windows Forms
    Private components As System.ComponentModel.IContainer

    'NOTA: el Diseñador de Windows Forms requiere el siguiente procedimiento
    'Puede modificarse utilizando el Diseñador de Windows Forms. 
    'No lo modifique con el editor de código.
    Friend WithEvents Button1 As System.Windows.Forms.Button
    Friend WithEvents Button2 As System.Windows.Forms.Button
    Friend WithEvents GroupBox1 As System.Windows.Forms.GroupBox
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Dim resources As System.Resources.ResourceManager = New System.Resources.ResourceManager(GetType(Form1))
        Me.Button1 = New System.Windows.Forms.Button
        Me.Button2 = New System.Windows.Forms.Button
        Me.GroupBox1 = New System.Windows.Forms.GroupBox
        Me.SuspendLayout()
        '
        'Button1
        '
        Me.Button1.Font = New System.Drawing.Font("Verdana", 18.0!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
        Me.Button1.Location = New System.Drawing.Point(24, 32)
        Me.Button1.Name = "Button1"
        Me.Button1.Size = New System.Drawing.Size(208, 72)
        Me.Button1.TabIndex = 0
        Me.Button1.Text = "Desactivar X"
        '
        'Button2
        '
        Me.Button2.Font = New System.Drawing.Font("Verdana", 18.0!, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, CType(0, Byte))
        Me.Button2.Location = New System.Drawing.Point(240, 32)
        Me.Button2.Name = "Button2"
        Me.Button2.Size = New System.Drawing.Size(112, 72)
        Me.Button2.TabIndex = 1
        Me.Button2.Text = "Salir"
        '
        'GroupBox1
        '
        Me.GroupBox1.Location = New System.Drawing.Point(8, 8)
        Me.GroupBox1.Name = "GroupBox1"
        Me.GroupBox1.Size = New System.Drawing.Size(360, 112)
        Me.GroupBox1.TabIndex = 2
        Me.GroupBox1.TabStop = False
        Me.GroupBox1.Text = "Botones de comando"
        '
        'Form1
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(376, 130)
        Me.Controls.Add(Me.Button2)
        Me.Controls.Add(Me.Button1)
        Me.Controls.Add(Me.GroupBox1)
        Me.Icon = CType(resources.GetObject("$this.Icon"), System.Drawing.Icon)
        Me.Name = "Form1"
        Me.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen
        Me.Text = "  Botón cerrar ->"
        Me.ResumeLayout(False)

    End Sub

#End Region

    'Declaración de constantes necesarias (valores en hexadecimal)

    Private Const MF_BYPOSITION As Integer = &H400
    Private Const MF_REMOVE As Integer = &H1000
    Private Const MF_DISABLED As Integer = &H2

    'Variable para saber si ya está desactivado el botón X
    Private pulsado As Boolean = True

    'Importación de procedimientos externos almacenados
    'en la librería de Windows USER32.DLL

    'Obtener el menú de sistema
    Private Declare Function GetSystemMenu Lib "User32" _
            (ByVal hWnd As Integer, _
            ByVal bRevert As Boolean) As IntPtr

    'Obtener el número de elementos del menú de sistema
    Private Declare Function GetMenuItemCount Lib "User32" _
             (ByVal hMenu As Integer) As IntPtr

    'Quitar elementos del menú de sistema
    Private Declare Function RemoveMenu Lib "User32" _
        (ByVal hMenu As Integer, _
        ByVal nPosition As Integer, _
        ByVal wFlags As Long) As IntPtr

    'Redibujar la barra de título de la ventana
    Private Declare Function DrawMenuBar Lib "User32" _
            (ByVal hWnd As Integer) As IntPtr

    'Método que desactiva el botón X (cerrar)
    Private Sub DisableCloseButton(ByVal hWnd As IntPtr)
        Try 'captura de excepciones

            Dim menuItemCount As IntPtr
            Dim hMenu As IntPtr
            'Obtener el manejador del menú de sistema del formulario
            hMenu = GetSystemMenu(hWnd.ToInt32(), False)
            'Obtener la cuenta de los ítems del menú de sistema.
            'Es el menú que aparece al pulsar sobre el icono a la izquierda
            'de la Barra de título de la ventana, consta de los ítems: Restaurar, Mover,
            'Tamaño,Minimizar,  Maximizar, Separador, Cerrar.
            menuItemCount = GetMenuItemCount(hMenu.ToInt32())
            'Quitar el ítem Close (Cerrar), que es el último de ese menú
            RemoveMenu(hMenu.ToInt32(), menuItemCount.ToInt32() - 1, MF_DISABLED Or MF_BYPOSITION)
            'Quitar el ítem Separador, el penúltimo de ese menú, entre Maximizar y Cerrar
            RemoveMenu(hMenu.ToInt32(), menuItemCount.ToInt32() - 2, MF_DISABLED Or MF_BYPOSITION)
            'Redibujar la barra de menú
            DrawMenuBar(hWnd.ToInt32())

            'mostrar un mensaje con la excepción producida
        Catch pollo As Exception
            MessageBox.Show("Se ha producido la excepción: " + vbCrLf + pollo.Message, _
            "Error del programa", MessageBoxButtons.OK)
        End Try
    End Sub

    'Al pulsar el botón Desactivar
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        'Si no hemos desactivado el botón Cerrar
        If (pulsado) Then
            'Método desarrollado más arriba, pasando como parámetro
            'el identificador de la ventana sobre la que vamos a actuar
            DisableCloseButton(Me.Handle)
            'Aviso al usuario, no funciona el botón cerrar ni las teclas ALT+F4
            MessageBox.Show("El botón Cerrar ha sido desactivado." + vbCrLf + _
            "Pulsa Salir para cerrar la aplicación", "Cerrar desactivado 1")
            'para saber que ya hemos desactivado el b´tón
            pulsado = False
        Else
            'Si ya hemos desactivado el botón Cerrar
            MessageBox.Show("Ya habías pulsado aquí antes.", "Cerrar desactivado 2")
        End If
    End Sub

    'Al pulsar el botón Salir
    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        'Salir de la aplicación
        Me.Close()
    End Sub

End Class

Imagen del programa en funcionamiento:

Fichero con el código de ejemplo usando Visual Studio .NET 2003:


ir al índice