Post

Windows Forms - Disable the Close Button

Disable Close Button

Download source code - 6.34kb

Introduction

I’ve seen it asked many times, “How do I disable the close button on a Windows Form, but still have the system menu, minimize and maximize available?”. I’ve also seen a lot of answers to how to accomplish this. Let’s look at the answer that appears to be very popular for solving this issue.

CloseButtonRemove.vb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
Option Explicit On
Option Strict On

Imports System.Runtime.InteropServices

Public Class CloseButtonRemove

  Private Declare Function GetSystemMenu Lib "user32" (ByVal hwnd As Integer, ByVal revert As Integer) As Integer
  Private Declare Function GetMenuItemCount Lib "user32" (ByVal menu As Integer) As Integer
  Private Declare Function RemoveMenu Lib "user32" (ByVal menu As Integer, ByVal position As Integer, ByVal flags As Integer) As Integer
  Private Declare Function DrawMenuBar Lib "user32" (ByVal hwnd As Integer) As Integer
  Private Declare Function FormatMessageA Lib "kernel32" (ByVal flags As Integer, ByRef source As Object, ByVal messageID As Integer, ByVal languageID As Integer, ByVal buffer As String, ByVal size As Integer, ByRef arguments As Integer) As Integer

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

  Private Sub New ()
  End Sub

  Public Shared Sub Disable(ByVal form As System.Windows.Forms.Form)
    ' Get handle to system menu for the form provided.
    Dim menu As Integer = GetSystemMenu(form.Handle.ToInt32, 0)
    ' Get number of items in this system menu.
    Dim count As Integer = GetMenuItemCount(menu)
    ' Remove last item from system menu (last item should be ’Close’).
    If RemoveMenu(menu, count - 1, MF_DISABLED Or MF_BYPOSITION) = 0 Then
      Throw New Exception(FormatMessage(Err.LastDllError))
    Else
      ' On success, force a redraw of the system menu.
      If DrawMenuBar(form.Handle.ToInt32) = 0 Then
        Throw New Exception(FormatMessage(Err.LastDllError))
      End If
    End If
  End Sub

  Public Shared Function FormatMessage(ByVal [error] As Integer) As String
    Const FORMAT_MESSAGE_FROM_SYSTEM As Short = &H1000
    Const LANG_NEUTRAL As Short = &H0
    Dim buffer As String = Space(999)
    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, [error], LANG_NEUTRAL, buffer, 999, 0)
    buffer = Replace(Replace(buffer, Chr(13), ""), Chr(10), "")
    Return buffer.Substring(0, buffer.IndexOf(Chr(0)))
  End Function

End Class

I’ve attempted to document the code fully so that it’s easier to see what is going on. Also, bear in mind that most of the sample code floating around on the net doesn’t take into account possible errors occurring doing this task in more of a brute force method. Based on the SDK documentation, I’ve added error checking where appropriate. Also, take note of the FormatMessage function that is included in this Class. It may come in handy when your working with other Win32 Interop functions as it will (usually) return a string version of a numeric error value.

So, you might ask, what’s the problem with this code? It does what was asked. Actually, it doesn’t. The question was “How do I disable the close button?”; which would mean disabling the ‘close’ menu item as well… not deleting it from the menu.

The following code shows how to disable the close button according to the SDK documentation.

CloseButton.vb

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Option Explicit On
Option Strict On

Public Class CloseButton

  Private Declare Function GetSystemMenu Lib "user32" (ByVal hwnd As Integer, ByVal revert As Integer) As Integer
  Private Declare Function EnableMenuItem Lib "user32" (ByVal menu As Integer, ByVal ideEnableItem As Integer, ByVal enable As Integer) As Integer

  Private Const SC_CLOSE As Integer = &HF060
  Private Const MF_BYCOMMAND As Integer = &H0
  Private Const MF_GRAYED As Integer = &H1
  Private Const MF_ENABLED As Integer = &H0

  Private Sub New()
  End Sub

  Public Shared Sub Disable(ByVal form As System.Windows.Forms.Form)
    ' The return value specifies the previous state of the menu item (it is either
    ' MF_ENABLED or MF_GRAYED). 0xFFFFFFFF indicates   that the menu item does not exist.
    Select Case EnableMenuItem(GetSystemMenu(form.Handle.ToInt32, 0), SC_CLOSE, MF_BYCOMMAND Or MF_GRAYED)
      Case MF_ENABLED
      Case MF_GRAYED
      Case &HFFFFFFFF
        Throw New Exception("The Close menu item does not exist.")
      Case Else
    End Select
  End Sub

End Class

As you can see, we now only require two API declarations. Also, there are not LastDllError checks necessary in order to determine the error since our single call returns one of three values as described by the SDK documentation.

To use this code within your Windows Form application, just import one of the classes into your code and add the CloseButton.Disable(Me) line to your Form1_Load (as an example). For example:

1
2
3
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  CloseButton.Disable(Me)
End Sub

You will also need to add the following code to your form if you will be allowing the form to minimize or maximize:

1
2
3
Protected Overrides Sub OnSizeChanged(ByVal e As System.EventArgs)
  CloseButton.Disable(Me)
End Sub

Thanks to EdwardA on GDN for pointing out that whenever the form is minimized or maximized, the close button becomes enabled again. Not really sure at this point why this is, however, overriding the OnSize event will allow you to handle this issue.

UPDATE: - 10.31.2003 12:39PM - Added information about using OnResize.

This post is licensed under CC BY 4.0 by the author.