VB.NET Handle event from one class in another without them knowing eachother...? - vb.net

I have an application.
Module1 - Main application
DataAccessMananger - Class in main application to handle data
Configuration - Class in a different project (common dll) that handles configuration settings.
The problem / Question. How can the Configuration class handle a data changed event in the DataAccessMananger without it knowing what a DataAccessManager is since they are in different classes?
The only way I can think of making it work is to have Module 1 handle the event from the DataAccessMananger and have it call a method in the Configuration class, however I dont like this, I would rather Configuration be able to handle its own data updates...
Clear as mud? Any ideas? VB.NET 4.5, and I know a bit about delegates, but not sure how I could use them here, they must be the answer some how...
Ideally, I would like to be able to pass an "Event" to the config class from the DAM class using the module...

The best way I can think of would be to add an interface in the configuration class (common.dll) that would be implemented by the DataAccessMananger. I assume the mainmodule is aware of both the DataAccessMananger and the Configuration, right ? If so, the following might be a solution.
Add an interface to common.dll for the Configuration class to use (not implement) that contains the event to be managed. For instance:
Public Interface IConfiguration
Event ConfigChanged(sender As Object, e As configPropertyChanged)
End Interface
In my case, I also create a class inheriting Event args.
Public class configPropertyChanged
Inherits EventArgs
Public Property PropertyName() As string
Public Property NewValue() As String
Public Property OldValue() As String
Public sub New(Newvalue as string,OldValue as string,<CallerMemberName()> optional PropertyName as string = "")
Me.NewValue = Newvalue
Me.OldValue =OldValue
Me.PropertyName = PropertyName
End sub
End Class
The configuration class is then modified to be able to monitor any class (which means that in the main module, the configuration must be made aware of the DataAccessManager class (Notice Idisposable is implemented to cleanup).
Public Class Configuration
Implements IDisposable
Private _ConfigList As New List(Of IConfiguration)
Public Sub RegisterConfig(Config As IConfiguration)
_ConfigList.Add(Config)
AddHandler Config.ConfigChanged, AddressOf ConfigChanged
End Sub
Public Sub ConfigChanged(sender As Object, e As configPropertyChanged)
Console.WriteLine("Config has changed.")
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean ' To detect redundant calls
Public Sub Dispose() Implements IDisposable.Dispose
For Each config As IConfiguration In _ConfigList
RemoveHandler config.ConfigChanged, AddressOf ConfigChanged
Next
_ConfigList.clear()
End Sub
#End Region
End Class
DataAccessManager does implement the Iconfiguration interface (available from common.dll)
Public Class DataAccessMananger
Implements IConfiguration
Public Event ConfigChanged(sender As Object, e As configPropertyChanged) Implements IConfiguration.ConfigChanged
Private _Name As String
Public Property Name() As String
Get
Return _Name
End Get
Set(value As String)
If String.Compare(_Name, value, True) <> 0 Then
RaiseEvent ConfigChanged(Me, New configPropertyChanged(Value,_Name))
_Name = value
End If
End Set
End Property
End Class
Finally, the main module, which is the only one to be aware of the existence of both Configuration and DataAccessManager, register the DataAccessManager into the configuration.
Public Sub Main()
Dim MyManager As New DataAccessMananger
Dim MyConfig As New Configuration
MyConfig.RegisterConfig(MyManager)
MyManager.Name = "New name"
End Sub
In this scenario.
The main module load the configuration and the data access manager at some point and then, register the data access manager into the configuration object. It can also register any other class implementing the Iconfiguration process.
At some point, something triggers a raise event into the data access manager (In my example, changing the property name do exactly that). The data access manager raise the event, which the configuration object handles it since we registered the data class into the configuration object.
If you wanted, you could have skipped the interface entirely and just had the DataAccessManager raise an event to the main module, then in the main module event handler, call a public method from the configuration class.

Related

C# to VB6 COM events (“object or class does not support the set of events”), but different

I am aware of a 10 year old question with the same title as this one but I have double checked and I am not mistakenly using the delegate name. This is a different issue.
Here at work we have an old VB6 application I need to teach new(er) tricks. The first thing I had to do was have it call methods from a .Net COM-visible DLL written in C#. I have that working. Now I need to have it handle incoming progress notification events from that same DLL. I asked a similar question yesterday wherein the VB6 IDE was not even seeing that the DLL had events to offer. That issue was solved by decorating the C# interfaces and classes correctly.
First, the C# codez:
namespace NewTricksDLL
{
[ComVisible(true)]
[Guid("16fb3de9-3ffd-4efa-ab9b-0f4117259c75")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ITransfer
{
[DispId(2)]
string SendAnEvent();
}
[ComVisible(true)]
[Guid("16fb3de9-3ffd-4efa-ab9b-0f4117259c74")]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IManagedEventsToCOM
{
[DispId(2)]
void NotificationEvent();
}
[ComVisible(true)]
[Guid("dcf177ab-24a7-4145-b7cf-fa06e892ef21")]
[ComSourceInterfaces(typeof(IManagedEventsToCOM))]
[ProgId("ADUTransferCS.NewTricks")]
public class NewTricks : ITransfer
{
public delegate void NotificationEventHandler();
public event NotificationEventHandler NotifificationEvent;
public string SendAnEvent()
{
if (NotifificationEvent != null)
NotifificationEvent();
}
}
}
An now my attempt to use it in VB6. Please note that the _tricky_NotificationEvent event handler was generated by the IDE by picking _tricky from the left-hand dropdown and NotificationEvent from the right-hand dropdown so I know this event is visible to the VB6 IDE.
Option Explicit
Public WithEvents _tricky As NewTricksDLL.NewTricks
Private Sub Command1_Click()
' The next line fails with 'Object or class does not support the set of events'
Set _tricky = CreateObject("NewTricksDLL.NewTricks")
' Execution never makes to the next line
_tricky.SendAnEvent()
End Sub
Private Sub _tricky_NotificationEvent()
' This handler was auto generated by the IDE
End Sub
Hopefully this can help - here's a minimal bare bones VB.net implementation of your requirements: COM interface consumable by VB6, one event, one method.
Option Explicit On
Option Strict On
<ComClass(newTricks.ClassId, newTricks.InterfaceId, newTricks.EventsId)>
Public Class newTricks
#Region "COM GUIDs"
' These GUIDs provide the COM identity for this class
' and its COM interfaces. If you change them, existing
' clients will no longer be able to access the class.
Public Const ClassId As String = "386d540d-f8b8-46e1-939d-7b69dd5eff0a"
Public Const InterfaceId As String = "78b4036e-86a0-4671-997d-da5a33bf026f"
Public Const EventsId As String = "7b0fa5b5-b45e-4db2-9282-c06e09852161"
#End Region
' A creatable COM class must have a Public Sub New()
' with no parameters, otherwise, the class will not be
' registered in the COM registry and cannot be created
' via CreateObject.
Public Sub New()
MyBase.New()
End Sub
Public Event NotificationEvent()
Public Sub SendAnEvent()
RaiseEvent NotificationEvent()
End Sub
End Class
This was created by starting a new project (Windows class library), delete from the project the Class1.vb that's automatically created, add a COM Class item renaming it to NewTricks. Then added the event declaration and the sub declaration and code; also added the Option statements. Built the project.
This was successfully used from this VB6 code. Clicking the button resulted in "Event fired" being written to the immediate window. This was successful with using both New and CreateObject methods of creating the reference to newTricks.
Option Explicit
Private WithEvents oTricks As NewTricksDll.newTricks
Private Sub Command1_Click()
Set oTricks = New NewTricksDll.newTricks
'Set oTricks = CreateObject("NewTricksDll.newTricks")
oTricks.SendAnEvent
End Sub
Private Sub oTricks_NotificationEvent()
Debug.Print "Event fired"
End Sub
Here is the corresponding C# code for the VB.net code, as converted straight-up by https://converter.telerik.com/
using Microsoft.VisualBasic;
[ComClass(newTricks.ClassId, newTricks.InterfaceId, newTricks.EventsId)]
public class newTricks
{
// These GUIDs provide the COM identity for this class
// and its COM interfaces. If you change them, existing
// clients will no longer be able to access the class.
public const string ClassId = "386d540d-f8b8-46e1-939d-7b69dd5eff0a";
public const string InterfaceId = "78b4036e-86a0-4671-997d-da5a33bf026f";
public const string EventsId = "7b0fa5b5-b45e-4db2-9282-c06e09852161";
// A creatable COM class must have a Public Sub New()
// with no parameters, otherwise, the class will not be
// registered in the COM registry and cannot be created
// via CreateObject.
public newTricks() : base()
{
}
public event NotificationEventEventHandler NotificationEvent;
public delegate void NotificationEventEventHandler();
public void SendAnEvent()
{
NotificationEvent?.Invoke();
}
}
I have not tried this C# code.
If you use CreateObject, it creates an object of type Object I believe. You should create the object simply by using New:
Set _tricky = New NewTricksDLL.NewTricks
Since you declared the variable as WithEvents, you can't just declare it using As New NewTricksDLL.NewTricks which I imagine you probably tried.

How To Access A Shared Property Of A Class Passed As A Type Parameter

I'm trying to access a shared property of a class passed as a parameter to a type-parametrised procedure. The reason why I'm doing this is so I can embed the various API call endpoints (among other class-specific things) as properties within the class itself. I've read some similar SO posts but nothing is close enough to be sure that it isn’t possible (which I think is likely).
Below is the essence of the structure - there's some pseudo code towards the end:
MustInherit Class BaseClass
Shared Property Endpoint As String
End Class
Class Person
Inherits BaseClass
Property Age As Integer
Property Name As String
Sub New()
_Endpoint = "/GetPerson"
End Sub
End Class
Class Event
Inherits BaseClass
Property When As Date
Property Type As String
Sub New()
_Endpoint = "/GetEvent"
End Sub
End Class
Function Retrieve(T As BaseClass)(Id As String) As T
Dim oResp As HttpResponse = MakeGetCall(T.Endpoint, Id) <- T.Endpoint throws a compile error
Return Deserialize(Of T)(oResp.Content)
End Function
Dim oPerson As Person = Retrieve(Of Person)("123")
Dim oEvent As Event = Retrieve(Of Event)("123")
To my tiny mind, I would have thought that, since T’s base class is BaseClass which contains the property Endpoint, I’d be ok. But seemingly not.
I've tried a fair few things from here on SO and other places to overcome this to no avail. Yes, I realize I could perform some kind of endpoint look-up based on the type of T but the above represents a very clean solution and I’d like to get it to work if possible.
Any ideas?
Assuming you want EndPoint to be different for each subclass, you should use MustOverride instead of Shared...
MustInherit Class BaseClass
Public MustOverride Property EndPoint As String
End Class
Then return a constant in each subclass
Class Person
Inherits BaseClass
Public Overrides Property EndPoint As String
Get
Return "/Person"
End Get
You might want to declare EndPoint as ReadOnly too.
The small limitation is that you'll need an instance of the class to access EndPoint (since it isn't Shared). If you have a parameterless constructor, you could use (New Person).EndPoint where needed.

Implement an interface in partial class

I need all my TableAdapters to implement a custom interface. The problem is that some of the members defined by the interface reside in the DataSet's designer file, which I don't want to (and shouldn't) alter, as that code will be regenerated automatically. I can't relocate those members to my code file either for the same reason. What's my way out of it?
When you implement an interface, the members you declare do not have to have the same names as the members of the interface and they don't have to be public. Let's say that you have this designer-generated class:
Partial Public Class SomeClass
Public Sub FirstMethod()
Console.WriteLine("FirstMethod")
End Sub
Public Sub SecondMethod()
Console.WriteLine("SecondMethod")
End Sub
End Class
and you want it to implement this interface:
Public Interface ISomeInterface
Sub FirstMethod()
Sub ThirdMethod()
End Interface
Notice that the interface has a method named FirstMethod but SomeClass already has a method named FirstMethod. You can add your own partial class to implement the interface like this:
Partial Public Class SomeClass
Implements ISomeInterface
Private Sub FirstMethodInternal() Implements ISomeInterface.FirstMethod
Me.FirstMethod()
End Sub
Public Sub ThirdMethod() Implements ISomeInterface.ThirdMethod
Console.WriteLine("ThirdMethod")
End Sub
End Class
The method that implements ISomeInterface.FirstMethod is not named FirstMethod so it doesn't clash with the existing method with that name and it is also Private so it cannot be accessed from outside using a reference of type SomeClass. Using a reference of type ISomeInterface is another matter though. If you use code like this:
Dim sc As ISomeInterface = New SomeClass
sc.FirstMethod()
sc.ThirdMethod()
you'll find that the FirstMethodInternal method of your SomeClass object gets invoked and, in turn, invokes the FirstMethod method of the same object. Try running that code and placing breakpoints on the FirstMethod and FirstMethodInternal methods to prove it to yourself.

Override variable from abstract class

I need to define a base class that contains the common implementation of logging in my application and create two specific classes LogA and LogB that will use the methods from the base class with a specific variable determining the logger to use.
Now I have the following code:
Public MustInherit Class BaseLog
Private Shared _Log As ILog 'This must be overridden in specific classes
Shared Sub WriteDebug (value As String)
End Sub
End Class
Public Class LogA
Inherits BaseLog
Private Shared _Log As ILog = LogManager.GetLogger ("aaaa")
End Class
How can I do this?
PS: I don't know how to format code on stackexchange mobile app.
You cannot override a field or a Shared member of your base class. Only a method or property that you declared Overridable can be overridden. You can a declare a method MustInherit to force a derived class to provide an implementation, you'd consider a GetLogger() function.
In a case like this, were you absolutely want to be sure that the client code hands you a valid logger, the more obvious solution is to add a constructor to your base class:
Public MustInherit Class BaseLog
Public Sub New(logger As ILog)
If logger is Nothing Then
Throw New ArgumentNullException("You must provide a logger")
End If
_Log = logger
End Sub
'' etc..
End Class
The client code is now forced to provide you with a valid ILog implementation, the only way they can create an instance of the derived class.
Without knowing your entire spec and what you're trying to achieve; I wonder if the singleton pattern get done what you need to get done.
Instead of LogB.WriteDebug("Oh noes!") you would have to do LogB.GetInstance().WriteDebug("Oh noes!"). Its not quite as nice, but when you're talking about the base class accessing a member set by a child class it might be the only way to go. This assumes you could use different logs in the same instance of your application.
If you just want to inject different loggers at runtime/startup for the duration (i.e. if you're running in production, development or unit testing), then you may want to look at dependency injection (such as Ninject), or some other form of IOC (inversion of control).
Clearly, this is a more complex problem than it first appears to be! However, it may be that we're stuck looking at it from the wrong angle.
You could have an instance property which is implemented in the derived class and returns the shared log variable:
Public MustInherit Class BaseLog
Public MustOverride ReadOnly Property _Log As ILog
Shared Sub WriteDebug(value As String)
End Sub
End Class
Public Class LogA
Inherits BaseLog
Private Shared log As ILog = LogManager.GetLogger("aaaa")
Public Overrides ReadOnly Property _Log As ILog
Get
Return log
End Get
End Property
End Class

In VB, How do you force an inherited class to use an attribute on the class?

I'm trying to force an inherited class to use a custom attribute. I'm creating a class library where the user who wants to create an item will do so, but be forced to add an attribute (or visual studio will automatically add the default attribute) to their inherited class. Here is what I'm hoping to achieve:
BaseClass.vb:
<CustomAttribute(10)> _
Public Class BaseClass
End Class
MyClass.vb:
<CustomAttribute(12)> _
Public Class MyClass
Inherits BaseClass
Public Sub New()
Mybase.New()
End Sub
End Class
So the thought is that much like when you mark a function as "MustOverride" and then the inherited class must override the function, I want the attribute to be "MustOverride" causing the inherited class to specify the value.
I've tried this, and it will work, but it would be much cleaner if I could use attributes:
BaseClass.vb:
Public MustInherit Class BaseClass
Public MustOverride ReadOnly Property CustomAttribute() As String
End Class
MyClass.vb:
Public Class MyClass
Inherits BaseClass
Public Sub New()
Mybase.New()
End Sub
Public Overrides ReadOnly Property CustomAttribute() As String
Get
Return "testing"
End Get
End Property
End Class
Thank you for any help you can provide.
Scott
Did you consider implementing an interface instead? I assume that you're using a base class as you want to provide some code in the base, if not then an Interface might be better depending on your other requirements:
Interface IBase
ReadOnly Property CustomAttribute() As String
End Interface
It's still very compact and when you type 'Implements IBase' in a new class Visual Studio will fill in the code for you.
There's no way in .NET to force a class to define an attribute at compile time. The best you'll be able to do is check at run-time whether the attribute was defined, and if not to throw an exception.