STA Threading, Rtf to HTML converter issues - c#

I'm using this code to attempt to convert my Rtf text from a RichTextBox in my UI to Html. I have 2 projects in my solution: the UI & application written in MVVM- IsesTextEditor and the pre-written Converter as provided in the link- MarkupConverter.
A btn in my view is bound to a command in my view model which passes the RichTextBox text into the ConvertRtfToHtml method as per the example:
private string ConvertRtfToHtml(string PastedText)
{
var thread = new Thread(ConvertRtfInSTAThread);
var threadData = new ConvertRtfThreadData { RtfText = PastedText };
thread.SetApartmentState(ApartmentState.STA);
thread.Start(threadData);
thread.Join();
return threadData.HtmlText;
}
private void ConvertRtfInSTAThread(object rtf)
{
var threadData = rtf as ConvertRtfThreadData;
threadData.HtmlText = markupConverter.ConvertRtfToHtml(threadData.RtfText);
}
private class ConvertRtfThreadData
{
public string RtfText { get; set; }
public string HtmlText { get; set; }
}
The MarkupConverter.ConvertRtfToHtml method then calls ConvertRtfToXaml which instantiates a new RichTextBox object:
public static class RtfToHtmlConverter
{
private const string FlowDocumentFormat = "<FlowDocument>{0}</FlowDocument>";
public static string ConvertRtfToHtml(string rtfText)
{
var xamlText = string.Format(FlowDocumentFormat, ConvertRtfToXaml(rtfText));
return HtmlFromXamlConverter.ConvertXamlToHtml(xamlText, false);
}
private static string ConvertRtfToXaml(string rtfText)
{
var richTextBox = new RichTextBox();
if (string.IsNullOrEmpty(rtfText)) return "";
var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
using (var rtfMemoryStream = new MemoryStream())
{
using (var rtfStreamWriter = new StreamWriter(rtfMemoryStream))
{
rtfStreamWriter.Write(rtfText);
rtfStreamWriter.Flush();
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
textRange.Load(rtfMemoryStream, DataFormats.Rtf);
}
}
using (var rtfMemoryStream = new MemoryStream())
{
textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
textRange.Save(rtfMemoryStream, DataFormats.Xaml);
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
using (var rtfStreamReader = new StreamReader(rtfMemoryStream))
{
return rtfStreamReader.ReadToEnd();
}
}
}
}
At creating the RichTextBox object I'm getting a The calling thread cannot access this object because a different thread owns it. exception.
Can anyone suggest a fix for this? I'm sure it's a relatively simple threading issue but I'm a junior dev & don't have much experience with threading or STA...

As the exception suggest, move the GUI related code from work thread to the GUI thread.
Please refer this post, I copied some text from it:
Like the frames of the user interface, like many Windows Forms, WPF also imposes a single threading model, which means you can only access a specified derivative DispatcherObject thread that creates it. In Windows Forms controls that implement the interface ISynchronizeInvoke, this interface exposes a set of methods such as Invoke and BeginInvoke to impose a contract common thread synchronization we can use to access a control from another thread. In WPF, we also have that kind of thing, but these operations are involved in a class called Dispatcher, Dispatcher WPF is the way to allow this kind of thread synchronization model.

To use a WPF control on a thread which is not the main UI thread, you need to do some plumbing, like start and finish the WPF Dispatcher loop.
I've put together a sample app app showing how to do this, using some helper code I posted earlier here.
This is a console app, although you should be able to use RunOnWpfThreadAsync(() => ConvertRtfToXaml(RTF)).Result in any other execution environment.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Threading;
namespace ConsoleApplication_22717365
{
// by Noseratio - https://stackoverflow.com/q/22717365/1768303
public class Program
{
const string RTF = #"{\rtf1\ansi{\fonttbl\f0\fswiss Helvetica;}\f0\pard This is some {\b bold} text.\par}";
static void Main()
{
var xaml = RunOnWpfThreadAsync(() => ConvertRtfToXaml(RTF)).Result;
Console.WriteLine(xaml);
}
// http://code.msdn.microsoft.com/windowsdesktop/Converting-between-RTF-and-aaa02a6e
private static string ConvertRtfToXaml(string rtfText)
{
var richTextBox = new RichTextBox();
if (string.IsNullOrEmpty(rtfText)) return "";
var textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
using (var rtfMemoryStream = new MemoryStream())
{
using (var rtfStreamWriter = new StreamWriter(rtfMemoryStream))
{
rtfStreamWriter.Write(rtfText);
rtfStreamWriter.Flush();
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
textRange.Load(rtfMemoryStream, DataFormats.Rtf);
}
}
using (var rtfMemoryStream = new MemoryStream())
{
textRange = new TextRange(richTextBox.Document.ContentStart, richTextBox.Document.ContentEnd);
textRange.Save(rtfMemoryStream, DataFormats.Xaml);
rtfMemoryStream.Seek(0, SeekOrigin.Begin);
using (var rtfStreamReader = new StreamReader(rtfMemoryStream))
{
return rtfStreamReader.ReadToEnd();
}
}
}
// https://stackoverflow.com/a/22626704/1768303
public static async Task<TResult> RunOnWpfThreadAsync<TResult>(Func<Task<TResult>> funcAsync)
{
var tcs = new TaskCompletionSource<Task<TResult>>();
Action startup = async () =>
{
// this runs on the WPF thread
var task = funcAsync();
try
{
await task;
}
catch
{
// propagate exception with tcs.SetResult(task)
}
// propagate the task (so we have the result, exception or cancellation)
tcs.SetResult(task);
// request the WPF tread to end
// the message loop inside Dispatcher.Run() will exit
System.Windows.Threading.Dispatcher.ExitAllFrames();
};
// the WPF thread entry point
ThreadStart threadStart = () =>
{
// post the startup callback
// it will be invoked when the message loop starts pumping
System.Windows.Threading.Dispatcher.CurrentDispatcher.BeginInvoke(
startup, DispatcherPriority.Normal);
// run the WPF Dispatcher message loop
System.Windows.Threading.Dispatcher.Run();
};
// start and run the STA thread
var thread = new Thread(threadStart);
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
try
{
// propagate result, exception or cancellation
return await tcs.Task.Unwrap().ConfigureAwait(false);
}
finally
{
// make sure the thread has fully come to an end
thread.Join();
}
}
// a wrapper to run synchronous code
public static Task<TResult> RunOnWpfThreadAsync<TResult>(Func<TResult> func)
{
return RunOnWpfThreadAsync(() => Task.FromResult(func()));
}
}
}

Related

Why would my timer does not dynamically display the file size?

So basically, I m making a program where 25 tasks write the same random task to one file until it reaches a certain size, and I would like to dynamically display the file size of the selected file in 0.5 seconds interval, so I made a timer hooked with Timer_Elapsed which should be executed every 0.5 seconds and display on UI which I specify on mainWindow.xaml on the textblock x:Name="fileSize", so I placed the createTimer function in to btnGo_Click function in mainwindow.xaml.cs so the event would extract the right fileInfo of the selectedFile. Any advice for my wrong would be appreciated. I'm also sharing the FileIO class in case it is needed, so they are full solutions. Even aside from the questions I asked, any general advice to better my code would be appreciated because I need to get a grasp of the good code example.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Windows;
using System.Threading;
namespace WriteFile
{
internal class FileIO
{
private object _lock = new object();
public volatile bool sizeReached = false;
private StreamWriter sw = null;
Mutex mut = null;
public FileIO()
{
if (!Mutex.TryOpenExisting("MyMutex", out mut))
{
mut = new Mutex(true, "MyMutex");
mut.ReleaseMutex();
}
}
internal void WriteFile(string FilePath)
{
while (!sizeReached)
{
mut.WaitOne();
try
{
using (sw = new StreamWriter(FilePath, true))
{
sw.WriteLine(Guid.NewGuid());
}
}
catch (Exception ex)
{
MessageBox.Show("Exception: " + ex.Message);
}
finally
{
if (sw != null)
{
sw.Close();
}
}
mut.ReleaseMutex();
}
}
internal void SizeMonitor(string FPath, int MaxSize, Task[] tasks)
{
FileInfo fi = null;
while (!sizeReached)
{
if (File.Exists(FPath))
{
fi = new FileInfo(FPath);
if (fi.Length >= MaxSize)
{
sizeReached = true;
}
}
if (sizeReached)
{
foreach (Task task in tasks)
{
task.Wait();
}
}
Thread.Sleep(1);
}
MessageBox.Show(fi.Length.ToString());
MessageBox.Show("Done");
}
}
}
mainWindow.xaml
<Window x:Class="WriteFile.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WriteFile"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<TextBlock x:Name="fileSize"/>
<TextBox Name ="TargetSize" VerticalAlignment="Center" FontSize="20">
</TextBox>
<Label Content="Target Size" HorizontalAlignment="Left" Margin="92,150,0,0" VerticalAlignment="Top"/>
<Button Name ="btnGo" Content="Write to file" HorizontalAlignment="Left" Margin="92,267,0,0" VerticalAlignment="Top" Width="100" Click="btnGo_Click"/>
</Grid>
</Window>
mainWindow.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using Microsoft.Win32;
using System.ComponentModel;
using System.Timers;
using System.Threading;
using System.Runtime.CompilerServices;
namespace WriteFile
{
public partial class MainWindow : Window
{
System.Timers.Timer timer = new System.Timers.Timer();
Task[] tasks;
Task MonitorTask;
static FileIO fio = new FileIO();
static string fPath;
static FileInfo fileInfo;
public MainWindow()
{
InitializeComponent();
CreateTimer();
}
public void CreateTimer()
{
var timer = new System.Timers.Timer(500); // fire every 0.5 second
timer.Enabled = true;
timer.Elapsed += Timer_Elapsed;
}
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
fileSize.Text = fileInfo.Length.ToString();
}
private void btnGo_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.ShowDialog();
Stream myStream;
saveFileDialog.FilterIndex = 2;
saveFileDialog.RestoreDirectory = true;
if (File.Exists(saveFileDialog.FileName))
{
File.Delete(saveFileDialog.FileName);
}
if ((myStream = saveFileDialog.OpenFile()) != null)
{
StreamWriter sw = new StreamWriter(myStream);
sw.Write(" your text");
myStream.Close();
}
int NoOfTasks = 25;
int Target = Convert.ToInt32(TargetSize.Text);
fPath = saveFileDialog.FileName;
tasks = new Task[NoOfTasks];
fio.sizeReached = false;
fileInfo = new FileInfo(fPath);
for (int i = 0; i < NoOfTasks; i++)
{
tasks[i] = new Task(() => fio.WriteFile(fPath));
tasks[i].Start();
}
MonitorTask = new Task(() => fio.SizeMonitor(fPath, Target, tasks));
MonitorTask.Start();
}
}
}
As mentioned by others, FileInfo.Length value is pre-cached. If the associated file has changed, the FileInfo requires to refresh those cached values. To accomplish this, you must explicitly call the FileInfo.Refresh method. FileInfo does not monitor the associated file.
Furthermore, System.Threading.Timer executes the callback on a background thread. In WPF you can only reference a DispatcherObject (for example TextBlock) from a dispatcher thread (UI thread). For this reason you should use the DispatcherTimer. DispatcherTimer executes the callback on the dispatcher thread.
Additionally, you would want to stop the timer once the writing to the file is completed.
You should not use Task.Start. Instead of Task.Start and a Mutex you should simply await the Task. In your context, you can await a collection of Task objects using Task.WhenALl.
And instead of creating Task explicitly, you should use the async API of the StreamWriter.
You actually don't have concurrent code (neither would you benefit from it): the Mutex, declaration of a volatile field and the lock object are redundant. Also closing the StreamWriter explicitly in a finally block is not needed as you already use a using-block to handle the lifetime of the instance.
There are some more flaws in your code (some logical issues for instance). I have refactored your version to eliminate some of the issues for example by using the asynchronous StreamWriter API and async/await.
Because it is difficult to make any sense of your code (due to the lack of context) it is not possible to further improve it. For example, creating a single concatenated string (consisting of e.g. 25 values) would result in a single file write (and resource allocation), which would significantly improve the overall performance.
FileIO.cs
namespace WriteFile
{
internal class FileIO
{
public bool IsSizeReached { get; private set; }
public FileIO()
{
}
internal async Task WriteToFileAsync(string filePath)
{
if (this.IsSizeReached)
{
return;
}
using (var sw = new StreamWriter(filePath, true))
{
await sw.WriteLineAsync(Guid.NewGuid());
}
}
internal async Task SizeMonitorAsync(string fPath, int maxSize, IEnumerable<Task> tasks)
{
FileInfo fi = new FileInfo(fPath);
this.IsSizeReached = fi.Length >= maxSize;
if (this.IsSizeReached)
{
return;
}
await Task.WhenAll(tasks);
MessageBox.Show(fi.Length.ToString());
MessageBox.Show("Done");
}
}
}
MainWindow.xaml.cs
namespace WriteFile
{
public partial class MainWindow : Window
{
private DispatcherTimer Timer { get; }
private FileIO Fio { get; }
private FileInfo DestinationFileInfo { get; }
public MainWindow()
{
InitializeComponent();
this.Fio = new FileIO();
}
public void StartTimer()
{
this.Timer = new DispatcherTimer(
TimeSpan.FromMilliseconds(500),
DispatcherPriority.Background,
OnTimerElapsed,
this.Dispatcher);
this.Timer.Start();
}
public void StopTimer() => this.Timer.Stop();
private void OnTimerElapsed(object sender, EventArgs e)
{
this.fileSize.Text = this.DestinationFileInfo.Length.ToString();
}
private async void btnGo_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.ShowDialog();
saveFileDialog.FilterIndex = 2;
saveFileDialog.RestoreDirectory = true;
if (File.Exists(saveFileDialog.FileName))
{
File.Delete(saveFileDialog.FileName);
}
Stream myStream;
if ((myStream = saveFileDialog.OpenFile()) != null)
{
// Dispose StreamWriter and underlying Stream
using (var sw = new StreamWriter(myStream))
{
await sw.WriteAsync(" your text");
}
}
int noOfTasks = 25;
// TODO::You must validate the input to prevent invalid or unreasonable values.
// Currently, this line will throw and crash the application if the input is invalid (not numeric).
int target = Convert.ToInt32(TargetSize.Text);
string fPath = saveFileDialog.FileName;
var tasks = new List<Task>();
this.DestinationFileInfo = new FileInfo(fPath);
StartTimer();
for (int i = 0; i < noOfTasks; i++)
{
Task writeToFileTask = this.Fio.WriteToFileAsync(fPath);
tasks.Add(writeToFileTask);
}
await this.Fio.SizeMonitorAsync(fPath, target, tasks));
StopTimer();
}
}
}
Timers.Timer works asynchronously and it raises the Elapsed event on the pool thread if SynchronizingObject is not set.
And work with WPF UI elements can only be done on the thread of their Dispatcher (almost always this is the main UI thread of the application).
Two solutions out of many possible:
private void Timer_Elapsed(object sender, ElapsedEventArgs e)
{
// Asynchronously getting the right data
string text = fileInfo.Length.ToString();
// Using the Window's Dispatcher
// to setting ready-made data to UI elements.
Dispatcher.BeginInvoke(() => fileSize.Text = text);
}
The second option is without using a timer:
public MainWindow()
{
Initialized += RefreshAsync;
InitializeComponent();
}
public async void RefreshAsync(object? sender, EventArgs e)
{
while(true)
{
string text = await Task.Run(() => fileInfo.Length.ToString());
fileSize.Text = text;
await Task.Delay(500);
}
}

Instantiating the same class multiple times from threads appears as instantiated only once C# WPF

I'm developing an application in C # on WPF.
I need to launch more than once the same MyListWindow class which consists of XAML and the code behind XAML.CS.
The problem is that strange things happen (from my point of view and because of my ignorance).
In particular, I have a main thread that launches a PIPE Server which should launch multiple secondary threads, each of which instantiates a new MyListWindow window.
Strange things happen just when more than one secondary thread is launched from the main thread. First of all he throws me an exception which warns me that the MyListWindow object is already in use by another thread, but it does not make sense, because each secondary thread invokes a new one.
Part of the code is posted below:
namespace Kyactus
{
public partial class App
{
private Registration _reg = new Registration();
public App()
{
if (Process.GetProcessesByName(Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location)).Count() > 1)
{
Process currentProcess = Process.GetCurrentProcess();
if (Environment.GetCommandLineArgs().Length != 2)
{
currentProcess.Kill();
return;
}
var client = new NamedPipeClientStream("IPC_Context_Menu");
client.Connect();
var writer = new StreamWriter(client);
writer.WriteLine(Environment.GetCommandLineArgs()[1]);
writer.Flush();
currentProcess.Kill();
}
else
{
Globals.MainWindow = new MainWindow();
Globals.MainWindow.Show();
StartContextMenuListener();
}
}
public void StartContextMenuListener()
{
Thread t = new Thread(myTask);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
void myTask()
{
NamedPipeServerStream server;
while (true)
{
server = new NamedPipeServerStream("IPC_Context_Menu");
server.WaitForConnection();
var reader = new StreamReader(server);
string FileOrFolderFromContextMenu = reader.ReadLine();
Thread t = new Thread(() =>
{
ListUsersContextMenu listWindow = new ListUsersContextMenu(Globals.MainWindow.UsersXAML.Items, FileOrFolderFromContextMenu);
listWindow.ShowDialog(); // Thorws: System.InvalidOperationException because object 'listWindow' is property of another thread
IList selectedUsers = listWindow.DialogResult();
foreach (User selectedUser in selectedUsers)
{
Client c = new Client();
c.invoke(selectedUser.IP.ToString(), FileOrFolderFromContextMenu, selectedUser.ID, selectedUser.Name);
}
});
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
t.Start();
t.Join();
server.Close();
}
}
}
}
How can I solve this problem? I wish I could run the lambda several times.

C# WPF Method call executes synchronous but not when called in explicit Thread

I'm having a problem running my SaveToFile method in a Thread.
Synchronously it works fine.
I need to save a simple UML diagram containing some classes and such to XML.
I have a SaveLoadController:
public void SaveToFile(ItemsCollection d, string path)
{
using (FileStream fs = File.Create(path))
{
XmlSerializer s = new XmlSerializer(typeof(ItemsCollection), new Type[] { typeof(Item), typeof(ClassViewModel) });
s.Serialize(fs, d);
}
}
And here is the problem:
public void SaveAsToFile()
{
FileDialog fd = new SaveFileDialog();
bool? result = fd.ShowDialog();
if (result.HasValue && result.Value)
{
//Thread t = new Thread(() => saveLoadController.SaveToFile(this.Items, fd.FileName));
saveLoadController.SaveToFile(this.Items, fd.FileName);
}
}
It works as it is now, but if I use the Thread t... line, no file is saved.
Can someone tell me what the problem is?

Writing to StorageFile concurrently

How can I write some text to a StorageFile concurrently in Windows Phone 8.1? I tried to implement mutex provided by lock in C#, but I'm unable to open the stream of the file that I want to write to under in the lock block.
This is what I'm doing:
StorageFile file = await folder.GetFileAsync(logFileName);
// Lock file access object and open filestream for writing.
lock (fileLockObject)
{
StreamWriter writer = new StreamWriter(await file.OpenStreamForWriteAsync());
writer.Write(sometext);
}
But lock doesn't allow any kind of async operation in it's body. How can I still maintain mutex and concurrency without using some third-party library like AsyncLock?
In async operations you need use AsyncLock instead lock
private readonly AsyncLock asyncLocker = new AsyncLock();
.....
using(await asyncLocker.LockAsync())
{
enter code here
}
AsyncLock msdn, github
Usually in this scenario you want the writes to be in order as much as possible and you want to offload the actual writing to another single thread. Below is a simplified example however in most cases you will want to optimize it to end and restart the thread when needed.
AutoResetEvent _waitHandle = new AutoResetEvent(false);
List<string> _writeCache = new List<string>();
bool _threadRunning = false;
void WriteToFile(string text)
{
lock (_writeCache)
{
_writeCache.Add(text);
_waitHandle.Set();
if(_threadRunning)
{
return;
}
_threadRunning = true;
}
Task.Run(async () =>
{
while (true)
{
_waitHandle.WaitOne();
StorageFile file = await folder.GetFileAsync(logFileName);
using (var f = await file.OpenStreamForWriteAsync())
{
using (StreamWriter writer = new StreamWriter(await file.OpenStreamForWriteAsync()))
{
lock (_writeCache)
{
while (_writeCache.Count > 0)
{
string s = _writeCache[0];
_writeCache.RemoveAt(0);
writer.Write(s);
}
}
}
}
}
});
}
I was able to resolve this using Semaphores. I used SlimSemaphore() to acquire and release a semaphore with a single resource before and writing to file. This enabled use of await operation and removed the limitations associated with lock and mutex as you can run await operations in the block (which mutex and lock don't really allow).
// define semaphore with only one resource to allocate.
SemaphoreSlim semaphore = new SemaphoreSlim(1);
// acquire semaphore
await semaphore.WaitAsync();
try { // write to file }
catch { // catch any exception }
finally
{
semaphore.Release();
}
Since you do need to use a lock statement, you could try:
using System.Threading.Tasks;
public async Task WriteToFile(string text)
{
StorageFile file = await folder.GetFileAsync(logFileName);
lock (fileLockObject)
{
Task<Stream> streamTask = file.OpenStreamForWriteAsync();
try
{
streamTask.Wait();
}
catch (AggregateException ex)
{
// You may want to handle errors here
}
if (!streamTask.IsFaulted)
{
using (StreamWriter writer = new StreamWriter(streamTask.Result))
{
writer.Write(text);
}
}
}
}
Usage (supposed this is an event handler, else use public async Task YourMethod(...)):
public async void YourMethod()
{
// 1. Blocks UI thread
Task t = WriteToFile("text");
t.Wait();
// or
// 2. Does not block UI thread
await WriteToFile("text");
}
Try this code
public static void WriteLog(string Error)
{
using (StreamWriter logfile = new StreamWriter(filePath + "Log.txt", true))
{
logfile.WriteLine(DateTime.Now.ToString() + ":" + DateTime.Now.Millisecond.ToString() + " -#: " + Error);
logfile.Close();
}
}

Threading a Function in c# is not Working

I have a problem with this code. The function is playing a music track, so it takes a while to finish executing.... However, even through it is threaded, it does not return untill it is done, holding up the rest of the program. Can I have the function exit so that the program continues but have the music keep on it's own thread. Any solutions are welcome.
using System;
using Gtk;
using NAudio;
using NAudio.Wave;
using System.Threading;
public class Trackbox {
public static void Main() {
Application.Init();
//Create the Window
Window myWin = new Window("Trackbox");
myWin.SetIconFromFile("Assets//logo.png");
myWin.Resize(200, 100);
//Add the label to the form
//myWin.Add(myLabel);
Button playButton = new Button("Play Sound");
//This when playwav is called here, the rest of the application waits for it to finish playing
playButton.Clicked += new EventHandler(playWav);
myWin.Add(playButton);
myWin.DeleteEvent += delegate { Application.Quit(); };
//Show Everything
myWin.ShowAll();
Application.Run();
}
private static void playWav(object sender, EventArgs e)
{
var soundFile = #"C:\sound.wav";
using (var wfr = new WaveFileReader(soundFile))
using (WaveChannel32 wc = new WaveChannel32(wfr) { PadWithZeroes = false })
using (var audioOutput = new DirectSoundOut())
{
audioOutput.Init(wc);
audioOutput.Play();
while (audioOutput.PlaybackState != PlaybackState.Stopped)
{
Thread.Sleep(20);
}
audioOutput.Stop();
}
}
}
Thanks for the help. If you have any ideas please post.
Your playWav is executed on the same thread as your UI is running on. That is why your UI is blocked.
You can start a new thread like this:
private volatile bool _QuitThread;
private void playWav(object sender, EventArgs e)
{
_QuitThread = false;
Thread thread = new Thread(playWavThread);
thread.Start();
}
// This method should be called when the music should stop. Perhapse when a button has been pressed.
private void StopTheMusic()
{
_QuitThread = true;
}
private void playWavThread()
{
var soundFile = #"C:\sound.wav";
using (var wfr = new WaveFileReader(soundFile))
using (WaveChannel32 wc = new WaveChannel32(wfr) { PadWithZeroes = false })
using (var audioOutput = new DirectSoundOut())
{
audioOutput.Init(wc);
audioOutput.Play();
while (!_QuitThread && audioOutput.PlaybackState != PlaybackState.Stopped)
{
Thread.Sleep(20);
}
audioOutput.Stop();
}
}
EDIT
At request, I added code to quit the thread.
DirectSoundOut already creates its own playback thread. Get rid of the Thread.Sleep altogether which is blocking your thread and simply call Play. Subscribe to the PlaybackStopped event to detect when playback has finished. The audioOutput would need to be a class member so you could Dispose it after playback had finished.

Categories

Resources