UWP MediaPlayer stops playing sounds - c#

I'm using the MediaPlayer class (https://learn.microsoft.com/en-us/windows/uwp/controls-and-patterns/media-playback) to play sounds in my UWP Project. This all works fine at the beginning but suddenly the Play() call starts "playing" the sound (e.g. the state of the PlaybackSession is "Playing") but there is no sound output...
Dictionary<string, MediaPlayer> Effects = new Dictionary<string, MediaPlayer>();
public async Task LoadEffects()
{
Effects?.Clear();
StorageFolder folder = await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFolderAsync("sounds");
foreach (var effect in await folder.GetFilesAsync())
{
MediaPlayer snd = new MediaPlayer();
snd.AutoPlay = false;
snd.Source = MediaSource.CreateFromStorageFile(effect);
snd.RealTimePlayback = true;
snd.AudioCategory = MediaPlayerAudioCategory.GameMedia;
Effects.Add(effect.DisplayName, snd);
}
IsLoaded = true;
}
public void StartPlaying(string name, float volume = 1.0f, bool force = false)
{
lock (_instance)
{
if (Effects.ContainsKey(name))
{
//if force is true, force the sound to stop and play again otherwise just exit
if (Effects[name].PlaybackSession.PlaybackState == MediaPlaybackState.Playing && !force)
{
return;
}
Effects[name].Pause();
Task.Delay(100).ContinueWith((res) =>
{
Effects[name].Volume = volume;
Effects[name].PlaybackSession.Position = TimeSpan.Zero;
Effects[name].IsLoopingEnabled = true;
Effects[name].Play();
});
}
}
}
public void StopPlaying(string name)
{
lock (_instance)
{
try
{
//if nothing is playing just return
if (Effects[name].PlaybackSession.PlaybackState != MediaPlaybackState.Playing) return;
}
catch(Exception ex)
{
//weird error happens sometimes, catch it and just return
return;
}
if (Effects.ContainsKey(name))
{
Debug.WriteLine($"Stopping {name} now; state before: {Effects[name].PlaybackSession.PlaybackState.ToString()}");
Effects[name].IsLoopingEnabled = false;
Effects[name].Pause();
Effects[name].PlaybackSession.Position = TimeSpan.Zero;
}
}
}
I noticed that it seems to happen more frequently when I'm not using the Task.Delay().
The lock is used, since I have a singleton class.

Related

How can I pause thread?

Relevant code:
private static Thread m_thread = null;
private static Boolean m_stop = false;
public static Boolean Start(SearcherParams pars)
{
Boolean success = false;
if (m_thread == null)
{
// Perform a reset of all variables,
// to ensure that the state of the searcher is the same on every new start:
ResetVariables();
// Remember the parameters:
m_pars = pars;
// Start searching for FileSystemInfos that match the parameters:
m_thread = new Thread(new ThreadStart(SearchThread));
m_thread.Start();
success = true;
}
return success;
}
private static void SearchThread()
{
Boolean success = true;
String errorMsg = "";
// Search for FileSystemInfos that match the parameters:
if ((m_pars.SearchDir.Length >= 3) && (Directory.Exists(m_pars.SearchDir)))
{
if (m_pars.FileNames.Count > 0)
{
// Convert the string to search for into bytes if necessary:
if (m_pars.ContainingChecked)
{
if (m_pars.ContainingText != "")
{
try
{
m_containingBytes =
m_pars.Encoding.GetBytes(m_pars.ContainingText);
}
catch (Exception)
{
success = false;
errorMsg = "The string\r\n" + m_pars.ContainingText +
"\r\ncannot be converted into bytes.";
}
}
else
{
success = false;
errorMsg = "The string to search for must not be empty.";
}
}
if (success)
{
// Get the directory info for the search directory:
DirectoryInfo dirInfo = null;
try
{
dirInfo = new DirectoryInfo(m_pars.SearchDir);
}
catch (Exception ex)
{
success = false;
errorMsg = ex.Message;
}
if (success)
{
// Search the directory (maybe recursively),
// and raise events if something was found:
SearchDirectory(dirInfo);
}
}
}
else
{
success = false;
errorMsg = "Please enter one or more filenames to search for.";
}
}
else
{
success = false;
errorMsg = "The directory\r\n" + m_pars.SearchDir + "\r\ndoes not exist.";
}
// Remember the thread has ended:
m_thread = null;
// Raise an event:
if (ThreadEnded != null)
{
ThreadEnded(new ThreadEndedEventArgs(success, errorMsg));
}
}
private static void SearchDirectory(DirectoryInfo dirInfo)
{
if (!m_stop)
{
try
{
foreach (String fileName in m_pars.FileNames)
{
FileSystemInfo[] infos = dirInfo.GetFileSystemInfos(fileName);
foreach (FileSystemInfo info in infos)
{
if (m_stop)
{
break;
}
if (MatchesRestrictions(info))
{
// We have found a matching FileSystemInfo,
// so let's raise an event:
if (FoundInfo != null)
{
FoundInfo(new FoundInfoEventArgs(info));
}
}
}
}
if (m_pars.IncludeSubDirsChecked)
{
DirectoryInfo[] subDirInfos = dirInfo.GetDirectories();
foreach (DirectoryInfo subDirInfo in subDirInfos)
{
if (m_stop)
{
break;
}
// Recursion:
SearchDirectory(subDirInfo);
}
}
}
catch (Exception)
{
}
}
}
The stop is working fine I wanted to add also a pause button to pause and resume the thread.
I added a button click event but how do I make the pause/resume actions and where?
This is a link for the complete code : https://pastebin.com/fYYnHBB6
You can use the ManualResetEvent object.
Here is the simplified version.
In your class, create the object:
public static ManualResetEvent _mrsevent = new ManualResetEvent(false);
In your thread function, as part of the loops that search for files/directories:
private static void SearchThread()
{
foreach (String fileName in m_pars.FileNames)
{
_mrsevent.WaitOne();
}
}
From outside the thread you can call:
a) _mrsevent.Set(); // resume the thread.
b) _mrsevent.Reset(); // pause

WasapiLoopbackCapture.DataAvailable reading silence bytes

I am trying to record the speaker sound to a wave file using NAudio's WasapiLoopbackCapture by writing the stream of bytes available. The WasapiLoopbackCapture.DataAvailable BytesRecorded will be 0 is there is no sound. however in my case i am getting bytecount in BytesRecorded even though the speakers are silent. could you please let me know whats wrong here.
class CallResponse
{
private WaveFileWriter _writer;
private WasapiLoopbackCapture _waveIn;
private string _inFile;
private string _inFileCompressed;
private int _duration;
public bool _isRecording;
public bool _speechDetected;
public CallResponse()
{
_inFile = #"C:\Naresh\test.wav";
_inFileCompressed = #"C:\Naresh\test16Hz.wav";
_waveIn = new WasapiLoopbackCapture();
_waveIn.DataAvailable += (s, e) =>
{
Console.WriteLine(e.BytesRecorded);
_writer.Write(e.Buffer, 0, e.BytesRecorded);
if (_writer.Position > _waveIn.WaveFormat.AverageBytesPerSecond * _duration)
{
Console.Write("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bRecording stopped...");
_waveIn.StopRecording();
}
};
_waveIn.RecordingStopped += (s, e) =>
{
if (_writer != null)
{
_writer.Close();
_writer.Dispose();
_writer = null;
}
Console.Write("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bCompressing Audio...");
using (var reader = new AudioFileReader(_inFile))
{
var resampler = new WdlResamplingSampleProvider(reader, 16000);
WaveFileWriter.CreateWaveFile16(_inFileCompressed, resampler);
}
_isRecording = false;
};
}
public void DisposeObjects()
{
if (_waveIn != null)
{
_waveIn.Dispose();
_waveIn = null;
}
}
public void StartRecording(int duration = 5)
{
_writer = new WaveFileWriter(_inFile, _waveIn.WaveFormat);
this._duration = duration;
_speechDetected = false;
_isRecording = true;
Console.WriteLine("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\bRecording....");
_waveIn.StartRecording();
}
}
if something is playing audio, then WasapiLoopbackCapture will capture that audio, even if it contains silence. So there's nothing particularly wrong or surprising that you are getting non-zero BytesRecorded values. In fact, if no applications are sending audio to the device being captured, then what typically happens is that you won't get any DataAvailable callbacks at all.

Xamarin Android take screenshot

I am working on a project which makes drawing.
I don't use axml because I do my drawing in a class called filledpolygon and calling the function in MainActivity. I just want to take screenshot in my project. Is there any basic function, which I can call in onCreate method? So, when the program runs, it will automatically take the screenshot. I found answers except Xamarin platform.
Since Android 28 DrawingCacheEnabled is deprecated and without it we are forcing our view to to redraw on our custom canvas wich can cause artifacts with custom controls and renderers and the screenshot version might be different from what we see on screen.
The legacy code that is still working on simple cases is:
public byte[] CaptureScreenshot()
{
var view=
Xamarin.Essentials.Platform.CurrentActivity.Window.DecorView.RootView;
if (view.Height < 1 || view.Width < 1)
return null;
byte[] buffer = null;
view.DrawingCacheEnabled = true;
using (var screenshot = Bitmap.CreateBitmap(
view.Width,
view.Height,
Bitmap.Config.Argb8888))
{
var canvas = new Canvas(screenshot);
view.Draw(canvas);
using (var stream = new MemoryStream())
{
screenshot.Compress(Bitmap.CompressFormat.Png, 90, stream);
buffer = stream.ToArray();
}
}
view.DrawingCacheEnabled = false;
return buffer;
}
Use legacy method above as follows
if ((int)Android.OS.Build.VERSION.SdkInt < 28)
{
//legacy
}
The DrawingCacheEnabled obsolete warning redirects us to use PixelCopy. This method is acting with a callback so to use it synchronously have made some helpers:
Usage:
public byte[] CaptureScreenshot()
{
using var helper = new ScreenshotHelper(
Xamarin.Essentials.Platform.CurrentActivity.Window.DecorView.RootView,
Xamarin.Essentials.Platform.CurrentActivity);
byte[] buffer = null;
bool wait = true;
Task.Run(async () =>
{
helper.Capture((Bitmap bitmap) =>
{
try
{
if (!helper.Error)
{
using (var stream = new MemoryStream())
{
bitmap.Compress(Bitmap.CompressFormat.Png, 90, stream);
buffer = stream.ToArray();
}
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
wait = false;
}
});
}).ConfigureAwait(false);
while (wait)
{
Task.Delay(10).Wait();
}
return buffer;
}
The helper:
public class ScreenshotHelper : Java.Lang.Object, PixelCopy.IOnPixelCopyFinishedListener
{
public void OnPixelCopyFinished(int copyResult)
{
var stop = true;
if (copyResult == (int) PixelCopyResult.Success)
{
Error = false;
//todo CallbackGotScreenshot();
_callback(_bitmap);
}
else
{
Error = true;
}
_callback(_bitmap);
}
public bool Error { get; protected set; }
public ScreenshotHelper(Android.Views.View view, Activity activity)
{
_view = view;
_activity = activity;
_bitmap = Bitmap.CreateBitmap(
_view.Width,
_view.Height,
Bitmap.Config.Argb8888);
}
// Starts a background thread and its {#link Handler}.
private void StartBackgroundThread()
{
_BackgroundThread = new HandlerThread("ScreeshotMakerBackground");
_BackgroundThread.Start();
_BackgroundHandler = new Handler(_BackgroundThread.Looper);
}
// Stops the background thread and its {#link Handler}.
private void StopBackgroundThread()
{
try
{
_BackgroundThread.QuitSafely();
_BackgroundThread.Join();
_BackgroundThread = null;
_BackgroundHandler = null;
}
catch (Exception e)
{
//e.PrintStackTrace();
}
}
public void Capture(Action<Bitmap> callback)
{
//var locationOfViewInWindow = new int[2];
//_view.GetLocationInWindow(locationOfViewInWindow);
_callback = callback;
try
{
StartBackgroundThread();
//todo could create-use background handler
PixelCopy.Request(_activity.Window, _bitmap, this,
_BackgroundHandler);
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
Task.Run(StopBackgroundThread);
}
}
private Android.Views.View _view;
private Activity _activity;
private Bitmap _bitmap;
private HandlerThread _BackgroundThread;
private Handler _BackgroundHandler;
private Action<Bitmap> _callback;
public new void Dispose()
{
_bitmap?.Dispose();
_bitmap= null;
_activity = null;
_view = null;
_callback = null;
base.Dispose();
}
}
In your View you could run the following code which will take a screenshot. I have not tried running it in OnCreate() before so you may need to test that out to make sure the view has been fully rendered.
*Edit: According to this post you may have trouble running this code in OnCreate() so you will need to find a better place. I was unable to figure out what post the user was referring to in the link he posted.
*Edit #2: Just found out that Compress() does not take the quality parameter (which is listed as 0 below) into account since PNG is lossless, but if you change the format to JPEG for example, then you may want to turn up the quality parameter since your image will look like garbage.
public byte[] SaveImage() {
DrawingCacheEnabled = true; //Enable cache for the next method below
Bitmap bitmap = GetDrawingCache(true); //Gets the image from the cache
byte[] bitmapData;
using(MemoryStream stream = new MemoryStream()) {
bitmap.Compress(Bitmap.CompressFormat.Png, 0, stream);
bitmapData = stream.ToArray();
}
return bitmapData;
}

Unity with Naudio RadioStreaming

I'm working in a script based on the NAudio Demo modified for streaming a Shoutcast inside my Unity game.
I have tried to remove the original while loop using the update from the MonoBehvaiour class, I only get some noises but not music during the time I'm streaming with this script.
I usually get an error while the execution related with the format
MmException: AcmNotPossible calling acmStreamConvert
NAudio.MmException.Try (MmResult result, System.String function)
NAudio.Wave.Compression.AcmStreamHeader.Convert (Int32 bytesToConvert,
System.Int32& sourceBytesConverted)
NAudio.Wave.Compression.AcmStream.Convert (Int32 bytesToConvert,
System.Int32& sourceBytesConverted)
NAudio.Wave.AcmMp3FrameDecompressor.DecompressFrame
(NAudio.Wave.Mp3Frame frame, System.Byte[] dest, Int32 destOffset)
I have tried with different radios online, but I always get that error. I don't know what is happening... Any help?
public class NAudioStreamer : MonoBehaviour {
private IWavePlayer mWaveOutDevice;
private WaveStream mMainOutputStream;
private WaveChannel32 mVolumeStream;
private VolumeWaveProvider16 volumeProvider;
private string m_Url = "http://37.59.32.115:8122/";
enum StreamingPlaybackState
{
Stopped,
Playing,
Buffering,
Paused
}
private volatile StreamingPlaybackState playbackState = StreamingPlaybackState.Stopped;
private bool fullyDownloaded = false;
public bool m_Play = false;
float timer;
void Update()
{
if (m_Play)
{
playbackState = StreamingPlaybackState.Buffering;
StreamMP3(m_Url);
m_Play = false;
}
switch (playbackState)
{
case StreamingPlaybackState.Buffering:
case StreamingPlaybackState.Playing:
StreamMP3(m_Url);
break;
default:
break;
}
}
HttpWebRequest webRequest;
BufferedWaveProvider bufferedWaveProvider = null;
byte[] buffer = new byte[16384 * 4];
private void StreamMP3(string lUrl)
{
this.fullyDownloaded = false;
webRequest = (HttpWebRequest)WebRequest.Create(lUrl);
int metaInt = 0; // blocksize of mp3 data
webRequest.Headers.Clear();
webRequest.Headers.Add("GET", "/ HTTP/1.0");
webRequest.Headers.Add("Icy-MetaData", "1");
webRequest.UserAgent = "WinampMPEG/5.09";
HttpWebResponse resp = null;
try
{
resp = (HttpWebResponse)webRequest.GetResponse();
}
catch(WebException e)
{
if (e.Status != WebExceptionStatus.RequestCanceled)
{
Debug.LogError(e.Message);
}
return;
}
// needs to be big enough to hold a decompressed frame
try
{
// read blocksize to find metadata block
metaInt = Convert.ToInt32(resp.GetResponseHeader("icy-metaint"));
}
catch
{
}
IMp3FrameDecompressor decompressor = null;
try
{
using (var responseStream = resp.GetResponseStream())
{
ReadFullyStream readFullyStream = new ReadFullyStream(responseStream);
//do
{
if (bufferedWaveProvider != null && bufferedWaveProvider.BufferLength - bufferedWaveProvider.BufferedBytes < bufferedWaveProvider.WaveFormat.AverageBytesPerSecond / 4)
{
Debug.LogError("Buffer getting full, taking a break");
Thread.Sleep(500);
}
else
{
Mp3Frame frame = null;
try
{
frame = Mp3Frame.LoadFromStream(readFullyStream, true);
}
catch (EndOfStreamException)
{
this.fullyDownloaded = true;
Debug.LogError("reached the end of the MP3 file / stream");
// reached the end of the MP3 file / stream
// break;
}
catch (WebException)
{
// probably we have aborted download from the GUI thread
// break;
}
if (decompressor == null && frame != null)
{
// don't think these details matter too much - just help ACM select the right codec
// however, the buffered provider doesn't know what sample rate it is working at
// until we have a frame
WaveFormat waveFormat = new Mp3WaveFormat(frame.SampleRate, frame.ChannelMode == ChannelMode.Mono ? 1 : 2, frame.FrameLength, frame.BitRate);
decompressor = new AcmMp3FrameDecompressor(waveFormat);
if(bufferedWaveProvider == null)
{
this.bufferedWaveProvider = new BufferedWaveProvider(decompressor.OutputFormat);
this.bufferedWaveProvider.BufferDuration = TimeSpan.FromSeconds(20); // allow us to get well ahead of ourselves
}
}
int decompressed = decompressor.DecompressFrame(frame, buffer, 0);
if(bufferedWaveProvider != null)
{
bufferedWaveProvider.AddSamples(buffer, 0, decompressed);
}
}
}
if (this.mWaveOutDevice == null && this.bufferedWaveProvider != null)
{
Debug.Log("Creating WaveOut Device");
this.mWaveOutDevice = new WaveOut();
this.volumeProvider = new VolumeWaveProvider16(bufferedWaveProvider);
this.volumeProvider.Volume = 100.0f;
mWaveOutDevice.Init(volumeProvider);
}
else if (bufferedWaveProvider != null)
{
double bufferedSeconds = bufferedWaveProvider.BufferedDuration.TotalSeconds;
if(bufferedSeconds > 0.2f && playbackState == StreamingPlaybackState.Buffering)
{
Debug.Log("PLaying music...");
mWaveOutDevice.Play();
playbackState = StreamingPlaybackState.Playing;
}
}
}
}
finally
{
if (decompressor != null)
{
decompressor.Dispose();
}
}
}
}
The ACM error either means there is no ACM MP3 decoder on the machine, or that possibly a corrupt frame has been received (or some album art misinterpreted as a frame). It it's the latter, you can just catch the error and ignore it. If the former, you'll need to install a decoder, or use a different MP3 frame decompressor. (Possibly the NLayer one).

WinRT - MessageDialog.ShowAsync will throw UnauthorizedAccessException in my custom class

I Want to write my own control, when the ctor is invoked, a MessageBox is shown.
public class Class1
{
public Class1()
{
ShowDialog();
}
void ShowDialog()
{
SynchronizationContext context = SynchronizationContext.Current;
if (context != null)
{
context.Post((f) =>
{
MessageDialog dialog = new MessageDialog("Hello!");
dialog.ShowAsync();
}, null);
}
}
}
If my class is used by someone, and write the codes as below, UnauthorizedAccessException is always thrown in dialog.ShowAsync();
void MainPage_Loaded(object sender, RoutedEventArgs e)
{
ClassLibrary1.Class1 c1 = new ClassLibrary1.Class1();
MessageDialog dialog1 = new MessageDialog("");
dialog1.ShowAsync();
}
Is there a way to show a message dialog without exception?
I found a way, enjoy it!
Task ShowDialog()
{
CoreDispatcher dispatcher = Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher;
Func<object, Task<bool>> action = null;
action = async (o) =>
{
try
{
if (dispatcher.HasThreadAccess)
await new MessageDialog("Hello!").ShowAsync();
else
{
dispatcher.RunAsync(CoreDispatcherPriority.Normal,
() => action(o));
}
return true;
}
catch (UnauthorizedAccessException)
{
if (action != null)
{
Task.Delay(500).ContinueWith(async t => await action(o));
}
}
return false;
};
return action(null);
}
As MessageDialogue needs to run on the UI thread, can you try switching it to:
var dispatcher = Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher;
dispatcher.RunAsync(DispatcherPriority.Normal, <lambda for your code which should run on the UI thread>);
The cleaner solution may look like this. The interesting part ist hidden in die showDialogAsync(). For convenience you can use the Close() method to close the current dialog again programmatically. The IUIDispatcher is another helper interface you can rebuild yourself easily:
private readonly IUIDispatcher _dispatcher;
readonly Object _queueMonitor = new object();
readonly Object _showMonitor = new object();
private IAsyncOperation<IUICommand> _currentDialogOperation;
readonly Queue<MessageDialog> _dialogQueue = new Queue<MessageDialog>();
public async Task ShowAsync(string content)
{
var md = new MessageDialog(content);
await showDialogAsync(md);
}
public async Task ShowAsync(string content, string caption)
{
var md = new MessageDialog(content, caption);
await showDialogAsync(md);
}
public async Task<MessageDialogResult> ShowAsync(string content, MessageDialogType dialogType)
{
var messageDialogResult = await ShowAsync(content, null, dialogType);
return messageDialogResult;
}
public async Task<MessageDialogResult> ShowAsync(string content, string caption, MessageDialogType dialogType)
{
var result = MessageDialogResult.Ok;
var md = string.IsNullOrEmpty(caption) ? new MessageDialog(content) : new MessageDialog(content, caption);
switch (dialogType)
{
case MessageDialogType.Ok:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
md.CancelCommandIndex = 0;
md.DefaultCommandIndex = 0;
break;
case MessageDialogType.OkCancel:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonOk"], command => result = MessageDialogResult.Ok));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
case MessageDialogType.YesNo:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
case MessageDialogType.YesNoCancel:
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonYes"], command => result = MessageDialogResult.Yes));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonNo"], command => result = MessageDialogResult.No));
md.Commands.Add(new UICommand(ResWrapper.Strings["MessageDialogButtonCancel"], command => result = MessageDialogResult.Cancel));
md.DefaultCommandIndex = 0;
md.CancelCommandIndex = 1;
break;
default:
throw new ArgumentOutOfRangeException("dialogType");
}
await showDialogAsync(md);
return result;
}
/// <summary>
/// Shows the dialogs, queued and one after the other.
/// We need this as a workaround for the the UnauthorizedAcsess exception.
/// </summary>
/// <param name="messageDialog">The message dialog.</param>
/// <returns></returns>
async Task showDialogAsync(MessageDialog messageDialog)
{
//Calls this function in a separated task to avoid ui thread deadlocks.
await Task.Run(async () =>
{
lock (_queueMonitor)
{
_dialogQueue.Enqueue(messageDialog);
}
try
{
while (true)
{
MessageDialog nextMessageDialog;
lock (_queueMonitor)
{
if (_dialogQueue.Count > 1)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Next dialog is waiting for MessageDialog to be accessable!!");
Monitor.Wait(_queueMonitor); //unlock and wait - regains lock after waiting
}
nextMessageDialog = _dialogQueue.Peek();
}
var showing = false;
_dispatcher.Execute(async () =>
{
try
{
lock (_showMonitor)
{
showing = true;
_currentDialogOperation = nextMessageDialog.ShowAsync();
}
await _currentDialogOperation;
lock (_showMonitor)
_currentDialogOperation = null;
}
catch (Exception e)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | " + e);
}
lock (_showMonitor)
{
showing = false;
Monitor.Pulse(_showMonitor); //unlock and wait - regains lock after waiting
}
});
lock (_showMonitor)
{
if (showing)
{
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | Waiting for MessageDialog to be closed!!");
//we must wait here manually for the closing of the dialog, because the dispatcher does not return a waitable task.
Monitor.Wait(_showMonitor); //unlock and wait - regains lock after waiting
}
}
Debug.WriteLine("MessageDialogService.cs | showDialogAsync | MessageDialog was closed.");
return true;
}
}
finally
{
//make sure we leave this in a clean state
lock (_queueMonitor)
{
_dialogQueue.Dequeue();
Monitor.Pulse(_queueMonitor);
}
}
});
}
public void Close(string keyContent="")
{
try
{
if (keyContent.IsNullOrEmpty())
{
lock (_showMonitor)
{
if (_currentDialogOperation == null) return;
_currentDialogOperation.Cancel();
_currentDialogOperation = null;
}
}
else
{
var cancel = false;
lock (_queueMonitor)
{
if (_dialogQueue.Count == 0)
return;
var currentDialog = _dialogQueue.Peek();
Debug.WriteLine("MessageDialogService.cs | Close | {0}", currentDialog.Content);
if (currentDialog.Content == keyContent)
{
cancel = true;
}
}
if (!cancel) return;
lock (_showMonitor)
{
if (_currentDialogOperation == null) return;
_currentDialogOperation.Cancel();
_currentDialogOperation = null;
}
}
}
catch (Exception e)
{
Debug.WriteLine("MessageDialogService.cs | Close | " + e);
}
}
I think I've found it. I had the same problem when creating messageboxes in any other threads besides the main thread.
This is the C++ solution but I think you can convert it easily ;)
IAsyncOperation<IUICommand^> ^Op = msgbox->ShowAsync();
task<IUICommand^>( Op ).then([=](IUICommand^ C)
{
}).then([](task<void> t)
{
try
{
t.get();
}
catch (Platform::Exception ^e)
{
//ERROR!
}
});
On a side note this is the correct way to handle ANY WinRT/Windows 8 Store C++ exception.
You can always use
Execute.OnUIThread( async () => {
...
var dialog = new MessageDialog(yourMessage);
await dialog.ShowAsync();
...
});
This doesn't solve race conditions in the UI if you are trying to launch multiple dialogs from background threads. But you could use a try/catch to make sure you cover for that case.

Categories

Resources