How to Open Window in New Thread and Pass Data? - c#

I have a Window with a Rich Text Box. How can I open it in a New Thread, then write to the Text Box?
I've tried many different ways with Dispatcher.BeginInvoke and using Thread and BackgroundWorker, but I'm not setting it up right. I get errors such as "The calling thread cannot access this object because a different thread owns it." when trying to open the window or write.
I start a hidden background window using Hide(), open with Show():
MainWindow mainwindow = this;
myWindow = new NewWindow(mainwindow);
myWindow.Left = this.Left + 605;
myWindow.Top = this.Top + 0;
myWindow.Hide();
myWindow.myRichTextBox.Cursor = Cursors.Arrow;
I write to it using Actions that are saved to a list, then called at a certain time:
List<Action> LogActions = new List<Action>();
Action WriteAction;
// Create Multiple Actions
WriteAction = () =>
{
myWindow.myRichTextBox.Document = new FlowDocument(paragraph);
paragraph.Inlines.Add(new LineBreak());
paragraph.Inlines.Add(new Bold(new Run("Example")) { Foreground = Brushes.White });
};
// Add Actions to List
LogActions.Add(WriteAction);
// Write All Actions in List
foreach (Action Write in LogActions)
{
Write();
}
To simplify, it is basically doing:
myWindow.myRichTextBox.AppendText("Example");
It is able to pass data without new thread right now. But the mouse freezes for a few seconds when the Rich Text Box is being written to, I thought this might free it up. Would anyone advise against opening in new thread?

It's usually best practice for the user interface to stay in the main application thread, where the UI can spawn background threads to perform longer-running tasks. This way, the UI thread can still respond to user input while the background work is underway.
In the case of a Window, myWindow with a Rich Text Box that you want to append messages to, myWindow would use a BackgroundWorker to gather the messages in a separate thread and then update the RTF control through a callback on thread termination (for a BackgroundWorker this is the OnRunWorkerCompleted method).
If the window must run as a separate thread, this helper class will wrap a WPF Window in a new thread and set it up with a new SynchronizationContext, however, you will still need thread-safe messaging if you want to communicate between windows in different threads. The ConcurrentQueue<T> collection provides a thread-safe collection that may be useful for this purpose.
public static class WindowThreadHelpers
{
public static void LaunchWindowInNewThread<T>() where T : Window, new()
{
Dispatcher.CurrentDispatcher.Invoke(new ThreadStart(ThreadStartingPoint<T>));
}
private static void ThreadStartingPoint<T>() where T : Window, new()
{
SynchronizationContext.SetSynchronizationContext(
new DispatcherSynchronizationContext(
Dispatcher.CurrentDispatcher));
var win = new T();
win.Closing += (sender, args) =>
{
Dispatcher.ExitAllFrames();
win.Dispatcher.InvokeShutdown();
};
win.Show();
Dispatcher.Run();
}
}
Here's an example of how you would use it from the main thread
WindowThreadHelpers.LaunchWindowInNewThread<NewWindow>();

Related

In a C# class library, closing a WPF LogWatcher Window in its own thread, stops the main thread

I wrote a C# COM exposed class library. I call its exposed methods from third party software.
I wrote a WPF dialog to watch the log entries as the business logic is performed.
The WPF dialog is started on a separate thread, prior to starting the actual method generating log entries. This is working pretty good.
The issue I have is that if I close the logwatcher window before to the main method DoTheActualWorkAsync() is finished, the DoTheActualWorkAsync() method stops running and the methods seems deadlocked.
Any ideas how to solve this? Why does closing a WPF window in a separate thread freeze the main thread?
This is my code:
// Exposed Method
public void DoALotOfWorkWrapper()
{
// Start WPF LogWatchWindow on new thread
var logWatcherThread = this.GetLogWatcherThread();
// Peform a lot of business logic, block for procedure to be ready
DoTheActualWorkAsync().Wait();
// Join logwatcher thread so method doesn't finish before window is closed
WaitForLogWatcherThread(logWatcherThread);
}
private Thread GetLogWatcherThread()
{
var signalEvent = new ManualResetEvent(false);
var logWatchWindowThread = new Thread(() =>
{
var vm = new LogWatchWindowViewModel();
var win = new LogWatchWindow
{
DataContext = vm
};
win.Show();
vm.HookupLog4NetEvents(win);
win.Closed += (sender2, e2) =>
{
vm.DetachLog4NetEvents();
win.Dispatcher.InvokeShutdown();
};
signalEvent.Set();
Dispatcher.Run();
});
logWatchWindowThread.SetApartmentState(ApartmentState.STA);
logWatchWindowThread.Start();
// Block to wait for Window and its viewmodel to be ready for displaying new log4net entries
signalEvent.WaitOne();
signalEvent.Reset();
return logWatchWindowThread;
}
private static void WaitForLogWatcherThread(Thread logWatcherThread)
{
logWatcherThread.Join();
}

WPF modal progress window

I apologize if this question has been answered tons of times, but I can't seem to find an answer that works for me. I would like to create a modal window that shows various progress messages while my application performs long running tasks. These tasks are run on a separate thread and I am able to update the text on the progress window at different stages of the process. The cross-thread communication is all working nicely. The problem is that I can't get the window to be on top of only other application windows (not every application on the computer), stay on top, prevent interaction with the parent window, and still allow the work to continue.
Here's what I've tried so far:
First, my splash window is a custom class that extends the Window class and has methods to update the message box. I create a new instance of the splash class early on and Show/Hide it as needed.
In the simplest of cases, I instantiate the window and call .Show() on it:
//from inside my secondary thread
this._splash.Dispatcher.Invoke(new Action(() => this._splash.Show());
//Do things
//update splash text
//Do more things
//close the splash when done
this._splash.Dispatcher.Invoke(new Action(() => this._splash.Hide());
This correctly displays the window and continues running my code to handle the initialization tasks, but it allows me to click on the parent window and bring that to the front.
Next I tried disabling the main window and re-enabling later:
Application.Current.Dispatcher.Invoke(new Action(() => this.MainWindow.IsEnabled = false));
//show splash, do things, etc
Application.Current.Dispatcher.Invoke(new Action(() => this.MainWindow.IsEnabled = true));
This disables all the elements in the window, but I can still click the main window and bring it in front of the splash screen, which is not what I want.
Next I tried using the topmost property on the splash window. This keeps it in front of everything, and in conjunction with setting the main window IsEnabled property I could prevent interaction, but this makes the splash screen appear in front of EVERYTHING, including other applications. I don't want that either. I just want it to be the topmost window within THIS application.
Then I found posts about using .ShowDialog() instead of .Show(). I tried this, and it correctly showed the dialog and did not allow me to click on the parent window, but calling .ShowDialog() makes the program hang waiting for you to close the dialog before it will continue running code. This is obviously, not what I want either. I suppose I could call ShowDialog() on a different thread so that that thread would hang but the thread doing the work would not...is that the recommended method?
I have also considered the possibility of not using a window at all and instead putting a full-sized window element in front of everything else on the page. This would work except that I have other windows I open and I'd like to be able to use the splash screen when those are open too. If I used a window element I would have to re-create it on every window and I wouldn't be able to use my handy UpdateSplashText method in my custom splash class.
So this brings me to the question. What is the right way to handle this?
Thanks for your time and sorry for the long question but details are important :)
You are correct that ShowDialog gives you most of the UI behavior that you want.
It does have the problem that as soon as you call it you block execution though. How could you possibly run some code after you show the form, but define what it should be before it's shown? That's your problem.
You could just do all of the work within the splash class, but that's rather poor practice due to tight coupling.
What you can do is leverage the Loaded event of Window to define code that should run after the window is shown, but where it is defined before you show it.
public static void DoWorkWithModal(Action<IProgress<string>> work)
{
SplashWindow splash = new SplashWindow();
splash.Loaded += (_, args) =>
{
BackgroundWorker worker = new BackgroundWorker();
Progress<string> progress = new Progress<string>(
data => splash.Text = data);
worker.DoWork += (s, workerArgs) => work(progress);
worker.RunWorkerCompleted +=
(s, workerArgs) => splash.Close();
worker.RunWorkerAsync();
};
splash.ShowDialog();
}
Note that this method is designed to encapsulate the boilerplate code here, so that you can pass in any worker method that accepts the progress indicator and it will do that work in a background thread while showing a generic splash screen that has progress indicated from the worker.
This could then be called something like this:
public void Foo()
{
DoWorkWithModal(progress =>
{
Thread.Sleep(5000);//placeholder for real work;
progress.Report("Finished First Task");
Thread.Sleep(5000);//placeholder for real work;
progress.Report("Finished Second Task");
Thread.Sleep(5000);//placeholder for real work;
progress.Report("Finished Third Task");
});
}
The accepted answer from #Servy helped me a lot! And I wanted to share my Version with the async and MVVM approach. It also contains a small delay to avoid "window flickering" for too fast operations.
Dialog Method:
public static async void ShowModal(Func<IProgress<string>, Task> workAsync, string title = null, TimeSpan? waitTimeDialogShow = null)
{
if (!waitTimeDialogShow.HasValue)
{
waitTimeDialogShow = TimeSpan.FromMilliseconds(300);
}
var progressWindow = new ProgressWindow();
progressWindow.Owner = Application.Current.MainWindow;
var viewModel = progressWindow.DataContext as ProgressWindowViewModel;
Progress<string> progress = new Progress<string>(text => viewModel.Text = text);
if(!string.IsNullOrEmpty(title))
{
viewModel.Title = title;
}
var workingTask = workAsync(progress);
progressWindow.Loaded += async (s, e) =>
{
await workingTask;
progressWindow.Close();
};
await Task.Delay((int)waitTimeDialogShow.Value.TotalMilliseconds);
if (!workingTask.IsCompleted && !workingTask.IsFaulted)
{
progressWindow.ShowDialog();
}
}
Usage:
ShowModal(async progress =>
{
await Task.Delay(5000); // Task 1
progress.Report("Finished first task");
await Task.Delay(5000); // Task 2
progress.Report("Finished second task");
});
Thanks again #Servy, saved me a lot of time.
You can use the Visibility property on Window to hide the whole window while the splash screen runs.
XAML
<Window ... Name="window" />
Code
window.Visibility = System.Windows.Visibility.Hidden;
//show splash
//do work
//end splash
window.Visibility = System.Windows.Visibility.Visible;
You can have your progress window's constructor take a Task and then ensure the window calls task.Start on the OnLoaded event. Then you use ShowDialog from the parent form, which will cause the progress window to start the task.
Note you could also call task.Start in the constructor, or in the parent form anywhere before calling ShowDialog. Whichever makes most sense to you.
Another option would be just to use a progress bar in the status strip of the main window, and get rid of the popup. This option seems to be more and more common these days.
I found a way to make this work by calling ShowDialog() on a separate thread. I created my own ShowMe() and HideMe() methods in my dialog class that handle the work. I also capture the Closing event to prevent closing the dialog so I can re-use it.
Here's my code for my splash screen class:
public partial class StartupSplash : Window
{
private Thread _showHideThread;
public StartupSplash()
{
InitializeComponent();
this.Closing += OnCloseDialog;
}
public string Message
{
get
{
return this.lb_progress.Content.ToString();
}
set
{
if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread)
this.lb_progress.Content = value;
else
this.lb_progress.Dispatcher.Invoke(new Action(() => this.lb_progress.Content = value));
}
}
public void ShowMe()
{
_showHideThread = new Thread(new ParameterizedThreadStart(doShowHideDialog));
_showHideThread.Start(true);
}
public void HideMe()
{
//_showHideThread.Start(false);
this.doShowHideDialog(false);
}
private void doShowHideDialog(object param)
{
bool show = (bool)param;
if (show)
{
if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread)
this.ShowDialog();
else
Application.Current.Dispatcher.Invoke(new Action(() => this.ShowDialog()));
}
else
{
if (Application.Current.Dispatcher.Thread == System.Threading.Thread.CurrentThread)
this.Close();
else
Application.Current.Dispatcher.Invoke(new Action(() => this.Close()));
}
}
private void OnCloseDialog(object sender, CancelEventArgs e)
{
e.Cancel = true;
this.Hide();
}
}

Create a WPF Progress Window on another thread

I'm creating a custom add-in command through the API of a piece of architectural modeling software called Revit. My command may take some time to complete so I want to show the user a window with a progress bar as it is working.
Typically if I were to create a progress window like this it would be on the main UI thread, and the actual work being done would happen on a secondary worker thread. However, Revit requires that any access to the API be through the thread calling the custom command. So I must create my progress bar on a second thread.
I found this blog post about launching a WPF window in a separate thread, and based my solution on it. Here's my custom command class.
public class SampleProgressWindowCommand : Autodesk.Revit.UI.IExternalCommand
{
private ProgressWindow progWindow;
internal static EventWaitHandle _progressWindowWaitHandle;
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
//Starts New Progress Window Thread
using (_progressWindowWaitHandle = new AutoResetEvent(false))
{
//Starts the progress window thread
Thread newprogWindowThread = new Thread(new ThreadStart(ShowProgWindow));
newprogWindowThread.SetApartmentState(ApartmentState.STA);
newprogWindowThread.IsBackground = true;
newprogWindowThread.Start();
//Wait for thread to notify that it has created the window
_progressWindowWaitHandle.WaitOne();
}
//Does some work that takes a long time
for (int i = 1; i <= 100; i++)
{
//Updates Progress
this.progWindow.UpdateProgress("Item " + i.ToString(), i, 100);
//Does some fake work
System.Threading.Thread.Sleep(700);
}
//closes the Progress window
progWindow.Dispatcher.Invoke(new Action(progWindow.Close));
//Show Result to User
Autodesk.Revit.UI.TaskDialog.Show("Task", "Task Completed");
return Result.Succeeded;
}
private void ShowProgWindow()
{
//creates and shows the progress window
progWindow = new ProgressWindow();
progWindow.Show();
//makes sure dispatcher is shut down when the window is closed
progWindow.Closed +=new EventHandler(progWindow_Closed);
//Notifies command thread the window has been created
_progressWindowWaitHandle.Set();
//Starts window dispatcher
System.Windows.Threading.Dispatcher.Run();
}
}
And here is the UpdateProgress() method on my ProgressWindow class
public void UpdateProgress(string message, int current, int total)
{
this.Dispatcher.Invoke(new Action<string, int, int>(
delegate(string m, int v, int t)
{
this.progressBar1.Maximum = System.Convert.ToDouble(t);
this.progressBar1.Value = System.Convert.ToDouble(v);
this.messageLbl.Content = m;
}),
System.Windows.Threading.DispatcherPriority.Background,
message, current, total);
}
My first question is in general did I do this right? It seems to work, but I know enough about multithreaded programing to know that just because it works today, does not mean it's going to work tomorrow.
Second, I would like to add a cancel button to my progress window to be able to cancel the process. What is the best way to do this? I understand that ultimately I'll end up with a "cancelRequested" boolean flag that is checked regularly by the working thread, but how do I set this from the progress window thread?
The only improvement that I can see is that you have a potential race condition between setting your AutoResetEvent and calling Dispatcher.Run. I know because I've run into this issue in my own use of multi-threaded progress UIs.
The way to fix it is to BeginInvoke the call on the background Dispatcher. This will ensure it executes after the Dispatcher has begun pumping events:
System.Windows.Threading.Dispatcher.Current.BeginInvoke(
new Func<bool>(_progressWindowWaitHandle.Set));

Cannot use a DependencyObject that belongs to a different thread than its parent Freezable

Ive got a wpf form, from which i want to display a loading popup as soon as the user makes a choice from the controls, because the loading of the data could take long seeing as the Database is not Local. I got everything working up until where i create the thread for the popup window.
This is where i create my Thread:
public void Start()
{
if (_parent != null)
_parent.IsEnabled = false;
_thread = new Thread(RunThread);
_thread.IsBackground = true;
_thread.SetApartmentState(ApartmentState.STA);
_thread.Start();
_threadStarted = true;
SetProgressMaxValue(10);
Thread th = new Thread(UpdateProgressBar);
th.IsBackground = true;
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
And the thread Method:
private void RunThread()
{
_window = new WindowBusyPopup(IsCancellable);
_window.Closed += new EventHandler(WaitingWindowClosed);
_window.ShowDialog();
}
Now the moment that executes i Get this error :
Cannot use a DependencyObject that belongs to a different thread than its parent Freezable.
Any help would be appreciated :)
Try to use the Dispatcher property of the form.
Dispatcher.BeginInvoke(...)
Or just use the BackgroundWorker class, because it has a method called ReportProgress() to report the progress percentage. This will fire the ProgressChanged event, when you can refresh the value of the progressbar or something...
Cannot use a DependencyObject that belongs to a different thread than its parent Freezable.
This error is observed because your are trying to use a resource(of the type UIElement) which was created in a different thread in your STA thread(which you are using to show the popup window).
In your case it looks like the second thread Thread th = new Thread(UpdateProgressBar); , is trying to manipulate the UI in the WindowBusyPopup. As the popup is owned by a different thread you are getting this exception.
Possible Solution: (as I see you dont show the implementation of the function UpdateProgressBar)
private void UpdateProgressBar()
{
if(_window != null) /* assuming you declared your window in a scope accesible to this function */
_window.Dispatcher.BeginInvoke(new Action( () => {
// write any code to handle children of window here
}));
}

How to construct a new WPF form from a different thread in c#

I'm having trouble with the concept of threads and how to use them.
I'm trying to code a fairly basic chat program (as part of a larger program) and it currently works like this:
The 'NetworkSession' class receives the input from the server on a separate thread in a loop. If it receives input that indicates it should open a new chat window it constructs a new WPF class (ChatWindow) and displays it.
Originally I got the error that "The calling thread must be STA, because many UI components require this.". So i set the thread to be STA but now of course the WPF form is unusable because its running on the same thread as the blocking loop.
So my question is how do I create a new instance of a WPF form from within another thread.
I've seen alot of discussion about this but it tends to deal with running a delegate from a form that has already been constructed.
Here is some code.
while (Connected) //this loop is running on its own thread
{
Resp = srReceiver.ReadLine();
if (Resp.StartsWith("PING")) SendToServer("PONG");
if (Resp.StartsWith("CHAT FROM"))
{
String[] split = Resp.Split(' ');
Console.WriteLine("Incoming Chat from {0}", split[2]);
bool found = false;
if (Chats.Count != 0)
{
foreach (ChatWindow cw in Chats)
{
if (cw.User == split[2])
{
found = true;
cw.AddLine(cw.User, split[3]); // a function that adds a line to the current chat
}
}
}
if (!found)
{
ChatWindow temp = new ChatWindow(split[2], split[3]);
Chats.Add(temp); //this is a collection with T = ChatWindow
temp.Show();
}
}
}
If you're constructing NetworkSession from your UI Thread, you can snag a reference to the current Dispatcher that can manipulate the UI later.
NetworkSession.cs
private Dispatcher _dispatcher;
public NetworkSession()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
//any thread can call this method
public void DoStuff()
{
Action action = () =>
{
ChatWindow temp = new ChatWindow(split[2], split[3]);
Chats.Add(temp);
temp.Show();
};
_dispatcher.BeginInvoke(action);
}
The code below, which I took from here worked for me:
public static void StartChatWindow()
{
Thread thread = new Thread(() =>
{
ChatWindow chatWindow = new ChatWindow();
chatWindow.Chat(); // Do your stuff here, may pass some parameters
chatWindow.Closed += (sender2, e2) =>
// Close the message pump when the window closed
chatWindow.Dispatcher.InvokeShutdown();
// Run the message pump
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
What you really need to do is construct the window/form on your main UI thread. You probably need to define a delegate that you can call from your network thread and that delegate should have a method attached that will call this.Dispatcher.BeginInvoke() -> inside which you will construct your window.
The this.Dispatcher.BeginInvoke() call is necessary to execute code on the UI thread, otherwise even with a delegate, code would be executed on the network thread.
Both the delegate and the method for creating a new chat window should probably be attached to the MainWindow...

Categories