הערה
הגישה לדף זה מחייבת הרשאה. באפשרותך לנסות להיכנס או לשנות מדריכי כתובות.
הגישה לדף זה מחייבת הרשאה. באפשרותך לנסות לשנות מדריכי כתובות.
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!