I found this email in my inbox today:
“Hi Mauricio,I came across a reference to your blog at :http://stackoverflow.com/questions/1286746/c-open-link-in-new-tab-webbrowser-control
I have been studying your writings on extending the WebBrowser control, and verified that the extended web code you wrote for C# compiles and works fine in VS 2010 beta, against FrameWork 4.0.
Many thanks for the valuble code and writing !
I am "stuck" on how to read the contents of an IE browser page when the page is displaying a local file, like the contents of the Favorites folder.
All my attempts to get at the Document or DomDocument by casting it to the usual mshtml.dll interfaces fail.
I am NOT asking you to answer my question, or respond, but if you ever get interested in blogging about this aspect of use of IE, I think many people would be interested.
I have done a lot of research on the net, and posted my own question on StackOverFlow : so far not one real pointer, and, possibly, this is not "doable" (?) : maybe what you are seeing when IE shows a file contents is a kind of "virtual explorer" view that is not parseable.
best, Bill xxxxxx”
And I decided to take at look at it to see if I could be of any help and I found out that it is easy and doable.
So I find an useful link by Andreas M. if you want to look at it.
In general My Favorites, Desktop, etc are special folder. So they need a trick to be able to access them.
Take the code from my ExtendedWebBrowser sample published in http://blogs.artinsoft.net/mrojas/archive/2009/05/01/opening-popup-in-a-newwindow.aspx
and http://blogs.artinsoft.net/mrojas/archive/2009/08/07/newwindow3.aspx and
1. Add a reference to %windir%\system32\shell32.dll
2. Add a new property to the ExtendedWebBrowser like:
/// <summary>
/// Returns the shell folderview object displayed in the webbrowser control.
/// </summary>
public Shell32.IShellFolderViewDual2 FolderView
{
get
{
return ((SHDocVw.WebBrowser)base.ActiveXInstance).Document
as Shell32.IShellFolderViewDual2;
}
}
And now you can access the special folder from your code. As Bill mentioned, that “page” or “special page” is not real HTML and not parseable but you can examine its contents for example you can do something like:
/// <summary>
/// Button 1_ click
/// </summary>
private void button1_Click(object sender, EventArgs e)
{
Shell32.IShellFolderViewDual2 specialFolder = this.extendedWebBrowser1.FolderView;
string folderName = specialFolder.Folder.Title;
string parentFolder = specialFolder.Folder.ParentFolder.Title;
foreach (Shell32.ShellFolderItem f in specialFolder.Folder.Items())
{
if (f.IsFolder)
System.Diagnostics.Debug.WriteLine("Folder:" + f.Name);
else
System.Diagnostics.Debug.WriteLine("File:" + f.Name);
} // foreach
} // button1_Click(sender, e)
To be able to catch popup windows and open them in your own window you
have to manage WebBrowser events like NewWindow2.
But how do you do that in WPF?
Well it isn’t really that difficult. These are the steps that you have to follow:
1. Add a COM reference to a reference to %windir%\system32\shdocvw.dll
2. Add a new CodeFile to your project. Lets say CodeFile1.cs And put this code:
using System;
using System.Runtime.InteropServices;
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]
internal interface IServiceProvider
{
[return: MarshalAs(UnmanagedType.IUnknown)]
object QueryService(ref Guid guidService, ref Guid riid);
}
3. To make an easy example. Lets assume we have a very simple window like:
And in that form we need some code like this:
private void button1_Click(object sender, RoutedEventArgs e)
{
Guid SID_SWebBrowserApp =
new Guid("0002DF05-0000-0000-C000-000000000046");
IServiceProvider serviceProvider =
(IServiceProvider)myWebBrowser.Document; //<—It seams that you need to
// navigate first to initialize this
Guid serviceGuid = SID_SWebBrowserApp;
Guid iid = typeof(SHDocVw.IWebBrowser2).GUID;
//Here we will get a reference to the IWebBrowser2 interface
SHDocVw.IWebBrowser2 myWebBrowser2 =
(SHDocVw.IWebBrowser2)
serviceProvider.QueryService(ref serviceGuid, ref iid);
//To hook events we just need to do these casts
SHDocVw.DWebBrowserEvents_Event wbEvents =
(SHDocVw.DWebBrowserEvents_Event)myWebBrowser2;
SHDocVw.DWebBrowserEvents2_Event wbEvents2 =
(SHDocVw.DWebBrowserEvents2_Event)myWebBrowser2;
//Adding event handlers is now very simple
wbEvents.NewWindow +=
new SHDocVw.DWebBrowserEvents_NewWindowEventHandler(wbEvents_NewWindow);
wbEvents2.NewWindow2 +=
new SHDocVw.DWebBrowserEvents2_NewWindow2EventHandler(wbEvents2_NewWindow2);
}
void wbEvents2_NewWindow2(ref object ppDisp, ref bool Cancel)
{
//If you want make popup windows to open in your own window
// you need to assign the ppDisp to the .Application of
// the WebBrowser in your window
Window1 wnd = new Window1();
wnd.Show();
//Just navigate to make sure .Document is initilialized
wnd.myWebBrowser.Navigate(new Uri("about:blank"));
Guid SID_SWebBrowserApp = new Guid("0002DF05-0000-0000-C000-000000000046");
IServiceProvider serviceProvider = (IServiceProvider)wnd.myWebBrowser.Document;
Guid serviceGuid = SID_SWebBrowserApp;
Guid iid = typeof(SHDocVw.IWebBrowser2).GUID;
SHDocVw.IWebBrowser2 myWebBrowser2 = (SHDocVw.IWebBrowser2)serviceProvider.QueryService(ref serviceGuid, ref iid);
ppDisp = myWebBrowser2.Application;
}
void wbEvents_NewWindow(string URL, int Flags, string TargetFrameName, ref object PostData, string Headers, ref bool Processed)
{
MessageBox.Show(URL);
}
private void button2_Click(object sender, RoutedEventArgs e)
{
myWebBrowser.Navigate(new Uri("file://D:/MyProjects/ExtendedBrowserExample_v2/test0.htm"));
}
Now you can manage your popupwindows:
You can download the test application from HERE
I’m an enthusiastic user of DXCore and I have been working on some extensions of my own. But I could not find the DXCore or DevExpress menu.
Well there is a hack for that.
Please invoke the Registry editor, add the "HideMenu" DWORD value to the following Registry key, and set its Value to 0:
HKEY_LOCAL_MACHINE\SOFTWARE\Developer Express\CodeRush for VS\9.1
This should make the "DevExpress" menu visible.
Look at the post for more details and keep enjoying DXCore.
If I have a PInvoke call like the following:
[DllImport("Advapi32.dll", CharSet=CharSet.Auto)]
static extern Boolean FileEncryptionStatus(String filename,
out UInt32 status);
What is the difference between
[DllImport("Advapi32.dll", CharSet=CharSet.Auto)] static extern Boolean FileEncryptionStatus(String filename, out UInt32 status);
and
[DllImport("Advapi32.dll", CharSet=CharSet.Auto)] static extern Boolean FileEncryptionStatus(String filename, ref UInt32 status);
Well, as long as I have tested it, they exactly the same. From the MSDN you can even read
“I could have selected the ref keyword here as well, and in fact both result in the same machine code at run time. The out keyword is simply a specialization of a by-ref parameter that indicates to the C# compiler that the data being passed is only being passed out of the called function. In contrast, with the ref keyword the compiler assumes that data may flow both in and out of the called function.”
“When marshaling pointers through P/Invoke, ref and out are only used with value types in managed code. You can tell a parameter is a value type when its CLR type is defined using the struct keyword. Out and ref are used to marshal pointers to these data types”
So what should you use? Well using the out keyword for PInvoke will just add some information or documentation to your method, but because these functions are implemented in C or C++ they might treat an out parameter as an IN parameter so I really prefere to use ref when I’m calling functions with PInvoke.
.NET has been around for quite a while. According Wikipedia it has been around since on 3 April 2003.
So now there exist applications developed for .NET Framework 1.0 or 1.1 and people
need to migrate them to Framework 2.0 or Framework 3.5.
It is the general impression that there is not a direct path to 3.5.
As Zain Naboulsi explains in his blog you can go from 1.1 to 2.0 then from 2.0 to 3.5.
And From 2.0 to 3.5 the migration is a no-brainer because, both, 3.0 and 3.5 are based on 2.0.
A good reference also is the post of Peter Laudati on migration from 1.1 to 2.0.
Note: Peter’s post seem to have a broken link to the microsoft document about breaking changes in 2.0.
The correct link is this.
A more recent post by The Moth provides more links to breaking changes documents:
- Design time Breaking Changes in .NET Framework 2.0
- Runtime Breaking Changes in .NET Framework 2.0
- Microsoft .NET Framework 1.1 and 2.0 Compatibility
- Compatibility Testing Scenarios
Going from 1.1 to 2.0 or 3.5 can be just as simple as opening the solution in VS and compile
or it can take a lot of effort. Web Projects then to be more difficult due to several changes in ASP.NET.
So good luck.
Tools?
Well there a lot of static analyisis tools we have used
(some internal, some from Third Parties. I particulary like Understand and NDepend)
Recently an user of the ExtendedBrowser v2 commented that he needed access to the NewWindow3 event.
The NewWindow3 event is raised when a new window is to be created. It extends NewWindow2 with additional information about the new window.
Syntax
Private Sub object_NewWindow3( _
ByRef ppDisp As Object, _
ByRef Cancel As Boolean, _
ByVal dwFlags As Long, _
ByVal bstrUrlContext As String, _
ByVal bstrUrl As String)
Parameters
object Object expression that resolves to the objects in the Applies To list. ppDisp Object expression that, optionally, receives a new, hidden WebBrowser or InternetExplorer object with no URL loaded. Cancel A Boolean value that determines whether the current navigation should be canceled. true
Cancel the navigation. false
Do not cancel the navigation. dwFlags The flags from the NWMFenumeration that pertain to the new window.
typedef enum NWMF
{
NWMF_UNLOADING = 0x00000001,
NWMF_USERINITED = 0x00000002,
NWMF_FIRST = 0x00000004,
NWMF_OVERRIDEKEY = 0x00000008,
NWMF_SHOWHELP = 0x00000010,
NWMF_HTMLDIALOG = 0x00000020,
NWMF_FROMDIALOGCHILD = 0x00000040,
NWMF_USERREQUESTED = 0x00000080,
NWMF_USERALLOWED = 0x00000100,
NWMF_FORCEWINDOW = 0x00010000,
NWMF_FORCETAB = 0x00020000,
NWMF_SUGGESTWINDOW = 0x00040000,
NWMF_SUGGESTTAB = 0x00080000,
NWMF_INACTIVETAB = 0x00100000
} NWMF;
bstrUrlContext The URL of the page that is opening the new window. bstrUrlThe URL that is opened in the new window.
Please notice:
Note The NewWindow3 event is only fired when a new instance of Internet Explorer is about to be created. Calling showModalDialog or showModelessDialog does not trigger an event because they are not new instances of Internet Explorer. They are implemented as MSHTML host windows, which allows them to render and display HTML content but not hyperlinks between documents.
You can download from here
DOWNLOAD CODE HERE v3_1
ExtendedBrowserExampleVBNET.zip (92.56 kb)
I was looking for a “.net” way of detecting the CapsLock state, but almost all the references pointed to pinvoke code like:
<DllImport("user32.dll")> _
Public Shared Function GetKeyState(VirtKey As Integer) As Integer
End Sub
And I finally found two ways:
1) You can call methods from the System.Console class:
You can use the System.Console.CapsLock property and if you want the NumLock state use: System.Console.NumberLock
or
2) You can call make an instance of Microsoft.VisualBasic.Devices.Keyboard. (For this if you are in C# you need to add a reference to Microsoft.VisualBasic.dll)
For example:
Microsoft.VisualBasic.Devices.Keyboard key = new Microsoft.VisualBasic.Devices.Keyboard();
and use properties like:
key.CapsLock
key.NumLock
key.ScrollLock
key.ShiftKeyDown
key.CtrlKeyDown
key.AltKeyDown
This post discusses and provides the implementation of a helper class to add support
for printing the formatted contents of a richtextbox control.
The print model in .NET is a little different than one used in VB6.
For example see: http://support.microsoft.com/kb/146022.
Public Sub PrintRTF(RTF As RichTextBox, LeftMarginWidth As Long, _
TopMarginHeight, RightMarginWidth, BottomMarginHeight)
Dim LeftOffset As Long, TopOffset As Long
Dim LeftMargin As Long, TopMargin As Long
Dim RightMargin As Long, BottomMargin As Long
Dim fr As FormatRange
Dim rcDrawTo As Rect
Dim rcPage As Rect
Dim TextLength As Long
Dim NextCharPosition As Long
Dim r As Long
' Start a print job to get a valid Printer.hDC
Printer.Print Space(1)
Printer.ScaleMode = vbTwips
' Get the offsett to the printable area on the page in twips
LeftOffset = Printer.ScaleX(GetDeviceCaps(Printer.hdc, _
PHYSICALOFFSETX), vbPixels, vbTwips)
TopOffset = Printer.ScaleY(GetDeviceCaps(Printer.hdc, _
PHYSICALOFFSETY), vbPixels, vbTwips)
' Calculate the Left, Top, Right, and Bottom margins
LeftMargin = LeftMarginWidth - LeftOffset
TopMargin = TopMarginHeight - TopOffset
RightMargin = (Printer.Width - RightMarginWidth) - LeftOffset
BottomMargin = (Printer.Height - BottomMarginHeight) - TopOffset
' Set printable area rect
rcPage.Left = 0
rcPage.Top = 0
rcPage.Right = Printer.ScaleWidth
rcPage.Bottom = Printer.ScaleHeight
' Set rect in which to print (relative to printable area)
rcDrawTo.Left = LeftMargin
rcDrawTo.Top = TopMargin
rcDrawTo.Right = RightMargin
rcDrawTo.Bottom = BottomMargin
' Set up the print instructions
fr.hdc = Printer.hdc ' Use the same DC for measuring and rendering
fr.hdcTarget = Printer.hdc ' Point at printer hDC
fr.rc = rcDrawTo ' Indicate the area on page to draw to
fr.rcPage = rcPage ' Indicate entire size of page
fr.chrg.cpMin = 0 ' Indicate start of text through
fr.chrg.cpMax = -1 ' end of the text
' Get length of text in RTF
TextLength = Len(RTF.Text)
' Loop printing each page until done
Do
' Print the page by sending EM_FORMATRANGE message
NextCharPosition = SendMessage(RTF.hWnd, EM_FORMATRANGE, True, fr)
If NextCharPosition >= TextLength Then Exit Do 'If done then exit
fr.chrg.cpMin = NextCharPosition ' Starting position for next page
Printer.NewPage ' Move on to next page
Printer.Print Space(1) ' Re-initialize hDC
fr.hdc = Printer.hdc
fr.hdcTarget = Printer.hdc
Loop
' Commit the print job
Printer.EndDoc
' Allow the RTF to free up memory
r = SendMessage(RTF.hWnd, EM_FORMATRANGE, False, ByVal CLng(0))
End Sub
The VBCompanion provides excellent helpers that provide a lot fo the VB6 Printer object functionality, so you dont have to change any of your actual code, but in some cases, you might just want to remove that code, specially for very specific things like printing a RichTextBox.
So here I’m providing a .NET simplified helper that allows you to print the contents of a RichTextBox control. This helper is just based on the code published by Martin Muller in http://msdn.microsoft.com/en-us/library/ms996492.aspx. It provides an extension method for VS 2008 user so all you have to do is call RichTextBox.Print.
The implementation is simple. The RichTextBoxPrintHelper creates or receives an instance of a PrintDocument object, and event handlers are added to it for the BeginPrint, PrintPage and EndPrint events.
private int m_nFirstCharOnPage;
private void printDocument_BeginPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
{
// Start at the beginning of the text
m_nFirstCharOnPage = 0;
}
private void printDocument_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
{
// To print the boundaries of the current page margins
// uncomment the next line:
//e.Graphics.DrawRectangle(System.Drawing.Pens.Blue, e.MarginBounds);
// make the RichTextBoxEx calculate and render as much text as will
// fit on the page and remember the last character printed for the
// beginning of the next page
m_nFirstCharOnPage = FormatRange(false,
e,
m_nFirstCharOnPage,
control.TextLength);
// check if there are more pages to print
if (m_nFirstCharOnPage < control.TextLength)
e.HasMorePages = true;
else
e.HasMorePages = false;
}
private void printDocument_EndPrint(object sender, System.Drawing.Printing.PrintEventArgs e)
{
// Clean up cached information
FormatRangeDone();
}
The FormatRange method is called. This method will use the fill out some structures
with page information and use the RichTextBox handle to send messages that will render
the control contents to the Printer’s HDC.
/// <summary>
/// Calculate or render the contents of our RichTextBox for printing
/// </summary>
/// <param name="measureOnly">If true, only the calculation is performed,
/// otherwise the text is rendered as well</param>
/// <param name="e">The PrintPageEventArgs object from the
/// PrintPage event</param>
/// <param name="charFrom">Index of first character to be printed</param>
/// <param name="charTo">Index of last character to be printed</param>
/// <returns>(Index of last character that fitted on the
/// page) + 1</returns>
public int FormatRange(bool measureOnly, PrintPageEventArgs e,
int charFrom, int charTo)
{
// Specify which characters to print
STRUCT_CHARRANGE cr;
cr.cpMin = charFrom;
cr.cpMax = charTo;
// Specify the area inside page margins
STRUCT_RECT rc;
rc.top = HundredthInchToTwips(e.MarginBounds.Top);
rc.bottom = HundredthInchToTwips(e.MarginBounds.Bottom);
rc.left = HundredthInchToTwips(e.MarginBounds.Left);
rc.right = HundredthInchToTwips(e.MarginBounds.Right);
// Specify the page area
STRUCT_RECT rcPage;
rcPage.top = HundredthInchToTwips(e.PageBounds.Top);
rcPage.bottom = HundredthInchToTwips(e.PageBounds.Bottom);
rcPage.left = HundredthInchToTwips(e.PageBounds.Left);
rcPage.right = HundredthInchToTwips(e.PageBounds.Right);
// Get device context of output device
IntPtr hdc = e.Graphics.GetHdc();
// Fill in the FORMATRANGE struct
STRUCT_FORMATRANGE fr;
fr.chrg = cr;
fr.hdc = hdc;
fr.hdcTarget = hdc;
fr.rc = rc;
fr.rcPage = rcPage;
// Non-Zero wParam means render, Zero means measure
Int32 wParam = (measureOnly ? 0 : 1);
// Allocate memory for the FORMATRANGE struct and
// copy the contents of our struct to this memory
IntPtr lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fr));
Marshal.StructureToPtr(fr, lParam, false);
// Send the actual Win32 message
int res = SendMessage(control.Handle, EM_FORMATRANGE, wParam, lParam);
// Free allocated memory
Marshal.FreeCoTaskMem(lParam);
// and release the device context
e.Graphics.ReleaseHdc(hdc);
return res;
}
Using the RichTextBox is even more simple. You add a richtextbox to a form and call the Print method:
private void printToolStripMenuItem_Click(object sender, EventArgs e)
{
richTextBox1.Print();
}
I’m attaching the source code for the helper an this sample application so you can use this.
DOWNLOAD SOURCE CODE
Someone recently made me remind an old technology called DDE.
“Dynamic Data Exchange (DDE) is a technology for communication between multiple applications under Microsoft Windows or OS/2”
“The primary function of DDE is to allow Windows applications to share data. For example, a cell in Microsoft Excel could be linked to a value in another application and when the value changed, it would be automatically updated in the Excel spreadsheet. The data communication was established by a simple, three-segment model. Each program was known to DDE by its "application" name. Each application could further organize information by groups known as "topic" and each topic could serve up individual pieces of data as an "item". For example, if a user wanted to pull a value from Microsoft Excel which was contained in a spreadsheet called "Sheet1" in the cell in the first row and first column, the application would be "Excel", the topic "Sheet1" and the item "r1c1".
Note: In DDE, the application, topic and item are not case-sensitive.”
So in VB6 you can have something like:
Private Sub Form_Load()
Text1.LinkMode = 0
Text1.LinkTopic = "Excel|Sheet1"
Text1.LinkItem = "R1C1"
Text1.LinkMode = 1
End Sub
How can you do that in .NET. Is it possible in C#? Well I started looking around and found several forums explaining about all the API calls and I was just about to write my own solution when I found NDDE. This project hosted in CodePlex “provides a convenient and easy way to integrate .NET applications with legacy applications that use Dynamic Data Exchange (DDE)” :)
So this is a nice example of how to do the previous lines in C#:
//This class provides the infraestructure for DDE comunication
NDde.Client.DdeClient ddeClient_TextBox1 = null;
private void Form1_Load(object sender, EventArgs e)
{
//I initialize the DDEClient object. Application is Excel and Topic is Sheet1. I'm using the
//the TextBox as the syncronization object
ddeClient_TextBox1 = new NDde.Client.DdeClient("Excel", "Sheet1", textBox1);
//Connect to the DDE Server
ddeClient_TextBox1.Connect();
//Start the Advise Loop
ddeClient_TextBox1.StartAdvise("R1C1", 1, true, 60000);
//Setup the Advise Method
ddeClient_TextBox1.Advise += new EventHandler<NDde.Client.DdeAdviseEventArgs>(ddeClient_TextBox1_Advise);
//Setup a method to Poke the Server for TextBox cahnges
textBox1.TextChanged += new EventHandler(textBox1_TextChanged);
}
void textBox1_TextChanged(object sender, EventArgs e)
{
//Syncronous Poking the server
ddeClient_TextBox1.Poke("R1C1", textBox1.Text + "\0", 4000);
}
const string DDE_postFix = "\r\n\0";
void ddeClient_TextBox1_Advise(object sender, NDde.Client.DdeAdviseEventArgs e)
{
//Advise only if needed
if (e.Text.Length >=DDE_postFix.Length && textBox1.Text + DDE_postFix != e.Text)
textBox1.Text = e.Text.Substring(0,e.Text.Length-3);
}
NOTE: Remember that you need to download NDDE and add a reference to this library
This is very good library, you can also set up a lot of Async calls to even improve performance. I have even thought of making an extender as the ToolTip control to add LinkTopic, LinkMode and LinkItem properties for Winforms controls or provide extensions methods to make all the syntax easier, but that is for a future post. Good Luck.
How can I migrate property pages? Well that is a common question when migrating VB6 Activex controls.
Property Pages where commonly used in VB6 to provide a mechanism for your user controls to edit values.
.NET provides even more mechanisms for editing your control properties. You can provide an editor for each one of your component properties or you can provide a ComponentEditor for all the component, this is very similar to the VB6 concept.
In .NET the ComponentEditor can be actived in the designer selecting Properties from the context menu when you right click over the control.
This is from the MSDN documentation:
“A component editor is used to edit a component as a whole and can be used to implement a
user interface similar to that of the property pages. You associate a component editor with a
component by using the EditorAttribute attribute.” From: ComponentEditor Class
The VBUC does not process out of the box, your PropertyPages, but I developed a tool that can be
used so the VBUC can help you migrate those property pages. This tool will modify your VB6 project,
and VB6 PropertyPages source code to make those VB6 PropertyPages look like VB6 UserControls.
This will allow the VBUC migration tool to recover some of the VB6 PropertyPages code and appearance
and with some manual changes you can get your property pages to work again.
Use the following link to downlaod the tool: DOWNLOAD TOOL
So these are the steps to migrate a VB6 Project that has Property Pages with the VB6.
1) Make a backup copy of your source code.
2) Run the TOOL with your project file. For example if your project file is Project1.vbp then run the tool like this:
FixPropertyPages Project1.vbp
This will generate a new VB6 Project file called ModifiedProject1.vbp
3) Open the VBUC, and migrate the new project file ModifiedProject1.vbp
4) Open the migrated solution in Visual Studio.
5) All your property pages will be migrated to .NET UserControls. You might need to go thru some changes to make them completely functional. Remeber to add the [ToolboxItem(false)] to these property pages because they do not need to be visible in your toolbox.
6) Now, to associate those property pages with your UserControl do this:
6.1) Add a new code file to your migrated solution. We are going to create a ComponentEditor, that will hold all the pages and associate that to the migrated control. Lets say the control is named Control1 and the property pages are PropertyPage1 and PropertyPage2.
We will call the ComponentEditor ComponentEditorToAssociatePagesForMyControl.
In this ComponentEditor we will add an internal class for each PropertyPage. This class will inherit from ComponentEditorPage. We will call this internal classes Page1, and Page2. And we will associate those classes with the ComponentEditorToAssociatePagesForMyControl in the GetComponentEditorPages().
The resulting code will be like:
C#using System.Windows.Forms.Design;
using WindowsFormsApplication1;
using System.Drawing;
using System.ComponentModel;
[ToolboxItem(false)]
public class ComponentEditorToAssociatePagesForMyControl : WindowsFormsComponentEditor
{
// Methods
public override bool EditComponent(ITypeDescriptorContext context, object component)
{
return false;
}
class Page1 : ComponentEditorPage
{
// Methods
public Page1()
{
PropertyPage1ForControl1 page1 = new PropertyPage1ForControl1();
Size mysize = new Size(400, 250);
this.Size = mysize;
this.Text = "Page 1 for Control1";
this.Controls.Add(page1);
}
protected override void LoadComponent() { }
protected override void SaveComponent() { }
}
class Page2 : ComponentEditorPage
{
// Methods
public Page2()
{
PropertyPage2ForControl1 page2 = new PropertyPage2ForControl1();
Size mysize = new Size(400, 250);
this.Size = mysize;
this.Text = "Page 2 for Control1";
this.Controls.Add(page2);
}
protected override void LoadComponent() { }
protected override void SaveComponent() { }
}
protected override System.Type[] GetComponentEditorPages()
{
return new System.Type[] { typeof(Page1),typeof(Page2) };
}
protected override int GetInitialComponentEditorPageIndex()
{
return 0;
}
}
VB.NET
<ToolboxItem(False)> _
Public Class ComponentEditorToAssociatePagesForMyControl
Inherits WindowsFormsComponentEditor
' Methods
Public Overrides Function EditComponent(ByVal context As ITypeDescriptorContext, ByVal component As Object) As Boolean
Return False
End Function
Protected Overrides Function GetComponentEditorPages() As Type()
Return New Type() { GetType(Page1), GetType(Page2) }
End Function
Protected Overrides Function GetInitialComponentEditorPageIndex() As Integer
Return 0
End Function
' Nested Types
Private Class Page1
Inherits ComponentEditorPage
' Methods
Public Sub New()
Dim page1 As New PropertyPage1ForControl1
Dim mysize As New Size(400, 250)
MyBase.Size = mysize
Me.Text = "Page 1 for Control1"
MyBase.Controls.Add(page1)
End Sub
Protected Overrides Sub LoadComponent()
End Sub
Protected Overrides Sub SaveComponent()
End Sub
End Class
Private Class Page2
Inherits ComponentEditorPage
' Methods
Public Sub New()
Dim page2 As New PropertyPage2ForControl1
Dim mysize As New Size(400, 250)
MyBase.Size = mysize
Me.Text = "Page 2 for Control1"
MyBase.Controls.Add(page2)
End Sub
Protected Overrides Sub LoadComponent()
End Sub
Protected Overrides Sub SaveComponent()
End Sub
End Class
End Class
7) After creating the ComponentEditor you must associate the component Editor to your new component editors. This can be done with something like:
C#
[Editor(typeof(ComponentEditorToAssociatePagesForMyControl), typeof(ComponentEditor))]
public class Control1 : UserControl
|
VB.NET
<Editor(GetType(ComponentEditorToAssociatePagesForMyControl), GetType(ComponentEditor))> _
Public Class Control1
|
8) Now to use this property pages, go to the designer screen and open the context menu and select properties. And editor with your properties pages will appear :)
9) You still need to write some code for saving the property values that is something you have to add to the LoadComponent and SaveComponent methods of the internal classes in your ComponentEditor (ComponentEditorToAssociatePagesForMyControl in our previous example).
I hope this helps to get your code faster in .NET. I'm attaching a C# sample if you want to try it out.