The Amadeus.NET Blog

Development, Projects, Plans and Ideas on .NET, Visual Studio, Windows XP & Vista

Using the Windows Forms designer as your custom Design editor. (Part I)

While using the Visual Studio core editor as your text editor in a language service is a known and straightforward procedure, creating a designer has been totally left in the responsibility of the language service or editor implementer. In this quest, the most wanted designer is the Windows Forms Designer, that can be customized (along with the toolbox), and used as our custom designer.

Our instruments to accomplish this, will be the following classes and interfaces, some provided with the .NET Framework, while others come with the Visual Studio SDK:

.NET Framework:

System.ComponentModel.Design.DesignSurface
System.ComponentModel.Design.IDesignerHost (...and other services)
System.Windows.Forms.Design.DocumentDesigner
System.Drawing.Design.ToolboxItem

...and a number of controls and their corresponding designers, based on the controls we want to add to our designer and the toolbox.

Visual Studio SDK:

Microsoft.VisualStudio.Shell.WindowPane
Microsoft.VisualStudio.Shell.Interop.IVsToolboxUser
...optionally:
Microsoft.VisualStudio.Shell.Interop.IVsDocOutlineProvider
...and whatever else can be implemented by an editor. The majority are already implemented by the WindowPane class.

1) The first step of course, is to create and register the editor factory.

Click to collapseCreating and registering an editor in managed code (VB):

...
 MSVSIP.ProvideEditorFactory(GetType(MyEditorFactory), 106), _
 MSVSIP.ProvideEditorExtensionAttribute(GetType(MyEditorFactory), ".ext1", 32, _
    ProjectGuid:="{00000000-0000-0000-0000-000000000000}", _
    TemplateDir:="..\..\Templates", _
    NameResourceID:=114, _
    DefaultName:="MyEditor"), _
 MSVSIP.ProvideEditorExtensionAttribute(GetType(MyEditorFactory), ".ext2", 32), _
 MSVSIP.ProvideEditorExtensionAttribute(GetType(MyEditorFactory), ".ext3", 32), _
 ProvideOpenWithEntry(GetType(MyEditorFactory), LogicalView.Code, "#115"), _
 ProvideOpenWithEntry(GetType(MyEditorFactory), LogicalView.Designer, "#116"), _
 Guid("00000000-0000-0000-0000-000000000000"), _
 CLSCompliant(False)> _
Public NotInheritable Class MyLanguageServiceOrEditorPackage
...

(Example of the attributes added on a managed VsPackage class)

Protected Overrides Sub Initialize()
...
    ' Register the Editor Factory
    editorFactory = New MyEditorFactory(Me)
    MyBase.RegisterEditorFactory(editorFactory)
...
End Sub

(Registration of the editor factory in the Initialize() method of the VsPackage class)

<ProvideView(LogicalView.Code, Nothing), _
 ProvideView(LogicalView.Debugging, Nothing), _
 ProvideView(LogicalView.Primary, Nothing), _
 ProvideView(LogicalView.Text, Nothing), _
 ProvideView(LogicalView.Designer, "Form")> _
<Guid("00000000-0000-0000-0000-000000000000")> _
<CLSCompliant(False)> _
Public NotInheritable Class MyEditorFactory
    Implements IVsEditorFactory
...

(Optional attributes on the editor factory class)

#Region " MapLogicalView " ' This method is called by the Environment (inside IVsUIShellOpenDocument:: ' OpenStandardEditor and OpenSpecificEditor) to map a LOGICAL view to a ' PHYSICAL view. A LOGICAL view identifies the purpose of the view that is ' desired (e.g. a view appropriate for Debugging [LOGVIEWID_Debugging], or a ' view appropriate for text view manipulation as by navigating to a find ' result [LOGVIEWID_TextView]). A PHYSICAL view identifies an actual type ' of view implementation that an IVsEditorFactory can create. ' ' NOTE: Physical views are identified by a string of your choice with the ' one constraint that the default/primary physical view for an editor ' *MUST* use a NULL string as its physical view name (*pbstrPhysicalView = NULL). ' ' NOTE: It is essential that the implementation of MapLogicalView properly ' validates that the LogicalView desired is actually supported by the editor. ' If an unsupported LogicalView is requested then E_NOTIMPL must be returned. ' ' NOTE: The special Logical Views supported by an Editor Factory must also ' be registered in the local registry hive. LOGVIEWID_Primary is implicitly ' supported by all editor types and does not need to be registered. ' For example, an editor that supports a ViewCode/ViewDesigner scenario ' might register something like the following: ' HKLM\Software\Microsoft\VisualStudio\8.0\Editors\ ' {...guidEditor...}\ ' LogicalViews\ ' {...LOGVIEWID_TextView...} = s '' ' {...LOGVIEWID_Code...} = s '' ' {...LOGVIEWID_Debugging...} = s '' ' {...LOGVIEWID_Designer...} = s 'Form' ' Private Function MapLogicalView(ByRef rguidLogicalView As Guid, _
ByRef pbstrPhysicalView As String) As Integer Implements IVsEditorFactory.MapLogicalView
pbstrPhysicalView = Nothing Select Case rguidLogicalView Case VSConstants.LOGVIEWID_Primary, _ VSConstants.LOGVIEWID_Code, _ VSConstants.LOGVIEWID_Debugging, _ VSConstants.LOGVIEWID_TextView Return VSConstants.S_OK Case VSConstants.LOGVIEWID_Designer pbstrPhysicalView = "Form" Return VSConstants.S_OK Case Else Return VSConstants.E_NOTIMPL End Select End Function #End Region

 

(Standard implementation of the MapLogicalView method)

...And the greatest brick (This also demonstrates how to create and use an instance of the core editor).:

#Region " CreateEditorInstance " Private Function CreateEditorInstance(ByVal grfCreateDoc As System.UInt32, _
        ByVal pszMkDocument As String, ByVal pszPhysicalView As String, _
        ByVal pvHier As IVsHierarchy, ByVal itemid As System.UInt32, _
        ByVal punkDocDataExisting As System.IntPtr, ByRef ppunkDocView As System.IntPtr, _
        ByRef ppunkDocData As System.IntPtr, ByRef pbstrEditorCaption As String, _
        ByRef pguidCmdUI As Guid, _
        ByRef pgrfCDW As Integer) As Integer Implements IVsEditorFactory.CreateEditorInstance ' Initialize to null ppunkDocView = IntPtr.Zero ppunkDocData = IntPtr.Zero pgrfCDW = 0 pbstrEditorCaption = Nothing Dim buffer As IVsTextLines ' Validate inputs If (grfCreateDoc And (VSConstants.CEF_OPENFILE Or VSConstants.CEF_SILENT)) = 0 Then Throw New ArgumentException("Only Open or Silent is valid") End If If Not (pszMkDocument.ToLower.EndsWith(".ext1") OrElse _ pszMkDocument.ToLower.EndsWith(".ext2") OrElse _ pszMkDocument.ToLower.EndsWith(".ext3")) Then Throw New ArgumentException("Only .ext1, .ext2, or .ext3 files " _
"are supported by this factory."
) End If If punkDocDataExisting = IntPtr.Zero Then ' Create a text buffer if one does not exist already. Try ' Instantiate a text buffer of type VsTextBuffer. Dim clsidTextBuffer As Guid = GetType(VsTextBufferClass).GUID Dim iidTextBuffer As Guid = GetType(IVsTextLines).GUID buffer = My.Instance(Of VsTextBufferClass, IVsTextLines)() If buffer IsNot Nothing Then ' Site the text buffer with the service provider we were provided. Dim textBufferSite As IObjectWithSite = TryCast(buffer, IObjectWithSite) If textBufferSite IsNot Nothing Then textBufferSite.SetSite(m_OLESP) End If End If Catch ex2 As Exception Return VSConstants.E_FAIL End Try Else ' Check if the existing DocData implements IVsTextLines. buffer = TryCast(Marshal.GetObjectForIUnknown(punkDocDataExisting), IVsTextLines) If buffer Is Nothing Then Return VSConstants.VS_E_INCOMPATIBLEDOCDATA End If End If If pszMkDocument.ToLower.EndsWith(".ext1") Then ' Assign our language service with the new buffer. buffer.SetLanguageServiceID(GuidList.guidMyLanguage) ElseIf pszMkDocument.ToLower.EndsWith(".ext2") Then buffer.SetLanguageServiceID(GuidList.guidMyLanguage) ElseIf pszMkDocument.ToLower.EndsWith(".ext3") Then buffer.SetLanguageServiceID(GuidList.guidMyLanguage) End If If pszPhysicalView = "Form" Then Dim view As New MyDesignView(buffer, pvHier, itemid, pszMkDocument) If view IsNot Nothing Then ' Now tell the caller about all this new stuff ' that has been created. ppunkDocView = Marshal.GetIUnknownForObject(view) ppunkDocData = Marshal.GetIUnknownForObject(buffer) ' This caption is appended to the filename and ' lets us know our invocation of the core editor ' is up and running. pbstrEditorCaption = " [Design]" Return VSConstants.S_OK Else My.VsUIShell.ShowMessageBox(0, Guid.Empty, _ "My Editor Factory", _ "Design View is currently unavailable." & vbCrLf & _ "Make sure the file is part of a MyProject.", "", 0, _ OLEMSGBUTTON.OLEMSGBUTTON_OK, _ OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, _ OLEMSGICON.OLEMSGICON_WARNING, 0, 0) Return VSConstants.S_OK End If Else Try ' Instantiate a code window. Dim codeWindow As IVsCodeWindow codeWindow = My.Instance(Of VsCodeWindowClass, IVsCodeWindow)() If codeWindow IsNot Nothing Then ' We do any needed initialization, before ' we call SetBuffer(). Dim codeWindowEx As IVsCodeWindowEx = TryCast(codeWindow, IVsCodeWindowEx) If codeWindowEx IsNot Nothing Then Dim init(0) As INITVIEW codeWindowEx.Initialize( _ CUInt(_codewindowbehaviorflags.CWB_DEFAULT), _ 0, String.Empty, String.Empty, _ CUInt(TextViewInitFlags.VIF_SET_WIDGET_MARGIN), init) End If ' Give the text buffer to the code window. ' We are giving up ownership of the text buffer! codeWindow.SetBuffer(buffer) ' Now tell the caller about all this new stuff ' that has been created. ppunkDocView = Marshal.GetIUnknownForObject(codeWindow) ppunkDocData = Marshal.GetIUnknownForObject(buffer) ' Specify the command UI to use so keypresses are ' automatically dealt with. pguidCmdUI = VSConstants.GUID_TextEditorFactory ' This caption is appended to the filename and ' lets us know our invocation of the core editor ' is up and running. pbstrEditorCaption = " [Code]" Return VSConstants.S_OK End If Catch ex As Exception Return VSConstants.E_FAIL End Try End If Return VSConstants.E_FAIL End Function #End Region

 

 

2) The second step is to implement our designer. In the implementation of the CreateEditorInstance method above, we created and returned to the environment an instance of our designer (MyDesignView). The first thing that the environment asks for, and we provide through the ppunkDocView argument, is a class implementing Microsoft.VisualStudio.Shell.Interop.IVsWindowPane. When we create an instance of the core editor as our code editor, the VsCodeWindowClass that we instantiate and provide, already implements IVsWindowPane. But when developing a designer, we are the only implementers of the document view which we have to design from scratch. The environment uses the IVsWindowPane to tell us, among other things, when to create our window and who to parent our window to. In managed code, we can use many of the classes provided, that can save us work and time. In this case, we shall be using the Microsoft.VisualStudio.Shell.WindowPane class, which properly implements the IVsWindowPane interface, as well as other interfaces like System.IServiceProvider and Microsoft.VisualStudio.OLE.Interop.IOleCommandTarget. As with managed tool windows (the Microsoft.VisualStudio.Shell.ToolWindowPane class inherits WindowPane) we return the actual control (that in our case is going to be windows forms designer), through the Window property.

The windows forms designer is actually a design surface (System.ComponentModel.Design.DesignSurface), that hosts a control (root component). For that control to be able to be used as a root component in a design surface, it must have a designer assigned, that implements System.ComponentModel.Design.IRootDesigner. In a windows forms application a System.Windows.Forms.Form is usually used as root component. We will use a UserControl. Of all the designers in the .NET Framework that inherit IRootDesigner, the best to be used as a document view and provide all the functionality that we need in our designer, is System.Windows.Forms.Design.DocumentDesigner

To summarize, we need the following:

a) A class that inherits System.ComponentModel.Design.DesignSurface. The design surface is responsible for providing the environment with individual designers of every control added in the design surface; including our root component.

b) A control that will be used as our root component. In our case a UserControl. This is where controls picked from the toolbox will be dropped.

c) A designer that implements System.ComponentModel.Design.IRootDesigner. In our case a DocumentDesigner. The designer is responsible for providing all the design-time features of our design view, coordinating any new controls added and optionally updating the underlying text buffer of our document.

d) One or more classes that inherit System.Drawing.Design.ToolboxItem. These represent the toolbox items that will be added in the toolbox when our designer is loaded.

e) One or more controls and components that the toolbox items represent and that the user will be able to drop into our designer. As in a windows forms application, controls will be added as children of our root component, while components will be added in the Component Tray.
The addition of such controls can trigger the creation of code.

f) Designers for each of the control or component we add in the toolbox. The designers will be provided to the environment through our custom design surface and they are responsible for controlling the design-time functionality of the controls or components. The user interaction with the controls in design-time, helps us edit the underlying code (if any) in the text buffer.

 

Click to collapse Creating a Windows Forms Designer:

Public Class MyDesignSurface Inherits DesignSurface Public Sub New(ByVal sp As System.IServiceProvider) MyBase.New(sp, GetType(MyDocView)) Dim mcs As OleMenuCommandService = New OleMenuCommandService(Me) Me.ServiceContainer.RemoveService(GetType(IMenuCommandService)) Me.ServiceContainer.AddService(GetType(IMenuCommandService), mcs) End Sub ...
Protected Overrides Function CreateDesigner( _
ByVal component As System.ComponentModel.IComponent, _
ByVal rootDesigner As Boolean) As System.ComponentModel.Design.IDesigner
Dim designer As IDesigner If TypeOf component Is MyDocView Then designer = New MyDocViewDesigner ElseIf TypeOf component Is MyControl Then designer = New MyControlDesigner Else designer = MyBase.CreateDesigner(component, rootDesigner) End If Return designer End Function

... End Class

 

(Our custom design surface)

<Designer(GetType(MyDocViewDesigner), GetType(IRootDesigner))> _
<CLSCompliant(False)> _
Public Class MyDocView
    Inherits UserControl
...

(Our root component. We may add custom painting, code that arranges any child controls added or whatever else we want here.)

<ToolboxItemFilter("MyEditor.MyDesigner")> _ Public Class MyDocViewDesigner Inherits DocumentDesigner Private m_DocView As MyDocView Private m_DVC As DesignerVerbCollection = Nothing ... Public Overrides Sub Initialize(ByVal component As System.ComponentModel.IComponent) MyBase.Initialize(component) If Not TypeOf component Is MyDocView Then Throw New Exception("This designer requires a MyDocView control.") End If component.Site.Name = "MyDesigner" m_DocView = CType(component, MyDocView) m_DVC = New DesignerVerbCollection( _
New DesignerVerb() {New DesignerVerb("Say Hallo", AddressOf Me.SayHallo)}) Dim compChangeSvc As IComponentChangeService = _
CType(Me.GetService(GetType(IComponentChangeService)), IComponentChangeService) If compChangeSvc IsNot Nothing Then AddHandler compChangeSvc.ComponentRemoving, AddressOf Me.ComponentRemoving AddHandler compChangeSvc.ComponentRemoved, AddressOf Me.ComponentRemoved End If End Sub ... Protected Overrides Function CreateToolCore(ByVal tool As System.Drawing.Design.ToolboxItem, _
ByVal x As Integer, ByVal y As Integer, ByVal width As Integer, ByVal height As Integer, _
ByVal hasLocation As Boolean, ByVal hasSize As Boolean) As System.ComponentModel.IComponent() ' Creation sequence. ' This is the first method called when a component is about to be created. ' The base implementation will first call CreateComponentsCore(host, defaultValues) ' in the MyToolboxItem which in turn will call its CreateComponentsCore(host). ' The base implementation of CreateComponentsCore(host) will create the ' component which will ask from the MyDesignSurface to create its designer. ' MyDesignSurface will create the designer and its initialization will ' take place before this method returns. ' TODO: Designer Updating Procedure. ' We need to check for existing elements and deside if we can add ' the dropped element. We must also calculate the size based on existing ' elements and rearrange all components in the container of the root component. ' Child elements will do the same for their children. ' Finally we update the underlying code (if any). Dim component() As IComponent = MyBase.CreateToolCore(tool, x, y, width, height, _
hasLocation, hasSize) ... Return component End Function ... Public Overrides Function CanParent(ByVal control As System.Windows.Forms.Control) As Boolean ' The root component can only parent the controls we allow. If TypeOf control Is MyControl Then Return True End If End Function ... Protected Overrides Sub OnDragEnter(ByVal de As System.Windows.Forms.DragEventArgs) Dim tbxItem As System.Drawing.Design.ToolboxItem Dim tbxData As OleDataObject = New OleDataObject(de.Data) Dim tbxItemContainer As New ToolboxItemContainer(tbxData) Dim host As IDesignerHost = CType(Me.GetService(GetType(IDesignerHost)), IDesignerHost) Dim tbxSvc As IToolboxService = CType(Me.GetService(GetType(IToolboxService)), _
IToolboxService) If tbxSvc IsNot Nothing Then If tbxSvc.IsSupported(de.Data, host) Then If tbxItemContainer IsNot Nothing Then tbxItem = tbxItemContainer.GetToolboxItem(Nothing) If TypeOf tbxItem Is MyToolboxItem Then Dim myTbxItem As MyToolboxItem = CType(tbxItem, MyToolboxItem) 'We check here if we can parent the selected control. 'If yes then... MyBase.OnDragEnter(de) Return End If End If End If End If de.Effect = DragDropEffects.None End Sub ... Public Overrides ReadOnly Property Verbs() As System.ComponentModel.Design.DesignerVerbCollection Get Return m_DVC End Get End Property ... Private ReadOnly Property Window() As MyDesignView Get
'We have added our window pane as a service.
Return CType(Me.GetService(GetType(MyDesignView)), MyDesignView) End Get End Property ... Private Sub SayHallo(ByVal sender As Object, ByVal e As EventArgs) MsgBox("Hallo!") End Sub Private Sub ComponentRemoving(ByVal sender As Object, _
ByVal e As System.ComponentModel.Design.ComponentEventArgs)
If MsgBox("Are you sure?", MsgBoxStyle.YesNo) = MsgBoxResult.No Then Throw New Exception End If End Sub Private Sub ComponentRemoved(ByVal sender As Object, _
ByVal e As System.ComponentModel.Design.ComponentEventArgs)
'Update code? End Sub ... Protected Overrides Sub Dispose(ByVal disposing As Boolean) Dim compChangeSvc As IComponentChangeService = _
CType(Me.GetService(GetType(IComponentChangeService)), IComponentChangeService)
If compChangeSvc IsNot Nothing Then RemoveHandler compChangeSvc.ComponentRemoving, AddressOf Me.ComponentRemoving RemoveHandler compChangeSvc.ComponentRemoved, AddressOf Me.ComponentRemoved End If MyBase.Dispose(disposing) End Sub ... End Class

 

(Our Document Designer)

This example only shows a few of the design-time features we can provide to the user by overriding the methods of the DocumentDesigner this class inherits. The DocumentDesigner is actually the heart of our designer. Ever since we don't develop just another managed language that uses the .NET Framework to create windows forms applications or class libraries (where creating a windows forms designer would be much easier but we would first have to develop an advanced CodeDOM provider), this is where we must edit the underlying text buffer, based on the actions of the user in the designer. The overall appearence and functionality of our designer is also provided here.

We continue with the creation of a sample control that will be added in the toolbox and its corresponding designer. For each control, we also need to implement a ToolBoxItem that we add to the toolbox when our design view is loaded.

<Designer(GetType(MyControlDesigner), GetType(IDesigner))> _ Public Class MyControl Inherits GroupBox 'Or any other control. 'Supposing this represents an element in a code model. Private m_Element As CodeElement2 ... Public Sub New() MyBase.New() InitializeComponent() End Sub ' Used with existing elements. Friend Sub New(ByVal myElm As CodeElement2) MyClass.New() InitializeComponent() 'If this control represents an existing code element, 'this overloaded constructor allows us to adjust the appearance 'of the control to reflect the underlying code model. End Sub ... ' Code elements created by a MyToolboxItem, can be initialized ' by the designer in CreateToolCore and provided to the control. Friend Sub Initialize(ByVal myElm As CodeElement2) m_Element = myElm ... End Sub ... End Class

 

(Our custom toolbox control)

Public Class MyControlDesigner
    Inherits ParentControlDesigner
...

(Our custom control's designer)

We implement this more or less as our DocumentDesigner. In this example, we use a ParentControlDesigner that allows our control to behave as a parent control, meaning that the user can add other controls as children of this control too. Therefore, this designer too will have similar responsibilities to our DocumentDesigner. Details may be provided in later articles.

Before creating our window pane, we need one or more toolbox items that will be add to the toolbox for our custom controls.

<ToolboxItemFilter("MyEditor.MyDesigner", ToolboxItemFilterType.Require)> _ <Serializable()> _ Public Class MyToolboxItem Inherits Drawing.Design.ToolboxItem Public Sub New() With Me
'We can as well use the same ToolboxItem for
'all our custom controls. In this case we could use
'a parameterized constructor and then intialize the
'toolbox item according to the info passed.
MyBase.Initialize(GetType(MyControl)) .IsTransient = True .Description = "Creates a nice element" .DisplayName = "My Element" End With End Sub ... Public Overrides ReadOnly Property ComponentType() As String Get Return "MyElement" End Get End Property ... End Class

 

(Our toolbox item)

Finally, the last and most important thing before we see our designer for the first time, is to implement our window pane. This is what we will actually provide the environment through the CreateEditorInstance method. Here we do all initialization needed, access any underlying code model, create the design surface, create the toolbox items and populate the toolbox. We also add this class as a service of the design surface. This way all later created controls and designer can easily access the window pane, and coordinate any changes to the underlying code, provide document outline info and accomplish any operation where the top level window pane may be needed.

Lets go, step by step, through the implementation of the window pane...

<CLSCompliant(False)> _ Public Class MyDesignView Inherits WindowPane ... Implements IVsToolboxUser Implements IVsDocOutlineProvider ... ' Text Buffer Private m_Buffer As IVsTextLines ' The project's hierarchy. Private m_Hierarchy As IVsHierarchy ' Item id of our file in the project's hierarchy. Private m_ItemID As UInt32 ' File name. Private m_File As String ' Our code window's window frame. Private m_Frame As IVsWindowFrame ' Caption of the window frame. Private m_Caption As String ' Design surface selection service. Private m_SelService As ISelectionService Private m_SelContainer As MSVSIP.SelectionContainer Private m_TrackSel As ITrackSelection Private m_SelectableList As ArrayList ' OLE service provider. Private m_PaneOLESP As OLEInterop.IServiceProvider ' Design surface Private m_Surface As MyDesignSurface ' Root component. Private m_MyView As MyDocView ' View Code command Private m_ViewCodeCommand As OleMenuCommand ' Design surface control. Private WithEvents m_Ctrl As Control ' Outline window control. Private WithEvents m_Outline As MyOutline ... Private Sub New(ByVal buffer As IVsTextLines, ByVal hier As IVsHierarchy, _
        ByVal itemID As UInt32, ByVal file As String)
MyBase.New(Nothing) m_Buffer = buffer m_Hierarchy = hier m_ItemID = itemID m_File = file ... End Sub ...

 

(Our design view class)

As with tool windows, the first method the environment will call on our WindowPane, will be Initialize(). This is where we have to initialize our design surface.

Protected Overrides Sub Initialize() MyBase.Initialize() Try m_PaneOLESP = CType(Me.GetService(GetType(OLEInterop.IServiceProvider)), _
                          OLEInterop.IServiceProvider) m_Frame = CType(Me.GetService(GetType(IVsWindowFrame)), IVsWindowFrame) ' Create our custom design surface. m_Surface = New MyDesignSurface(Me) If m_Surface.View IsNot Nothing Then ' Obtain and initialize the frame's control. m_Ctrl = CType(m_Surface.View, Control) With m_Ctrl .AllowDrop = True .BackColor = Drawing.Color.White End With ' Obtain the surface's root component. We provided the type of our
' root component, when calling the base constructor of our design surface.
' By now, the design surface has already instanciated an instanve of this class.
Dim host As IDesignerHost = CType(m_Surface.GetService(GetType(IDesignerHost)), _
                                            IDesignerHost) m_MyView = CType(host.RootComponent, MyDocView) ' Set the font. Dim UIService As IUIService = CType(Me.GetService(GetType(IUIService)), IUIService) If UIService IsNot Nothing Then m_MyView.Font = CType(UIService.Styles("DialogFont"), Font) End If ' Add ourselves as a service for this design surface, ' so that controls and designers can access the window. m_Surface.SurfaceServiceContainer.AddService(GetType(MyDesignView), Me) ' We can build or access any existing CodeModel here. ... Me.InitializeToolBox() Me.InitializeCommands() Me.StartMonitoring() Me.UpdateProperties(m_MyView) End If Catch ex As Exception Throw End Try End Sub ... Protected Overrides Sub OnCreate() MyBase.OnCreate() ' We can call IVsTextManager.RegisterIndependentView(Me, m_Buffer) here. ... End Sub ...

(Implementation of the Initialize() method)

Private Sub InitializeToolBox() ' Get the toolbox service Dim toolboxService As IToolboxService = CType( _
                                                m_Surface.GetService(GetType(IToolboxService)), _
                                                IToolboxService) Dim toolbox As IVsToolbox = CType(GetService(GetType(SVsToolbox)), IVsToolbox) ' Create the data object that will store the data for the menu item. Dim toolboxData As OleDataObject ' Create the array of TBXITEMINFO structures to describe the items ' we are adding to the toolbox. Dim itemInfo(0) As TBXITEMINFO ' Our custom ToolboxItem Dim tbxItem As MyToolboxItem tbxItem = New MyToolboxItem() toolboxData = New OleDataObject( _ CType(toolboxService.SerializeToolboxItem(tbxItem), System.Windows.Forms.IDataObject)) itemInfo(0).bstrText = "MyElement" itemInfo(0).clrTransparent = CUInt(ColorTranslator.ToWin32(Color.Magenta)) itemInfo(0).hBmp = ControlPaint.CreateHBitmap16Bit( _
                           My.Resources.Control_GroupBox, Color.Magenta) itemInfo(0).dwFlags = _ CUInt(__TBXITEMINFOFLAGS.TBXIF_DONTPERSIST Or _ __TBXITEMINFOFLAGS.TBXIF_DELETEBITMAP Or _ __TBXITEMINFOFLAGS.TBXIF_CANTREMOVE) ErrorHandler.ThrowOnFailure( _ toolbox.AddItem(CType(toolboxData, OLEInterop.IDataObject), _ itemInfo, "My Elemenents")) ' Repeat the same for any more toolbox items we want to add. ... ' Make sure only the pointer item is initially selected. toolbox.DataUsed() End Sub

 

(Implementation of the internal, InitializeToolBox() method)

 

This actually concludes the first part of this article. Later we shall take a look into the implementation of InitalizeCommands() (that adds commands to be handled by our editor like VSStd97CmdID.ViewCode), StartMonitoring() (that starts tracking the selection context) and UpdateProperties() (that updates the properties window). We will see how the window pane controls the toolbox through IVsToolboxUser and get into the implementation of additional interfaces our window pane can implement (like IVsDocOutlineProvider). 

Using standard design-time features provided by the .NET Framework, we will extend our document designer, to provide advanced design-time functionality.

Comments

Bogdan said:

Thank you for this information!  Will there be a part II?

I was considering hacks like creating a separate C# project just to be used as a designer (with dummy forms) and then either either parsing their code files or tying into IDesignerHost and enumerating components (as shown in the WinFormsAutomation VS 2005 Automation Sample) to generate code in target language.

# November 26, 2006 12:20 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Please add 3 and 4 and type the answer here: