invoking a webbrowser to the second tab page visual c# - c#

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.

Related

Creating a form that subscribes to events from a blocking thread

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));

WinForms threading invoke handling

This code is running from other thread than the thread it was created on.
Thread gets create from the constructor of StartScanning
public StartScanning()
{
InitializeComponent();
Thread _IMSS_THREAD = new Thread(_IMSS_START_SCANNING);
_IMSS_THREAD.IsBackground = true;
_IMSS_THREAD.Start();
}
Main form
StartScanning _IMSS_START_SCANNING = StartScanning._IMSS_CREATE_CONTROLE();
_IMSS_START_SCANNING._IMSS_ON_ALL_SCAN_COMPLETE += _IMSS_ON_SCAN_COMPLETE;
this._IMSS_MainPanel.Controls.Add(_IMSS_START_SCANNING);
On scan complete user control, this code is in main form:
ScanComplete _IMSS_ON_COMPLETE = new ScanComplete();
public void _IMSS_ON_SCAN_COMPLETE(ref List<BetterListViewGroup> _IMSS_LIST_OF_GROUP_TARGETS)
{
List<BetterListViewGroup> IMSS_LIST_OF_GROUP_TARGETS = _IMSS_LIST_OF_GROUP_TARGETS;
_IMSS_ON_COMPLETE._IMSS_AddRangeTargets(ref IMSS_LIST_OF_GROUP_TARGETS);
this.Invoke(new MethodInvoker(() =>
{
this._IMSS_MainPanel.Controls.Clear();
this._IMSS_MainPanel.Controls.Add(_IMSS_ON_COMPLETE);
}));
}
If you take a look on this code, it runs OK but it's supposed to throw
Cross-thread operation not valid, cause when we start the program this UserControl
ScanComplete _IMSS_ON_COMPLETE = new ScanComplete();
Get created on the main thread (it's global) and when we use
_IMSS_ON_COMPLETE._IMSS_AddRangeTargets(ref IMSS_LIST_OF_GROUP_TARGETS);
It adds a list of groups of listview to it, and it's out of the invoke section, but it's not throwing thread error, Why it's not throwing errors?
Try this in constructor of form:
public StartScanning()
{
InitializeComponent();
StartScanning.CheckForIllegalCrossThreadCalls = false;
}
Remember that this is not proper way to do this but this will help you to solve your problem. Search for Thread safe calling.

Why can I access controls from a form in a thread without cross thread exception?

Usually, when you access controls in a Thread you end up with some cross thread exceptions. In my C# WinForms Application I have a picture box and a toolstriplabel which do not cause that exception. I don't understand why, can anybody explain this to me?
Here some code explanation:
In the main form I have a picturebox and a toolstriplabel. Also I have a reference to another Form, which has no controls and no additional source code. And then in the main form there is another object which works with a thread. This thread can raise three different events and the main form is subscribed to these three events.
Event1 causes the toolstriplabel to update (with some information from the thread).
Event2 causes the picturebox to update (with a new picture from the thread).
Event1 and Event2 work perfectly fine. I do not use any invoke methods, I directly change Text and BackgroundImage properties without cross thread exception.
Event3 though makes troubles. It is supposed to show the other form but I receive the cross therad exception. It works only if I use a BeginInvoke to show the form.
Why is that?
Edit:
The multithreading is done by an MJPEGStream object. I subscribe the NewFrame method of that MJPEGStream object.
public partial class Form1 : Form
{
private CAM cam;
private PeekWindow frmPeekWindow;
public Form1()
{
InitializeComponent();
cam = new CAM();
cam.NewImageMessageEvent += new NewImageEventHandler(cam_NewImageMessageEvent);
cam.DetectionEvent += new DetectionEventHandler(cam_DetectionEvent);
cam.FpsChangedMessageEvent += new FpsChangedEventHandler(cam_FpsChangedMessageEvent);
cam.DetectionThreshold = (float)this.numDetectionThreshold.Value;
frmPeekWindow = new PeekWindow();
// without the next two lines, frmPeekwindow.Show() won't work if called in an event
frmPeekWindow.Show();
frmPeekWindow.Hide();
}
void cam_FpsChangedMessageEvent(object sender, FpsChangedEventArgs e)
{
lblFPS.Text = string.Format("fps: {0:0.0}", e.FPS);
}
void cam_DetectionEvent(object sender, DetectionEventArgs e)
{
if (chkEnablePeakWindow.Checked)
{
if (frmPeekWindow.InvokeRequired)
{
frmPeekWindow.Invoke((MethodInvoker)delegate()
{
frmPeekWindow.Show();
frmPeekWindow.setImage(e.Image);
});
}
else
{
frmPeekWindow.Show();
frmPeekWindow.setImage(e.Image);
}
}
}
void cam_NewImageMessageEvent(object sender, NewImageEventArgs e)
{
picStream.BackgroundImage = e.Image;
}
}
And here's the CAM class:
class CAM
{
private object lockScale = new object();
private MJPEGStream stream;
private Bitmap image;
public event NewImageEventHandler NewImageMessageEvent;
public event FpsChangedEventHandler FpsChangedMessageEvent;
public event DetectionEventHandler DetectionEvent;
// configure (login, pwd, source)
public CAM()
{
this.stream = new MJPEGStream("...");
this.stream.Login = "...";
this.stream.Password = "...";
this.stream.NewFrame += new NewFrameEventHandler(OnNewFrame)
}
private void OnNewFrame(object sender, NewFrameEventArgs ev)
{
try
{
FpsChangedMessageEvent(this, new FpsChangedEventArgs(10));
// get image
image = ev.Frame;
NewImageMessageEvent(this, new NewImageEventArgs(new Bitmap(image)));
DetectionEvent(this, new DetectionEventArgs(new Bitmap(image)));
}
catch (Exception ex)
{
Console.Out.WriteLine(ex.Message);
}
}
}
You won't get cross thread exception, but it doesn't mean that this is a safe operation. There is always a possibility for your control to go unstable. You just don't know when it will happen.
See the following explanation from Microsoft.
http://msdn.microsoft.com/en-us/library/ms171728.aspx
Access to Windows Forms controls is not inherently thread safe. If you
have two or more threads manipulating the state of a control, it is
possible to force the control into an inconsistent state. Other
thread-related bugs are possible, such as race conditions and
deadlocks. It is important to make sure that access to your controls
is performed in a thread-safe way.
I have these three possibilites in mind:
The action is already dispatched to the gui thread.
The action doesn't need to be dispatched currently.
The action is somehow executed from the gui thread.
It's most likely number 3.
You don't necessarily always have to call BeginInvoke/Invoke. Sometimes the operation is running on the foreground thread, sometimes it is in the background.
Per the microsoft samples that are everywhere, You can SHOULD check to see if calling BeginInvoke/Invoke is required.
private void SetTextStandardPattern()
{
if (this.InvokeRequired)
{
this.Invoke(SetTextStandardPattern);
return;
}
this.text = "New Text";
}
Here is a nice microsoft article that has a sample:
http://msdn.microsoft.com/en-us/library/ms171728(v=vs.80).aspx
and here is another article on how to "avoid" the pattern:
http://www.codeproject.com/Articles/37642/Avoiding-InvokeRequired

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
}));
}

Separate Window Thread Closure

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"));

Categories