EDIT: After I found what I was really looking for, I edited my initial question so to describe better what I finaly wanted to do.
I am working on a UserControl and I want to place a DesignerVerb at it's properties, like TreeView control have. How can I do this? Is it possible?
Well, here is a simple example...
1. If we haven't done already, we should add a reference to System.Design. Goto Reference Manager > Assemblies > Framework and find System.Design. Check it and click OK.
2. Into our UserControl code, we make sure that we already have Imports System.ComponentModel and Imports System.ComponentModel.Design references.
3. Over our UserControl class, we add a Designer attribute, to specify our ControlDesigner for this UserControl.
Imports System.ComponentModel
Imports System.ComponentModel.Design
<Designer(GetType(MyControlDesigner))>
Public Class UserControl1
'Our UserControl code in here...
End Class
4. Under our UserControl class, we create a new class by the name "MyControlDesigner" which will be our ControlDesigner.
Public Class MyControlDesigner
End Class
5. Now, for example, lets create a Verb which will Dock and Undock our UserControl in ParentForm.
Public Class MyControlDesigner
Inherits System.Windows.Forms.Design.ControlDesigner 'Inherit from ControlDesigner class.
Private MyVerbs As DesignerVerbCollection
Public Sub New()
End Sub
Public Overrides ReadOnly Property Verbs() As DesignerVerbCollection
Get
If MyVerbs Is Nothing Then
MyVerbs = New DesignerVerbCollection 'A new DesignerVerbCollection to use for our DesignerVerbs.
MyVerbs.Add(New DesignerVerb("Dock In ParentForm", New EventHandler(AddressOf OnMyCommandLinkClicked))) 'An Event Handler for Docking our UserControl.
MyVerbs.Add(New DesignerVerb("Undock in ParentForm", New EventHandler(AddressOf OnMyCommandLinkClicked))) 'An Event Handler for Undocking our UserControl.
MyVerbs(1).Visible = False 'We hide second Verd by default.
End If
Return MyVerbs
End Get
End Property
Private Sub OnMyCommandLinkClicked(ByVal sender As Object, ByVal args As EventArgs)
Dim _UserControl As UserControl1 = CType(Me.Control, UserControl1) 'Reference to our UserControl1 Class, so we can access it's Properties and Methods.
If _UserControl.Dock = DockStyle.None Then 'If UserControl is Undocked then...
_UserControl.Dock = DockStyle.Fill 'Dock UserControl in ParentForm.
MyVerbs(0).Visible = False 'Hide "Dock In ParentForm" DesignerVerb.
MyVerbs(1).Visible = True 'Show "Undock in ParentForm" DesignerVerb.
Else
_UserControl.Dock = DockStyle.None 'Undock UserControl.
MyVerbs(1).Visible = False 'Hide "Undock in ParentForm" DesignerVerb.
MyVerbs(0).Visible = True 'Show "Dock in ParentForm" DesignerVerb.
End If
End Sub
End Class
6. Then we Build our project and we add our UserControl into our test Form.
You do not necessarily need to create a custom designer to access the various designer services exposed by the WinForm design environment. All you need is an instance of a IServiceProvider Interface. All classes that have System.ComponentModel.Component in their ancestry expose the Site Property. The Site Property is an instance of type ISite that itself inherits from IServiceProvider.
Most of the design services are defined by the interfaces documented in the System.ComponentModel.Design Namespace. Others like the BehaviorService Class are buried in the documentation and have to be specifically sought out.
Using a proper designer class has its advantages in that it automatically integrates into the design model and encapsulates that functionality. The technique shown below has the drawback of needing to know the proper time that services are accessible. The first temporal criteria is that the host designer has finished loading. This is achieved by using a combination the host's IsLoaded property and LoadComplete events. The second is knowing when the host is finished adding your component to the design surface. The setting of the Site Property is part of design transaction. When this transaction is completed, the component's designer is accessible. For this, you use the host's TransactionClosed Event.
With that stated, the entry point is to override the inherited Site Property so that you can gain access to the service provider. This example gains references to the designer host, its selection service, and the controls default designer. The default designer allows you to add a DesignerVerb to its Verb collection.
Imports System.ComponentModel
Imports System.ComponentModel.Design
Public Class DemoControl : Inherits Control
Public Sub New()
MyBase.New()
BackColor = Color.Red ' just so we can see it
End Sub
#Region "Designer Services"
Private designerHost As Design.IDesignerHost
Private myDesigner As Design.IDesigner
Private designerSelectionService As Design.ISelectionService
Private Shared customDesignerVerb1 As Design.DesignerVerb
Public Overrides Property Site As ISite
Get
Return MyBase.Site
End Get
Set(value As ISite)
MyBase.Site = value
If value Is Nothing Then ' being removed from the design surface
DetachDesignerServices()
Else ' being added to the design surface
designerHost = CType(value.GetService(GetType(Design.IDesignerHost)), Design.IDesignerHost)
If designerHost IsNot Nothing Then
If designerHost.Loading Then
' the designer has not finished loading,
' postpone all other connections until it has finished loading
AddHandler designerHost.LoadComplete, AddressOf DesignerHostLoaded
Else
' designerHost loaded, but is in the in the process of creating this instance
If designerHost.InTransaction Then
AddHandler designerHost.TransactionClosed, AddressOf DesignerTransactionClosed
Else
AttachDesignerServices()
End If
End If
End If
End If
End Set
End Property
Private Sub DesignerHostLoaded(sender As Object, e As EventArgs)
RemoveHandler designerHost.LoadComplete, AddressOf DesignerHostLoaded
AttachDesignerServices()
End Sub
Private Sub DesignerTransactionClosed(sender As Object, e As DesignerTransactionCloseEventArgs)
RemoveHandler designerHost.TransactionClosed, AddressOf DesignerTransactionClosed
AttachDesignerServices()
End Sub
Private Sub AttachDesignerServices()
myDesigner = designerHost.GetDesigner(Me)
If customDesignerVerb1 Is Nothing Then
customDesignerVerb1 = New Design.DesignerVerb("Verb1", AddressOf DesignerVerb1EventHandler)
End If
If myDesigner IsNot Nothing AndAlso
Not myDesigner.Verbs.Contains(customDesignerVerb1) Then
myDesigner.Verbs.Add(customDesignerVerb1)
End If
designerSelectionService = CType(designerHost.GetService(GetType(Design.ISelectionService)), Design.ISelectionService)
If designerSelectionService IsNot Nothing Then
AddHandler designerSelectionService.SelectionChanged, AddressOf DesignerSelectionChanged
End If
End Sub
Private Sub DetachDesignerServices()
If designerSelectionService IsNot Nothing Then
RemoveHandler designerSelectionService.SelectionChanged, AddressOf DesignerSelectionChanged
designerSelectionService = Nothing
End If
If designerHost IsNot Nothing Then
RemoveHandler designerHost.LoadComplete, AddressOf DesignerHostLoaded
designerHost = Nothing
End If
If myDesigner IsNot Nothing Then
myDesigner = Nothing
End If
End Sub
Private Sub DesignerSelectionChanged(sender As Object, e As EventArgs)
Static shownCount As Int32
If designerSelectionService.GetComponentSelected(Me) AndAlso shownCount < 2 Then
MessageBox.Show("I've been selected." & If(shownCount = 0, " This will show one more time on selecting.", ""))
shownCount += 1
End If
End Sub
Private Sub DesignerVerb1EventHandler(sender As Object, e As EventArgs)
MessageBox.Show("Verb1 Cicked")
End Sub
#End Region ' "Designer Services
End Class
Related
I have a weird problem that I can't wrap my head around.
I have the following code:
Public Class Form1
Public WithEvents MyClass1 As New MyClass
Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
End Sub
Private Sub MyClass_UpdateListbox() Handles MyClass1.UpdateListbox
For Each sItem as String In MyClass1.Listbox
MsgBox(sItem) 'an MsgBox shows correct items each time.
Next sItem
Me.Listbox = Me.MyClass1.Listbox 'doesn't work and breaks listbox.
Me.Listbox.Items.Clear() 'listbox is empty anyway, but has no effect.
Me.Listbox.Items.Add("event triggered") 'does nothing.
End Sub
End Class
Public Class MyClass
Public Listbox as new Listbox
Public Event UpdateListbox()
Public Sub New()
'Constructor. sub.
Me.AddItem("Populating listbox")
End Sub
Public Sub AddItem(sItem as String)
Me.Listbox.Items.Add(sItem)
RaiseEvent UpdateListbox()
End Sub
End Class
If I comment the following lines in above code, the listbox keeps adding event triggered, as expected. Of course, I don't have to remove the clear one. It will work, but then it just adds the same item. If I use a command button and call MyClass.AddItem("Something") that is correctly added too as long as the below is commented out. But if not, then once the listbox is in broken state, nothing can be added anymore.
Me.Listbox = Me.MyClass1.Listbox 'doesn't work and breaks listbox.
Me.Listbox.Items.Clear() 'listbox is empty anyway, but has no effect.
How can I use a virtual listbox and assign it to my real listbox?
Also, instead of assigning one listbox to the other, I can of course use that for each loop and add each item one by one which works, but that for each look was for debugging purpose in the first place.
EDIT:
My goal with this application is to build a Todo list with features that are not in a todolist. This is a project I build for work because there I need a tool like this. I already have a todolist that I use but I built it wrong in the past. Everything was condensed in form1, no modules no extra classes. As a result I got weird bugs that I patched with workarounds. I am now rebuilding the application from the ground up, separating tasks in its own classes so I can apply business logic and have a true OOP application. The todo list will become its own class, and managing the list etc will be handeled by this class. It interacts with controls on the form, such as buttons and listboxes. If I just use form1.listbox from the class, things break at program start. I started another question and the below code was a now deleted answer. At first I did not get it working because I did not realize the listbox crashes if I assign it the virtual instance.
So my goal is to have the todolist be handled entirely by the todolist class. It does need a way to interact with controls on form1, and that is the puzzle I'm currently trying to solve.
In the original code, the main problem is that the Field that hold the instance of a Control shown if a Form is reassigned to the instance of another ListBox Control defined in a custom class:
Me.Listbox = Me.MyClass1.Listbox
From now on, Me.Listbox points another ListBox that is not show on screen, so any attempt to update the Form's child ListBox fails, except when Me.Listbox.Items.Clear() is called - in the same procedure - after it's being reassigned, because the handle of the Owner of the ObjectCollection (the object that holds the Items shown in the ListBox) has not been updated yet. It's going to fail after the current method exits nonetheless.
As noted in comments, this is a simplified method to handle a Form and its child Controls using a handler class. The contract between the class handler and a Form is sealed by an Interface (named IFormHandler here).
A Form that implements this Interface exposes the methods defined by the Interface that allow to trigger Actions and specific behaviors, depending on the Type of Control and the implementation.
I suggest to take a look at the MVP or ReactiveUI (MVVM-derived) for WinForms Patterns.
How too proceed:
Open up the ApplicationEvents class object.
If you don't have it already, select Project -> Properties -> Application and click the View Application Events button. It will generate ApplicationEvents.vb. Find it in Solution Explorer and open it up.
It should look like this (plus a bunch of comments that explain what it's for):
Imports Microsoft.VisualBasic.ApplicationServices
Namespace My
Partial Friend Class MyApplication
End Class
End Namespace
Paste into MyApplication these lines of code:
Imports Microsoft.VisualBasic.ApplicationServices
Namespace My
Partial Friend Class MyApplication
Public SomeFormHandler As MyFormHandler(Of SomeForm)
Protected Overrides Function OnStartup(e As StartupEventArgs) As Boolean
SomeFormHandler = New MyFormHandler(Of SomeForm)
Return MyBase.OnStartup(e)
End Function
End Class
End Namespace
Add an Interface that defines the Actions (or Behaviors) that a Form must implement.
Here, the GetUsersList() method specifies that a Form that implements this Interface must return the instance of a child ListBox Control.
(To add an Interface, select Project -> Add -> New Item... and select the Interface template. Name the file IFormHandler)
Extend this Interface as needed, to add more Methods or Properties that define actions and behaviors.
Public Interface IFormHandler
Function GetUsersList() As ListBox
End Interface
A Form that implements the IFormHandler Interface implements and exposes the GetUsersList() method, which returns the instance of a ListBox Control (named usersList here)
There's nothing else to do with this Form, the control is handed over to the MyFormHandler object that is initialized with this Type.
Public Class SomeForm
Implements IFormHandler
Public Sub New()
InitializeComponent()
End Sub
Public Function GetUsersList() As ListBox Implements IFormHandler.GetUsersList
Return Me.usersList
End Function
End Class
Now, to show SomeForm, you can use the MyFormHandler class object show below.
' Set the Owner if called from another Form
My.Application.SomeFormHandler.Show(Me)
' Or without an Owner
My.Application.SomeFormHandler.Show()
To close SomeForm, you can either use its handler:
My.Application.SomeFormHandler.Close()
or close it as usual:
[SomeForm Instance].Close()
If MyFormHandler determines that the instance of SomeForm has been disposed, it creates a new one when you call its Show() method again later.
To update the ListBox Control of SomeForm, use the public methods exposed by the MyFormHandler class:
' Add a new element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.AddElement, "Some Item")
' Remove an element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.RemoveElement, "Some Item")
' Replace an element
My.Application.SomeFormHandler.UpdateUsersList(UpdateType.ReplaceElement, "New Item", "Some Item")
' Clears the ListBox
My.Application.SomeFormHandler.ClearUsersList()
All these actions generate an event that you can subscribe to when needed.
See also the example that shows how to raise a custom event when the ListBox raises one of its stardard events; SelectedIndexChanged is handled here.
See the implementation of MyFormHandler.
Generic Form handler:
A Form needs to implement the IFormHandler Interface for the MyFormHandler class to accept it as valid.
You can of course extend the Interface, to add more Actions, or build a MyFormHandler class object that uses a different Interface, or more than one.
Public Class MyFormHandler(Of TForm As {Form, IFormHandler, New})
Implements IDisposable
Private formObject As TForm
Private IsInstanceSelfClosing As Boolean = False
Public Event UsersListUpdate(item As Object, changeType As UpdateType)
Public Event UsersListIndexChanged(index As Integer)
Public Sub New()
InitializeInstance()
Dim lstBox = formObject.GetUsersList()
AddHandler lstBox.SelectedIndexChanged, AddressOf OnUsersListIndexChanged
End Sub
Private Sub InitializeInstance()
formObject = New TForm()
AddHandler formObject.FormClosing, AddressOf OnFormClosing
End Sub
Private Sub OnFormClosing(sender As Object, e As FormClosingEventArgs)
IsInstanceSelfClosing = True
Dispose()
End Sub
Public Sub UpdateUsersList(updateMode As UpdateType, newItem As Object, Optional oldItem As Object = Nothing)
If newItem Is Nothing Then Throw New ArgumentException("New Item is null")
Dim lstBox = formObject.GetUsersList()
Select Case updateMode
Case UpdateType.AddElement
lstBox.Items.Add(newItem)
Case UpdateType.RemoveElement
lstBox.Items.Remove(newItem)
Case UpdateType.ReplaceElement
If oldItem Is Nothing Then Throw New ArgumentException("Replacement Item is null")
Dim index = lstBox.Items.IndexOf(oldItem)
lstBox.Items.Remove(oldItem)
lstBox.Items.Insert(index, newItem)
Case Else : Return
End Select
RaiseEvent UsersListUpdate(newItem, updateMode)
End Sub
Public Sub ClearUsersList()
formObject.GetUsersList().Items.Clear()
End Sub
Private Sub OnUsersListIndexChanged(sender As Object, e As EventArgs)
RaiseEvent UsersListIndexChanged(DirectCast(sender, ListBox).SelectedIndex)
End Sub
Public Sub Show(Optional owner As IWin32Window = Nothing)
If formObject Is Nothing OrElse formObject.IsDisposed Then InitializeInstance()
If formObject.Visible Then
formObject.WindowState = FormWindowState.Normal
formObject.BringToFront()
Else
formObject.Show(owner)
End If
End Sub
Public Sub Close()
If formObject IsNot Nothing AndAlso (Not formObject.IsDisposed) Then
RemoveHandler formObject.FormClosing, AddressOf OnFormClosing
IsInstanceSelfClosing = False
Dispose()
End If
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
Protected Overridable Sub Dispose(disposing As Boolean)
If disposing Then
If formObject Is Nothing OrElse formObject.IsDisposed Then Return
Dim lstBox = formObject.GetUsersList()
RemoveHandler lstBox.SelectedIndexChanged, AddressOf OnUsersListIndexChanged
RemoveHandler formObject.FormClosing, AddressOf OnFormClosing
If Not IsInstanceSelfClosing Then formObject.Close()
IsInstanceSelfClosing = False
End If
End Sub
End Class
Enumerator used in MyFormHandler:
Public Enum UpdateType
AddElement
RemoveElement
ReplaceElement
End Enum
Using a Class I am trying to hide the DoubleBuffered property from form's property window but without make it nonfunctional. So I did something like this in code example below... Ηowever, DoubleBuffered property still appears. So, can we really hide DoubleBuffered property and if yes, how can we do that?
Imports System.ComponentModel
Imports System.ComponentModel.Design
Public Class MyForm
Inherits Form
<Browsable(False)>
Public Overloads Property DoubleBuffered As Boolean
Get
Return MyBase.DoubleBuffered
End Get
Set(ByVal value As Boolean)
MyBase.DoubleBuffered = value
End Set
End Property
Public Sub New()
Me.DoubleBuffered = True
End Sub
End Class
You could create a custom component designer for your Form, but that is a daunting task to just recreate the functionality of the inaccessible System.Windows.Forms.Design.FormDocumentDesigner. The simpler way is use the Form's Site property as I have shown you before to access the designer services.
In this case, you need to override the ITypeDescriptorFilterService service of the designer host. This service is used by the designer for all type discovery/filtering operations and is not limited to a specific component.
The first step is to create a class that implements ITypeDescriptorFilterService. The following is one such implementation. It is a generic implementation that allows it to filter components of the specified type and takes list of property names that you want to exclude from the PropertyGrid display. The final item it requires is a reference to the existing service used by the designer host.
Friend Class FilterService(Of T) : Implements ITypeDescriptorFilterService
Private namesOfPropertiesToRemove As String()
Public Sub New(baseService As ITypeDescriptorFilterService, ParamArray NamesOfPropertiesToRemove As String())
Me.BaseService = baseService
Me.namesOfPropertiesToRemove = NamesOfPropertiesToRemove
End Sub
Public ReadOnly Property BaseService As ITypeDescriptorFilterService
Public Function FilterAttributes(component As IComponent, attributes As IDictionary) As Boolean Implements ITypeDescriptorFilterService.FilterAttributes
Return BaseService.FilterAttributes(component, attributes)
End Function
Public Function FilterEvents(component As IComponent, events As IDictionary) As Boolean Implements ITypeDescriptorFilterService.FilterEvents
Return BaseService.FilterEvents(component, events)
End Function
Public Function FilterProperties(component As IComponent, properties As IDictionary) As Boolean Implements ITypeDescriptorFilterService.FilterProperties
' ref: ITypeDescriptorFilterService Interface: https://msdn.microsoft.com/en-us/library/system.componentmodel.design.itypedescriptorfilterservice(v=vs.110).aspx
'
' The return value of FilterProperties determines if this set of properties is fixed.
' If this method returns true, the TypeDescriptor for this component can cache the
' results. This cache is maintained until either the component is garbage collected or the Refresh method of the type descriptor is called.
' allow other filters 1st chance to modify the properties collection
Dim ret As Boolean = BaseService.FilterProperties(component, properties)
' only remove properties if component is of type T
If TypeOf component Is T AndAlso Not (properties.IsFixedSize Or properties.IsReadOnly) Then
For Each propName As String In namesOfPropertiesToRemove
' If the IDictionary object does not contain an element with the specified key,
' the IDictionary remains unchanged. No exception is thrown.
properties.Remove(propName)
Next
End If
Return ret
End Function
End Class
Example Usage in Form:
Imports System.ComponentModel
Imports System.ComponentModel.Design
Public Class TestForm : Inherits Form
Private host As IDesignerHost
Private altTypeDescriptorProvider As FilterService(Of TestForm)
' spelling and character casing of removedPropertyNames is critical
' it is a case-sensative lookup
Private Shared removedPropertyNames As String() = {"DoubleBuffered"}
Public Overrides Property Site As ISite
Get
Return MyBase.Site
End Get
Set(value As ISite)
If host IsNot Nothing Then
UnwireDesignerCode()
End If
MyBase.Site = value
If value IsNot Nothing Then
host = CType(Site.GetService(GetType(IDesignerHost)), IDesignerHost)
If host IsNot Nothing Then
If host.Loading Then
AddHandler host.LoadComplete, AddressOf HostLoaded
Else
WireUpDesignerCode()
End If
End If
End If
End Set
End Property
Private Sub HostLoaded(sender As Object, e As EventArgs)
RemoveHandler host.LoadComplete, AddressOf HostLoaded
WireUpDesignerCode()
End Sub
Private Sub WireUpDesignerCode()
AddFilter()
End Sub
Private Sub UnwireDesignerCode()
If host IsNot Nothing Then
RemoveFilter()
End If
host = Nothing
End Sub
Private Sub AddFilter()
Dim baseFilter As ITypeDescriptorFilterService = CType(host.GetService(GetType(ITypeDescriptorFilterService)), ITypeDescriptorFilterService)
If baseFilter IsNot Nothing Then
' remove existing filter service
host.RemoveService(GetType(ITypeDescriptorFilterService))
' create our replacement service and add it to the host's services
altTypeDescriptorProvider = New FilterService(Of TestForm)(baseFilter, removedPropertyNames)
host.AddService(GetType(ITypeDescriptorFilterService), altTypeDescriptorProvider)
TypeDescriptor.Refresh(Me.GetType) ' force a type description rescan
End If
End Sub
Private Sub RemoveFilter()
If altTypeDescriptorProvider IsNot Nothing Then
host.RemoveService(GetType(ITypeDescriptorFilterService))
host.AddService(GetType(ITypeDescriptorFilterService), altTypeDescriptorProvider.BaseService)
altTypeDescriptorProvider = Nothing
End If
End Sub
End Class
Now when you create a form that inherits from TestForm, the DoubleBuffered property will be excluded from the PropertyGrid display.
I'm currently trying to implement the second response from this thread How can I handle ComboBox selected index changing? in vb (the response that suggests subclassing ComboBox to introduce new SelectedIndexChangingEvent). The event handler
Private Sub MyComboBox1_SelectedIndexChanging(sender As Object, e As System.ComponentModel.CancelEventArgs) Handles MyComboBox1.SelectedIndexChanging
MsgBox("Changing")
End Sub
never gets hit. I'm thinking it has something to do with the way I'm initializing the selectedIndexChanging (lowercase first letter) variable. Any thoughts?
Imports System.ComponentModel
Public Class MyComboBox
Inherits ComboBox
Public Event SelectedIndexChanging as CancelEventHandler
Public LastAcceptedSelectedIndex As Integer
Public Sub New()
LastAcceptedSelectedIndex = -1
End Sub
Protected Sub OnSelectedIndexChanging(e As CancelEventArgs)
Dim selectedIndexChanging as CancelEventHandler = SelectedIndexChanging
If Not SelectedIndexChanging Is Nothing Then
selectedIndexChanging(Me, e)
End If
End Sub
Protected Overrides Sub OnSelectedIndexChanged(e As EventArgs)
If LastAcceptedSelectedIndex <> SelectedIndex Then
Dim cancelEventArgs = New CancelEventArgs
OnSelectedIndexChanging(cancelEventArgs)
If Not cancelEventArgs.Cancel Then
LastAcceptedSelectedIndex = SelectedIndex
MyBase.OnSelectedIndexChanged(e)
Else
SelectedIndex = LastAcceptedSelectedIndex
End If
End If
End Sub
End Class
VB handles event declaration a bit different than C#. The VB RaiseEvent keyword effectively generates code you attempted to translate for the `OnSelectedIndexChanging' method.
The correct VB implementation would be:
Protected Sub OnSelectedIndexChanging(e As CancelEventArgs)
RaiseEvent SelectedIndexChanging(Me, e)
End Sub
You could follow the original pattern, by using the hidden variable VB creates that is the real CancelEventHandler variable. These hidden variables follow the naming pattern of eventNameEvent. So the real CancelEventHandler variable is named: SelectedIndexChangingEvent.
Protected Sub OnSelectedIndexChanging(e As CancelEventArgs)
Dim selectedIndexChanging As CancelEventHandler = Me.SelectedIndexChangingEvent
If Not selectedIndexChanging Is Nothing Then
selectedIndexChanging(Me, e)
End If
End Sub
I am building a custom designer that will associate a control with a business property on the form. The form DealUI has properties Instrument and Product, which are a business items:
Public Class DealUI
Inherits System.Windows.Forms.Form ' repetition of Inherits in Deal.Designed.vb, just to make the point
Sub New()
InitializeComponent()
End Sub
<Business(True)> _
Public Property Product As String
<Business(True)> _
Public Property Instrument As String
End Class
The Business attribute is simply
NotInheritable Class BusinessAttribute
Inherits Attribute
Private _isBusiness As Boolean
Sub New(isBusiness As Boolean)
_isBusiness = isBusiness
End Sub
End Class
The form contains a custom control, ProductTextBox of type PilotTextBox:
<DesignerAttribute(GetType(PilotControlDesigner)), _
ToolboxItem(GetType(PilotToolboxItem))> _
Public Class PilotTextBox
Inherits TextBox
Public Property Source As String
End Class
In the designer, when the selected control changes to ProductTextbox, I want to populate its Source property with the names of the Form's properties that have the BusinessAttribute (Instrument and Product), the user can then choose between Instrument and Product. The designer code is
Public Class PilotControlDesigner
Inherits ControlDesigner
Private Sub InitializeServices()
Me.selectionService = GetService(GetType(ISelectionService))
If (Me.selectionService IsNot Nothing) Then
AddHandler Me.selectionService.SelectionChanged, AddressOf selectionService_SelectionChanged
End If
End Sub
Private Sub selectionService_SelectionChanged(ByVal sender As Object, ByVal e As EventArgs)
If Me.selectionService IsNot Nothing Then
If Me.selectionService.PrimarySelection Is Me.Control Then
Dim form As Object = DesigningForm()
If form IsNot Nothing Then
For Each prop As PropertyInfo In form.GetType.GetProperties
Dim attr As Attribute = GetCustomAttribute(prop.ReflectedType, GetType(BusinessAttribute), False)
If attr IsNot Nothing Then
' we've found a Business attribute
End If
Next
End If
End If
End If
End Sub
Private Function DesigningForm() As Object ' in fact, a form, or more precisely something that inherits from Form
Dim host As IDesignerHost = CType(Me.Component.Site.GetService(GetType(IDesignerHost)), IDesignerHost)
Dim container As IContainer = host.Container
For Each comp As Component In container.Components
If comp.GetType.IsAssignableFrom(GetType(Form)) Then ' or anything that inherits 'Form'
return comp ' returns a Form, not a Deal!!
End If
Next comp
Return nothing
End Function
End Class
The selected control is a Deal (which inherits from Form), but the component in the designer is a Form, not a Deal (!! in the comment). I need to examine the Instrument and Product properties, which only exist on a Deal.
How can I obtain the Deal object in the designer?
Public Event DocumentCompleted As WebBrowserDocumentCompletedEventHandler
Dim arg() As Object = {homeTeam, guestTeam}
AddHandler browser.DocumentCompleted, New
WebBrowserDocumentCompletedEventHandler(AddressOf DoStuff)
Private Sub DoStuff(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)
End Sub
How can I pass the homeTeam and guestTeam when firing the DocumentCompleted event.
I want to ge the above to values to inside the Dostuff method.
Please help.
First of all, you cannot have this hanging in the middle of nowhere:
Dim arg() As Object = {homeTeam, guestTeam}
AddHandler browser.DocumentCompleted,
New WebBrowserDocumentCompletedEventHandler(AddressOf DoStuff)
AddHandler probably needs to be in some Initialize method, which could be inside Sub New, after InitializeComponent, or inside Form_Load, or as soon as you expect it to be triggered (after a specific event). Notice here that you are using a default event of a native .NET component, with a default event type. In this case you cannot directly consume anything other than what it already provides, when triggered. See WebBrowser.DocumentCompleted Event on MSDN.
You can, however, override all relevant classes and have your own MyWebBrowser control and your own event, with would contain additional properties. See below example:
Public Class Form1
Sub New()
' This call is required by the designer.
InitializeComponent()
Dim browser As New MyWebBrowser
AddHandler browser.MyDocumentCompleted, AddressOf DoStuff
End Sub
Private Sub DoStuff(ByVal sender As Object, ByVal e As MyWebBrowserDocumentCompletedArgs)
Dim guestTeam As String = e.GuestTeam 'guest team
Dim homeTeam As String = e.HomeTeam 'and home team are both accessible
'so you can do some processing on them
End Sub
Public Class MyWebBrowserDocumentCompletedArgs : Inherits WebBrowserDocumentCompletedEventArgs
Dim _homeTeam As String
Dim _guestTeam As String
Public ReadOnly Property HomeTeam
Get
Return _homeTeam
End Get
End Property
Public ReadOnly Property GuestTeam
Get
Return _guestTeam
End Get
End Property
Sub New(url As Uri, homeTeam As String, guestTeam As String)
MyBase.New(url)
_homeTeam = homeTeam
_guestTeam = guestTeam
End Sub
End Class
Public Class MyWebBrowser : Inherits WebBrowser
Public Delegate Sub MyWebBrowserDocumentCompletedEventHandler(e As MyWebBrowserDocumentCompletedArgs)
Public Event MyDocumentCompleted As MyWebBrowserDocumentCompletedEventHandler
Protected Overrides Sub OnDocumentCompleted(e As System.Windows.Forms.WebBrowserDocumentCompletedEventArgs)
MyBase.OnDocumentCompleted(e)
'homeTeam and guestTeam need to be extracted from the current instance of MyWebBrowser, and passed further
RaiseEvent MyDocumentCompleted(New MyWebBrowserDocumentCompletedArgs(e.Url, "homeTeam", "guestTeam"))
End Sub
End Class
End Class
If your project is relatively small, you can indeed have those as global variables, as #Vlad suggested in the comments.