I'm new to WPF (and computer science in general) and I was given a small project from my boss where there are 5 tabs, each that go to a different site. After logging in, the user is directed to the website of the 1st tab. In the background, the 4 other tabs should be loading in the background.
Each has the following name: "tabItem1", "tabItem2", "tabItem3", "tabItem4" up to "tabItem5"
Inside each there is a up to "webBrowser5".
I think that I have to use threading to load the pages in the background, but I'm not sure how to implement it. I tried creating 4 different threads in the MainWindow such as:
public MainWindow()
{
InitializeComponent();
Thread thread1 = new Thread(Update1);
thread1.SetApartmentState(ApartmentState.STA);
thread1.Start();
Thread thread2 = new Thread(Update2);
thread2.SetApartmentState(ApartmentState.STA);
thread2.Start();
Thread thread3 = new Thread(Update3);
thread3.SetApartmentState(ApartmentState.STA);
thread3.Start();
Thread thread4 = new Thread(Update4);
thread4.SetApartmentState(ApartmentState.STA);
thread4.Start();
}
private void Update1()
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate()
{
tabItem2.Focus();
}
);
}
private void Update2()
{
this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(ThreadStart)delegate()
{
tabItem3.Focus();
}
);
}
//...Goes up to Update4
This seems to only focus on the last thread and also it doesn't do it the background. I appreciate any guidance and help. Thanks!
That IS what the dispatcher is doing, operating on the main thread. You told the main thread to set focus because the object is on the main thread.
The problem:
The answer lies in why you must do this. You cannot operate on a windows control in any other thread than the thread it was created on. You also can't create a control in one thread, and set it as a child of a control in another thread.
What this means to you... is that what your boss asked you to do can't be done. The only thing you can do in the background is calculate algorithms. At best, you'll be able to load data, and operate on data, and interpret data, but if you want to have that data be displayed in, or converted into, windows controls, you must do that on the main thread.
The only solution:
However, you can have multiple UIThreads. Which means you can create multiple Windows. So, the must-do alternative is to create windows on separate threads for each tab content, then host the thread-windows on each tab.
Threading windows
Host process window
Cross thread hosting
I do not agree with Xaade, I think you can do what you need using only this code:
Dispatcher.BeginInvoke((Action)(() =>
{
// load the pages
}), DispatcherPriority.Background, null);
That code will be executed in background, so, there you can load every webBrowser you need.
You just need to specify the DispatcherPriority to Background.
Related
I have to show a progress window in a different thread.
This is what I've done:
Thread loadT = new Thread(new ThreadStart(() =>
{
Loading ldd = new Loading();
ldd.SetContentMessage("Loading...");
ldd.Closed += (s, ec) =>
Dispatcher.CurrentDispatcher.BeginInvokeShutdown(DispatcherPriority.Background);
ldd.ShowDialog();
}));
loadT.SetApartmentState(ApartmentState.STA);
loadT.Start();
//do something
loadT.Abort();
But I don't think this is the right way. I want to use this window for different processes and i also want to set the window on the top of the others. Which is the best approach?
Ty!
You should do this the other way around when you are waiting for data and stuff to load.
Loading ldd = new Loading();
ldd.SetContentMessage("Loading...");
ldd.ShowDialog();
Thread loadT = new Thread(new ThreadStart() =>
{
//Do stuff here
});
loadT.Start();
Then you can get setup some events and such to either post updates to the loading window, or just leave it as is. You can also either monitor the threads state within the Loading window and close itself when the thread is complete or close the window from the thread.
as an example you could modify Loading to take a Thread as its parameter.
Thread loadT = new Thread(new ThreadStart() =>
{
//Do stuff here
});
Loading ldd = new Loading(loadT);
ldd.ShowDialog();
You can then move the starting of the thread, and monitoring of the thread/closing the window into the Loading class and it can look after itself.
There are 900,000 ways you can do this. You can also use BackgroundWorkers instead of spawning a new Thread, or you can use async/await in .Net 4.5+. Threading like this has been exhaustively done in the past and there should be lots of resources on google to help you in whatever path you decide to take. The important takeaway from this is your window should really be on the UI thread, and your loading should be done on another thread, not the other way around.
You could refer to the following blog post for an example of how to launch a WPF window in a separate thread the right way: http://reedcopsey.com/2011/11/28/launching-a-wpf-window-in-a-separate-thread-part-1/.
But you won't be able to mix controls that are created on different threads. A control can only be accessed on the thread on which it was originally created so it makes no sense to create a control on one thread and then trying to use it on another because this simply won't work because of the thread affinity.
Displaying a stand-alone top-level read-only window during the time a long-running operation is in progress is fine but you should probably close this window as soon as the operation has completed. You won't be able to move controls from this window to another one that was created on another thread anyway.
A WPF app has an operation of loading a user control from a separate file using XamlReader.Load() method:
StreamReader mysr = new StreamReader(pathToFile);
DependencyObject rootObject = XamlReader.Load(mysr.BaseStream) as DependencyObject;
ContentControl displayPage = FindName("displayContentControl") as ContentControl;
displayPage.Content = rootObject;
The process takes some time due to the size of the file, so UI becomes frozen for several seconds.
For keeping the app responsive I try to use a Background thread for performing the part of operation that is not directly involed in UI updating.
When trying to use BackgroundWorker I got an error: The calling thread must be STA, because many UI components require this
So, I went another way:
private Thread _backgroundThread;
_backgroundThread = new Thread(DoReadFile);
_backgroundThread.SetApartmentState(ApartmentState.STA);
_backgroundThread.Start();
void DoReadFile()
{
StreamReader mysr3 = new StreamReader(path2);
Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(Action<StreamReader>)FinishedReading,
mysr3);
}
void FinishedReading(StreamReader stream)
{
DependencyObject rootObject = XamlReader.Load(stream.BaseStream) as DependencyObject;
ContentControl displayPage = FindName("displayContentControl") as ContentControl;
displayPage.Content = rootObject;
}
This solves nothing because all time consuming operations remain in UI thread.
When I try like this, making all parsing in the background:
private Thread _backgroundThread;
_backgroundThread = new Thread(DoReadFile);
_backgroundThread.SetApartmentState(ApartmentState.STA);
_backgroundThread.Start();
void DoReadFile()
{
StreamReader mysr3 = new StreamReader(path2);
DependencyObject rootObject3 = XamlReader.Load(mysr3.BaseStream) as DependencyObject;
Dispatcher.BeginInvoke(
DispatcherPriority.Normal,
(Action<DependencyObject>)FinishedReading,
rootObject3);
}
void FinishedReading(DependencyObject rootObject)
{
ContentControl displayPage = FindName("displayContentControl") as ContentControl;
displayPage.Content = rootObject;
}
I got an exception: The calling thread cannot access this object because a different thread owns it. (in the loaded UserControl there are other controls present which maybe give the error)
Is there any way to perform this operation in such a way the UI to be responsive?
Getting the XAML to load an a background thread is essentially a non-starter. WPF components have thread affinity and are generally speaking only usable from the threads they are created one. So loading on a background thread will make the UI responsive but create components which then cannot be plugged into the UI thread.
The best option you have here is to break up the XAML file into smaller pieces and incrementally load them in the UI thread making sure to allow for a message pump in between every load operation. Possibly using BeginInvoke on the Dispatcher object to schedule the loads.
As you found out, you can't use XamlReader.Load unless the thread is STA and even if it is, you will have to have it start a message pump and funnel all access to the controls it created through that. This is a fundamental way of how WPF works and you can't go against it.
So your only real options are:
Break down the XAML into smaller pieces.
Start a new STA thread for each Load call. After Load returns, the thread will need to start a message loop and manage the controls it created. Your application will have to take into account the fact that different controls are now owned by different threads.
I don't have exact solution but you can get some direction from following links.
http://www.codehosting.net/blog/BlogEngine/post/Opening-WPF-Windows-on-a-new-thread.aspx
http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/
System.Xaml has a Xaml​Background​Reader class, perhaps you could get that to work for you. Parser the XAML on the background thread but build the objects on the UI thread.
You can call a method that allows to give control to another thread:
http://msdn.microsoft.com/en-us/library/ms748331.aspx
It is called .Freeze()
I am trying to run an application with at least two threads: One form for the user interface and one or more worker threads. They are jointly reading/writing from a static class through a number of other classes.
This is why I am passing an instance of the worker class to the display form. I guess that is why it goes wrong for me:
public class CoCoon
{
private Screen displayForm;
private Worker worker;
public ThreadedApp()
{
worker = new Worker (1024);
displayForm = new Screen(worker);
}
public void Run()
{
//thread 1: display form
Thread screenThread = new Thread(() => Application.Run(displayForm));
//thread 2: background worker
Thread workerThread = new Thread(worker.Run) {IsBackground = true};
screenThread.Start();
Thread.Sleep(1000); //if I don't wait a while, I get an ObjectDisposedException!
workerThread.Start();
}
The threads and objects are initiated just fine, but as soon after the Form_Load method is has been handled, an error is thrown on the Application.Run(displayForm) line above. It is an NotSupportedException, with a remark that I should use Control.Invoke. But I am not sure I understand, because I am not letting threads other than the display form's use the controls on it.
I am new to threading. Can anyone help me on my way? Thanks!
PS: One detail - I am developing this for the Windows Mobile platform.
EDIT After popular request hereby the Stack Trace
at Microsoft.AGL.Common.MISC.HandleAr(PAL_ERROR ar)\r\n at
System.Windows.Forms.Control.get_Visible()\r\n at
System.Windows.Forms.Form._SetVisibleNotify(Boolean fVis)\r\n at
System.Windows.Forms.Control.set_Visible(Boolean value)\r\n at
System.Windows.Forms.Application.Run(Form fm)\r\n at
CoCoonWM6.CoCoon.<Run>b__1()\r\n
I recommend that you only have one UI thread, the main thread. You can use your other threads for background operations, but keep all UI stuff on the main thread.
The UI thread should be the only one calling Application.Run. WinForms has other requirements for the UI thread (such as being STA), and those are satisfied by the main thread. In theory, it may be possible for WinForms to support two UI threads, but it's certainly not easy.
I normally recommend other forms of synchronization when you need to update UI controls from a background thread - TaskScheduler or SynchronizationContext. On the mobile platform, unfortunately, your only option is Control.Invoke.
Check out the stack trace for the exception (and post it). You are almost certainly accessing some Control from the worker thread.
This is how you can modify access to a Control (in this example a Label) after you find where you are accessing controls from non-UI threads:
if (label13.InvokeRequired)
{
ChangeTextDelegate changeText = new ChangeTextDelegate(anyChangeTextMethod);
label13.Invoke(changeText, new object[] { newText });
}
else
{
label13.Text = newText;
}
Looks like you're trying to use GUI elements in the background thread. That would explain why you have to call Sleep (otherwise the form and its controls don't finish loading before you try to use them) as well as the Control.Invoke exception (you can't use GUI elements from a non-UI thread). See the docs for Control.Invoke for how you should use it.
Since you don't have BackgroundWorker and Px in the CF, you're indeed forced to use threads directly - though the ThreadPool would probably be better than instantiating new threads, most of the time.
I have a Windows Form Application (Form1) that allow the user to open another Forms (FormGraph). In order to open the FormGraph App I use a thread that open it.
Here is the code that the thread is running:
private void ThreadCreateCurvedGraph()
{
FormGraph myGraph = new FormGraph();
myGraph.CreateCurvedGraph(...);
myGraph.Show();
}
My problem is that myGraph closed right after it's open.
1) Does anyone know why this is happening and how to make myGraph stay open?
2) After the user closed myGraph, How do I terminate the thread?
Many thanks!
The problem is not in the posted snippet. You'll need to start a new message loop with Application.Run() or Form.ShowDialog(). You'll also need to take care of thread properties so it is suitable to act as a UI thread. For example:
Thread t = new Thread(() => {
Application.Run(new Form2());
// OR:
//new Form2().ShowDialog();
});
t.SetApartmentState(ApartmentState.STA);
t.IsBackground = true;
t.Start();
There are some awkward choices here. The form cannot be owned by any form on your main thread, that usually causes Z-order problems. You'll also need to do something meaningful when the UI thread's main form is closed. Sloppily solved here by using IsBackground.
Windows was designed to support multiple windows running on one thread. Only use code like this if you really have to. You should never have to...
The main problem you ahve is that you do not establish a message pump in the new thread.
Check
Run multiple UI Threads
for a good overview how to run a high perforamnce user interface using multiple threads (one per form / group of forms).
What you basically miss is the call to Application.Run to set up the message pump on the separate UI thread.
I think once the last form of a message pump closes - it will dispose itself and end.
Note that all this ASSUMES you WANT to open the window in a separate UI thread... otherwise you need to invoke back to the main UI thread for the creation and all manipulation of the window, so it gets attached to the existing message pump. There are GOOD cases for both - one keeps thigns simple, the other allows a LOT more performance as every window has a separate message pump and can thus act individually - this is for example used a lot in trading applications which may need to update graphs on a number of screens and havea bottleneck if running single threaded in the UI.
As a rule of thumb you should avoid manipulating the UI from threads (creating a form is a sort of manipulation to the UI). You should always manipulate the UI from the main thread.
The form is closing because the thread has finished and is therefore free'd along with its resouces (the form). To make the thread stay running you need a loop
e.g.
private void ThreadCreateCurvedGraph()
{
FormGraph myGraph = new FormGraph();
myGraph.CreateCurvedGraph(...);
myGraph.Show();
while (myGraph.IsOpen)
{
//process incoming messages <- this could be fun on a thread....
}
}
You'll need a method of setting IsOpen (like a timeout or a button) and obviously you'll need to actually create IsOpen as a property of the form and set it to true when the form is created.
I'll add here the same as other users... You should have a good reason for not using the main thread.
If it takes a while to prepare the data for the form, you can do that in a separate thread to keep the application responsive. When the data is ready you can return the object to the main thread an let it show it.
You should declare a variable for the object in the form rather than locally in the method, so that it survives when you exit the thread.
When you are ready to show the form, you can use the Invoke method to make a method call that will be executed in the main thread.
do not create and show forms in non-main thread. do it in main form thread.
Or do this:
private void ThreadCreateCurvedGraph()
{
FormGraph myGraph = new FormGraph();
myGraph.CreateCurvedGraph(...);
Application.Run(myGraph);
}
but first version is better
Why are you creating a form on a new thread? There are times you need to use a new thread but other times you can use form.ShowDialog() on the main thread.
What about if you show the form as if it was a Dialog? You can use
private void ThreadCreateCurvedGraph()
{
FormGraph myGraph = new FormGraph();
myGraph.CreateCurvedGraph(...);
myGraph.ShowDialog();
}
This way the call will block until the myGraph form is closed. As you have the myGraph created on a separated thread calling the blocking ShowDialog should block only that thread.
Perhaps this is garbage collection:
After ThreadCreateCurvedGraph() exits, myGraph goes out of scope and closes.
You need to organise a way of the thread to hold on to the instance and wait (using a blocking wait) for it to close.
Edit: For instance add:
Application.Run(myGraph)
to the end of the method.
(See comments from TomTom)
I have recently started programming in WPF and bumped into the following problem. I don't understand how to use the Dispatcher.Invoke() method. I have experience in threading and I have made a few simple Windows Forms programs where I just used the
Control.CheckForIllegalCrossThreadCalls = false;
Yes I know that is pretty lame but these were simple monitoring applications.
The fact is now I am making a WPF application which retrieves data in the background, I start off a new thread to make the call to retrieve the data (from a webserver), now I want to display it on my WPF form. The thing is, I cannot set any control from this thread. Not even a label or anything. How can this be resolved?
Answer comments:
#Jalfp:
So I use this Dispatcher method in the 'new tread' when I get the data? Or should I make a background worker retrieve the data, put it into a field and start a new thread that waits till this field is filled and call the dispatcher to show the retrieved data into the controls?
The first thing is to understand that, the Dispatcher is not designed to run long blocking operation (such as retrieving data from a WebServer...). You can use the Dispatcher when you want to run an operation that will be executed on the UI thread (such as updating the value of a progress bar).
What you can do is to retrieve your data in a background worker and use the ReportProgress method to propagate changes in the UI thread.
If you really need to use the Dispatcher directly, it's pretty simple:
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(() => this.progressBar.Value = 50));
japf has answer it correctly. Just in case if you are looking at multi-line actions, you can write as below.
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(() => {
this.progressBar.Value = 50;
}));
Information for other users who want to know about performance:
If your code NEED to be written for high performance, you can first check if the invoke is required by using CheckAccess flag.
if(Application.Current.Dispatcher.CheckAccess())
{
this.progressBar.Value = 50;
}
else
{
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background,
new Action(() => {
this.progressBar.Value = 50;
}));
}
Note that method CheckAccess() is hidden from Visual Studio 2015 so just write it without expecting intellisense to show it up. Note that CheckAccess has overhead on performance (overhead in few nanoseconds). It's only better when you want to save that microsecond required to perform the 'invoke' at any cost. Also, there is always option to create two methods (on with invoke, and other without) when calling method is sure if it's in UI Thread or not. It's only rarest of rare case when you should be looking at this aspect of dispatcher.
When a thread is executing and you want to execute the main UI thread which is blocked by current thread, then use the below:
current thread:
Dispatcher.CurrentDispatcher.Invoke(MethodName,
new object[] { parameter1, parameter2 }); // if passing 2 parameters to method.
Main UI thread:
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Background, new Action(() => MethodName(parameter)));
The #japf answer above is working fine and in my case I wanted to change the mouse cursor from a Spinning Wheel back to the normal Arrow once the CEF Browser finished loading the page. In case it can help someone, here is the code:
private void Browser_LoadingStateChanged(object sender, CefSharp.LoadingStateChangedEventArgs e) {
if (!e.IsLoading) {
// set the cursor back to arrow
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background,
new Action(() => Mouse.OverrideCursor = Cursors.Arrow));
}
}