79279953

Date: 2024-12-14 02:28:41
Score: 1
Natty:
Report link

Finally, I found a solution to the issue of handling events from a C# COM object in VBA using the WithEvents keyword. Below is the code and explanation that helped me resolve the problem:

C# Code :

In the C# code, I implemented a COM object that raises an event (OnTaskCompleted) when a task is completed. The key part is the use of the [ComSourceInterfaces] attribute, which allows the COM object to expose the event to VBA.

TaskRunner.cs

using System;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace ComEventTest
{
    [Guid("cc6eeac0-fe23-4ce4-8edb-676a11c57c7c")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface ITaskRunner
    {
        [DispId(1)]
        void RunTask(string input);
    }

    [Guid("619a141c-5574-4bfe-a663-2e5590e538e2")]
    [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface ITaskRunnerEvents
    {
        [DispId(1)]
        void OnTaskCompleted(string result);
    }

    [ComVisible(true)]
    [Guid("9acdd19f-b688-48c0-88d9-b81b7697d6d4")]
    [ClassInterface(ClassInterfaceType.None)]
    [ComSourceInterfaces(typeof(ITaskRunnerEvents))]
    public class TaskRunner : ITaskRunner
    {
        [ComVisible(true)]
        public delegate void TaskCompletedEventHandler(string result);

        [DispId(1)]
        public event TaskCompletedEventHandler OnTaskCompleted;

        private ConcurrentQueue<string> taskQueue = new ConcurrentQueue<string>();
        private bool isProcessingQueue = false;

        public void RunTask(string input)
        {
            taskQueue.Enqueue(input);
            ProcessQueue();
        }

        private async void ProcessQueue()
        {
            if (isProcessingQueue)
                return;

            isProcessingQueue = true;

            while (taskQueue.TryDequeue(out string input))
            {
                try
                {
                    await Task.Delay(5000); // Simulate work
                    OnTaskCompleted?.Invoke($"Task completed with input: {input}");
                }
                catch (Exception ex)
                {
                    OnTaskCompleted?.Invoke($"Task failed: {ex.Message}");
                }
            }

            isProcessingQueue = false;
        }
    }
}

VBA Code :

In the VBA code, I used the WithEvents keyword to handle the OnTaskCompleted event. This allows VBA to listen for and process events raised by the C# COM object. I also created an event handler class (TaskRunnerEventHandler) to handle the event and process the results.

Class Module: TaskRunnerEventHandler

Option Compare Database
Option Explicit

Public WithEvents taskRunner As ComEventTest.taskRunner

Private Sub taskRunner_OnTaskCompleted(ByVal result As String)
    'MsgBox result
    Debug.Print result
End Sub

Public Sub InitializeTaskRunner()
    Set taskRunner = New ComEventTest.taskRunner
End Sub

Public Sub FireEvent(poraka As String)
    taskRunner.RunTask poraka
End Sub

Usage module

Option Compare Database
Option Explicit

Dim eventHandlers As Collection

Sub InitializeEventHandlers()
    Set eventHandlers = New Collection
End Sub

Sub TestTaskRunner(Optional retr As String)

    If eventHandlers Is Nothing Then
        InitializeEventHandlers
    End If

    Dim newEventHandler As TaskRunnerEventHandler
    Set newEventHandler = New TaskRunnerEventHandler

    newEventHandler.InitializeTaskRunner

    eventHandlers.Add newEventHandler

    Dim i As Integer
    For i = 1 To 10
        newEventHandler.FireEvent "Task " & retr & "-" & i
        Sleep 100 ' Simulate delay for async task running
        Debug.Print "Task " & retr & "-" & i & " is running asynchronously!"
    Next i
    
End Sub

Sub TestTaskRunner_MultCalls()
    ' Fire multiple calls to TestTaskRunner
    Dim i As Integer
    For i = 1 To 10
        Debug.Print "New CALL SUB fire " & i
        TestTaskRunner CStr(i)
        Sleep 500 ' Simulate delay between multiple calls
    Next i
End Sub

Explanation:

The C# COM object exposes an event (OnTaskCompleted) that is triggered after completing a task asynchronously.

In VBA, I used the WithEvents keyword to declare the COM object and catch the (OnTaskCompleted) event. This allows me to process the result of each task in the taskRunner_OnTaskCompleted method. I also simulated multiple task submissions in the VBA code using Sleep to delay the execution and give time for the events to be raised and handled.

This solution worked, and now I can handle asynchronous events from the C# COM object seamlessly in VBA.

Any ideas to improve the above solution are welcome!

Reasons:
  • Blacklisted phrase (1): Any ideas
  • Long answer (-1):
  • Has code block (-0.5):
  • Self-answer (0.5):
  • Low reputation (1):
Posted by: Adrijano Toys Shop