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.
Related
I'm tying to create a generic solution for instantiating my forms using singleton behavior in vb.net. But it's not working anyway and always protecting me to compile:
Public Class SingletonGenerator(Of TForm)
Private _inst As Object
Public ReadOnly Property Instance As SingletonInstance(Of TForm)
Get
If _inst Is Nothing Then
_inst = New TForm()
End If
Return _inst
End Get
End Property
End Class
But this error restricts me to continue:
Error 9 'New' cannot be used on a type parameter that does not have a 'New' constraint.
And I'm not sure if I replace my code with New Form() it works as expected (because it create objects of parent form() class and may loose some initialization in child class.)
Can somebody please explain why this happen or how can I have singleton instances of objects in an OOP way which not require to copy/paste those common lines of code which are used in singleton on every new defined class?
You have to convince the compiler that the TForm type in fact has a parameterless constructor so that New TForm() can never fail. That requires a constraint.
Not the only thing you need to do, a Form object becomes unusable when it is closed. And you'll have to create another one to re-display it. Failure to do so causes an ObjectDisposedException at runtime. In other words, you should be interested in the Disposed event. That requires a further constraint, the TForm type parameter always needs to derive from Form. Required to convince the compiler that it is okay to use the event. Adding it up:
Public Class SingletonGenerator(Of TForm As {Form, New})
Private _inst As TForm
Public ReadOnly Property Instance As TForm
Get
If _inst Is Nothing Then
_inst = New TForm()
AddHandler _inst.Disposed, Sub() _inst = Nothing
End If
Return _inst
End Get
End Property
End Class
Do be a bit careful with this, you are painting yourself into a corner. You can only ever use this code to create form objects whose constructor takes no argument. In practice you may find they often need one.
Check this code:
Module Startup
Public Sub Main()
Dim f As Form = FormsManager.Instance.GetForm(Of Form1)()
f.ShowDialog()
Dim f1 As Form = FormsManager.Instance.GetForm(Of Form1)()
f1.ShowDialog()
End Sub
End Module
Public Class FormsManager
Private Shared _formsManager As FormsManager
Private _forms As List(Of Form)
Public Shared ReadOnly Property Instance As FormsManager
Get
If (_formsManager Is Nothing) Then
_formsManager = New FormsManager
End If
Return _formsManager
End Get
End Property
Private Sub New()
If _forms Is Nothing Then _forms = New List(Of Form)
End Sub
Public Function GetForm(Of T As {Form, New})() As Form
Dim f As Form = _forms.Where(Function(o) o.GetType = GetType(T)).SingleOrDefault
If f Is Nothing Then
f = New T
_forms.Add(f)
End If
Return f
End Function
End Class
This is what I finally produced (a generic singlton forms generator):
Imports System.Windows.Forms
Imports System.Runtime.CompilerServices
<HideModuleName()> _
Public Module SingletoneForms
<Extension> _
Public Function GetInstance(Of TForm As {Form, New})(ByRef obj As TForm) As TForm
Return SingletonForm(Of TForm).Instance
End Function
Public Class SingletonForm(Of TForm As {Form, New})
Private Shared WithEvents _inst As TForm
Public Shared Property Instance As TForm
Get
If _inst Is Nothing Then
SetInstance(New TForm())
End If
Return _inst
End Get
Set(value As TForm)
SetInstance(value)
End Set
End Property
Private Shared Sub SetInstance(ByVal newInst As TForm)
If _inst IsNot Nothing Then
RemoveHandler _inst.FormClosing, AddressOf FormClosing
End If
_inst = newInst
AddHandler _inst.FormClosing, AddressOf FormClosing
End Sub
Private Shared Sub FormClosing(sender As Object, e As FormClosingEventArgs)
If e.CloseReason = CloseReason.UserClosing Then
e.Cancel = True
_inst.Hide()
Else
_inst = Nothing
End If
End Sub
End Class
End Module
and call it simply this way:
frmMain.GetInstance().Show()
Form1.GetInstance().Show()
Form1.GetInstance().Hide()
Form2.GetInstance().ShowDialog()
I am trying to make a reusable control similar to an Outlook-style sidebar. I have a CustomPanel. I also have a CustomCollectionControl, that inherits from flow layout panel. At design time I would like to add (x) CustomPanels to my CustomCollectionControl, through the properties window.
When I try to add from the (Collection) list in the properties window, it will show up in the list, but it will not add it to the control that is on the form.
Here is my code so far.
Imports System.Collections
Imports System.ComponentModel
Imports System.Windows.Forms
Public Class CustomCollectionControl
Inherits FlowLayoutPanel
''' <summary>
''' Required designer variable.
''' </summary>
Private _mComponents As Container = Nothing
Private _mCustompanels As CustomPanelCollection
Public Sub New()
' This call is required by the Windows.Forms Form Designer.
InitializeComponent()
SetStyle(ControlStyles.DoubleBuffer, True)
SetStyle(ControlStyles.AllPaintingInWmPaint, True)
_mCustompanels = New CustomPanelCollection(Me)
Padding = New Padding(0)
End Sub
#Region "Component Designer generated code"
''' <summary>
''' Required method for Designer support - do not modify
''' the contents of this method with the code editor.
''' </summary>
Private Sub InitializeComponent()
_mComponents = New System.ComponentModel.Container()
End Sub
#End Region
<EditorBrowsable(EditorBrowsableState.Always)> _
<Browsable(True)> _
<DesignerSerializationVisibility(DesignerSerializationVisibility.Content)> _
<Bindable(True)> _
Public Property CustomPanels() As CustomPanelCollection
Get
Return _mCustompanels
End Get
Set(value As CustomPanelCollection)
_mCustompanels = value
End Set
End Property
Protected Overrides Sub OnResize(e As EventArgs)
MyBase.OnResize(e)
End Sub
End Class
Public Class CustomPanelCollection
Inherits CollectionBase
Private _mControl As CustomCollectionControl
Private _mCustomCollectionControl As CustomCollectionControl
Friend Sub New(control As CustomCollectionControl)
_mCustomCollectionControl = control
End Sub
Default Public ReadOnly Property Item(index As Integer) As CustomPanel
Get
Return DirectCast(List(index), CustomPanel)
End Get
End Property
Public Function Contains(cPanel As CustomPanel) As Boolean
Return List.Contains(cPanel)
End Function
Public Function Add(cPanel As CustomPanel) As Integer
Dim i As Integer
i = List.Add(cPanel)
cPanel.Control = _mCustomCollectionControl
Return i
End Function
Public Sub Remove(cPanel As CustomPanel)
List.Remove(cPanel)
cPanel.Control = Nothing
End Sub
End Class
Public Class CustomPanel
Inherits Panel
Friend Control As CustomCollectionControl
Public Sub New()
' TODO Set Stuff!
Height = 100
BorderStyle = BorderStyle.FixedSingle
Margin = New Padding(0)
Padding = New Padding(0)
Dim cBtn As New Button
cBtn.Height = 30
Controls.Add(cBtn)
cBtn.Dock = DockStyle.Top
End Sub
End Class
I need to find out when a CustomPanel is added through the properties window during design time, how to update the control with the changes?
The basic problem is that in order for the flow-layout logic to work on your panels, they need to be in the base control's ControlCollection. If/When you expose this thru the properties IDE the standard collection editor allows any control to be added to it.
Your CustomPanels() property on the other hand, allows only CustomPanel controls but they get stored in a different collection, so they do not show up on the form.
The SmartTag action to only add CustomPanel is a very viable workaround if it adds to the Controls collection. I am not sure how many of the standard Panel properties you want them to be able to edit, and since there is no way to specify the child button properties, there doesnt seem much difference between the collection editor and the SmartTag. I assume this is because it is a work in progress and/or removed to post a minimal example.
Another way is to get rid if the extra collection and use a custom collection editor which will restrict the type of control to what you want. This is shown below.
Notes:
I changed the generic names to make it easier to read. CustomCollectionControl is now FlowLayoutPanelEx and CustomPanel is FlowPanel.
Your Buttons arent hooked up to anything, nor are they exposed, so I am not sure how you plan to use them.
Since all that the FlowPanel does is store that one button, why not omit it and just add buttons of a certain size?
There are several other issues with the code(e.g. CustomPanel/FlowPanel should implement IDisposable since it is creating stuff). These and other issues are ignored in order to focus on implementing a minimal custom collection editor.
FlowLayoutPanelEx and FlowPanel:
' collection editor will need this:
Imports System.ComponentModel.Design
Public Class FlowLayoutPanelEx
Inherits FlowLayoutPanel
Public Sub New()
' This call is required by the Windows.Forms Form Designer.
' {PL} - no, it is not
'InitializeComponent()
SetStyle(ControlStyles.DoubleBuffer, True)
SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Padding = New Padding(0)
End Sub
<EditorBrowsable(EditorBrowsableState.Always),
Browsable(True),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Bindable(True),
Editor(GetType(FlowPanelCollectionEditor),
GetType(System.Drawing.Design.UITypeEditor))>
Public Overloads Property Controls() As ControlCollection
Get
Return MyBase.Controls
End Get
Set(value As ControlCollection)
End Set
End Property
End Class
Public Class FlowPanel
Inherits Panel
' ToDo: implememt IDisposable
Private myBtn As Button
' allow user to specify the text for the child button
Public Property ButtonText As String
Get
If myBtn IsNot Nothing Then
Return myBtn.Text
Else
Return String.Empty
End If
End Get
Set(value As String)
myBtn.Text = value
End Set
End Property
Public Sub New()
' TODO Set Stuff!
Height = 100
BorderStyle = BorderStyle.FixedSingle
Margin = New Padding(0)
Padding = New Padding(0)
Height = 40
myBtn = New Button
myBtn.Height = 30
Controls.Add(myBtn)
myBtn.Dock = DockStyle.Top
End Sub
End Class
The way you have it, the user can change any FlowPanel property in the Collection Editor including those you have explicitly set. I dont know enough about what you ultimately want to do to offer alternatives other than it seems like perhaps the Panel is cosmetic and maybe a Button alone would suffice.
Note the additional Editor attribute on the Controls property. This tells VS to use that collection editor:
Public Class FlowPanelCollectionEditor
Inherits CollectionEditor
Public Sub New(t As Type)
MyBase.New(t)
End Sub
' *** Magic happens here: ***
' override the base class to SPECIFY the Type allowed
' rather than letting it derive the Types from the collection type
' which would allow any control to be added
Protected Overrides Function CreateNewItemTypes() As Type()
Dim ValidTypes As Type() = {GetType(FlowPanel)}
Return ValidTypes
End Function
Public Overrides Function EditValue(context As ITypeDescriptorContext,
provider As IServiceProvider,
value As Object) As Object
Return MyBase.EditValue(context, provider, value)
End Function
End Class
Results:
The collection editor adds only FlowPanels:
As you can see, the new ButtonText property can be set from the collection editor. When the controls are added to the Controls collection for use on the form, ButtonText shows on the buttons:
Note that the user can still drag a TextBox or whatever to your FlowLayoutPanelEx and it will accept it. This is another of those "other issues" mentioned above.
An article on CodeProject, Enhanced CollectionEditor Framework provides a fairly comprehensive overview of collections and custom collection editors.
It includes a custom collection editor framework but it wont handle this situation as is. If you remove NotOverridable from the CreateNewItemTypes method and recompile, you should be able to inherit from EnhancedCollectionEditor and use some of the other features it provides.
It is not really needed; as the code above shows there is not much involved in restricting the Type allowed. The article might be of value though as you modify and refine FlowPanel and the button into their final form. (Disclaimer: I wrote the article).
I am adding this here because I cannot do it in the comments because there is too much text and images. Also, maybe someone coming here from a search engine will be able to get an idea of what to do.
This is what I wanted to achieve with the control:
Closed
Open
And here is the edited code to allow the (flat style) buttons to be clicked and open the parent panel. This is a very crude method of doing it, but I put it together to check if it worked before I tied up too much time in it:
' collection editor will need this:
Imports System.ComponentModel.Design
Imports System.Windows.Forms
Imports System.ComponentModel
Imports System.Drawing
Public Class FlowLayoutPanelEx
Inherits FlowLayoutPanel
Public Sub New()
SetStyle(ControlStyles.DoubleBuffer, True)
SetStyle(ControlStyles.AllPaintingInWmPaint, True)
Padding = New Padding(0)
BackColor = Color.FromKnownColor(KnownColor.ControlDark)
End Sub
<EditorBrowsable(EditorBrowsableState.Always),
Browsable(True),
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
Bindable(True),
Editor(GetType(FlowPanelCollectionEditor),
GetType(System.Drawing.Design.UITypeEditor))>
Public Overloads Property Controls() As ControlCollection
Get
Return MyBase.Controls
End Get
Set(value As ControlCollection)
End Set
End Property
End Class
Public Class HeaderButton
Inherits Button
Public Property BtnID As Integer
Public Property BtnColor As System.Drawing.Color
Public Event ButtonClicked(sender As HeaderButton, buttonID As Int32)
Private Sub clicked(sender As Object, e As EventArgs) Handles Me.Click
RaiseEvent ButtonClicked(Me, BtnID)
End Sub
End Class
Public Class FlowPanel
Inherits Panel
' ToDo: implememt IDisposable
Private myBtn As HeaderButton
' allow user to specify the text for the child button
Public Property ButtonText As String
Get
If myBtn IsNot Nothing Then
Return myBtn.Text
Else
Return String.Empty
End If
End Get
Set(value As String)
myBtn.Text = value
End Set
End Property
Public Sub New()
BorderStyle = BorderStyle.FixedSingle
Margin = New Padding(0)
Padding = New Padding(0)
Height = 32
BackColor = Color.FromKnownColor(KnownColor.Info)
myBtn = New HeaderButton
AddHandler myBtn.ButtonClicked, AddressOf Me.ItemButtonClicked
myBtn.Height = 30
myBtn.Margin = New Padding(0)
myBtn.Padding = New Padding(0)
myBtn.Dock = DockStyle.Top
myBtn.FlatStyle = FlatStyle.Flat
BackColor = Color.FromKnownColor(KnownColor.Control)
Controls.Add(myBtn)
End Sub
Public Sub ItemButtonClicked(ByVal btn As HeaderButton, ByVal buttonID As Int32)
If btn.Parent.Height = 32 Then
btn.Parent.Height = 200
Else : btn.Parent.Height = 32
End If
End Sub
End Class
Public Class FlowPanelCollectionEditor
Inherits CollectionEditor
Public Sub New(t As Type)
MyBase.New(t)
End Sub
' *** Magic happens here: ***
' override the base class to SPECIFY the Type allowed
' rather than letting it derive the Types from the collection type
' which would allow any control to be added
Protected Overrides Function CreateNewItemTypes() As Type()
Dim ValidTypes As Type() = {GetType(FlowPanel)}
Return ValidTypes
End Function
Public Overrides Function EditValue(context As ITypeDescriptorContext,
provider As IServiceProvider,
value As Object) As Object
Return MyBase.EditValue(context, provider, value)
End Function
End Class
There is so much more that I have to do, like displaying changes to the controls in the designer, implementing Idisposable, adding a collapsible button on the side, and passing the height value of the panel through the form so it will open the full height. I'm probably going to draw the buttons to get some effects that are not available with the standard button.
I have a class 'oBnd' defined as Object that can be assigned as type clsBound or clsClaim.
Claim and bound are identical from the outside, same methods etc.
I call the various properties using 'CallByName'
ie Dim Current As String = CallByName(oBnd, PropName, CallType.Get)
When a property is changed in either class the DirtyStatus event is raised by that class.
I am having a problem attaching to this event.
If I try
AddHandler oBnd.DirtyStatus, AddressOf oBnd_DirtyStatus
I get the error "DirtyStatus is not an event of Object" I guess that makes sense as clearly object know nothing of my dirtystatus.
I tried using:
AddHandler DirectCast(oBnd, clsBound).DirtyStatus, AddressOf oBnd_DirtyStatus
While this does fix the error it does not get called when the DirtyStatus event is raised.
oBnd is defined as
Private WithEvents oBnd As Object
It is global to the form
oBnd gets set as
oBnd = New clsBound(mvarBUDConnection)
AddHandler oBnd.DirtyStatus, AddressOf oBnd_DirtyStatus
oBnd.Load(CInt(txtTrans.Text))
BuildPage(oBnd)
Or
oBnd = New clsClaim(mvarBUDConnection)
AddHandler oBnd.DirtyStatus, AddressOf oBnd_DirtyStatus
oBnd.Load(CInt(txtTrans.Text))
BuildPage(oBnd)
The oBnd_DirtyStatus sub, that I am trying to attach to, looks like this
Private Sub oBnd_DirtyStatus(IsDirty As Boolean) ' Handles oBnd.DirtyStatus
Me.Text = "QFix"
If IsDirty Then
Me.Text = "QFix - Pending Save"
btnSave.Enabled = True
Else
btnSave.Enabled = False
End If
End Sub
How can I attach a handle to this event?
Here is how you can both get Events working and get away from using Reflection to access properties. Even given the public methods are similar but the data being carried is very different it should still possible to use OOP/Inheritance.
Public Enum ClaimBoundType
None ' error!!!!
Claim
Bound
End Enum
Public MustInherit Class ClaimBase
' type tracker usually rather handy
Public Property ItemType As ClaimBoundType
Public Sub New(t As ClaimBoundType)
ItemType = t
End Sub
' low rent INotifyPropertyChanged
Public Event DataChanged(sender As Object, e As EventArgs)
' "universal" prop: works the same for all derived types
Private _name As String = ""
Public Property Name As String
Get
Return _name
End Get
Set(value As String)
If value <> _name Then
_name = value
BaseDataChanged(Me)
End If
End Set
End Property
' props which must be implemented; 1 or 100 doesnt matter
MustOverride Property CurrentValue As Integer
' methods which must be implemented
MustOverride Function DoSomething() As Integer
' raise the changed event for base or derived classes
Protected Friend Sub BaseDataChanged(sender As Object)
RaiseEvent DataChanged(sender, New EventArgs())
End Sub
End Class
You'd have to do some basic data analysis to figure out which Properties and Methods can be implemented in the base class (as with Name above) and which in the inherited classes. There are usually at least some which can be done in the base class.
Your derived classes can implement the methods in totally different ways and load data from where ever:
Public Class Claim
Inherits ClaimBase ' the IDE will add all the MustInherits when
' you press enter
Public Sub New()
MyBase.New(ClaimBoundType.Claim)
End Sub
Public Overrides Function DoSomething() As Integer
' what happens here can be completely different
' class to class
End Function
Private _CurValue As Integer = 0
Public Overrides Property CurrentValue As Integer
Get
Return _CurValue
End Get
Set(Value As Integer)
If _CurValue <> Value Then
_CurValue = Value
OnDataChanged("CurrentValue")
End If
End Set
End Property
' name of prop that changed not actually used here, but
' is usually good to know (use custom args or INotifyPropertyChanged)
Public Sub OnDataChanged(pname As String)
' fire shared datachanged event
MyBase.BaseDataChanged(Me)
End Sub
End Class
How to Use Them
Now you can implement them without resorting to Object, subscribe to the event and not have to use Reflection to get/set properties:
' 'generic' object variable: DONT/CANT USE [New] w/ClaimBase
Private myCB As ClaimBase
...
' set it as a Claim instance...
' This is perfectly legal because Claim is also a ClaimBase Type:
myCB = New Claim
' hook up the event handler
AddHandler myCB.DataChanged, AddressOf cb_DataChanged
You can declare your object variables as ClaimBase, but you cannot create an instance of ClaimBase since it is abstract/MustInherit. Since the event is part of the base class, there is no problem with syntax. The form level handler:
' Use standard (sender, e) signature
' (CA will object to other signatures:)
Private Sub cb_DataChanged(sender As Object, e As EventArgs)
' do change stuff here
...
End Sub
Best of all, you can reference properties directly:
cbObj.Name = "Ziggy" ' will fire the event from the base class
cbObj.CurrentValue = 42 ' fires event from the Claim class
I added the ItemType property so you can tell them apart at run time (ie when you hold the mouse over a ClaimBase object variable). If/when there are Type specific properties/methods to access, cast it (from what you said, there cant be any of these now):
If cbObj.ItemType = ClaimBoundType.Claim Then
CType(cbObj, Claim).ClaimSomething = 5
End If
Also use ClaimBase as the declaration Type for Lists and method signatures also to allow either type to be passed rather than boxing them (converting to Object):
Private cbList As New List(Of ClaimBase)
...
' just an example of the declaration
Private Sub AddThingToList(cb As ClaimBase)
cbList.Add(cb)
End Sub
I did not go into INotifyProperty in order to focus on Inheritance, though the basics of it are in that base class. It is a more systemic way to implement the DataChanged/DirtyStatus event and detection.
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?
Conceptually, I am a little bit lost on Events themselves, so any information here would be helpful.
Specifically, I have a very simple Property Setter which raises an event, and I want to confirm
that this event is raised and also
that the proper parameters have been passes to said event. (although 2. may be unnecessary for this particular event.)
I am trying to contain the overhead in my Unit Testing project, that is, avoid any additional coding in the project being tested.
The code is as follows.
Public Class myItem
Shared Event ItemOpened As EventHandler(Of EventArgs)
.........
Public Property Open() As Boolean
Get
Return mOpen
End Get
Set(ByVal value As Boolean)
mOpen = value
RaiseEvent ItemOpened(Me, New EventArgs)
End Set
End Property
All code is being done in VB.net, which is primary reason why I have not found a good enough resource online for this yet. And also, I am not using a third-party mocking framework such as Nunit or RhinoMock, only MS built in Unit Testing frameworks in VS2012.
Also, similarly, I would like to test FirePropertyChangedNotification() on certain setter methods such as follows....
Public Property myBool() As Boolean
Set(ByVal Value As Boolean)
FirePropertyChangedNotification("myBool")
mViewOnly = Value
End Set
End Property
In which FirstPropertyChangedNotification() is as follows.....
Protected Sub FirePropertyChangedNotification(ByVal propName As String)
If Not Initializing Then
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
RaiseEvent EntityChanged(Me, New PropertyChangedEventArgs(propName))
End If
End Sub
I'm not sure why you want to use Fakes here... You can just use a delegate in you test to subscribe to your event. Now change the property from the test and set a boolean or even place the assert in the delegate.
This is what I'd do in C#, autoconverted to VB.NET (which in my case is very rusty...) This works on my machine. Any improvements from a VB.NET expert are welcome:
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports System.ComponentModel
Namespace UnitTestProject2
Public Class Sut
Implements INotifyPropertyChanged
Public Property StringProperty() As String
Get
Return String.Empty
End Get
Set(value As String)
OnPropertyChanged("StringProperty")
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Sub OnPropertyChanged(ByVal name As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(name))
End Sub
End Class
<TestClass> _
Public Class UnitTest1
<TestMethod> _
Public Sub TestMethod1()
Dim sut As New Sut()
Dim triggered = False
AddHandler sut.PropertyChanged, Sub(o, e)
Assert.AreEqual("StringProperty", e.PropertyName)
triggered = True
End Sub
sut.StringProperty = "test"
Assert.IsTrue(triggered)
End Sub
<TestMethod> _
Public Sub TestMethod2()
Dim sut As New Sut()
Dim triggered = False
AddHandler sut.PropertyChanged, Sub(o, e)
Assert.AreSame(sut, o)
triggered = True
End Sub
sut.StringProperty = "test"
Assert.IsTrue(triggered)
End Sub
End Class
End Namespace