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.
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 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 change the background color of a button (cmdLogQry) from a Set-procedure of a public property (LogQry) - according to the new value of the property.
It works if the property is being changed in the code belongig to the form containing the button (in the Click method of the same or even another button). But it does not work if the property is being changed from another module (handler procedure for COM ports DataReceived event). No error message or anything - the LogQry gets its value changed all right, but the color of the button does not change.
What do I do wrong?
Public Class Handler
Private _logQry As Boolean = False
Public Property LogQry() As Boolean
Get
Return _logQry
End Get
Set(ByVal value As Boolean)
_logQry = value
If value Then
frmMain.cmdLogQry.BackColor = Color.Red
Else
frmMain.cmdLogQry.BackColor = Color.Blue
End If
End Set
End Property
Private Sub comPort_DataReceived(ByVal sender As Object, ByVal e As SerialDataReceivedEventArgs)
...
LogQry = Not LogQry ' does NOT change color
...
End Sub
End Class
Public Class frmMain
Private comm As New Handler()
...
Private Sub cmdLogQry_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdLogQry.Click
comm.LogQry = Not comm.LogQry ' does change color
End Sub
...
End Class
This problem is caused by the default instance of the form class created by the VB.NET implementation. More on default instances could be found here and in this answer from Hans Passant.
Essentially when you define a form class, VB.NET compiler creates a default instance of that class named with the same name of the class, but this creates a lot of misunderstanding in an object oriented environment like NET.
To fix your problem you need to implement a constructor in your Handler class that receives the actual instance of frmMain, store it inside a class variable and use that instance when you want to modify something on the actual displayed form
Public Class Handler
Private _logQry As Boolean = False
Private _mainInstance As frmMain
Public Sub New(mainInstance as frmMain)
_mainInstance = mainInstance
End Sub
Public Property LogQry() As Boolean
Get
Return _logQry
End Get
Set(ByVal value As Boolean)
_logQry = value
If value Then
_mainInstance.cmdLogQry.BackColor = Color.Red
Else
_mainInstance.cmdLogQry.BackColor = Color.Blue
End If
End Set
End Property
....
End Class
Now, when you create the Handler instance pass the reference to the current frmMain
Public Class frmMain
Private comm As Handler
...
Private Sub cmdLogQry_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles cmdLogQry.Click
comm = new Handler(Me)
comm.LogQry = Not comm.LogQry ' does change color
End Sub
...
End Class
Keep in mind that this solution creates also problems. It couples the class Handler to your frmMain and the two are now inseparable. Probably a better approach is to create an Event in the Handler class so, every form that wants to be notified could subscribe to the event and receieves the information when needed.
VB NET 2010, Framework 3.5
Trying to understand why this works. I create two objects from the the same Class1. GlobalClass1 with Global Scope and LocalClass1 with Module Scope.
In UControl's Load event I set LocalClass1 = GlobalClass1 From this point on anytime I change value of GlobalClass1.TestProperty the property value is also updated in LocalClass1. The Events in LocalClass1 are triggered when GlobalClass1 events fire.
This is the result I was looking for => being able to have a Global Object's events fire in several other Class Modules and User Controls.
I don't quite understand why simply setting the Local Object = Global Object causes the Local Object to automatically update it's property values when the Global Object properties are updated or why the Local Events automatically fire when the Global Object is raising the event?
Module Module1
Public WithEvents frm As New MainForm
Public WithEvents GlobalClass1 As New Class1
Public Sub Main()
frm.Init()
frm.ShowDialog()
End Sub
End Module
Public Class MainForm
Private uiUserControl As UControl
Public Function Init() As Boolean
uiUserControl = New UControl
uiUserControl.Location = New System.Drawing.Point(60, 80)
Me.Controls.Add(uiUserControl)
Return True
End Function
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Static count As Integer
GlobalClass1.TestProperty = count.ToString ' This line propagates to the Private Local LocalClass1 and cause it's event to fire??
count += 1
End Sub
End Class
Public Class UControl
Private WithEvents LocalClass1 As New Class1
Private Sub UControl_Load(sender As Object, e As System.EventArgs) Handles Me.Load
LocalClass1 = GlobalClass1
End Sub
Private Sub LocalClass1_TestPropertyChanged() Handles LocalClass1.TestEvent
Me.TextBox1.Text = LocalClass1.TestProperty 'This Event fires when events are raised in the other object => GlobalClass1??
End Sub
End Class
Public Class Class1
Public Event TestEvent()
Private _testProperty As String
Public Property TestProperty() As String
Get
Return _testProperty
End Get
Set(ByVal value As String)
_testProperty = value
RaiseEvent TestEvent()
End Set
End Property
End Class
The class instances (objects) are both referencing the same memory space allocated to them on the heap.
from comments:
The problem is with this line: LocalClass1 = GlobalClass1
They started out as different objects but then you made them refer to the same thing. Object references - or reference types - work differently than value types like integer:
Dim x As Int32
Dim y As Int32 = 42
x = y
As value types, the value of y is assigned to x. A reference type is essentially a wrapper or alias for a pointer. Your assignment code therefore replaced the original pointer to a New Class1, with the pointer already assigned to GlobalClass1
Read more about Value vs Reference types at MSDN