How to: Webbrowser NewWindow and Navigating equivalent to axWebbrowser

At first, I enjoyed the new webbrowser control but then I noticed that the args provided by the events NewWindow3 and BeforeNavigate of the axWebBrowser had not been implemented in the new control. Arg.

Good news, the guys from Microsoft gave us the a workaround recently in C# and said they will publish a sample soon on MSDN.

In the mean time, here is someVB.net code inspired from Microsoft's C# that will give you 2 new events and the args people like me were crying for.

Idea NavigatingExtendedgives you the following args:
-Url as string
-Frame as string
-Headers as string
-Postdata as string
-PostdataByte() as byte
-Cancel as boolean (to cancel navigation)

Idea NewWindowExtendedgives you the following args:
-Url as string
-UrlContext as string
-Flags as NWMF (see NewWindow3 on MSDN for definition)
-Cancel as boolean (to cancel navigation to new window)

Ok let's go.
1. Create a new Windows form project.
2. Add a new Module and Copy/Paste the following:



Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.ComponentModel
Imports System.Runtime.InteropServices

Module
Module1

Public
Enum NWMF
NWMF_UNLOADING = &H1&
NWMF_USERINITED = &H2&
NWMF_FIRST_USERINITED = &H4&
NWMF_OVERRIDEKEY = &H8&
NWMF_SHOWHELP = &H10&
NWMF_HTMLDIALOG = &H20&
NWMF_FROMPROXY = &H40&
EndEnum

'First define a new EventArgs class to contain the newly exposed data
PublicClass WebBrowserNavigatingExtendedEventArgs
Inherits CancelEventArgs

Private
m_UrlAsString
Private m_FrameAsString
Private m_Postdata()AsByte
Private m_HeadersAsString

Public
ReadOnlyProperty Url()AsString
Get
Return m_Url
EndGet
EndProperty

Public
ReadOnlyProperty Frame()AsString
Get
Return m_Frame
EndGet
EndProperty

Public
ReadOnlyProperty Headers()AsString
Get
Return m_Headers
EndGet
EndProperty

Public
ReadOnlyProperty Postdata()AsString
Get
Return PostdataToString(m_Postdata)
EndGet
EndProperty

Public
ReadOnlyProperty PostdataByte()AsByte()
Get
Return m_Postdata
EndGet
EndProperty

Public
SubNew(ByVal urlAsString,ByVal frameAsString,ByVal postdataAsByte(),ByVal headersAsString)
m_Url = url
m_Frame = frame
m_Postdata = postdata
m_Headers = headers
EndSub

Private
Function PostdataToString(ByVal p()AsByte)AsString
'not sexy but it works...
Dim tabpd()AsByte, bstopAsBoolean =False, stmpAsString = "", iAsInteger = 0
tabpd = p
If tabpdIsNothingOrElse tabpd.Length = 0Then
Return ""
Else
For i = 0To tabpd.Length - 1
stmp += ChrW(tabpd(i))
Next
stmp = Replace(stmp, ChrW(13), "")
stmp = Replace(stmp, ChrW(10), "")
stmp = Replace(stmp, ChrW(0), "")
EndIf
If stmp =NothingThen
Return ""
Else
Return stmp
EndIf
EndFunction

End
Class

Public
Class WebBrowserNewWindowExtendedEventArgs
Inherits CancelEventArgs

Private
m_UrlAsString
Private m_UrlContextAsString
Private m_FlagsAs NWMF

Public
ReadOnlyProperty Url()AsString
Get
Return m_Url
EndGet
EndProperty

Public
ReadOnlyProperty UrlContext()AsString
Get
Return m_UrlContext
EndGet
EndProperty

Public
ReadOnlyProperty Flags()As NWMF
Get
Return m_Flags
EndGet
EndProperty

Public
SubNew(ByVal urlAsString,ByVal urlcontextAsString,ByVal flagsAs NWMF)
m_Url = url
m_UrlContext = urlcontext
m_Flags = flags
EndSub

End
Class

Public
Class ExtendedWebBrowser
Inherits WebBrowser

Private
cookieAs AxHost.ConnectionPointCookie
Private weventsAs WebBrowserExtendedEvents

'This method will be called to give you a chance to create your own event sink
ProtectedOverridesSub CreateSink()
'MAKE SURE TO CALL THE BASE or the normal events won't fire
MyBase.CreateSink()
wevents =
New WebBrowserExtendedEvents(Me)
cookie =
New AxHost.ConnectionPointCookie(Me.ActiveXInstance, wevents,GetType(DWebBrowserEvents2))
EndSub

Protected
OverridesSub DetachSink()
IfNot cookieIsNothingThen
cookie.Disconnect()
cookie =
Nothing
EndIf
MyBase.DetachSink()
EndSub

'This new event will fire when the page is navigating
PublicDelegateSub WebBrowserNavigatingExtendedEventHandler(ByVal senderAsObject,ByVal eAs WebBrowserNavigatingExtendedEventArgs)
PublicEvent NavigatingExtendedAs WebBrowserNavigatingExtendedEventHandler

'This event will fire when a new window is about to be opened
PublicDelegateSub WebBrowserNewWindowExtendedEventHandler(ByVal senderAsObject,ByVal eAs WebBrowserNewWindowExtendedEventArgs)
PublicEvent NewWindowExtendedAs WebBrowserNewWindowExtendedEventHandler

Protected
FriendSub OnNavigatingExtended(ByVal UrlAsString,ByVal FrameAsString,ByVal PostdataAsByte(),ByVal HeadersAsString,ByRef CancelAsBoolean)
Dim eAs WebBrowserNavigatingExtendedEventArgs =New WebBrowserNavigatingExtendedEventArgs(Url, Frame, Postdata, Headers)
RaiseEvent NavigatingExtended(Me, e)
Cancel = e.Cancel
EndSub

Protected
FriendSub OnNewWindowExtended(ByVal UrlAsString,ByRef CancelAsBoolean,ByVal FlagsAs NWMF,ByVal UrlContextAsString)
Dim eAs WebBrowserNewWindowExtendedEventArgs =New WebBrowserNewWindowExtendedEventArgs(Url, UrlContext, Flags)
RaiseEvent NewWindowExtended(Me, e)
Cancel = e.Cancel
EndSub

End
Class

'This class will capture events from the WebBrowser
Class WebBrowserExtendedEvents
Inherits System.Runtime.InteropServices.StandardOleMarshalObject
Implements DWebBrowserEvents2

Private
m_BrowserAs ExtendedWebBrowser

Public
SubNew(ByVal browserAs ExtendedWebBrowser)
m_Browser = browser
EndSub

'Implement whichever events you wish
PublicSub BeforeNavigate2(ByVal pDispAsObject,ByRef URLAsString,ByRef flagsAsObject,ByRef targetFrameNameAsString,ByRef postDataAsObject,ByRef headersAsString,ByRef cancelAsBoolean)Implements DWebBrowserEvents2.BeforeNavigate2

m_Browser.OnNavigatingExtended(URL, targetFrameName,CType(postData,Byte()), headers, cancel)

End
Sub

Public
Sub NewWindow3(ByVal pDispAsObject,ByRef CancelAsBoolean,ByRef FlagsAsObject,ByRef UrlContextAsString,ByRef UrlAsString)Implements DWebBrowserEvents2.NewWindow3

m_Browser.OnNewWindowExtended(Url, Cancel,CType(Flags, NWMF), UrlContext)

End
Sub

End
Class

<ComImport(), _
Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D"), _
InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIDispatch), _
TypeLibType(TypeLibTypeFlags.FHidden)> _
PublicInterface DWebBrowserEvents2

<DispId(250)> _
Sub BeforeNavigate2(<[In](), MarshalAs(UnmanagedType.IDispatch)>ByVal pDispAsObject, _
<InAttribute(), MarshalAs(UnmanagedType.BStr)>
ByRef URLAsString, _
<InAttribute()>
ByRef flagsAsObject, _
<InAttribute(), MarshalAs(UnmanagedType.BStr)>
ByRef targetFrameNameAsString, _
<InAttribute()>
ByRef postdataAsObject, _
<InAttribute(), MarshalAs(UnmanagedType.BStr)>
ByRef headersAsString, _
<InAttribute(), OutAttribute()>
ByRef cancelAsBoolean)

'Note: Postdata is a SafeArray but for some reason, if I do a proper declaration, the event will not be raised:
'<[In](), MarshalAs(UnmanagedType.SafeArray, safearraysubtype:=VarEnum.VT_UI1)> ByRef postdata() As Byte, _

<DispId(273)> _
Sub NewWindow3(<InAttribute(), MarshalAs(UnmanagedType.IDispatch)>ByVal pDispAsObject, _
<InAttribute(), OutAttribute()>
ByRef cancelAsBoolean, _
<InAttribute()>
ByRef FlagsAsObject, _
<InAttribute(), MarshalAs(UnmanagedType.BStr)>
ByRef UrlContextAsString, _
<InAttribute(), MarshalAs(UnmanagedType.BStr)>
ByRef UrlAsString)

End
Interface

EndModule

3. Now, Open your form1 in Code view
4. Copy/Paste the following to test your control



PublicClass Form1
'Source from wilfridB

Private
wbAsNew ExtendedWebBrowser

Private
Sub Form1_Load(ByVal senderAsObject,ByVal eAs System.EventArgs)HandlesMe.Load

AddHandler
wb.NavigatingExtended,AddressOf wb_NavigatingExtended
AddHandler wb.DocumentCompleted,AddressOf wb_DocumentCompleted
AddHandler wb.NewWindowExtended,AddressOf wb_NewWindowExtended
Me.Controls.Add(wb)
wb.Dock = DockStyle.Fill
wb.Navigate(
New Uri("http://www.microsoft.com"))

End
Sub

Private
Sub wb_NavigatingExtended(ByVal senderAsObject,ByVal eAs module1.WebBrowserNavigatingExtendedEventArgs)
'This is a new event

Dim
postdataAsString = e.Postdata
Dim msgAsString = "Navigating to : " & e.Url & ControlChars.CrLf
msg &= "Postdata : " & postdata & ControlChars.CrLf
msg &= "Headers : " & e.Headers & ControlChars.CrLf
msg &= "Frame : " & e.Frame & ControlChars.CrLf
msg &= "Continue ?"
Dim resAs DialogResult = MessageBox.Show(msg, "NavigatingExtended", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
If res = Windows.Forms.DialogResult.NoThen e.Cancel =True

End
Sub

Private
Sub wb_DocumentCompleted(ByVal senderAsObject,ByVal eAs System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)
'This is a standard event

MessageBox.Show("Document complete: " & e.Url.ToString, "DocumentCompleted", MessageBoxButtons.OK, MessageBoxIcon.Information)

End
Sub

Private
Sub wb_NewWindowExtended(ByVal senderAsObject,ByVal eAs Module1.WebBrowserNewWindowExtendedEventArgs)
'This is a new event

Dim
msgAsString = "Navigation vers : " & e.Url & ControlChars.CrLf
msg &= "UrlContext : " & e.UrlContext & ControlChars.CrLf
msg &= "Flags : " & e.Flags.ToString & ControlChars.CrLf
msg &= "Continue ?"
Dim resAs DialogResult = MessageBox.Show(msg, "NewWindowExtended", MessageBoxButtons.YesNo, MessageBoxIcon.Question)
If res = Windows.Forms.DialogResult.NoThen e.Cancel =True

End
Sub

EndClass

That's it and it should work...
Any comment or additional event implementation is welcome...
And if somebody could give me the solution for properly retrieving the postdata using something like:
<[In](), MarshalAs(UnmanagedType.SafeArray, safearraysubtype:=VarEnum.VT_UI1)> ByRef postdata() As Byte, _
And converting an array of byte in a more decent way, that would be great.
wilfridB

[40504 byte] By [WilfridB] at [2008-2-20]
# 1
As for the daclaration question: no idea so far

But you could replace 'PostdataToString' with

Public ReadOnly Property Postdata() As String

Get

'Return PostdataToString(m_Postdata)

Dim encode As System.Text.Encoding = System.Text.Encoding.UTF8

Return encode.GetString(m_Postdata)

End Get

End Property

orbit at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 2
Thanks for the reply, Orbit.
Actually, I wonder if the best solution is not to leave it as an array of byte and in the calling code to add to the sub mybrowser_NavigatingExtended something like


'do not forget to check if objects exist
Dim CurrentDoc as IHTMLDocument2 = Ctype(mybrowser.Document, IHTMLDocument2)
Dim PageEncoding as Encoding = Encoding.GetEncoding(CurrentDoc.defaultCharset)
Dim PostDataString as String = System.Web.HttpUtility.UrlDecode(CType(e.postData, Byte()), PageEncoding)


then, you get the right encoding.
By the way, does anybody have an idea how I could add the "Application" property to the WebbrowserExtended Class (similar to the one from axWebBrowser) so I can set ppDisp in NewWindow3 and redirect the new page to a custom browser ?

wilfridB

WilfridB at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 3

"Good news, the guys from Microsoft gave us the a workaround recently in C# and said they will publish a sample soon on MSDN."

Is this work-around available to share ?

thanks, Bill

dotScience at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 5
WilfridB wrote:

By the way, does anybody have an idea how I could add the "Application" property to the WebbrowserExtended Class (similar to the one from axWebBrowser) so I can set ppDisp in NewWindow3 and redirect the new page to a custom browser ?

wilfridB

Sorry, but I do not think this is possible. The reason for this is that the object "AxWebbrowser" (used internally by the webbrowser control) is not exposed, even not for inheritors. (It's declared "Private")Therefore the property is not available, since it's never called by any member of the class.

I also would like to use this functionality, but it seems that this will only be possible when you write your own (complete) control Sad

jlandheer at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 6
WilfridB wrote:
Thanks for the reply, Orbit.
Actually, I wonder if the best solution is not to leave it as an array of byte and in the calling code to add to the sub mybrowser_NavigatingExtended something like


'do not forget to check if objects exist
Dim CurrentDoc as IHTMLDocument2 = Ctype(mybrowser.Document, IHTMLDocument2)
Dim PageEncoding as Encoding = Encoding.GetEncoding(CurrentDoc.defaultCharset)
Dim PostDataString as String = System.Web.HttpUtility.UrlDecode(CType(e.postData, Byte()), PageEncoding)


then, you get the right encoding.
By the way, does anybody have an idea how I could add the "Application" property to the WebbrowserExtended Class (similar to the one from axWebBrowser) so I can set ppDisp in NewWindow3 and redirect the new page to a custom browser ?

wilfridB

Oooops, it seems that I missed one. Here goes:

1: Declare an interface "IWebBrowser2"
2: Override "AttachInterfaces" and set a private field to the value of the parameter "nativeActiveXObject"
3: Override "DetachInterfaces" to set this private field back to "null"
3: Use this private field's "Application" property

That's all there's to it.

An example in c#:



internal static class UnsafeNativeMethods
{
[System.Runtime.InteropServices.ComImport(), System.Runtime.InteropServices.Guid("34A715A0-6587-11D0-924A-0020AFC7AC4D"),
System.Runtime.InteropServices.InterfaceTypeAttribute(System.Runtime.InteropServices.ComInterfaceType.InterfaceIsIDispatch),
System.Runtime.InteropServices.TypeLibType(System.Runtime.InteropServices.TypeLibTypeFlags.FHidden)]
public interface DWebBrowserEvents2
{
[System.Runtime.InteropServices.DispId(250)]
void BeforeNavigate2(
[System.Runtime.InteropServices.In,
System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.IDispatch)] object pDisp,
[System.Runtime.InteropServices.In] ref object URL,
[System.Runtime.InteropServices.In] ref object flags,
[System.Runtime.InteropServices.In] ref object targetFrameName, [System.Runtime.InteropServices.In] ref object postData,
[System.Runtime.InteropServices.In] ref object headers,
[System.Runtime.InteropServices.In,
System.Runtime.InteropServices.Out] ref bool cancel);
[System.Runtime.InteropServices.DispId(273)]
void NewWindow3(
[System.Runtime.InteropServices.In,
System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.IDispatch)] object pDisp,
[System.Runtime.InteropServices.In, System.Runtime.InteropServices.Out] ref bool cancel,
[System.Runtime.InteropServices.In] ref object flags,
[System.Runtime.InteropServices.In] ref object URLContext,
[System.Runtime.InteropServices.In] ref object URL);
}

[ComImport, SuppressUnmanagedCodeSecurity, TypeLibType(TypeLibTypeFlags.FOleAutomation | (TypeLibTypeFlags.FDual | TypeLibTypeFlags.FHidden)), Guid("D30C1661-CDAF-11d0-8A3E-00C04FC9E26E")]
public interface IWebBrowser2
{
[DispId(100)]
void GoBack();
[DispId(0x65)]
void GoForward();
[DispId(0x66)]
void GoHome();
[DispId(0x67)]
void GoSearch();
[DispId(0x68)]
void Navigate([In] string Url, [In] ref object flags, [In] ref object targetFrameName, [In] ref object postData, [In] ref object headers);
[DispId(-550)]
void Refresh();
[DispId(0x69)]
void Refresh2([In] ref object level);
[DispId(0x6a)]
void Stop();
[DispId(200)]
object Application { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
[DispId(0xc9)]
object Parent { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
[DispId(0xca)]
object Container { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
[DispId(0xcb)]
object Document { [return: MarshalAs(UnmanagedType.IDispatch)] get; }
[DispId(0xcc)]
bool TopLevelContainer { get; }
[DispId(0xcd)]
string Type { get; }
[DispId(0xce)]
int Left { get; set; }
[DispId(0xcf)]
int Top { get; set; }
[DispId(0xd0)]
int Width { get; set; }
[DispId(0xd1)]
int Height { get; set; }
[DispId(210)]
string LocationName { get; }
[DispId(0xd3)]
string LocationURL { get; }
[DispId(0xd4)]
bool Busy { get; }
[DispId(300)]
void Quit();
[DispId(0x12d)]
void ClientToWindow(out int pcx, out int pcy);
[DispId(0x12e)]
void PutProperty([In] string property, [In] object vtValue);
[DispId(0x12f)]
object GetProperty([In] string property);
[DispId(0)]
string Name { get; }
[DispId(-515)]
int HWND { get; }
[DispId(400)]
string FullName { get; }
[DispId(0x191)]
string Path { get; }
[DispId(0x192)]
bool Visible { get; set; }
[DispId(0x193)]
bool StatusBar { get; set; }
[DispId(0x194)]
string StatusText { get; set; }
[DispId(0x195)]
int ToolBar { get; set; }
[DispId(0x196)]
bool MenuBar { get; set; }
[DispId(0x197)]
bool FullScreen { get; set; }
[DispId(500)]
void Navigate2([In] ref object URL, [In] ref object flags, [In] ref object targetFrameName, [In] ref object postData, [In] ref object headers);
[DispId(0x1f5)]
NativeMethods.OLECMDF QueryStatusWB([In] NativeMethods.OLECMDID cmdID);
[DispId(0x1f6)]
void ExecWB([In] NativeMethods.OLECMDID cmdID, [In] NativeMethods.OLECMDEXECOPT cmdexecopt, ref object pvaIn, IntPtr pvaOut);
[DispId(0x1f7)]
void ShowBrowserBar([In] ref object pvaClsid, [In] ref object pvarShow, [In] ref object pvarSize);
[DispId(-525)]
WebBrowserReadyState ReadyState { get; }
[DispId(550)]
bool Offline { get; set; }
[DispId(0x227)]
bool Silent { get; set; }
[DispId(0x228)]
bool RegisterAsBrowser { get; set; }
[DispId(0x229)]
bool RegisterAsDropTarget { get; set; }
[DispId(0x22a)]
bool TheaterMode { get; set; }
[DispId(0x22b)]
bool AddressBar { get; set; }
[DispId(0x22c)]
bool Resizable { get; set; }
}

}

and in your class:
private UnsafeNativeMethods.IWebBrowser2 axIWebBrowser2;

protected override void AttachInterfaces(object nativeActiveXObject)
{
this.axIWebBrowser2 = (UnsafeNativeMethods.IWebBrowser2)nativeActiveXObject;
base.AttachInterfaces(nativeActiveXObject);
}

protected override void DetachInterfaces()
{
this.axIWebBrowser2 = null;
base.DetachInterfaces();
}


jlandheer at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 7
Hi jlandheer,

Could you please post the full ExtendedWebBrowser (with the worknig ppDisp / Application part) class or send it to me by email ( gjunge AT gmail DOT com), since I cannot get it to work. This would help me enormously.

Thank you in advance,
Gidon

gidon at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 8
I'll send it over Smile

I'm using it allready in one of my programs, there was one thing I needed to change on the interface declaration to get things going.

Here is the modified part:



public interface DWebBrowserEvents2
{
[System.Runtime.InteropServices.DispId(250)]
void BeforeNavigate2(
[System.Runtime.InteropServices.In,
System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.IDispatch)] object pDisp,
[System.Runtime.InteropServices.In] ref object URL,
[System.Runtime.InteropServices.In] ref object flags,
[System.Runtime.InteropServices.In] ref object targetFrameName, [System.Runtime.InteropServices.In] ref object postData,
[System.Runtime.InteropServices.In] ref object headers,
[System.Runtime.InteropServices.In,
System.Runtime.InteropServices.Out] ref bool cancel);
[System.Runtime.InteropServices.DispId(273)]
void NewWindow3(
[System.Runtime.InteropServices.In, System.Runtime.InteropServices.Out] ref object pDisp,
[System.Runtime.InteropServices.In, System.Runtime.InteropServices.Out] ref bool cancel,
[System.Runtime.InteropServices.In] ref object flags,
[System.Runtime.InteropServices.In] ref object URLContext,
[System.Runtime.InteropServices.In] ref object URL);
}

jlandheer at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 9

Can someone please send me the entire class/module/whatever
Which includes the full ppDisp implementation?

Also is there a reason you chose to omit the NewWindow2 event?
I need to use that in lieu of NewWindow3.

Any chance anyone has that?

I'd prefer to use this over the AxWebBrowser (if I can)...

jshapcott AT MSN dot com

FundamentalDiscord at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 10
Never mind... I got it sorted out.
FundamentalDiscord at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 11

Nice that you figured this out. I've posted the complete source code on my weblog, you can download the file here: http://weblogs.servehttp.com/jeroen/files/extendedwebbrowser.zip

jlandheer at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 12

OUTSTANDING! I'm not so good with translating C# (especially interop stuff) to VB, so this is great to have you spelling it out.

The plumbing was the hard part... now I have the entire interface built, just need to wire up some code.

So... dumb question: why hasn't anyone just wired up ALL of these events into one control? Why wouldn't MS have passed these events in the first place? So many of us spend our time wiring this stuff up...

Oh well, I feel like the doors are opening!

THANK YOU!!!

JR

JR_runnfool at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 13

Do you have an example in VB to declare this interface. I am interested in accessing the Application property.

Thanks

UseCode at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...
# 14

In VS 2003, I was able to use pDisp to maintain session in a new window. I assume that there is some way of doing this with the module above, but I haven't been able to figure it out. (I'm working with Visual Basic .Net 2005)

Thanks for the code, and for any assistance.

CEisen at 2007-9-8 > top of Msdn Tech,Windows Forms,Windows Forms General...