C# "InvalidCastException" when trying to access webbrowser control from TimerCallback - c#

Basically I have the same problem as this user:
How to check for TrackBar sliding with mouse hold and release
I fixed this using the first solution provided. However, when the timer is called, I want to call InvokeScript on a webbrowser control. InvokeScript runs without an error, but the javascript function is never called. When I call this script from like a button clicked event handler, the function is called properly.
I found out that when I try to access properties from the webbrowser control (like MessageBox.Show(webBrowser1.DocumentText), this throws a InvalidCastException.
// in constructor:
webBrowser1.AllowWebBrowserDrop = false;
webBrowser1.IsWebBrowserContextMenuEnabled = false;
webBrowser1.WebBrowserShortcutsEnabled = false;
webBrowser1.ObjectForScripting = this;
timer = new System.Threading.Timer(this.TimerElapsed);
private void trackBar2_ValueChanged(object sender, EventArgs e)
{
timer.Change(500, -1);
}
private void TimerElapsed(object state)
{
this.webBrowser1.InvokeScript("jmp_end");
MessageBox.Show(this.webBrowser1.DocumentText);
timerRunning = false;
}
private void TimerElapsed(object state)
{
WebBrowser brw = getBrowser();
brw.Document.InvokeScript("jmpend");
MessageBox.Show(brw.DocumentText);
timerRunning = false;
}
Does anyone know what I am doing wrong here? Or is there another way to get the same result?
After comments about InvokeRequired, this sounds exactly like what I need.. But I can't get it working.. This is what I made from the sample code from C# System.InvalidCastException
public delegate WebBrowser getBrowserHandler();
public WebBrowser getBrowser()
{
if (InvokeRequired)
{
return Invoke(new getBrowserHandler(getBrowser)) as WebBrowser;
}
else
{
return webBrowser1;
}
}
private void TimerElapsed(object state)
{
WebBrowser brw = getBrowser();
brw.Document.InvokeScript("jmpend");
MessageBox.Show(brw.DocumentText);
timerRunning = false;
}
What have I missed here?

The caller (the timer) is on a different thread than the control was created on.
See Control.InvokeRequired Property
Sample code that should address your issue is posted on this question: C# System.InvalidCastException

I had the same problem. As pointed out by Kevin P. Rice, the caller is on a diferente thread than the one the control was created on. A simple solution for this is to use this.Invoke() everytime the thread needs to interact with a control, therefore, if you desire to have the browser invoke a script, and you wish to call it from inside a separate thread, just do it like this:
this.Invoke(new Action(() => { brw.Document.InvokeScript("jmpend"); }));
Or if you wish to change the property of the browse or another control within the form:
this.Invoke(new Action(() => { button1.Enabled = false; }));
If the declaration of your thread is in another scope than that of your form, and you can't use the this keyword, you need to find a way to reference the current instance of the form.
I hope this helps. :)

Related

Form freezes when opened for second time

I am developing a Windows Forms application that access a WCF service. I ran into a great problem that I can't predict the reason of it. Even the Visual Studio debugger not showing any exception in the Output view. The scenario is like this, I have a custom user control that has a linkLabel on it. Whenever the link label is clicked, a form is opened and a class object is passed to it. The class definition of this object resides on WCF service on a remote server. Now the problem is that when I click the linkLabel, the form opens perfectly loading each of its component according to the class object passed to it. But when I close this form and click that linkLabel again, the form opens but immediately freezes after loading some elements. I tried many code variations. Edited many part of code that I think can affect. But none of them showed the difference. Since, I don't know where actually is the code has error, I am posting the linkLabel click code and functions that are called after it is clicked.
private void linkLabel1_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
Enabled = false;
string temp = Title.Text;
Title.Text = "Opening...";
System.Threading.Thread t = new System.Threading.Thread(new System.Threading.ThreadStart(openTopic));
t.Start();
Title.Text = temp;
Enabled = true;
}
void createTopicWindow()
{
TopicViewer t = new TopicViewer(t);
Invoke(new Action(() => t.Show()));
}
private void openTopic()
{
Invoke(new Action(() => createTopicWindow()));
}
The above is the edited code, since I was getting Cross thread exception before.
Following is the code of constructor of the form that is called when clicked the linkLabel:
try
{
InitializeComponent();
this.t = topic;
if (IsHandleCreated == false)
CreateHandle();
System.Threading.Thread th = new System.Threading.Thread(new System.Threading.ThreadStart(loadTopic));
th.Start();
Common.openedTopics.Add(this);
AddComment addComment1 = new AddComment();
addComment1.Topic = t;
addComment1.Dock = DockStyle.Fill;
panel5.Controls.Add(addComment1);
backgroundWorker1.RunWorkerAsync();
}
catch (Exception)
{ }
void loadTopic()
{
Invoke(new Action(()=>tHead = new TopicHeader()));
Global.SetControlPropertyThreadSafe(tHead,"Topic", t);
Global.SetControlPropertyThreadSafe(tHead,"Dock", DockStyle.Fill);
Invoke(new Action(()=>panel1.Controls.Add(tHead)));
Global.SetControlPropertyThreadSafe(this,"Text", t.Title + " - Topic Viewer");
if (t.Description.Trim().Length > 0)
{
Global.SetControlPropertyThreadSafe(webBrowser1, "DocumentText", t.Description);
}
else
{
Invoke(new Action(() => tabControl1.TabPages[0].Dispose()));
}
Global.SetControlPropertyThreadSafe(tabPage2, "Text", "Comments (" + client.getComCount(t.TopicID) + ") ");
}
TopicHeader is another small user control.
Please anyone tell me the solution to this?
If you are using .Net 4.5, then using async/await would be easiest solution. That way, you don't need any Invokes
async private void Form1_Load(object sender, EventArgs e)
{
string s = await Task<string>.Factory.StartNew(LongRunningTask,
TaskCreationOptions.LongRunning);
this.Text = s;
}
string LongRunningTask()
{
Thread.Sleep(10000);
return "------";
}
I can't give a direct answer to you question, but this may give a hold on.
public void Form_Load()
{
// do some stuff on the gui-thread
// i need to do something that takes a long time:
ThreadPool.QueueUserWorkItem((state) =>
{
// i'll execute it on the ThreadPool
// Long running code....
// update results in mainform on gui thread.
Invoke(new Action( delegate
{
// because the invoke will execute this on the gui-thread, you'll able to update controls.
// update my gui controls.
DataGrid.Source = myReceiveDataThing;
}));
}
}
You might expand the code, to check if the form is still valid.

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

C# error: object reference is required for the non static field

I have little windows forms program that plays music using console.beep from strings. I have the string playing bit (which is basically a for loop that goes through the strings char by char and plays the appropriate note) set up in a new thread 't'. When you hit the "play" button the thread starts and the button changes to a "stop" button. If you hit this now "stop" button while the music is playing it will stop and the button changes back to play (by calling the "finished" method.
My problem is that I want the loop running in the new thread to also call the "finished" method when the loop has run its course and the song is over.
But if i put finished() after my loop i get the "object reference is required for the non static field" error. If I change the "finshed" method to static then the bit that changes the button text doesnt work...
Heres the code for when you press the button...
//This is the method for when the "start" button is clicked
public void button1_Click(object sender, EventArgs e)
{
if (music == null) { return; }
if (button1.Text == "Play")
{
// it makes a new thread which calls my "parse" method which
// interprets the music and then calls "playnote" to play it.
Thread t = new Thread(parse);
t.Start();
button1.Text = "Stop";
}
else
{
finished();
}
}
public void finished()
{
stop = true;
button1.Text = "Play";
}
Any suggestions?
Thanks a lot in advance!
Edit: Thanks all!! I dont really have time to figure out the background worker atm so I just have seperate start and stop buttons now! :p
I think this would be better accomplished with a BackgroundWorker thread. That way you can call the finished() method in the RunWorkerCompleted event.
Is the parse() method static? It seems like you are trying to call the finished() from a static method without providing the instance, which is why you are getting the "object reference is required for the non static field" error.
What you need to do is make sure that the created thread has access to the object that created it (i.e. "this", in the scope of your "button1_Click()" method). To do this, you can call t.Start(this) instead of t.Start(). In doing so, you will be passing to the thread the object capable of calling the "finished()" method.
Next, you need to make sure the "parse" method takes an argument of type Object. In doing so, when you call "t.Start(this)", the method "parse" will receive "this" as a parameter. A simple cast will be required to convert to the appropriate type. Now, when you want the song to be stopped in the thread, simply call the "finish()" method on the casted parameter.
So your code should look something like:
public void parse(object o)
{
MyClass c = o as MyClass; // replace MyClass with the name of your class
[...] // play music here
c.finished(); // call finished when you are done
}
public void button1_Click(object sender, EventArgs e)
{
[...]
Thread t = new Thread(parse);
t.Start(this); // instead of t.Start()
[...]
}
Static versus member method
Either you have a member (non static) method and you call it
obj.SomeMethod();
Or you have a static method and you call it with
ClassName.SomeMethod();
unless it is called inside the defining class itself. Then it can simply be called with
SomeMethod();
in both cases.
Threading
The only thread which is allowed to interact with the UI (e.g. with button1.Text) is the UI-thread itself. The UI-thread is the main thread in which your code is normally running.
See How to update the GUI from another thread in C#? for interacting with the form from another thread.
What you need to do is raise an event on the main thread to call your finished method.
Start by declaring the event and delegate in your form class.
public delegate void CallFinishedEventHandler();
public event CallFinishedEventHandler CallFinished;
Then create an event handler that will be called when the event is raised, and in the make your call to your finished method.
void Form1_CallFinished()
{
Finished();
}
Then in your form constructor wire up your event handler to your event.
public Form1()
{
CallFinished += Form1_CallFinished;
}
Finally, in your music playing code, when you want to call your finished method (on the UI thread) invoke your event so that it will be fired on the UI thread.
this.Invoke(CallFinished);
This is simple, create a global thread, and then access it where you want.
Thread t;
public void button1_Click(object sender, EventArgs e)
{
if (music == null) { return; }
if (button1.Text == "Play")
{
// it makes a new thread which calls my "parse" method which
// interprets the music and then calls "playnote" to play it.
t = new Thread(parse);
t.Start();
button1.Text = "Stop";
}
else
{
finished();
}
}
public void finished()
{
stop = true;
button1.Text = "Play";
}
for calling finished, make an instance of your form class and then call the method.
Form1 frm1 = new Form1();
frm1.finished();

C# Event without Invoke

im new to C# Event and i want to fire an event without getting a Cross-Thread error..
using System;
using System.Timers;
public class SampleTickEvent
{
private string passStr = string.Empty;
Timer t = new Timer(1000);
public delegate void ImageEventHandler(string s);
public event ImageEventHandler ImageEventTrigger;
public void Start(string ss)
{
passStr = ss;
t.Start();
t.Elapsed += t_Elapsed;
}
public void t_Elapsed(object sender, ElapsedEventArgs eea)
{
ImageEventTrigger(passStr);
}
}
private void button1_Click(object sender, EventArgs e)
{
SampleTickEvent ste = new SampleTickEvent();
ste.Start("sample");
ste.ImageEventTrigger += ste_ImageEventTrigger;
}
private void ste_ImageEventTrigger(string s)
{
Action act = () => listBox1.Items.Add(s);
Invoke(act);
}
is there another way that i will not put the Action act = () = ... and put listbox1.Items.Add(s) instead?
Rather than using System.Timers.Timer, trying using System.Windows.Forms.Timer, which is written so that it raises the event on the UI thread.
If you're in Windows Forms, you can use System.Windows.Forms.Timer instead of System.Timers.Timer. The tick event executes in the UI Thread : http://netpl.blogspot.com/2010/05/systemwindowsformstimer-vs.html
No, Timers operate on a thread from the thread pool, and so to safely modify your form controls, you need to use Invoke.
You could inline the action, but that's about it.
If you want to put the Invoke into the SampleTickEvent.t_Elapsed method, then you'll have to pass a Control as a handle into the SampleTickEvent first. Or, you could just create a new Control as a member of the the SampleTickEvent in its constructor, and call Invoke on that. I've done it that way before.
You could change this option during application startup:
System.Windows.Forms.Form.CheckForIllegalCrossThreadCalls = false;
This would go in whichever Form you're using as the main application thread.
Try using the System.Windows.Forms.Timer instead of System.Timers.Timer.
From above Msdn Link:
This Windows timer is designed for a single-threaded environment where UI threads are used to perform processing. It requires that the user code have a UI message pump available and always operate from the same thread, or marshal the call onto another thread.

How to create a control and manage its events in one method?

I'm still stuck.
Assume that I've got a user control with a button. And an event called damnIt_ButtonClicked.
In the main window I want to emulate the control's lifetime like it is a modal dialog, although it's not.
I want to wrap everything into one method, it returns true if the Button on the control clicked.
public bool Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
result = true;
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
return result;
}
Now. As you see the problem is this method will return always false;
But I need to return a result only when damnIt_ButtonClicked event fires. It means I have to put the thread on wait, till the user clicks button.
Right? Or how it should be done. Help me please....
You're going to need to re-architect your solution. Without knowing a broader scope of what you're trying to do, here's a possible solution.
private bool buttonResult;
public void Show() {
var control = new ControlWithSingleButton();
bool result;
control.damnIt_ButtonClicked += (object sender, EventArgs args) =>
{
this.ProcessButtonClick();
};
MainWindowGrid.Children.Add(control);
MainWindowGrid.Visibility = Visibility.Visible;
}
private void ProcessButtonClick()
{
this.buttonResult = true;
//do whatever you would have before if Show had returned true
}
You know what? I give up!
I decided to make the control a window, although it was strictly prohibited in given specifications to use any other windows but the Main. Anyway it's gonna be a chromeless, borderless transparent window, so nobody can see the difference.
Thank you so much.

Categories