How do I open a window on a new thread? - c#

I have a options window and a window that displays color based on these options and Kinect data. So far everything's on one thread (as far as I know; I haven't done any threading).
Now, I'm adding an option to open a viewer window that will need to be updated with lowest possible latency. All this entails is creating a window and showing it:
viewer = new SkeletalViewer.MainWindow();
viewer.Show();
When this event fires, the color window stops displaying colors (i.e. the event that fires 30 times a second on the main thread stops firing), but the viewer is displayed perfectly. I want the viewer and the color window to both be updated.
From reading other questions, it sounds like the solution is to create the viewer on a new thread. I'm encountering a lot of problems with this, though.
This fires when I click the button to open the viewer:
private void launchViewerThread_Click(object sender, RoutedEventArgs e)
{
Thread viewerThread = new Thread(delegate()
{
viewer = new SkeletalViewer.MainWindow();
viewer.Dispatcher.Invoke(new Action(delegate()
{
viewer.Show();
}));
});
viewerThread.SetApartmentState(ApartmentState.STA); // needs to be STA or throws exception
viewerThread.Start();
}
Regardless of if I just call viewer.Show() or Invoke() it as above, the line throws an exception: Cannot use a DependencyObject that belongs to a different thread than its parent Freezable. Here's how I understand Invoke(): it accesses viewer's dispatcher, which knows what thread the object is running on, and can then call methods from that thread.
Should I be trying to put this viewer on a new thread? Is the problem even a question of threads? The user will not be interacting with the viewer.
Anyone know why this doesn't work? Thanks for the help.

You need to call Show() on the same thread that the window is created on - that's why you are getting the error. Then you also need to start a new Dispatcher instance to get the runtime to manage the window.
private void launchViewerThread_Click(object sender, RoutedEventArgs e)
{
Thread viewerThread = new Thread(delegate()
{
viewer = new SkeletalViewer.MainWindow();
viewer.Show();
System.Windows.Threading.Dispatcher.Run();
});
viewerThread.SetApartmentState(ApartmentState.STA); // needs to be STA or throws exception
viewerThread.Start();
}
See the Multiple Windows/Multiple Threads example at: http://msdn.microsoft.com/en-us/library/ms741870.aspx

So I was running into a similar issue where a new window failed to open on a new thread. The exception was "cannot use a dependencyobject that belongs to a different thread".
The issue ended up being that the window was using a global resource (Background brush). Once I froze the brush resource, the window loaded just fine.

I am not sure if this will solve your problem but can you try creating a thread proc (to open a viewer window) which is executed on a different thread and then have a dispatcher.beginInvoke to update the main window ,
Here is some code-
in the constructor register this
public MainWindow()
{
UpdateColorDelegate += UpdateColorMethod;
}
// delegate and event to update color on mainwindow
public delegate void UpdateColorDelegate(string colorname);
public event UpdateColorDelegate updateMainWindow;
// launches a thread to show viewer
private void launchViewerThread_Click(object sender, RoutedEventArgs e)
{
Thread t = new Thread(this.ThreadProc);
t.Start();
}
// thread proc
public void ThreadProc()
{
// code for viewer window
...
// if you want to access any main window elements then just call DispatchToMainThread method
DispatchToUiThread(color);
}
//
private void DispatchToUiThread(string color)
{
if (updateMainWindow != null)
{
object[] param = new object[1] { color};
Dispatcher.BeginInvoke(updateMainWindow, param);
}
}
// update the mainwindow control's from this method
private void UpdateColorMethod(string colorName)
{
// change control or do whatever with main window controls
}
With this you can update the main window controls without freezing it, Let me know if you have any questions

Related

WPF Window stays open after debugging stops - WPFSurface process

this might sound dumb, I am new in WPF and in my application I open an additional window using Task.Factory.StartNew because the function runs in separate thread. After the debugging stops, this window stays open but without any images, only the text/clicking/animations are shown, but through this I can open other windows, as if the software was still running, which actually does through a process called "WPFSurface.exe", I assume it's some sort of debugging or just a bug but I can't find any information on google or stackoverflow. Here's the code that I use to create the window:
Task.Factory.StartNew(() =>
{
System.Windows.Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>
{
var controls = new ScreenControls(display);
controls.Show();
}));
});
I also close the application using this code (Yes it's binded correctly to Closed event)
private void MainWindow1_Closed(object sender, EventArgs e)
{
Application.Current.Shutdown();
}
Thank you for your time!

White Screen Appearing When using ShowDialog method Of WPF Window after Hiding

I am developing an Add-in for Autodesk Revit, I have created a WPF Window using XAML and C# as shown in image 1. User of my add-in needs to switch between my Window and Revit and my window needs to maintain its state.
Being it is a modal dialog user cannot interact with Revit until my window is opened. So, in this situation I use Hide instead of close window. And when the user needs it again the add-in uses ShowDialog to display the window again.
But the problem is when I show the window again, it appears white washed (no controls are visible) as shown in image 2. And as soon as I resize my window, all the controls appear back and window starts working normally.
Image 1:
Image 2:
public partial class NavigationWindow : Window, INotifyPropertyChanged
{
...
}
using this code to show,
NavigationWindow navigationWindow = new NavigationWindow();
navigationWindow.ShowDialog ();
To Hide Window, I am using,
Application.Current.Dispatcher.Invoke (() =>
{
this.Visibility = System.Windows.Visibility.Hidden;
});
Any ideas on how can this be fixed?
I would recommend to go with a modeless window instead. This is the proper way to keep the window up while interacting with Revit. The way to go about this is to use the IdleEvent or ExternalEvent. Here's a simple scenario with an idle event. Just switch the window.ShowDialog() to window.Show() that will make it modeless.
First you need to add an idle event queue and handler to the IExternalApplication so something like this:
public class AppCommand : IExternalApplication
{
private static Queue<Action<UIApplication>> Tasks;
public Result OnStartup(UIControlledApplication application)
{
Tasks = new Queue<Action<UIApplication>>();
application.Idling += OnIdling;
return Result.Succeeded;
}
private static void OnIdling(object sender, IdlingEventArgs e)
{
var app = (UIApplication)sender;
lock (Tasks)
{
if (Tasks.Count <= 0) return;
var task = Tasks.Dequeue();
task(app);
}
}
public static void EnqueueTask(Action<UIApplication> task)
{
lock (Tasks)
{
Tasks.Enqueue(task);
}
}
}
So, Idling event is going to fire when Revit is not doing anything. That's a perfect time to interact with Revit from another thread (UI). So when you are in a modless dialog and want to send a task to Revit, you can just use the EnquuqTask utility to put a task into a queue and then Revit when it's not busy will fire Idling event, dequeue the task and execute it. This guarantees that you are not executing anything outside of Revit's scope, and will let you keep the window in modeless state allowing for interaction with Revit while the window is open.
Here's how to add a task to queue from anywhere:
EnqueueTask(app =>
{
//do something
//use app object to interact with Revit
});

Error loading custom user controls from different threads

I have a WPF project and from the main window i am creating and loading some bunch of user controls, there is some large data i am loading in background and then updating a built-in control throw the dispatcher, that works fine, the problem is that some of the user controls loads a lot of data, for example the very first thing i load in the main area of my main window, what i want is to put a loading label instead, load the main window as fast as possible so the user see this label and run in background the creation of that user control and when is done add it as a child of my main container area on my main window while i remove the loading label, if i follow the same philosophy i run into the same error like when i run a task and then try to update the window without using the dispatcher. i want to be able of create the user control asynchronous then update the main window.
Code:
User Control:
public partial class CustomUserControlGallery : UserControl
{
public CustomUserControlGallery()
{
InitializeComponent();
}
...
}
On the backend class of the main window:
public partial class MainWindow : Window
{
CustomUserControlGallery _customUserControlGallery;
public MainWindow()
{
InitializeComponent();
Task t = new Task({
//Can't use the _customUserControlGallery's Dispatcher because object is uninitialized and this.Dispatcher not working either.
_customUserControlGallery = new CustomUserControlGallery(); //Error Here.
_gridContainer.Dispatcher.Invoke(new Action(() => _gridContainer.Children.Add(_customUserControlGallery)));
_loadingLabel.Visbility = Visibility.Collapse;
});
t.Start();
}
...
}
I don't know how to handle this situation with the thread associated to the user control and the main thread.
Error:
{"The calling thread must be STA, because many UI components require this."}
You're doing this wrong. All controls must be created & operate on the UI Thread. That said, you can use the BackgroundWorker class to load the data.
You typically do this by disabling the control whose data is being loaded in the background or hiding it & displaying a progress indicator in its place. Then, you start your BackgroundWorker. That can communicate how far along it is using the ReportProgress method. Finally, when it's finished running, the RunWorkerCompleted event is fired, and you use that to either enable the control, or to hide the progress indicator & show the control.
Some quick & dirty (untested) code:
Place this in your Initialize() or control constructor:
private BackgroundWorker loadData = new BackgroundWorker();
loadData.DoWork += loadData_DoWork;
loadData.ProgressChanged += loadData_ProgressChanged; // Only do this if you are going to report progress
loadData.WorkerReportsProgress = true;
loadData.WorkerSupportsCancellation = false; // You can set this to true if you provide a Cancel button
loadData.RunWorkerCompleted += loadData_RunWorkerCompleted;
private void DoWork( object sender, DoWorkEventArgs e ) {
BackgroundWorker worker = sender as BackgroundWorker;
bool done = false;
while ( !done ) {
// If you want to check for cancellation, include this if statement
if ( worker.CancellationPending ) {
e.Cancel = true;
return;
}
// Your code to load the data goes here.
// If you wish to display progress updates, compute how far along you are and call ReportProgress here.
}
}
private void loadData_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
// You code to report the progress goes here.
}
private void loadData_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
// Your code to do whatever is necessary to put the UI into the completed state goes here.
}
What you are essentially saying (I think) is that Your app becomes sluggish while your control renders a large amount of data.
This is a problem that needs to be solved via virtualisation. You cannot create a control on a background thread, have it render its data behind the scenes and then pop it into existence. You can create controls on separate dispatchers, but they cannot share the same visual and logical tree, so you will not be able to have one as a child of the other.
Virtualisation is what you need to focus on. Depending on the control you can use a variety of virtualisation settings. Try googleing the subject as there is a lot of information on how to achieve this effectively. Most likely you will want to use things like virtualizing stackpanels and container recycling.
You cannot create UI controls with different Dispatchers and use them with each other. It's just not allowed. What you want to do is on your Task you do the heavy lifting work without UI updates and when it is done you push it back to the Dispatcher to update the UI.
In your case, I wouldn't even use Dispatcher.Invoke. Since you are using Task, it has a TaskScheduler.FromCurrentSynchronizationContext() that you can pass in the constructor.
What is the purpose of instantiating controls in a different thread if you're just going to put it back to the Main dispatcher? It's not expensive to do that.

How to open second UI in separate thread

I am working with a team on an application that has a report generator and then a report viewer. At the moment we have not been able to open up the report viewer in a separate thread. These two applications should be independent once opened, and if one is closed it should not effect the other.
The Report Viewer has one UI set of files and the main UI has another set. My question is how can we open up the Viewer UI in a separate thread once the main UI is back in it's "idle" state (not hidden, just not processing anything)?
Any short code snippets would be helpful, at this point I am completely lost on how to open up the viewer in a new thread...
Perhaps something along the lines of this:
private void MethodName {
System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(ThreadProc));
t.Start();
}
and the ThreadProc code will look like this:
public static void ThreadProc()
{
Application.Run(new Application_Name());
}
MethodName (for me) is actually
serverToolStripMenuItem_Click(object sender, EventArgs e)
As it is an event-driven code.
Application_Name will be the form or Application you want to run.
Hope this helps.

Removing a Control from a Form

So I've got some serious problems with removing a Control from a Form of my application. It's kinda messed up but I can't change anything. I have a form and I have a separated user Control. The control opens an exe file and shows a progress bar while loading it's bytes. And here comes the problem. I do all of it with a BackgroundWorker and when the worker_DoWorkerCompleted method is called the original form should show a MessageBox and remove the Control.
BackGround_Loader bgLoad = new BackGround_Loader();
bgLoad.Location = new Point(this.Width/2 - bgLoad.Width/2, this.Height/2 - bgLoad.Height/2);
this.Controls.Add(bgLoad);
bgLoad.BringToFront();
bgLoad.AddReferences(this.executableFile, this.SourceReader);
bgLoad.occuredEvent();
At first I set the control's location to be in the middle of the Form itself. Then I add the control to the form, and bring it to the front. After these I send the path of the executable and a RichTextBox's reference to this. With the occuredEvent I start the BackgroundWorker itself. And here comes my problem. I should show a MessageBox in the Form when the in the bgLoad the backgroundworker gets to the DoWorkerCompleted status. Kindly I have no idea how to do it. It works just perfect however the control stays in the middle of the form.
UI actions must be performed on the main UI thread. The events that get raised from the background worker thread are (obviously) in a different thread.
You need something like the following code:
private void backgroundWorker_DoWork(object sender, AlbumInfoEventArgs e)
{
// Check with an element on the form whether this is a cross thread call
if (dataGridView.InvokeRequired)
{
dataGridView.Invoke((MethodInvoker)delegate { AddToGrid(e.AlbumInfo); });
}
else
{
AddToGrid(e.AlbumInfo);
}
}
In this case AddToGrid is my method for adding a row to a DataGridView, but in your case it will be a method that does what you need to do.
Similarly for the backgroundWorker_RunWorkerCompleted method
See this MSDN example
I could find a way to solve the problem but I don't really like it. In the addReferences method I pass the Form itself and an object of the bgLoad class. Then in the RunWorkerCompleted I check if the control is on the form and if it is then I remove it.
bgLoad.AddReferences(this, bgLoad, this.executableFile, this.SourceReader);
...
private void worker_DoWorkerCompleted(object sender, DoWorkerEventArgs e) {
if(this.MainForm.Controls.Contains(this.Control) {
this.MainForm.Controls.Remove(this.Control);
}
}
Like this it works but it's awful for me.

Categories