Is it possible to listen to a parent class' object's event via the property accessor?
What I've tried (a minimal example):
Public Class ParentFoo
Private WithEvents m_bar As EventyObj
Public Property Bar() As EventyObj
Get
Return m_bar
End Get
Set(ByVal value As EventyObj)
m_bar = value
End Set
End Property
End Class
Public Class ChildFoo
Inherits ParentFoo
[...]
Public Sub Bar_OnShout() Handles Bar.Shout
' Some logic
End Sub
End Class
The specific error message I'm getting (VS2005) is "Handles clause requires a WithEvents variable defined in the containing type or one of its base types." Does accessing a private WithEvents variable via a public property strip away the 'WithEvents'?
In ParentFoo:
Public Overridable Sub OnShout() Handles m_bar.Shout
'No Logic Necessary
End Sub
In ChildFoo:
Public Overrides OnShout()
'Logic Here
End Sub
Since ParentFoo will call OnShout when m_bar raises a Shout Event and you override it in ChildFoo, your ChildFoo's OnShout will handle that event.
Related
I'm using the base class Button in VB.net (VS2017) to create a new class called CDeviceButton. The CDeviceButton then forms as a base for other classes such as CMotorButton, CValveButton.
I want to set the Tag property in the child class CMotorButton but access it in the constructor in CDeviceButton. Doesn't work for me. It turns up being empty.
The Tag is set in the standard property when inserting the CMotorButtom instance into a form.
I've also tried to ensure teh the parent classes' constructors are run by setting mybase.New() as the first action in each constructor but that didn't change anything.
Any ideas for improvements?
Public Class CDeviceButton
Inherits Button
Public MMIControl As String = "MMIC"
Public Sub New()
MMIControl = "MMIC" & Tag
End Sub
End class
Public Class CMotorButton
Inherits CDeviceButton
Sub New()
'Do Something
end Sub
End Class
When you try to concatenate Tag with a string, you are trying to add an object that is probably nothing. I set the Tag property first and used .ToString and it seems to work.
Public Class MyButton
Inherits Button
Public Property MyCustomTag As String
Public Sub New()
'Using an existing Property of Button
Tag = "My Message"
'Using a property you have added to the class
MyCustomTag = "Message from MyCustomTag property : " & Tag.ToString
End Sub
End Class
Public Class MyInheritedButton
Inherits MyButton
Public Sub New()
If CStr(Tag) = "My Message" Then
Debug.Print("Accessed Tag property from MyInheritedButton")
Debug.Print(MyCustomTag)
End If
End Sub
End Class
And then in the Form
Private Sub Test()
Dim aButton As New MyInheritedButton
MessageBox.Show(aButton.Tag.ToString)
MessageBox.Show(aButton.MyCustomTag)
End Sub
Below is my solution I came up with that works. Basically I make sure that all initialization has taken place before reading the Tag property. What I experienced is that the Tag property is empty until the New() in CMotorButton has completed, even though the Tag property has been set when creating the instance of CMotorButton in the Form. TimerInitate has a Tick Time of 500 ms.
Not the most professional solution but works for what I need at the moment.
Another option could be multi threading but that I haven't tried and leave that for future tryouts.
Public Class CDeviceButton
Inherits Button
Public MMIControl As String = "MMIC"
Public Sub New()
TimerInitiate = New Timer(Me)
End Sub
Private Sub TimerInitiate_Tick(sender As Object, e As EventArgs) Handles TimerInitiate.Tick
If Tag <> Nothing Then
TimerInitiate.Stop()
MMIControl = "MMIC" & Tag
End If
End Sub
End class
Public Class CMotorButton
Inherits CDeviceButton
Sub New()
'Do Some stuff
TimerInitiate.Start()
End Sub
Private Sub CMotorButton_Click(sender As Object, e As EventArgs) Handles Me.Click
End Class
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 have a singleton class, but I want its object to be able to raise events.
My current singleton code is as follows:
Private Shared ReadOnly _instance As New Lazy(Of WorkerAgent)(Function() New _
WorkerAgent(), LazyThreadSafetyMode.ExecutionAndPublication)
Private Sub New()
End Sub
Public Shared ReadOnly Property Instance() As WorkerAgent
Get
Return _instance.Value
End Get
End Property
Whenever I change ReadOnly _instance As New.. into ReadOnly WithEvents _instance As New...
I get an error saying ReadOnly is not valid on a WithEvents deceleration
Although I can create the instance in the property itself, but I liked the above code because it is using .NET Lazy keyword which probably have great multithreading benefits.
This isn't an answer to your question as asked but it demonstrates why that question doesn't make sense. It also requires a fair chunk of code so posting in a comment wasn't really an option. This is how your singleton class would raise events, i.e. just like any other class, and how a consumer would handle those events, i.e. just like for any other type.
Singleton:
Public Class WorkerAgent
Private Shared ReadOnly _instance As New Lazy(Of WorkerAgent)
Private _text As String
Public Shared ReadOnly Property Instance As WorkerAgent
Get
Return _instance.Value
End Get
End Property
Public Property Text As String
Get
Return _text
End Get
Set
If _text <> Value Then
_text = Value
OnTextChanged(EventArgs.Empty)
End If
End Set
End Property
Public Event TextChanged As EventHandler
Private Sub New()
End Sub
Protected Overridable Sub OnTextChanged(e As EventArgs)
RaiseEvent TextChanged(Me, e)
End Sub
End Class
Note that the instance event is raised when the instance property changes, just as for any other type, singleton or not.
Consumer:
Public Class Form1
Private WithEvents agent As WorkerAgent = WorkerAgent.Instance
Private Sub agent_TextChanged(sender As Object, e As EventArgs) Handles agent.TextChanged
'...
End Sub
End Class
The field that the single instance is assigned to is where WithEvents gets used. As your error message states, that field cannot be declared ReadOnly too. If they want a ReadOnly field then they need to use AddHandler to handle events.
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'm trying to update my UI when a property in my BL class changes. Please can someone advise the best way to do this in vb.net
Not a really precise question so I will explain the standard way (in my opinion).
Implement the INotifyPropertyChanged interface in your class and handle the PropertyChanged event of your object.
First the the class of the object that contains the property in question:
Public Class MySweetClass
Implements System.ComponentModel.INotifyPropertyChanged
Private _MyProperty As String
Public Property MyProperty As String
Get
Return _MyProperty
End Get
Set(value As String)
_MyProperty = value
RaiseEvent PropertyChanged(Me, New System.ComponentModel.PropertyChangedEventArgs("MyProperty"))
End Set
End Property
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
End Class
Notice that the PropertyChanged event is raised once the value of the property changes.
In your form handle this event:
Public Class Form1
Private WithEvents MySweetObject As MySweetClass
Private Sub MySweetObject_PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Handles MySweetObject.PropertyChanged
'Update gui here
End Sub
End Class
This lets you update the GUI whenever the value changes.