COM events from C# COM components don't work in Excel - c#

I am currently testing COM components in .NET6. I am having issues with using COM events. I have created this repository on Github, where the source code of my test project can be found. The problem I am encountering is that the events defined in the 'ComObjectWithEvents' project don't work properly.
Whenever I trigger the event inside a function that is directly called by Excel, the event will be triggered and handled properly. In my case I have created a COM client in Excel which can also be found inside the Github repository. The client can trigger the event manually with a button click. If the event is triggered this way the message box that should be displayed by Excel when the event is invoked will show up.
Option Explicit
Dim WithEvents comObj As ComObject
Dim val_1 As Double
Dim val_2 As Double
Dim result As Double
Sub Start_ComObject()
Set comObj = New ComObject
MsgBox "Object started."
End Sub
Sub Do_Addition()
val_1 = Worksheets("Addition").Cells.Range("A2").Value
val_2 = Worksheets("Addition").Cells.Range("B2").Value
result = comObj.Addition(val_1, val_2)
Worksheets("Addition").Cells.Range("C2").Value = result
End Sub
Sub Trigger_Addition()
comObj.TriggerAdditionDone
End Sub
Sub comObj_OnAdditionDone()
MsgBox "Addition done."
End Sub
However, there is also timer inside the default constructor of the COM component which also invokes the event periodically every 10 seconds. In this case the event will still be triggered as intended, however Excel will not handle the event this time. The message box will not show up.
public ComplexComObject()
{
System.Timers.Timer timer = new System.Timers.Timer(10000);
timer.Elapsed += (Object source, ElapsedEventArgs args) =>
{
TriggerAdditionDone();
};
timer.AutoReset = true;
timer.Enabled = true;
}
[Guid(AssemblyInfo.OnAdditionDoneDelegateGuid)]
public delegate void OnAdditionDoneDelegate();
private event OnAdditionDoneDelegate OnAdditionDone;
public double Addition(double firstValue, double secondValue)
{
TriggerAdditionDone();
return firstValue + secondValue;
}
public void TriggerAdditionDone() => OnAdditionDone?.Invoke();
I don't know why COM events work one way but not the other way.
To test this issue the project from Github can be used. However, for personal testing the paths inside the project file of the Visual Studio project will have to be adjusted to fit one's system. This has to be done, due to the fact that a MIDL compiler and a CL preprocessor are required to create a Type Library. Also, Visual Studio has to be started as administrator since it will try to automatically register the COM component alongside the Type Library.
For testing, Visual Studio can attach itself to a process through the 'Debug' tab.

Related

.NET raise event on UI thread [duplicate]

This question already has answers here:
Run method on UI thread from another thread
(3 answers)
Closed 1 year ago.
I am using the SQLDependency class to listen for SQL notifications and the SQLDependency.OnChange event is executed on a different thread from the UI thread. My intention is to update the data in a grid view when this event is fired so SQL server database changes can instantly be reflected in the grid.
If I run my LoadData method to reload the data into the grid I get a cross-threading error:
I have created a custom class to hold all the SQLDependency related code, and there is a single instance of this class declared globally across my application. I have an event on the class that is raised inside the SQLDependency.OnChanged event. This event is then handled on various different forms so their data can be reloaded. However, because the OnChange event is raised on a different thread, I need to add logic to run first:
Delegate Sub ReloadCallback()
Private Sub LoadOnUI()
If Me.InvokeRequired Then
Dim d As ReloadCallback = New ReloadCallback(AddressOf LoadOnUI)
Me.Invoke(d)
Else
Invoke(New MethodInvoker(Sub() RefreshData()))
End If
End Sub
I would prefer to avoid having to duplicate this code and copy it to each for where my custom event is handled. This answer is pretty much exactly what I am looking for but I can't figure out how to implement the code provided.
Logic down the lines of, get the invocation list of the event and get the thread of one of the handlers, then raise the event on that thread. With my limited experience I am unable to write code to do this on my own. Any assistance would be appreciated.
Objective: Raise an event on the UI thread with no extra code repeated in the event handlers.
Solution:
Run this code on the target thread ahead of time (e.g. class constructor).
objSyncContext = SynchronizationContext.Current
Call this method RunOnUIThread(AddressOf RefreshData) from another thread to run the referenced method on the target thread.
Delegate Sub CallDelegate()
Private Sub RunOnUIThread(objEvent As CallDelegate)
If objSyncContext Is Nothing Then
objEvent()
Else
objSyncContext.Post(Sub() objEvent(), Nothing)
End If
End Sub
Here's a quick example using SynchronizationContext.
Note that it assumes you are creating the class from the main UI thread, and you store that current context from the constructor:
Public Class Form1
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim d As New Duck("Bob")
AddHandler d.Quack, AddressOf duck_Quack
Label1.Text = "Waiting for Quack..."
d.RaiseThreadedQuack(3)
End Sub
Private Sub duck_Quack(source As Duck)
Label1.Text = "Quack received from: " & source.Name
End Sub
End Class
Public Class Duck
Public ReadOnly Property Name() As String
Private sc As WindowsFormsSynchronizationContext
Public Sub New(ByVal name As String)
Me.Name = name
sc = WindowsFormsSynchronizationContext.Current
End Sub
Public Event Quack(ByVal source As Duck)
Public Sub RaiseThreadedQuack(ByVal delayInSeconds As Integer)
Dim T As New Threading.Thread(AddressOf ThreadedQuack)
T.Start(delayInSeconds)
End Sub
Private Sub ThreadedQuack(ByVal O As Object)
System.Threading.Thread.Sleep(TimeSpan.FromSeconds(O).TotalMilliseconds)
sc.Post(AddressOf RaiseUIQuack, Me)
End Sub
Private Sub RaiseUIQuack(ByVal O As Object)
RaiseEvent Quack(O)
End Sub
End Class

Xamarin - Call System.IDisposable.Disposeon object created before all references to it are out of scope

I am quite new to Xamarin and C# (however, I do know C++ and Java, so I have been doing fine with the latter so far). I ran into a warning when creating an IOnCheckedChangedListener for a checkbox that I am using in my (Android-)app.
So far, I have created a simple class OnCheckedChangedListener which inherits from Java.lang.Object and implements IOnCheckedChangeListener. Naturally, the method OnCheckedChanged() has also been implemeneted.
I have set the listener by using
OnCheckedChangedListener listener = new OnCheckedChangedListener();
checkBox.SetOnCheckedChangeListener(listener);
Everything works fine so far, but I get the warning "Call System.IDisposable.Dispose on object created by 'new OnCheckedChangedListener()' before all references to it are out of scope."
I can remove this warning by either calling listener.Dispose() after setting the listener or by wrapping the entire thing (thing as in the creating of the listener and setting it in the checkbox) into a using block. This gets rid of the warning, but results in a runtime-error whenever the listener would have been called (I assume this is because I have effectively deleted the listener but the program still tried to call it).
According to this thread, there is no need to actually call Dispose() but I would really like to get rid of the warning. Is there a good way to get rid of it (optimally without telling the compiler to simply ignore it)?
If you want to do something when user check or uncheck the checkbox, you could register for the RadioGroup's CheckedChange event like the following
checkBox.CheckedChange += OnCheckedChange;
private void OnCheckedChange(object sender, RadioGroup.CheckedChangeEventArgs e)
{
//include your code logic here
}
Or you could use lambda directly
checkBox.CheckedChange += (s, e) =>
{
//include your logic here
//s = sender (checkbox)
//e = RadioGroup.CheckedChangeEventArgs
};

Visual Studio Automated UI Test Hangs

Firstly I am using VS2013 Winforms .net 4.0.
After excluding all other possibilities (from my set of possibilities) the culprit appears to be Me.close() in one specific form. After the me.close() method executes the coded-ui-test application seems to freeze and does not give any feedback about the buttons i am pressing or text i am entering. When I ask it to generate the code it goes as long as 1 hour before I decide to give up and kill the process. When I try the same test without the me.close it works as expected. Does anyone have any idea how to fix this bug in the automated ui testing? If not can you suggest any alternatives please?
Edit: This does not happen when I simply press the forms 'X' button in the top right. This is very strange.
Edit2: I have tried this in a fresh project. It is indeed me.close that causes the coded ui test application to 'freeze' such that I cannot generate the automated code and I will end up stuck at the 'please wait' loading bar.
Edit3: It appears to be specific to calling me.close in an infragistics click handler of a ultrabutton. Wow, here is example.
Designer
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class closemepls
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.UltraButton1 = New Infragistics.Win.Misc.UltraButton()
Me.SuspendLayout()
'
'UltraButton1
'
Me.UltraButton1.Location = New System.Drawing.Point(45, 47)
Me.UltraButton1.Name = "UltraButton1"
Me.UltraButton1.Size = New System.Drawing.Size(232, 157)
Me.UltraButton1.TabIndex = 0
Me.UltraButton1.Text = "UltraButton1"
'
'closemepls
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(284, 262)
Me.Controls.Add(Me.UltraButton1)
Me.Name = "closemepls"
Me.Text = "closemepls"
Me.ResumeLayout(False)
End Sub
Friend WithEvents UltraButton1 As Infragistics.Win.Misc.UltraButton
End Class
Code
Public Class closemepls
Private Sub UltraButton1_Click(sender As Object, e As EventArgs) Handles UltraButton1.Click
Me.Close()
End Sub
End Class
If I call closemepls.showdialog() and click the button the coded ui test application freezes! Infragistics FTW.
This is the result, it does not complete even after 1 hour.
I had problems like that in WPF before.
Here's how I found it: Comment everything out then find the code that causes the problem by adding blocks back one by one.
It's something that is binded (anything though, not data and more than likely a control) not being released.
I haven't tried mixing CodedUI and VB much, but I was hoping this question may help you:
Me.Close does not work
Alone, that doesn't mean much, right? But in conjunction with how CodedUI works it may provide a clue. Remember that when you're running a test you're technically initiating a UITesting.Playback, which is a process. You may want to add something to your TestCleanup method that makes sure all the processes are terminated, like so (Keep in mind that this is for a browser test):
/// <summary>
/// Closes the test browser and ends test playback
/// </summary>
[TestCleanup]//The decorator is what makes this a method a test cleanup
public void CleanTest()
{
if (Playback.IsInitialized) //This is the important part.
{
Playback.Cleanup();
}
if (browserWindow.Exists)
{
browserWindow.Close();
}
}
This is just a shot in the dark and I may even be misunderstanding what you need, really but I'm assuming that in both your real and example areas you're closing the entire application? This may be a question for Infragistics at the end of the day. Good luck!
This might be the same issue I've experienced when using MTM, on a few machines if they were running any form of capture for the resulting test. They where trying to save it to a illegal path(Found in Event Viewer). After performing a repair on VS thus MTM to was repaired and it worked for some machines. Others only seamed to be fixed when going to update 4.
But code wise I would suggest trying to click the close button on the form itself to see if you get a different behavior.
Dim closeButton = New WinButton(YourWindow);
closeButton.SearchProperties(UITestControl.PropertyNames.Name) = "Close";
Mouse.Click(closeButton);

Working around InvokeRequired in a Windows Service

I'm trying to make a simple windows service.
All this service does is change the volume for one device when another device's volume is altered. It currently works as an application, and is only two Subs and a dll to function.
Basically an event handler is created on the Form Load:
AddHandler device.AudioEndpointVolume.OnVolumeNotification, AddressOf volume
Which calls the sub:
Private Sub volume(data As CoreAudioApi.AudioVolumeNotificationData)
If Me.InvokeRequired Then
Me.Invoke(New AudioEndpointVolumeNotificationDelegate(AddressOf volume), data)
Else
loopback.AudioEndpointVolume.MasterVolumeLevelScalar = data.MasterVolume
loopback.AudioEndpointVolume.Mute = data.Muted
End If
End Sub
Now in a windows service I can't use invoke, and when I remove it the error The function evaluation requires all threads to run is thrown.
What I believe is happening is that the control data and loopback (which are the controls of the audio Device accessed via the CoreAudioApi.dll) is not resolving without the invoke, but I don't know how to make them resolve properly.
Determine changes of master audio volume using NAudio.dll from: https://github.com/SjB/NAudio
Imports NAudio.CoreAudioApi
Public Class Form1
Private enumer As MMDeviceEnumerator = New MMDeviceEnumerator()
Private dev As MMDevice = enumer.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia)
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Control.CheckForIllegalCrossThreadCalls = False
AddHandler dev.AudioEndpointVolume.OnVolumeNotification, AddressOf AudioEndpointVolume_OnVolumeNotification
End Sub
Private Sub AudioEndpointVolume_OnVolumeNotification(ByVal data As AudioVolumeNotificationData)
' This shows data.MasterVolume, you can do whatever you want here
Me.Text = CInt(data.MasterVolume.ToString() * 100) & " Mute=" & data.Muted
End Sub
End Class
If I remove the if around the invoke statement so it just has the line:
Me.Invoke(New AudioEndpointVolumeNotificationDelegate(AddressOf volume), data)
followed by the other two lines it throws the error:
"'Invoke' is not a member of 'VolumeSet.VolumeSet'"
So I don't think invoke can be used inside a windows service at all.
But thanks for the idea!

Naudio,how to tell playback is completed

I am using the NAudio library to write a simple WinForms audio recorder/player. My problem is how can I tell that playback is completed? I need to close the wave stream after that.
I knew there is a PlaybackStopped event listed below:
wfr = new NAudio.Wave.WaveFileReader(this.outputFilename);
audioOutput = new DirectSoundOut();
WaveChannel32 wc = new NAudio.Wave.WaveChannel32(wfr);
audioOutput.Init(wc);
audioOutput.PlaybackStopped += new EventHandler<StoppedEventArgs>(audioOutput_PlaybackStopped);
audioOutput.Play();
But this PlaybackStopped event seems can only be triggered by calling audioOutput.stop(), does anyone know how to determine if playback is completed?
I create an open source project for this question, you can find it here: https://code.google.com/p/stack-overflow-questions/
The PlaybackStopped event is raised when you either manually stop playing, or the Read method of the IWaveProvider you are using returns 0. The issue here is that WaveChannel32 does not stop returning data when it's source stream ends, so playback never ends. The PadWithZeroes property should be set to false to fix this.
As #Mark Heath described in addition I want to add coding example of Naudio wich will play a mp3 file in Debug/Sound Folder folder and wait until it isn't finished.
Playback is completed can be checked by waveOut.PlaybackState == PlaybackState.Stopped
play_string = #"SOUND/space.mp3";
var reader = new Mp3FileReader(play_string);
var waveOut = new WaveOut(); // or WaveOutEvent()
waveOut.Init(reader);
waveOut.Play();
while (waveOut.PlaybackState != PlaybackState.Stopped) ; // Wait untill the playing isn't finished.
Here is my code in Visual Basic that works well for me to determine when the playback is done. It uses a loop to keep testing WaveOut.PlaybackState
In the loop, you will notice that a short thread sleep of 50 is used to stop the cpu running away. I picked 50 from experience, you may find another value works better for you through trial and error.
And finally, the loop allows windows.forms events so the user can click the Stop button, with the statement:
System.Windows.Forms.Application.DoEvents
In the code I have supplied below, you can see events being raised - for example:
RaiseEvent Enable_PlayButton
I have defined events to allow a parent class to enable the Play, Stop, and Record buttons:
Public Event Enable_PlayButton(Enabled As Boolean)
Public Event Enable_StopButton(Enabled As Boolean)
Public Event Enable_RecordButton(Enabled As Boolean)
Public Event Enable_SaveButton(Enabled As Boolean)
Public Event Enable_RevertButton(Enabled As Boolean)
Public Event RecordingChanged(NewRecording As Byte())
In a parent class, I use AddHandler to wire these up to method in the parent class. In the following example, I have methods such as in the first case, "EnablePlay". Likewise for the other events.
AddHandler mMicrophoneRecorder.Enable_PlayButton, AddressOf EnablePlay
AddHandler mMicrophoneRecorder.Enable_StopButton, AddressOf EnableStop
AddHandler mMicrophoneRecorder.Enable_RecordButton, AddressOf EnableRecord
AddHandler mMicrophoneRecorder.Enable_RevertButton, AddressOf EnableRevert
AddHandler mMicrophoneRecorder.RecordingChanged, AddressOf MicRecorder_RecordingChanged
In this method, there are 3 fields that are defined elsewhere:
mWaveOut_via_SoundCard - a class-level (field) of WaveOut.
mAudioFile - instantiated here but held at the class level (a field).
mWavRecordingPath - the path to the audio (wav) file being played.
These 3 fields could probably be passed in to this method as parameters if you prefer to inject them. I used fields, because that's just the way my code for this evolved.
Here then, is the code:
Public Sub A_PlayClicked()
If mWaveOut_via_SoundCard Is Nothing Then
mWaveOut_via_SoundCard = New WaveOutEvent()
End If
If mAudioFile Is Nothing AndAlso mWavRecordingPath <> "" Then
mAudioFile = New AudioFileReader(mWavRecordingPath)
mWaveOut_via_SoundCard.Init(mAudioFile)
End If
RaiseEvent Enable_StopButton(True)
mWaveOut_via_SoundCard.Play()
Dim PlayDone As Boolean = False
Dim PState As PlaybackState
Do
'if stopped or finished, mSoundCard will be nothing,
'so we test that by trying to get PlayBackState
' from the WaveOut object (mSoundCard)
If mWaveOut_via_SoundCard Is Nothing Then
PlayDone = True
Else
Try
PState = mWaveOut_via_SoundCard.PlaybackState
Catch ex As Exception
'mSoundCard is probably nothing - but
'no matter what the problem, for now we will say
'that PlayDone is true.
PlayDone = True
End Try
End If
'Okay we got the PlayState, so evaluate and
'decide whether to continue here:
If Not PlayDone Then
If PState <> PlaybackState.Stopped Then
'let the system do stuff (e.g. user might click Stop button)
System.Windows.Forms.Application.DoEvents()
'don't use all the cpu:
Threading.Thread.Sleep(50)
Else
'well it's stopped so we're done:
PlayDone = True
End If
End If
Loop Until PlayDone = True
'here we could raiseevent stopped as well
RaiseEvent Enable_PlayButton(mCanPlay)
RaiseEvent Enable_RecordButton(True)
End Sub

Categories