Working around InvokeRequired in a Windows Service - c#

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!

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

Running a form on a separate thread waiting for main UI thread to complete

In .NET 4.0 I'm dealing with an app which has a long loading time (about 20 seconds), so I wanted to display a swish scrolling marquee on a form that comes on top of the application whilst it is loading.
Since the main UI thread is doing all the loading of data UI elements, I couldn't get that to execute on a separate thread, so I ended up trying to run the form on a new thread. I ended up sticking this code in the form itself, to have it show itself on a new thread, like this:
Public Class frmWait
Public Property Message As String
Get
Return Me.lblMessage.Text
End Get
Set(value As String)
If Not String.IsNullOrEmpty(value) Then
Me.lblMessage.Text = value
Else
Me.lblMessage.Text = DefaultMessage
End If
End Set
End Property
Private OwnerThread As Thread
Private OwnerForm As Form
Private Const DefaultMessage As String = "しばらくお待ちください..."
Public Sub New(ByVal ParentForm As Form)
InitializeComponent()
Me.Message = DefaultMessage
Me.OwnerForm = ParentForm
End Sub
Public Sub New(ByVal Message As String, ByVal ParentForm As Form)
Call InitializeComponent()
Me.Message = Message
Me.OwnerForm = ParentForm
End Sub
Public Sub ShowOnThread()
' Position the form in the center of the owner
With Me.OwnerForm
Dim ownerCenter As New Point(.Location.X + CInt(.Width / 2), .Location.Y + CInt(.Height / 2))
Me.Location = New Point(ownerCenter.X - CInt(Me.Width / 2), ownerCenter.Y - CInt(Me.Height / 2))
End With
Me.OwnerThread = New Thread(New ThreadStart(AddressOf Me.ShowDialog))
Call Me.OwnerThread.Start()
End Sub
Public Shadows Sub Close()
If Me.OwnerThread IsNot Nothing AndAlso Me.OwnerThread.IsAlive Then
Call Me.OwnerThread.Abort()
Else
Call MyBase.Close()
End If
End Sub
End Class
This is probably quite clumsy, but I am showing it in different places in the application, so this seemed the most code-efficient way of doing this...
It actually works quite well, but I am encountering problems from time to time with this and need some help on how to address these issues.
Sometimes when the form gets closed I get an error about the thread being aborted in an unsafe manner.
At the moment I position the form manually in the centre of the form I want it to cover form. Ideally I'd like to be able to call .ShowDialog(ParentForm) on it, but of course that raises an exception because of cross-thread access from one form to the other.
Any suggestions on how to resolve this would be most appreciated.
Because I know virtually nothing about threading I probably coded this like a monkey, so if there is a better method to get this done, I would very much like to know about it.
The code I list is in VB.NET, but answer code in C# is fine too (for any overzealous retaggers)
UPDATE:
I realise now that I should have given a lot more details in my question... The wait form is actually not the first form I am displaying the app. There first is a login screen. When the user is authenticated, the login form launches the main interface of the app, which is the form which actually takes a long time to load.
I am displaying the wait form in between the login form and the main interface. I also use this form to cover for any long running tasks launched on the main interface by the user.
VisualStudio can do this for you. You can certainly write your own, of course, but if you look at your project options under Application there is a setting for Splash Screen. You can assign any form (other than the startup form) to be your splash screen. This runs on its own thread and you can marshall calls to it during startup to advance a progress bar, etc.
MSDN : Splash Screen
Instead of trying to show a dialog on a separate thread you should be moving your loading code to a separate thread / background worker.
Then just start your threads and show the progressbar on form_load and hide the progressbar when the thread completes:
Dim _progressForm As frmLoading
Private Sub frmMain_Load(sender As System.Object, e As System.EventArgs) Handles Me.Load
'start loading on a separate thread
BackgroundWorker1.RunWorkerAsync()
'show a marquee animation while loading
_progressForm = New frmLoading
_progressForm.ShowDialog(Me)
End Sub
Private Sub BackgroundWorker1_DoWork(sender As System.Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
'simulate a long load
For i As Integer = 1 To 10
System.Threading.Thread.Sleep(1000)
Next
End Sub
Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As System.ComponentModel.RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
_progressForm.Close()
End Sub
Have a look at TaskFactory.New
Run your threaded code of a different thread to keep the UI responsive
http://msdn.microsoft.com/en-us/library/ee782519.aspx?cs-save-lang=1&cs-lang=vb#code-snippet-3
You could use the ApplicationContext class to allow the splash screen to be run first and then swap in the Main form when it is ready. You use it in place of your main form in the call to Application.Run:
Application.Run(new MyApplicationCtx())
Just googled up a article for you as well:
http://www.codeproject.com/Articles/5756/Use-the-ApplicationContext-Class-to-Fully-Encapsul

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

Capture keystrokes (e.g., function keys) while a messagebox is up

We have a large WinForms app, and there is a built-in bug reporting system that can be activated during testing via the F5 Key. I am capturing the F5 key with .Net's PreFilterMessage system. This works fine on the main forms, modal dialog boxes, etc.
Unfortunately, the program also displays windows messageboxes when it needs to. When there is a bug with that, e.g., wrong text in the messagebox or it shouldn't be there, the messagefilter isn't executed at all when the messagebox is up!
I realize I could fix it by either rewriting my own messagebox routine, or kicking off a separate thread that polls GetAsyncKeyState and calls the error reporter from there. However I was hoping for a method that was less of a hack. Here's code that manifests the problem:
Public Class Form1
Implements IMessageFilter
Private Sub Form1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Click
MsgBox("now, a messagebox is up!")
End Sub
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Application.AddMessageFilter(Me)
End Sub
Public Function PreFilterMessage(ByRef m As System.Windows.Forms.Message) _
As Boolean Implements IMessageFilter.PreFilterMessage
Const VK_F5 As Int32 = &H74
Const WM_KEYDOWN As Integer = &H100
If m.Msg = WM_KEYDOWN And m.WParam.ToInt32 = VK_F5 Then
' In reality code here takes a screenshot, saves the program state, and shows a bug report interface '
IO.File.AppendAllText("c:\bugs.txt", InputBox("Describe the bug:"))
End If
End Function
End Class
Many thanks.
IMessageFilters are a .Net feature and are invoked by the .Net message loop.
Since MessageBox.Show runs the native message loop (inside the MessageBox API call), the IMessageFilters are not called in it.
You need to make a keyboard hook, like this.

WebBrowser.Print() wait until complete. .NET

I am using a WebBrowser control in VB.NET and calling the Print() method. I am printing out using a PDF printer and when Print() is called it is not immediately kicked off (it waits until it has completed running code for the entire Sub or block.
I need to make sure the file I am printing too is complete and continue process with this file, therefore, I would like to print on demand and get some status of when the operation is complete. I have tried usign printDocument and process without luck.
Anyone have any ideas?
Check out the PrintTemplateTeardown event of the underlying unmanaged WebBrowser object. Sometimes that event gets fired multiple times but hopefully this will point you in the right direction. You need to add a reference to Microsoft Internet Controls.
Private Sub Print()
AddHandler DirectCast(WebBrowser1.ActiveXInstance, SHDocVw.WebBrowser).PrintTemplateTeardown, AddressOf PrintDone
WebBrowser1.Print()
End Sub
Private Sub PrintDone(ByVal obj As Object)
Trace.WriteLine("printed")
RemoveHandler DirectCast(WebBrowser1.ActiveXInstance, SHDocVw.WebBrowser).PrintTemplateTeardown, AddressOf PrintDone
End Sub
Your best bet is to get a handle on your 'printjobscollection' for your default printer and ensure that the jobcount = 0
like this in vb.net:
Dim intprint As Integer = Nothing
retry2:
intprint = GetPrintJobsCollection(printerinuse)
If Not intprint = 0 Then
System.Threading.Thread.Sleep(1000)
GoTo retry2
End If
'do what you want to do after print completes here

Categories