שתף באמצעות


Disabling and enabling the close button on a Windows Form

Question

Wednesday, January 28, 2009 5:36 PM

Let's say I want the ability to disable and enable the close button on a Windows Form.  By 'close button', I'm talking about the little 'x' in the upper right hand corner of a forms title bar. 

I already wrote some code to do this using some Win32 API calls.  It look a little something like this:

    Private Const MF_BYCOMMAND As Integer = &H0  
    Private Const MF_DISABLED As Integer = &H2  
    Private Const MF_GRAYED As Integer = &H1  
    Private Const SC_CLOSE As Integer = &HF060  
 
    <DllImport("user32")> _  
    Private Shared Function GetSystemMenu(ByVal hWnd As IntPtr, _  
            ByVal bRevert As Boolean) As IntPtr  
    End Function 
 
    <DllImport("user32")> _  
        Private Shared Function DrawMenuBar(ByVal hwnd As IntPtr) As Integer 
    End Function 
 
    <DllImport("user32")> _  
    Private Shared Function EnableMenuItem(ByVal hMenu As IntPtr, _  
    ByVal uIDEnableItem As Int32, ByVal uEnable As Int32) As Boolean 
    End Function 
 
    Public Property CloseButton() As Boolean 
        Get 
            Return _closeButton  
        End Get 
        Set(ByVal value As Boolean)  
            If _closeButton <> value Then 
                _closeButton = value  
                If _closeButton Then 
                    EnableCloseButton()  
                Else 
                    DisableCloseButton()  
                End If 
            End If 
        End Set 
    End Property 
 
    Protected Overrides Sub OnResize(ByVal e As System.EventArgs)  
        If Not _closeButton Then 
            DisableCloseButton()  
        End If 
        MyBase.OnResize(e)  
    End Sub 
 
    Private Sub EnableCloseButton()  
        DrawMenuBar(Me.Handle)  
    End Sub 
 
    Private Sub DisableCloseButton()  
        Dim hMenu As IntPtr = GetSystemMenu(Me.Handle, False)  
        EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND Or MF_DISABLED Or MF_GRAYED)  
        DrawMenuBar(Me.Handle)  
    End Sub 

 

Today, however, I discovered this article which shows an alternate way to do it.  The code in the article is in C# but here's the code in VB.NET:

    Private Const CP_NOCLOSE_BUTTON As Integer = &H200     
    Protected Overrides ReadOnly Property CreateParams() As CreateParams     
        Get     
            Dim myCp As CreateParams = MyBase.CreateParams     
            If _closeButton Then     
                myCp.ClassStyle = myCp.ClassStyle And CP_NOCLOSE_BUTTON     
            Else     
                myCp.ClassStyle = myCp.ClassStyle Or CP_NOCLOSE_BUTTON     
            End If     
            Return myCp     
        End Get     
    End Property    
 

As you can see, the article's code has two major advantages over my code.  First, it's a hell of a lot simpler.  Second, it doesn't have to rely on making native Win32 API calls.  (I try to avoid calling Win32 API unless there's no .NET way to do it.)

I was about to throw out my code in favor of the article's code.

But then discovered that the article's code has two disadvantage versus my code.  With mine, I can both enable and disable the close button at will at run-time.  The article's code only disables the button;  there's no way to re-enable it.  Also, it only works before the form is created.  So, if you want to disable the button after the form has been displayed, you can't.

Does anyone know if possible to solve either of these two problems with the article's code? 

I'm still researching it but my guess is that the answer is no.  I'm not familiar with CreateParams but given the name, I'm guessing that these are parameters that are only used when the form is created.

All replies (8)

Thursday, January 29, 2009 12:03 AM ✅Answered | 1 vote

"This method calls the CreateParams method to get the styles to apply. The styles assigned to the Style and ExStyle properties of the CreateParams assigned to the control's CreateParams property are reapplied. The control is repainted to reflect the style changes if necessary."

 

But not the ClassStyle.

 

You can follow through the MinimizeBox property with reflector to see that it works by updating the CreateParams. There's an override of the CreateParams property that calls FillInCreateParamsBorderIcons which will set the windows style WS_MinimizeBox, which is declared in Winuser.h:

#define WS_MINIMIZEBOX      0x00020000L

Code from Form.FillInCreateParamsBorderIcons:

If (Me.MinimizeBox OrElse Me.IsRestrictedWindow) Then   
   cp.Style = (cp.Style Or &H20000)  
Else   
   cp.Style = (cp.Style And -131073)  
End If  

So if Me.MinimizeBox is true then it will set WS_MINIMIZEBOX, otherwise it will clear it (-131073 is NOT WS_MINIMIZEBOX)

Setting the MinimizeBox property causes this flag to be set, and then triggers some method calls that end up back in Control.UpdateStylesCore

Friend Overridable Sub UpdateStylesCore() 
    If Me.IsHandleCreated Then 
        Dim createParams As CreateParams = Me.CreateParams 
        Dim windowStyle As Integer = Me.WindowStyle 
        Dim windowExStyle As Integer = Me.WindowExStyle 
        If ((Me.state And 2) <> 0) Then 
            createParams.Style = (createParams.Style Or &H10000000) 
        End If 
        If (windowStyle <> createParams.Style) Then 
            Me.WindowStyle = createParams.Style 
        End If 
        If (windowExStyle <> createParams.ExStyle) Then 
            Me.WindowExStyle = createParams.ExStyle 
            Me.SetState(&H40000000, ((createParams.ExStyle And &H400000) <> 0)) 
        End If 
        SafeNativeMethods.SetWindowPos(New HandleRef(Me, Me.Handle), NativeMethods.NullHandleRef, 0, 0, 0, 0, &H37) 
        Me.Invalidate(True) 
    End If 
End Sub 

So at this point it has changed createParams.Style, so it will set the WindowsStyle property to createParams.Style:

 

Friend Property WindowStyle As Integer 
    Get 
        Return CInt(CLng(UnsafeNativeMethods.GetWindowLong(New HandleRef(Me, Me.Handle), -16))) 
    End Get 
    Set(ByVal value As Integer) 
        UnsafeNativeMethods.SetWindowLong(New HandleRef(Me, Me.Handle), -16, New HandleRef(Nothing, DirectCast(value, IntPtr))) 
    End Set 
End Property 

 

The update is applied by calling SetWindowLong, and then back in UpdateStylesCore it will call SetWindowPos. Incidently - this is from SetWindowPos documentation:

If you have changed certain window data using SetWindowLong, you must call SetWindowPos for the changes to take effect

 

So yeah, you can change some styles using CreateParams, but only the changable Windows styles and WindowsEx styles (they aren't all changeable). These are defined in constants starting WS_ and WS_EX_, Class constants start CS_, the close window constant is CS_NOCLOSE. The one for the close box is a Class Style, and it is not changeable - look in UpdateStylesCore, it doesn't make any calls for ClassStyles.


Wednesday, January 28, 2009 7:45 PM

After modifying the closeButton value, simply call Me.UpdateStyles().

I'm almost positive that that's all you need to do, but I don't currently have VS installed (new rebuild) so can't check myself.

Mick Doherty
http://dotnetrix.co.uk


Wednesday, January 28, 2009 8:32 PM

 Me.UpdateStyles() didn't seem to work

although I am from the school that if it is important not to close a form until all criteria is meet you need to use the FormClosing event so no matter how it is being closed whatever criteria is meet.

 


Wednesday, January 28, 2009 8:51 PM | 1 vote

There's a difference between Windows Styles (which UpdateStyles will update) and Class Styles (including CS_NOCLOSE - which is called CP_NOCLOSE_BUTTON in the linked code).

Class styles are used with RegisterWindowEx to register a new windows class.

Windows styles are used later to specify some other stuff when you create a window with CreateWindowEx.

(UpdateStyles will call the SetWindowLong and then SetWindowPosition to update windows styles.)

I don't think you can do it.


Wednesday, January 28, 2009 9:03 PM

TechNoHick said:

 Me.UpdateStyles() didn't seem to work

although I am from the school that if it is important not to close a form until all criteria is meet you need to use the FormClosing event so no matter how it is being closed whatever criteria is meet. 

I'm creating a general-purpose coding library.  Although I can change the design, for now I consider that the responsibility of the application developer.


Wednesday, January 28, 2009 9:21 PM

Microsoft's documenation on UpdateStyles states:

"This method calls the CreateParams method to get the styles to apply. The styles assigned to the Style and ExStyle properties of the CreateParams assigned to the control's CreateParams property are reapplied. The control is repainted to reflect the style changes if necessary."

So I would think it would work.  However, it doesn't.  I inserted a break point inside my CreateParams() method and I can see it execute the code, but the close button stays enabled.

    Public Property CloseButton() As Boolean 
        Get 
            Return _closeButton  
        End Get 
        Set(ByVal value As Boolean)  
            If _closeButton <> value Then 
                _closeButton = value  
                Me.UpdateStyles()  
                MyBase.Refresh()   
            End If 
        End Set 
    End Property 
 
    Private Const CP_NOCLOSE_BUTTON As Integer = &H200  
    Protected Overrides ReadOnly Property CreateParams() As CreateParams  
        Get 
            Dim myCp As CreateParams = MyBase.CreateParams  
            If _closeButton Then 
                myCp.ClassStyle = myCp.ClassStyle And CP_NOCLOSE_BUTTON  
            Else 
                myCp.ClassStyle = myCp.ClassStyle Or CP_NOCLOSE_BUTTON  
            End If 
            Return myCp  
        End Get 
    End Property 

Wednesday, January 28, 2009 9:32 PM

The documentation also states that the ClassName must be null.  You can't update the Form after it's instantiated.


Thursday, January 29, 2009 3:09 PM

"So yeah, you can change some styles using CreateParams, but only the changable Windows styles and WindowsEx styles (they aren't all changeable). These are defined in constants starting WS_ and WS_EX_, Class constants start CS_, the close window constant is CS_NOCLOSE. The one for the close box is a Class Style, and it is not changeable"

OK, I was confusing the different styles.

Thanks to everyone for their answers!