Issue's with form's taskbar button

G

Guest

I have a case where I need to set my main form's FormBorderStyle property to
None and I also can not set the form's Text property (i.e., Form.Text = "").
These requirements are to allow me to implement a custom titlebar and border
for the main form and to avoid bugs.

But, I have two issues after implementing the above settings:
1. Setting FormBorderStyle = None prevents the system's context menu from
appearing when the user right clicks the form's taskbar button.
2. Leaving Form.Text = "" prevents any text from being shown in the form's
taskbar button.

So, I need a way to force the system's context menu to be shown when the
user right clicks the form's taskbar button and a way to set the text of the
form's taskbar button to a custom value.

Thanks for any help.
Lance
 
L

Linda Liu [MSFT]

Hi Lance,

Yes, you're right. When a form's border style is set to None, the window
menu won't appear when you right click the form's taskbar button.

I use Spy++ to monitor the Windows messages that a form receives when I
right click the form's taskbar button. I notice that the form receives the
following messages in sequence:

WM_NCACTIVATE fActive:False
WM_KILLFOCUS
WM_NCACTIVATE fActive:True
WM_SETFOCUS
WM_ENTERMENULOOP
WM_INITMENU
WM_INITMENUPOPUP

So my initial thought is to set the form's FormBorderStyle property to
Sizable when the form receives the WM_NCACTIVATE Windows message(when the
fActive=false) and restore the FormBorderStyle property to None when the
form receives the WM_INITMENUPOPUP Windows message. The following is the
code:

public partial class Form1 : Form
{
int WM_NCACTIVATE = 0x0086;
int WM_INITMENUPOPUP = 0x0117;

protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCACTIVATE)
{
if (m.WParam.ToInt32() == 0)
{
this.FormBorderStyle = FormBorderStyle.Sizable;
}
}
else if (m.Msg == WM_INITMENUPOPUP)
{
this.FormBorderStyle = FormBorderStyle.None;
}
base.WndProc(ref m);
}
}

When I right click the form's taskbar button, the window menu does appear.
But the problem is that when I switch to another program running on my
computer, the default form border appears.

Actually, it's hard to distinguish whether it is the user right clicks the
form's taskbar button or switch to another program on the computer when the
form receives the WM_NCACTIVATE message.

Alternatively, I suggest that you create your own ContextMenuStrip to mimic
the window menu and show it when the form receives the WM_NCACTIVATE
message. The following is a sample.

using System.Runtime.InteropServices;
public partial class Form1 : Form
{
[DllImport("user32.dll")]
private extern static bool GetCursorPos(ref Point lpPoint);

int WM_NCACTIVATE = 0x0086;

protected override void WndProc(ref Message m)
{
if (m.Msg == WM_NCACTIVATE)
{
if (m.WParam.ToInt32() == 0)
{
Point pt = new Point();
GetCursorPos(ref pt);
this.contextMenuStrip1.Show(pt);
}
}
base.WndProc(ref m);
}
}

As for displaying custom text in the form's taskbar button, you could set
the form's Text property to any text you want and the text will be shown in
the taskbar button.

Hope this helps.
If you have any question, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support

==================================================
Get notification to my posts through email? Please refer to
http://msdn.microsoft.com/subscriptions/managednewsgroups/default.aspx#notif
ications.

Note: The MSDN Managed Newsgroup support offering is for non-urgent issues
where an initial response from the community or a Microsoft Support
Engineer within 1 business day is acceptable. Please note that each follow
up response may take approximately 2 business days as the support
professional working with you may need further investigation to reach the
most efficient resolution. The offering is not appropriate for situations
that require urgent, real-time or phone-based interactions or complex
project analysis and dump analysis issues. Issues of this nature are best
handled working with a dedicated Microsoft Support Engineer by contacting
Microsoft Customer Support Services (CSS) at
http://msdn.microsoft.com/subscriptions/support/default.aspx.
==================================================

This posting is provided "AS IS" with no warranties, and confers no rights.
 
G

Guest

Hi Linda,

Thank you for your help on this issue. Your second suggestion about
handling the WM_NCACTIVATE message in the WndProc method is close to what I
am looking for, but I have run into a number of issues:

1. Relying on the WM_NCACTIVATE message does not work because the
WM_NCACTIVATE message is not exclusive to when the user right-clicks a form's
taskbar button. In fact, it seems to be raised whenever the active state of
the form changes. I noticed that a message with m.Msg = &H313 seems to occur
whenever I right-click the taskbar button. Could this be the message that is
exclusive to right-clicking the taskbar button? I'm not sure because I can't
find any documentation about this message.

2. How do you mimic the behavior of the Restore, Move and Size items in the
system's default context menu?
2.a. The issue with the Restore item is knowing whether the form's
WindowState should be set to Normal or Maximized when the form is currently
in the Minimized state. Of course I could keep track of the previous
WindowState and then determine the appropriate state when the user clicks the
Restore item, but I thought there might already be system method or property
that handles this form me.
2.b. As far as mimicing the behavior of the Move and Size items I'm not
sure how to do this so your help would be greatly appreciated.

3. I am using a Windows.Forms.ContextMenuStrip for my custom context menu.
If I use the ContextMenuStrip.Show(location) method to show the menu, then
the menu is always shown above the taskbar. This can be an issue if the user
has setup their taskbar to have mutiple rows and the form's taskbar button is
on a bottom row (in which case the context menu could be far from the form's
taskbar button). This behavior is also different than the system's context
menu which is shown at the location of the mouse. Is there a way to allow
the ContextMenuStrip to be shown anywhere on the screen, rather than just
within the WorkingBounds of the screen?

Thanks again for all your help and please let me know if anything wasn't
clear.
Lance
 
L

Linda Liu [MSFT]

Hi Lance,

Thank you for your feedback!

Yes, you're right. The Windows message with message id of value &H313 is
sent to a window whenever the user right clicks the window's taskbar
button. This message is undocumented, but we can use it without any
problem. You're great to find this message out!

The following sample code should solve your problem fully:

Public Class Form1

Dim WM_INITMENUPOPUP As Integer = &H117

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)

If (m.Msg = &H313) Then
Me.FormBorderStyle = Windows.Forms.FormBorderStyle.Sizable
ElseIf (m.Msg = WM_INITMENUPOPUP) Then
Me.FormBorderStyle = Windows.Forms.FormBorderStyle.None
End If

MyBase.WndProc(m)

End Sub
End Class

Since it is the standard system menu that pops up when we right click the
form's taskbar button, we needn't implement a custom ContextMenuStrip to
mimic the system menu.

For your curiousity, if you need, you can send the WM_SYSCOMMAND message to
the window to implement the functions of the menu items within the system
menu.

Hope this helps.
If you have any question, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support
 
G

Guest

Hi Linda,

Thank you for your continued help on this issue. I tried your technique,
but I am unable to set Me.FormBorderStyle = Sizable during the &H313 message
because doing so causes the system's title bar to show up and also causes a
layout to occur which results in all kinds of issues. But, I am able to
listen for the &H313 message and show a custom ContextMenuStrip during that
message. I am also able to use WM_SYSCOMMAND message to implement the
functions of the menu items within the system menu as you suggested.

My only remaining issue is that I can not get the ContextMenuStrip to show
over the taskbar. I've tryied several techniques, such as using the
ContextMenuStrip.Show(location) method, overriding the
ContextMenuStrip.SetBoundsCore method, and also setting
ContextMenuStrip.TopLevelControl.Bounds in the
ContextMenuStrip.OnVisibleChanged method.

So, In order to get this to work I either need to find a way to show the
ContextMenuStrip over the taskbar (which would be preferable) or a way to
show the system's context menu without changing the FormBorderStyle property.

Thanks again,
Lance
 
G

Guest

At Design time leave the border at it's default value. Override the Forms
CreateParams property to remove the WS_CAPTION style from the window at
runtime.
This will give you a borderless form which still has the TaskBarButton
window with Text and WindowMenu intact.

\\\
Protected Overrides ReadOnly Property CreateParams() As CreateParams
Get
Dim cp As CreateParams = MyBase.CreateParams
cp.Style = cp.Style And Not &HC00000 ' WS_CAPTION
Return cp
End Get
End Property
///
 
G

Guest

Hi Mick,

Thanks a lot for the idea – it is a very intriguing approach.
Unfortunately, when I tried your sample code it resulted in a form that had a
border (although there was no title bar and the border was different than the
default border). Do you know if there are any other flags that can be used
to completely remove the form’s border and still preserve the functionality
of the taskbar button’s context menu?

Thanks again for the help!
Lance
 
L

Linda Liu [MSFT]

Thanks Mick for your great suggestion!

Hi Lance,

Changing the window style of a form to get what you want is a clever way.

To remove the thick frame of the form, we could use the WS_THICKFRAME
parameter. The following is the modified code of Mick:

Protected Overrides ReadOnly Property CreateParams() As CreateParams
Get
Dim cp As CreateParams = MyBase.CreateParams
cp.Style = cp.Style And Not &HC00000 ' WS_CAPTION
cp.Style = cp.Style And Not &H40000 'WS_THICKFRAME
Return cp
End Get
End Property

It works well on my side.

As for showing a ContextMenStrip over the taskbar, I have tried to pass the
mouse cursor in screen coordinates to the Show method of the
ContexMenuStrip, but the ContextMenuStrip still pops up above the taskbar.
I think it is the internal implementation of the ContextMenuStrip that
makes this behavior.

Nevertheless, we have the above good solution to solve your problem.

If you have any question, please feel free to let me know.

Sincerely,
Linda Liu
Microsoft Online Community Support
 
M

Mick Doherty

Hi Linda and Lance,

I've done a fair amount of work on this type of problem and there are
several solutions, but each has their own problems. With the introduction of
the MenuStrip in VS2005, the one major problem of the MenuBar being
positioned above the custom Titlebar has been solved and so IMO the
following are the 2 best solutions depending upon requirements.

1. If you don't want the window to be Sizeable or you're happy to size
the window by using a SizeGrip, as in the resizable shaped form example on
my site (http://www.dotnetrix.co.uk/misc.html), then removing the
WS_THICKFRAME style is a good solution.


2. If you want to keep the Size options then you will instead have to
trick the window into not expanding the NonClientArea. In VS2005 you will
also have to override the SetBoundsCore method to stop the window shrinking
as it is Minimised/Maximised and Restored.

\\\
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg <> &H83 Then 'WM_NCCALCSIZE
MyBase.WndProc(m)
End If
End Sub

Protected Overrides Sub SetBoundsCore(ByVal x As Integer, _
ByVal y As Integer, ByVal width As Integer, _
ByVal height As Integer, _
ByVal specified As System.Windows.Forms.BoundsSpecified)
Dim bs As Size = Size.Add(SystemInformation.BorderSize, _
SystemInformation.Border3DSize)
MyBase.SetBoundsCore(x, y, width - (bs.Width * 2), _
height - (bs.Height * 2), specified)
End Sub
///

The ContextMenuStrip does realign itself outside of the TaskBar. Is it
necessary to show a ContextMenuStrip in place of the standard WindowMenu?
It is possible to add MenuItems to the WindowMenu if necessary.
http://www.dotnetrix.co.uk/menus.html
 
G

Guest

Thanks to Mick and Linda for your help on this issue. I implemented the fix
by overriding the CreateParams property and you both are correct in that it
results in the desired behavior.

Unfortunately, I have observed a new issue with this approach. When a form
is first created the ClientSize and ClientRectagle properties report
incorrect values. Having the form report incorrect values causes issues
during initialization. e.g., if I use the ClientSize property to determine
how much I need to adjust the size of a form in order to fit a control that
will be added to the form, then the form will be set to the wrong size if the
ClientSize property reports the wrong value.

You can reproduce this new issue with the following sample. Note that
reproducing the issue requires you to insert a breakpoint and observe the
form's ClientSize and ClientRectangle properties within the Locals window.


Public Class Form1
Inherits Windows.Forms.Form

Public Sub New()
MyBase.New()

Debug.WriteLine(Me.Size)
Debug.WriteLine(Me.ClientSize)
Debug.WriteLine(Me.ClientRectangle)

'Put your breakpoint at the End Sub line.
'When the code is paused, view the value of Me.ClientSize and
Me.ClientRectangle in the Locals window.
'Notice that the values in the Output window are wrong, but the values
in the Locals window correct.
End Sub

Protected Overrides ReadOnly Property CreateParams() As
System.Windows.Forms.CreateParams
Get
Const WS_CAPTION As Integer = &HC00000
Const WS_THICKFRAME As Int32 = &H40000
Dim cp As System.Windows.Forms.CreateParams = MyBase.CreateParams
cp.Style = (cp.Style And (Not WS_CAPTION))
cp.Style = (cp.Style And (Not WS_THICKFRAME))
Return cp
End Get
End Property

End Class


Is there any way to force the ClientSize and ClientRectangle properties to
be updated so they report the correct values? Apparently, viewing the
properties in the Locals window does this. This is just a guess, but perhaps
the ClientSize and ClientRectangle properties are updated when the Locals
window gets the value of some other property.

Thanks for any help and please let me know if you have any questions.
Lance
 
L

Linda Liu [MSFT]

Hi Lance,

Thank you for your reply.

I performed a test based on your sample code and did see the problem. The
Size property and the ClientSize property should return a same size in the
sub New, but they don't actually.

Fortunately, we have another way to set a form's window style, i.e. use the
SetWindowLong Win32 API to do it. I have tried using this API to set the
form's windows style in the sub New. Then the Size and ClientSize
properties return a same size.

The following is a sample code.

Public Class Form1

Const GWL_STYLE As Integer = -16
Const WS_CAPTION As Integer = &HC00000
Const WS_THICKFRAME As Int32 = &H40000

Declare Auto Function GetWindowLong Lib "user32.dll" (ByVal hWnd As
IntPtr, ByVal nIndex As Integer) As Integer
Declare Auto Function SetWindowLong Lib "user32.dll" (ByVal hWnd As
IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As Integer) As Integer

Sub New()
MyBase.New()
' This call is required by the Windows Form Designer.
InitializeComponent()

' Add any initialization after the InitializeComponent() call.
Dim style = GetWindowLong(Me.Handle, GWL_STYLE)
style = style And Not WS_CAPTION
style = style And Not WS_THICKFRAME
SetWindowLong(Me.Handle, GWL_STYLE, style)

Debug.WriteLine(Me.Size)
Debug.WriteLine(Me.ClientSize)
Debug.WriteLine(Me.ClientRectangle)

End Sub
End Class

Please try this in your project to see if it solves the problem and let me
know the result.

Sincerely,
Linda Liu
Microsoft Online Community Support
 
M

Mick Doherty

The Windows Height and Width also need to be modified in the CreateParams
method.

\\\
Protected Overrides ReadOnly Property CreateParams() As _
System.Windows.Forms.CreateParams
Get
Const WS_CAPTION As Integer = &HC00000
Const WS_THICKFRAME As Int32 = &H40000
Dim cp As System.Windows.Forms.CreateParams = MyBase.CreateParams
cp.Style = (cp.Style And (Not WS_CAPTION))
cp.Style = (cp.Style And (Not WS_THICKFRAME))
cp.Height = Me.ClientSize.Height
cp.Width = Me.ClientSize.Width
Return cp
End Get
End Property
///
 
G

Guest

Linda and Mick,

I got it to work. Thanks to both of you for all your help on this issue. I
am very grateful.

Lance
 
G

Guest

Hi Mick and Linda,

Sorry to dig up this issue again, but I've noticed a bug that is introduced
when I remove the form's border and title bar using the CreateParams
technique. The issue is that my technique for allowing the user to resize
the form fails to work when I remove the form's border via the CreateParams
technique. If I remove the form's border and title bar via the
Form.ControlBox and Form.FormBorderStyle properties then the user is able to
resize the form, but the form's context menu is not displayed when the user
right clicks the form's taskbar button. I have included a sample that
demonstrates these issues:


Public Class Form1
Inherits Windows.Forms.Form

Protected WithEvents Button1 As Windows.Forms.Button
Protected WithEvents Button2 As Windows.Forms.Button

Public Sub New()
MyBase.New()

Me.Button1 = New Windows.Forms.Button
Me.Button1.Location = New Drawing.Point(4, 4)
Me.Button1.AutoSize = True
Me.Button1.Text = "Show CreateParams Form"
Me.Controls.Add(Me.Button1)

Me.Button2 = New Windows.Forms.Button
Me.Button2.Location = New Drawing.Point(Me.Button1.Left,
Me.Button1.Bottom + 4)
Me.Button2.AutoSize = True
Me.Button2.Text = "Show Properties Form"
Me.Controls.Add(Me.Button2)
End Sub

Private Sub Button1_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim frm As New CustomFormByCreateParams
frm.ShowDialog()
End Sub

Private Sub Button2_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Button2.Click
Dim frm As New CustomFormByProperties
frm.ShowDialog()
End Sub

End Class

Public Class CustomFormBase
Inherits Windows.Forms.Form

Protected Const BorderWidth As Integer = 4

Protected WithEvents Button1 As Windows.Forms.Button
Protected WithEvents Label1 As Windows.Forms.Label

Public Sub New()
MyBase.New()

Me.Button1 = New Windows.Forms.Button
Me.Button1.Location = New Drawing.Point(4, 4)
Me.Button1.Text = "Close"
Me.Controls.Add(Me.Button1)

Me.Label1 = New Windows.Forms.Label
Me.Label1.Bounds = New Drawing.Rectangle(Me.Button1.Left,
Me.Button1.Bottom + 4, Me.ClientSize.Width - 2 * BorderWidth,
Me.ClientSize.Height - (Me.Button1.Bottom + 4) - BorderWidth)
Me.Label1.Anchor = AnchorStyles.Top Or AnchorStyles.Bottom Or
AnchorStyles.Left Or AnchorStyles.Right
Me.Controls.Add(Me.Label1)
End Sub

Protected Overrides Sub OnPaint(ByVal e As
System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
Using region As Drawing.Region = Me.CreateBorderRegion
e.Graphics.FillRegion(Drawing.Brushes.Red, region)
End Using
End Sub

Protected Overrides Sub OnSizeChanged(ByVal e As System.EventArgs)
MyBase.OnSizeChanged(e)
Me.Invalidate()
End Sub

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Const WM_NCHITTEST As Integer = &H84 'WM_NCHITTEST
If (m.Msg = WM_NCHITTEST) Then
If (Me.HandleNonClientHitTest(m)) Then
Exit Sub
End If
End If

MyBase.WndProc(m)
End Sub

Protected Function HandleNonClientHitTest(ByRef m As
System.Windows.Forms.Message) As Boolean
Dim screenBounds As Drawing.Rectangle = Me.Bounds
Dim screenPoint As Drawing.Point = GetPoint(m.LParam.ToInt32)
If (Not screenBounds.Contains(screenPoint)) Then
Return False
End If

If (screenPoint.X < screenBounds.Right) AndAlso (screenPoint.X >=
(screenBounds.Right - BorderWidth)) Then
m.Result = New System.IntPtr(11) 'Right
Return True
End If

Return False
End Function

Protected Function CreateBorderRegion() As Drawing.Region
Dim region As New Drawing.Region(Me.ClientRectangle)
Dim workingBounds As Drawing.Rectangle = Me.ClientRectangle
workingBounds.Inflate(-BorderWidth, -BorderWidth)
region.Exclude(workingBounds)
Return region
End Function

Public Shared Function GetPoint(ByVal value As Integer) As Drawing.Point
Dim x As Integer = (value And 65535)
Dim y As Integer = (value >> 16)
Return New Drawing.Point(x, y)
End Function

Private Sub Button1_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Button1.Click
Me.Close()
End Sub

End Class

Public Class CustomFormByCreateParams
Inherits CustomFormBase

Public Sub New()
MyBase.New()
Me.Label1.Text = "Notice that this form shows its context menu when you
right click the form's taskbar button, but it does not support resizing when
you move the mouse over the right edge of the form."
End Sub

Protected Overrides ReadOnly Property CreateParams() As
System.Windows.Forms.CreateParams
Get
Dim params As System.Windows.Forms.CreateParams = MyBase.CreateParams

Const WS_CAPTION As Integer = &HC00000
Const WS_THICKFRAME As Integer = &H40000
Const flags As Integer = (WS_CAPTION Or WS_THICKFRAME)
params.Style = (params.Style And (Not flags))

Return params
End Get
End Property

End Class

Public Class CustomFormByProperties
Inherits CustomFormBase

Public Sub New()
MyBase.New()
MyBase.ControlBox = False
MyBase.FormBorderStyle = Windows.Forms.FormBorderStyle.None
Me.Label1.Text = "Notice that this form supports resizing when you move
the mouse over the right edge of the form, but it does not show its context
menu when you right click the form's taskbar button."
End Sub

End Class


What I need is a technique that disables the form's border and title bar and
also supports resizing and the taskbar button's context menu.

Thanks for any help!
Lance
 
M

Mick Doherty

Hi Lance,

This is because when you have the WindowMenu with the Size option disabled
the window does not respond to the mouse dragging the form by its edges.

As I mentioned in an earlier response, you need to keep the WS_THICKFRAME
style and prevent the NonClient edge from showing by intercepting the
WM_NCCALCSIZE messages.

In VS2003 this works perfect, but VS2005 is doing something which I haven't
found a full solution to just yet. The form is sized relative to the
ClientArea and so the window does not respect it's design time size. If we
correct this by overriding CreateBoundsCore() and CreateClientSizeCore(),
then we find that child controls do not respect their design time size.
 
G

Guest

Hi Mick,

Thank you very much for your continued help on this issue. I should have
mentioned that I tried your technqiue of intercepting the WM_NCCALCSIZE
messages but I had issues with the border, title bar and title bar buttons
appearing or flickering when the form was resized. Also, the form's top
corners are rounded when I use that technique. I also tried Linda's
suggestion of using SetWindowLong but that had the same issue as CreateParams
(i.e., the form can not be resized). Here is a sample that shows all four
techniques:


Public Class Form1
Inherits Windows.Forms.Form

Protected WithEvents Button1 As Windows.Forms.Button
Protected WithEvents Button2 As Windows.Forms.Button
Protected WithEvents Button3 As Windows.Forms.Button
Protected WithEvents Button4 As Windows.Forms.Button

Public Sub New()
MyBase.New()

Me.Button1 = New Windows.Forms.Button
Me.Button1.Location = New Drawing.Point(4, 4)
Me.Button1.AutoSize = True
Me.Button1.Text = "Show CreateParams Form"
Me.Controls.Add(Me.Button1)

Me.Button2 = New Windows.Forms.Button
Me.Button2.Location = New Drawing.Point(Me.Button1.Left,
Me.Button1.Bottom + 4)
Me.Button2.AutoSize = True
Me.Button2.Text = "Show Properties Form"
Me.Controls.Add(Me.Button2)

Me.Button3 = New Windows.Forms.Button
Me.Button3.Location = New Drawing.Point(Me.Button2.Left,
Me.Button2.Bottom + 4)
Me.Button3.AutoSize = True
Me.Button3.Text = "Show NonClientCalcSize Form"
Me.Controls.Add(Me.Button3)

Me.Button4 = New Windows.Forms.Button
Me.Button4.Location = New Drawing.Point(Me.Button3.Left,
Me.Button3.Bottom + 4)
Me.Button4.AutoSize = True
Me.Button4.Text = "Show Interop Form"
Me.Controls.Add(Me.Button4)
End Sub

Private Sub Button1_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Button1.Click
Dim frm As New CustomFormByCreateParams
frm.ShowDialog()
End Sub

Private Sub Button2_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Button2.Click
Dim frm As New CustomFormByProperties
frm.ShowDialog()
End Sub

Private Sub Button3_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Button3.Click
Dim frm As New CustomFormByNonClientCalcSize
frm.ShowDialog()
End Sub

Private Sub Button4_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Button4.Click
Dim frm As New CustomFormByInterop
frm.ShowDialog()
End Sub

End Class

Public Class CustomFormBase
Inherits Windows.Forms.Form

Protected Const BorderWidth As Integer = 4

Protected WithEvents Button1 As Windows.Forms.Button
Protected WithEvents Label1 As Windows.Forms.Label

Public Sub New()
MyBase.New()

Me.Button1 = New Windows.Forms.Button
Me.Button1.Location = New Drawing.Point(4, 4)
Me.Button1.Text = "Close"
Me.Controls.Add(Me.Button1)

Me.Label1 = New Windows.Forms.Label
Me.Label1.Bounds = New Drawing.Rectangle(Me.Button1.Left,
Me.Button1.Bottom + 4, Me.ClientSize.Width - 2 * BorderWidth,
Me.ClientSize.Height - (Me.Button1.Bottom + 4) - BorderWidth)
Me.Label1.Anchor = AnchorStyles.Top Or AnchorStyles.Bottom Or
AnchorStyles.Left Or AnchorStyles.Right
Me.Controls.Add(Me.Label1)
End Sub

Protected Overrides Sub OnPaint(ByVal e As
System.Windows.Forms.PaintEventArgs)
MyBase.OnPaint(e)
Using region As Drawing.Region = Me.CreateBorderRegion
e.Graphics.FillRegion(Drawing.Brushes.Red, region)
End Using
End Sub

Protected Overrides Sub OnSizeChanged(ByVal e As System.EventArgs)
MyBase.OnSizeChanged(e)
Me.Invalidate()
End Sub

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Const WM_NCHITTEST As Integer = &H84 'WM_NCHITTEST
If (m.Msg = WM_NCHITTEST) Then
If (Me.HandleNonClientHitTest(m)) Then
Exit Sub
End If
End If

MyBase.WndProc(m)
End Sub

Protected Function HandleNonClientHitTest(ByRef m As
System.Windows.Forms.Message) As Boolean
Dim screenBounds As Drawing.Rectangle = Me.Bounds
Dim screenPoint As Drawing.Point = GetPoint(m.LParam.ToInt32)
If (Not screenBounds.Contains(screenPoint)) Then
Return False
End If

If (screenPoint.X < screenBounds.Right) AndAlso (screenPoint.X >=
(screenBounds.Right - BorderWidth)) Then
m.Result = New System.IntPtr(11) 'Right
Return True
End If

Return False
End Function

Protected Function CreateBorderRegion() As Drawing.Region
Dim region As New Drawing.Region(Me.ClientRectangle)
Dim workingBounds As Drawing.Rectangle = Me.ClientRectangle
workingBounds.Inflate(-BorderWidth, -BorderWidth)
region.Exclude(workingBounds)
Return region
End Function

Public Shared Function GetPoint(ByVal value As Integer) As Drawing.Point
Dim x As Integer = (value And 65535)
Dim y As Integer = (value >> 16)
Return New Drawing.Point(x, y)
End Function

Private Sub Button1_Click(ByVal sender As Object, ByVal e As
System.EventArgs) Handles Button1.Click
Me.Close()
End Sub

End Class

Public Class CustomFormByCreateParams
Inherits CustomFormBase

Public Sub New()
MyBase.New()
Me.Label1.Text = "Notice that this form shows its context menu when you
right click the form's taskbar button, but it does not support resizing when
you move the mouse over the right edge of the form."
End Sub

Protected Overrides ReadOnly Property CreateParams() As
System.Windows.Forms.CreateParams
Get
Dim params As System.Windows.Forms.CreateParams = MyBase.CreateParams

Const WS_CAPTION As Integer = &HC00000
Const WS_THICKFRAME As Integer = &H40000
Const flags As Integer = (WS_CAPTION Or WS_THICKFRAME)
params.Style = (params.Style And (Not flags))

Return params
End Get
End Property

End Class

Public Class CustomFormByProperties
Inherits CustomFormBase

Public Sub New()
MyBase.New()
MyBase.ControlBox = False
MyBase.FormBorderStyle = Windows.Forms.FormBorderStyle.None
Me.Label1.Text = "Notice that this form supports resizing when you move
the mouse over the right edge of the form, but it does not show its context
menu when you right click the form's taskbar button."
End Sub

End Class

Public Class CustomFormByNonClientCalcSize
Inherits CustomFormBase

Public Sub New()
MyBase.New()
Me.Label1.Text = "Notice that this form shows its context menu when you
right click the form's taskbar button, but the form's top corners are rounded
and there are issues with the border, title bar and title bar buttons
appearing or flickering when the form is resized."
End Sub

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg <> &H83 Then
MyBase.WndProc(m)
End If
End Sub

Protected Overrides Sub SetBoundsCore(ByVal x As Integer, ByVal y As
Integer, ByVal width As Integer, ByVal height As Integer, ByVal specified As
System.Windows.Forms.BoundsSpecified)
Dim bs As Drawing.Size = Drawing.Size.Add(SystemInformation.BorderSize,
SystemInformation.Border3DSize)
MyBase.SetBoundsCore(x, y, width - (bs.Width * 2), height - (bs.Height *
2), specified)
End Sub

End Class

Public Class CustomFormByInterop
Inherits CustomFormBase

Const GWL_STYLE As Integer = -16
Const WS_CAPTION As Integer = &HC00000
Const WS_THICKFRAME As Int32 = &H40000

Declare Auto Function GetWindowLong Lib "user32.dll" (ByVal hWnd As
IntPtr, ByVal nIndex As Integer) As Integer
Declare Auto Function SetWindowLong Lib "user32.dll" (ByVal hWnd As
IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As Integer) As Integer

Public Sub New()
MyBase.New()

Dim style As Integer = GetWindowLong(Me.Handle, GWL_STYLE)
style = style And Not WS_CAPTION
style = style And Not WS_THICKFRAME
SetWindowLong(Me.Handle, GWL_STYLE, style)

Me.Label1.Text = "Notice that this form shows its context menu when you
right click the form's taskbar button, but it does not support resizing when
you move the mouse over the right edge of the form."
End Sub

End Class


If it is possible, it seems to me that the best solution would be to use the
technique of setting the form's ControlBox = False and FormBorderStyle = None
and then manually show the form's context menu whenever the &H313 message
occurs in WndProc (i.e., whenever the user right clicks the form's taskbar
button). The issues with this technique are that I do not know how to tell
the system to show the form's context menu and if I show a custom
ContextMenuStrip then the menu always appears above the taskbar. The later
is an issue if the form's taskbar button is not in the top row of the taskbar
in which case the context menu could be far from the form's taskbar button.

I would be very interested to hear your thoughts and of course I am open to
using any other techniques that work.

Thanks again!
Lance
 
M

Mick Doherty

Hi Lance,

As well as intercepting the WM_NCCALCSIZE messages you need to remove the
WS_CAPTION style by overriding CreateParams, but leave the WM_THICKFRAME
style as this is needed for Window Resizing.

\\\
Protected Overrides ReadOnly Property CreateParams() As _
System.Windows.Forms.CreateParams
Get
Dim params As System.Windows.Forms.CreateParams = _
MyBase.CreateParams

Const WS_CAPTION As Integer = &HC00000
params.Style = (params.Style And (Not WS_CAPTION))

Return params
End Get
End Property
///

You need to leave the controlbox and formborderstyle so that the window is
still sizable and shows a WindowMenu.

If you remove the ControlBox then the form will have no WindowMenu. Although
it is possible to reset the Menu, it will have the Close option grayed out.
You will also have to remove the minimize and maximize buttons as these will
cause the Caption and CaptionButtons to paint themselves on the form.

If you remove the forms border then even if you reset the WindowMenu the
Size and Minimize menu options will be grayed out and so the form will not
minimize or resize via Non-Client Hit Testing.

If you really want to, you can pop up your own ContextMenu in response to
the WM_TASKBUTTONMENU message (this is my friendly name for the undocumented
messsage &H313). Unlike the ContextMenuStrip, this will pop up in the
correct location. It is not in the VS2005 toolbox by default, but it is in
the list of available items if you want to add it.

Assumes you have a ContextMenu named ContextMenu1 added to the forms
Components.
\\\
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
Const WM_TASKBUTTONMENU As Int32 = &H313
If m.Msg = WM_TASKBUTTONMENU Then
Me.ContextMenu.Show(Me, Me.PointToClient(New _
Point(m.LParam.ToInt32)))
Else
MyBase.WndProc(m)
End If
End Sub
///

Personally, I would recommend using the default WindowMenu, leaving the
ControlBox and FormBorderStyle at their default values, removing the
WS_CAPTION window style and intercepting the WM_NCCALCSIZE message.
 
L

Linda Liu [MSFT]

Hi Lance,

I agree with Mick. I think the best solution for your problem is to remove
the WS_CAPTION style by overriding CreateParams and intercept the
WM_NCCALCSIZE and WM_NCACTIVATE messages.

The following is the modified code on your sample code.

Public Class CustomFormByNonClientCalcSize
Inherits CustomFormBase

Private WM_NCCALCSIZE As Integer = &H83
Private WM_NCACTIVATE As Integer = &H86

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
If m.Msg = WM_NCCALCSIZE Then
Return
ElseIf m.Msg = WM_NCACTIVATE Then
MyBase.WndProc(m)
Me.Refresh()
Else
MyBase.WndProc(m)
End If
End Sub

Protected Overrides ReadOnly Property CreateParams() As
System.Windows.Forms.CreateParams
Get
Dim params As System.Windows.Forms.CreateParams =
MyBase.CreateParams
Const WS_CAPTION As Integer = &HC00000
params.Style = (params.Style And (Not WS_CAPTION))
Return params
End Get
End Property
End Class

Hope this helps.
If you have any question, please feel free to let us know.

Sincerely,
Linda Liu
Microsoft Online Community Support
 
L

Linda Liu [MSFT]

Hi Lance,

How about the problem now?

If you have any question, please feel free to let me know.

Thank you for using our MSDN Managed Newsgroup Support Service!

Sincerely,
Linda Liu
Microsoft Online Community Support
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top