I have a WPF app that when a button is clicked, a new Window (BrowserWindow) is instantiated that loads a full screen WebBrowser control. The Window is kicked off on a second thread like this.
private void BrowserThreadStart(BrowserWindow browser, String address)
{
browser = new BrowserWindow();
browser.LoadPage(address);
browser.Show();
System.Windows.Threading.Dispatcher.Run();
}
private void Press(object sender, MouseButtonEventArgs e)
{
Thread mainBrowserThread = new Thread(() => BrowserThreadStart(myBrowser, "http://www.google.com"));
mainBrowserThread.SetApartmentState(ApartmentState.STA);
mainBrowserThread.IsBackground = true;
mainBrowserThread.Start();
}
That much works great.
Based on this, what is the proper way for my MainWindow to programatically Hide or Close the BrowserWindow instance running on the separate thread?
I noticed on my main thread (in MainWindow), the BrowserWindow myBrowser is null (even though I can see it up and running on the second thread.
You are "passing by value" which means BrowserThreadStart is modify a copy of the reference not the reference. You need to add ref or out to browser in your BrowserThreadStart method declaration and method call. Here is an example you can substitute ref for out, depending on need and preference.
Try changing:
private void BrowserThreadStart(BrowserWindow browser, String address)
to
private void BrowserThreadStart(out BrowserWindow browser, String address)
and
Thread mainBrowserThread = new Thread(() => BrowserThreadStart(myBrowser, "http://www.google.com"));
to
Thread mainBrowserThread = new Thread(() => BrowserThreadStart(out myBrowser, "http://www.google.com"));
Related
So I have to update a program to use a newer version of Awesomium, specifically 1.7.5
Well with the update Awesomium now has to operate on it's own thread, and it's blocking.
I can queue work to the blocking thread using WebCore.QueueWork() and this will complete the action passed on the thread WebCore.Run() was called. I made sure to give it it's own thread so the rest of my application isn't blocking.
The way the program used to function was by creating a worker object that had a constructor which instantiated a WebView and WebSession using the WebCore library. It then created a form which accepts a worker object as an argument which allows the form to subscribe to events from the WebCore library.
var worker = new Worker();
var debugForm = new PBForm(worker);
debugForm.Show();
The worker constructor has this line of code which calls the function SurfaceIsDirty whenever the view is updated.
((ImageSurface)_view.Surface).Updated += (s, e) => { if (webView_SurfaceIsDirty != null) webView_SurfaceIsDirty(s, e); };
This function is assigned in the form constructor:
this.worker.webView_SurfaceIsDirty = (sender, e) =>
{
ImageSurface buffer = (ImageSurface)this.worker._view.Surface;
pictureBox1.Image = buffer.Image;
};
So the form picture updates whenever the WebView is updated.
This used to be able to run in the WebCore thread but now since the WebCore thread is blocking I can't get this form to work properly on it.
So this is where I'm stuck. I need to run the Form in a separate thread so it doesn't just hang because it's stuck with the WebCore thread which is blocking.
My idea is as follows:
When a worker is created create a form in a new thread as a property of the worker instance.
When a WebCore event occurs the worker instance should be able to update it's Form.
It's compiling, the form is responsive, yet the picture is not updating and I suspect it's related to the form being in a different thread now. Here's the relevant code I have right now:
I added this property to the worker class:
public PBForm2 DebugForm;
I instantiate the worker class in the WebCore blocking thread:
WebCore.QueueWork(AddWorker);
In the AddWorker method I make a new thread and run a Form while attaching it to the worker property:
static void AddWorker()
{
var worker = new Worker();
Workers.Add(worker);
new Thread(() =>
{
worker.DebugForm = new PBForm2(worker.Id);
var debugForm = new PBForm2(worker.Id);
Application.Run(debugForm);
Application.DoEvents();
}).Start();
}
And finally the worker event itself is now:
((ImageSurface)_view.Surface).Updated += (s, e) =>
{
ImageSurface buffer = (ImageSurface)_view.Surface;
DebugForm.pictureBox1.Image = buffer.Image;
DebugForm.pictureBox1.Refresh();
};
It seems very close to working, the form responds to user interaction and the workers are doing their thing and triggering events, but the picture isn't changing in the form. The event is getting hit and the new image is there, I suspect the fact that the form is in a different thread is causing the image on the form to not update.
This was a very long post so if you are reading this thank you for taking the time to get through it all. I'm very much a novice when it comes to threading and any suggestions or links or even what exactly to search up to solve this issue would be greatly appreciated.
You are creating 2 of the same forms:
worker.DebugForm = new PBForm2(worker.Id);
var debugForm = new PBForm2(worker.Id);
then loading debugForm, but your updates are being done to DebugForm.picturebox1 so your updates will not be seen. Updates would need to be done to debugForm.picturebox1, but you should only have one created.
Without seeing all the code, why not just load the one in the worker class or point one to the other?
Application.Run(worker.DebugForm);
Application.DoEvents();
or
worker.DebugForm = new PBForm2(worker.Id);
var debugForm = worker.DebugForm;
Application.Run(debugForm);
Application.DoEvents();
I figured it out, after fixing the issue where I was updating the wrong Form object (thanks Troy Mac1ure) I ran into a threading issue where I couldn't access the Form's picturebox from the Awesomium thread.
I solved it using a helper class:
public static class ThreadHelper
{
private delegate void SetPictureCallback(PBForm f, Image image);
private delegate void AppendTextCallback(PBForm f, string text);
public static void SetPicture(PBForm form, Image image)
{
if (form.InvokeRequired)
{
SetPictureCallback d = SetPicture;
form.Invoke(d, form, image);
}
else
{
form.pictureBox1.Image = image;
form.pictureBox1.Refresh();
}
}
public static void AppendText(PBForm form, string text)
{
if (form.InvokeRequired)
{
AppendTextCallback d = AppendText;
form.Invoke(d, form, text);
}
else
{
form.textBox1.Text += text;
form.textBox1.SelectionStart = form.textBox1.TextLength - 1;
form.textBox1.ScrollToCaret();
form.textBox1.ScrollToCaret();
}
}
}
When the event is triggered in the worker thread I call the function to update the Form:
_view.Surface = new ImageSurface();
((ImageSurface)_view.Surface).Updated += (s, e) =>
{
ImageSurface buffer = (ImageSurface)_view.Surface;
ThreadHelper.SetPicture(DebugForm, buffer.Image);
Application.DoEvents();
};
_view.ConsoleMessage += (s, e) =>
ThreadHelper.AppendText(DebugForm, string.Format("{0} : {1} [{2}]\r\n", e.LineNumber, e.Message, e.Source));
So I currently have this code below, which has a background worker call showdialog(). However, I thought that the UI cannot be updated on a background thread, so how does the dialog display? Does the dialog actually get opened on the UI thread? what happens?
public partial class ProgressDialog : Window
{
BackgroundWorker _worker;
public BackgroundWorker Worker
{
get { return _worker; }
}
public void RunWorkerThread(object argument, Func<object> workHandler)
{
//store reference to callback handler and launch worker thread
workerCallback = workHandler;
_worker.RunWorkerAsync(argument);
//display modal dialog (blocks caller)
//never returns null, but is a nullable boolean to match the dialogresult property
ShowDialog();
}
I have gotten suggestions that I just run the code and check, but how do i check whether the show dialog window was opened on a new thread or on the background thread itself? Not sure how I would check that.
Anyway this was just a post to try to help my understanding of what is actually happening in my code.
Anyway finally understood more of the comments, so I think I understand everything that is going on. Most of my real problems weren't caused by this dialog anyway, they were caused by updating observable collections from a non-ui thread while controls were bound to them.
Technically you are not changing a property on your Main thread just creating a instance of another object.
But it could help if you elaborate a bit more on your method ShowDialog().
I had also problem with calling ShowDialog() from non-UI thread. And my answer is that it depends on the thread which calls the ShowDialog(). If you set the ApartamentState property for this thread before its start then everything will work as called from the UI thread. I have finally ended up with such a code:
private async void button1_Click(object sender, EventArgs e)
{
var foo = new Foo();
// MessageBox.Show(foo.DirPath) - this works as a charm but
// if, it is called from non UI thread needs special handling as below.
await Task.Run(() => MessageBox.Show(foo.DirPath));
}
public class Foo
{
private string dirPath;
public string DirPath
{
get
{
if (dirPath == null)
{
var t = new Thread(() =>
{
using (var dirDialog = new FolderBrowserDialog())
{
if (dirDialog.ShowDialog() == DialogResult.OK)
dirPath = dirDialog.SelectedPath;
}
}
);
t.IsBackground = true;
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();
}
return dirPath;
}
set
{
dirPath = value;
}
}
}
I dont know for sure but i thought that the showDialog doesnt create the object only showing it. So when u say ShowDialog it only tells to show. So it will run on the UI thread instead of the backgroundworker
(dont know for sure)
I am faced with a problem. I am clicking a button that is calling several methods, although the main thread is locking up, so I created an instance of my class (which is Form1) e.g. Form1Object and then the button called the methods as so: Form1Object.Check1 and so on.
Although the thread still locked up (i.e. the GUI became unresponsive for a period) Is there anyway of getting around this, any examples would be greatly appreciated.
The code in question is below:
private void StartChecks_Click(object sender, EventArgs e)
{
Form1 Form1Object = new Form1();
Form1Object.InitChecks();
}
public void InitChecks()
{
Check1();
Check2();
Check3();
Check4();
Check5();
Check6();
Check7();
}
Creating a new Form does not start a new Thread.
You will have to move those CheckN() methods to a BackgroundWorker.
private void StartChecks_Click(object sender, EventArgs e)
{
Form1 Form1Object = new Form1();
var worker = new BackgroundWorker();
worker.DoWork += (s, arg) =>
{
Form1Object.InitChecks();
};
// add progress, completed events
worker.RunWorkerAsync();
}
But note that this require that the checks are independent and do not interact with any Control.
What you need to do is start a parallel thread to do the check, so you won't lock up the main thread:
private void StartChecks_Click(object sender, EventArgs e)
{
Form1 Form1Object = new Form1();
Thread t = new Thread(
o =>
{
Form1Object.InitChecks();
});
t.Start();
}
Hopefully you don't need to actually retrieve anything from those calculations, so you can just fire and forget about it.
You have several options here, and use them depending of your skill/preference/requirement:
if you don't update anything on the form while you process, start another thread and call everything on that thread, and update UI when appropriate (when everything is finished)
if you need to update things on your form while processing, you have several options:
either use Application.DoEvents() from the processing loop of every method you use
start a new thread then update form controls with Invoke() - if you try to update them directly, you'll be in trouble
If you care to comment and decide for one of the options, I can provide more info on just that...
hey i am trying to create two webbrowsers on a sperate thread from the rest of the form. one goes to tabpage1 and the other is added to tabpage2. the first browser creates fine to page1 however the second browser wont add and the error "Unable to get the window handle for the 'WebBrowser' control. Windowless ActiveX controls are not supported." happens. heres my code:
private Thread t;
WebBrowser webBrowser1, webBrowser2;
public delegate void Addc1(Control o);
public delegate void Addc2(Control o);
public Addc1 AddControl1;
public Addc2 AddControl2;
public Form1()
{
InitializeComponent();
AddControl1 = new Addc1(AddTabControl1);
AddControl2 = new Addc2(AddTabControl2);
}
private void button2_Click(object sender, EventArgs e)
{
t = new Thread(new ThreadStart(this.UIStart));
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
public void UIStart()
{
WebBrowser webBrowser1 = new WebBrowser();
webBrowser1.Location = new System.Drawing.Point(1,1);
webBrowser1.Size = new System.Drawing.Size(936, 35);
webBrowser1.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.webBrowser2_DocumentCompleted);
tabPage1.Invoke(AddControl1, new Object[] { webBrowser1 });
WebBrowser webBrowser2 = new WebBrowser();
webBrowser2.Location = new System.Drawing.Point(1,1);
webBrowser2.Size = new System.Drawing.Size(936, 935);
webBrowser2.DocumentCompleted += new System.Windows.Forms.WebBrowserDocumentCompletedEventHandler(this.webBrowser2_DocumentCompleted);
tabPage2.Invoke(AddControl2, new Object[] { wedBrowser2 });
webBrowser1.Navigate("www.ask.com");
webBrowser2.Navigate("www.google.com");
}
public void AddTabControl1(Control o)
{
tabPage1.Controls.Add(o);
}
public void AddTabControl2(Control o)
{
tabPage2.Controls.Add(o);
}
}
as i said the webbrowser1 will create and navigate but the other one will add to the controls on page2 but will not create. any ideas?
thanks adds
You are violating several threading rules:
WebBrowser is an ActiveX control with single-threaded apartment requirements. You are correctly setting the thread to STA but you are violating the second requirement: an STA thread must pump a message loop. You didn't get that far yet, but the DocumentCompleted event will never fire.
Windows requires child controls to belong to the same thread as their parent. In your case, there's a nasty mismatch. The AxHost wrapper will be created on the UI thread due to the Controls.Add() call but the native window handle that the WebBrowser uses may not. I think that's the source of the exception you get.
You can't make this work as you intended, a web browser is simply not a chunk of code that can handle multiple threads. Even if you do create it on the correct thread, calls made on a background thread will be marshaled by COM to implement the STA contract, there is no concurrency.
Using it on a separate STA thread that pumps a message loop (Application.Run) is fine but the form and its controls must be created on that same thread.
I don't know why this is happening, but when I create a new form inside an EventHandler, it disappears as soon as the method is finished.
Here's my code. I've edited it for clarity, but logically, it is exactly the same.
static void Main()
{
myEventHandler = new EventHandler(launchForm);
// Code that creates a thread which calls
// someThreadedFunction() when finished.
}
private void someThreadedFunction()
{
//Do stuff
//Launch eventhandler
EventHandler handler = myEventHandler;
if (handler != null)
{
handler(null, null);
myEventHandler = null;
}
}
private void launchForm(object sender, EventArgs e)
{
mf = new myForm();
mf.Show();
MessageBox.Show("Do you see the form?");
}
private myForm mf;
private EventHandler myEventHandler;
The new form displays as long as the MessageBox "Do you see the form?" is there. As soon as I click OK on it, the form disappears.
What am I missing? I thought that by assigning the new form to a class variable, it would stay alive after the method finished. Apparently, this is not the case.
I believe the problem is that you are executing the code within the handler from your custom thread, and not the UI thread, which is required because it operates the Windows message pump. You want to use the Invoke method here to insure that the form gets and shown on the UI thread.
private void launchForm(object sender, EventArgs e)
{
formThatAlreadyExists.Invoke(new MethodInvoker(() =>
{
mf = new myForm();
mf.Show();
MessageBox.Show("Do you see the form?");
}));
}
Note that this assumes you already have a WinForms object (called formThatAlreadyExists) that you have run using Application.Run. Also, there may be a better place to put the Invoke call in your code, but this is at least an example of it can be used.
I think if you create a form on a thread, the form is owned by that thread. When creating any UI elements, it should always be done on the main (UI) thread.
this looks as if you are not on the form sta thread so once you show the form it is gone and the thread finishes it's job it kills it self since there is nothing referenceing the thread. Its not the best solution out there for this but you ca use a showdialog() rather than a show to accomplish it keeping state if you need a code example i use this exact same process for a "loading...." form
public class Loading
{
public delegate void EmptyDelegate();
private frmLoadingForm _frmLoadingForm;
private readonly Thread _newthread;
public Loading()
{
Console.WriteLine("enteredFrmLoading on thread: " + Thread.CurrentThread.ManagedThreadId);
_newthread = new Thread(new ThreadStart(Load));
_newthread.SetApartmentState(ApartmentState.STA);
_newthread.Start();
}
public void Load()
{
Console.WriteLine("enteredFrmLoading.Load on thread: " + Thread.CurrentThread.ManagedThreadId);
_frmLoadingForm = new frmLoadingForm();
if(_frmLoadingForm.ShowDialog()==DialogResult.OK)
{
}
}
/// <summary>
/// Closes this instance.
/// </summary>
public void Close()
{
Console.WriteLine("enteredFrmLoading.Close on thread: " + Thread.CurrentThread.ManagedThreadId);
if (_frmLoadingForm != null)
{
if (_frmLoadingForm.InvokeRequired)
{
_frmLoadingForm.Invoke(new EmptyDelegate(_frmLoadingForm.Close));
}
else
{
_frmLoadingForm.Close();
}
}
_newthread.Abort();
}
}
public partial class frmLoadingForm : Form
{
public frmLoadingForm()
{
InitializeComponent();
}
}
Is
dbf.Show();
a typo? Is it supposed to be this instead?
mf.Show();
Is it possible that there is another form that you are showing other than the one you intend to show?
You created a window on a non UI thread. When the thread aborts it will take your window along with it. End of story.
Perform invoke on the main form passing a delegate which will execute the method that creates the messagebox on the UI thread.
Since the MessageBox is a modal window, if dont want the launchForm method to block the background thread, create a custom form with the required UI and call show() on it, not ShowDialog().