Problem with Application.DoEvents() while waiting for WebBrowser to finish loading - c#

I'm trying to load WebBrowser content and after that I want to add some text and scroll to the bottom.
Here's example of my code:
webBrowser1.Url = new System.Uri("file:///" + filePath);
webBrowser1.Document.Body.InnerHtml += text;
webBrowser1.Document.Body.ScrollTop = webBrowser1.Document.Body.ScrollRectangle.Height;
When I run it, there's an unhandled exception "Object reference not set to an instance of an object". Or when I comment line that does the scrolling, then text is added to previous content of the WebBrowser and then navigating to new content.
So after 1st line of my example code I put:
while (webBrowser1.ReadyState != WebBrowserReadyState.Complete) Application.DoEvents();
but it messes up everything. My application is doing really strange things, for example calling the same method many times, when it should be called once.
Is there any solution?

I think you actually want to subscribe to DocumentCompleted event.
Application.DoEvents only processes pending Windows message loop items.
In either case, make sure you understand possible drawbacks of calling DoEvents before using it at all.

DoEvents() is a bad solution here. You should use explicit thread or BackgroundWorker to load pages and leave UI thread to handle other stuff.

Related

SetAttribute is too slow, following InvokeMember is ignored by the program

I am quite new in visual C# and I'm trying to code an auto login program. Basically all i need is to know SetAttribute and InvokeMember for now.
To simplify the task, I have tried to make a form that will go to google, use SetAttribute to change query box to the user input and use InvokeMember to click "search" button. However code ignores the InvokeMember function when I try to run them in order.
Here is my code:
webBrowser1.Navigate("www.google.com");
while (webBrowser1.ReadyState != WebBrowserReadyState.Complete) { Application.DoEvents(); }
webBrowser1.Document.GetElementById("q").SetAttribute("value", searchTXT.Text);
//MessageBox.Show(webBrowser1.Document.GetElementById("q").GetAttribute("value"));
webBrowser1.Document.GetElementById("btnK").InvokeMember("click");
it basically ignores the last line. However when I uncomment the messagebox, it works. It also works when I use a seperate button for clicking, so last line itself should be okay.
I think it is some kind of a delay problem. So I have to wait SetAttribute to finish its job before I call InvokeMember. I tried a while loop to put some delay but it didnt work. Is there any way to wait an operation to complete before proceeding? Is it the actual cause of my problem?
I would really appreciate any help,
Thanks in Advance!
Well eventually "System.Threading.Thread.Sleep(150);" did not work, and I noticed that for some reason
while (webBrowser1.ReadyState != WebBrowserReadyState.Complete) { Application.DoEvents(); }
terminates itself before the Web Browser completely loads. In order to make sure that the pages is loaded what I did was to create a bool variable called "wait". Then I changed the code to the following:
wait = true;
webBrowser1.Navigate("www.google.com");
while (wait==true) { Application.DoEvents(); }
webBrowser1.Document.GetElementById("q").SetAttribute("value", searchTXT.Text);
//MessageBox.Show(webBrowser1.Document.GetElementById("q").GetAttribute("value"));
webBrowser1.Document.GetElementById("btnK").InvokeMember("click");
and I double clicked webBrowser1 and set its "completed" event to be wait=false; which worked perfectly fine for me.

Updating a form through another form in C#

I have a button in a form. When the button is clicked, the button click function is called. In the function at first another form (let's call it object form2 from class Form2) it is created with some objects including labels and progress bars (these objects are created through form2 constructor). Then form2.Show() is called. Then a while loop comes which by end of it some variables are updated. I use these variables to update several objects in form2. The problem is that the form objects are not shown rightly until the button click function is finished. For example instead of labels, white rectangles are shown. I tried using Thread.Sleep(1000) after my while to see if objects are shown rightly but it didn't have any effect on form2 shape. Then I used a MessageBox.Show() after my while and it worked surprisingly! Objects were shown correctly in form2.
I appreciate if anyone could say what is the problem and how I can solve it.
You're blocking the UI thread, thus preventing updates until you release the UI thread to continue handling other messages sent to it.
If you have long running CPU bound work, you should be offloading it to another background thread. If you have IO or other non-CPU bound work to do you should be doing it asynchronously, rather than synchronously blocking on it, so that the UI thread can continue processing other events.
If you do heavy work you really should do it in a background worker.
Meanwhile (to get it work) you can call Application.DoEvents which should give your controls "time" to update.
Taken from MSDN:
http://msdn.microsoft.com/en-us/library/system.windows.forms.application.doevents%28v=vs.110%29.aspx
foreach (string file in files )
{
System.IO.FileInfo fileInfo = new System.IO.FileInfo(file);
System.IO.FileStream fileStream = fileInfo.OpenRead();
pictureBox1.Image = System.Drawing.Image.FromStream(fileStream);
Application.DoEvents();
fileStream.Close();
// Call Sleep so the picture is briefly displayed,
//which will create a slide-show effect.
System.Threading.Thread.Sleep(2000);
}
If you are interested in more details, Hans has a good explanation: https://stackoverflow.com/a/5183623/2243584
One could always argue about the use of Application.DoEvents but as Hans stated ShowDialog uses it internally as well - thats the reason why your MessageBox example worked!

How long am I supposed to wait to do something after I call WebBrowser.Refresh?

I have a web page that I am tearing elements out of for use in a program I am writing. It takes a moment for a particular element to load into the web page, so I thought I would use WebBrowser.Refresh() in order to accomplish getting the up to date code.
Unfortunately doing so causes a whole lot of headache if you want to do ANYTHING afterwards since it doesn't fire the DocumentCompleted event (before anyone asks, it even says that explicitly on the MSDN).
So I need a method to allow the program like 2 seconds of doing nothing after it refreshes the page. The problem is that I don't know how to do that and be able to get the updated WebBrowser.Document. If I use Thread.Sleep, the entire application hangs while it sleeps and nothing gets updated.
And if I immediately try, I invariable get the crappy crappy problem of the WebBrowser object hanging forever or throwing Null Cast exceptions.
Has anyone else had this sort of problem (or something similar) and found a solution?
Why don't you just navigate to the URL again?
webBrowser1.Navigate(webBrowser1.Url);
This will fire the event handlers that you need.
I had a similar problem in getting a WebBrowser to refresh at a desired time. As you are discovering, it is hopeless to try to get it to refresh on demand; the HTTP response and HTML rendering are both non-deterministic.
My advice is to attach to the DocumentCompleted event of the WebBrowser, call the desired web page early in the background and show the user something to distract them, then show the completed WebBrowser after DocumentCompleted fires.
If you want to be lazy and not use background threads:
wb.Refresh();
while ((wb.ReadyState != WebBrowserReadyState.Complete)) {
Application.DoEvents();
}
//Refresh is now complete
ReadyState should only be WebBrowserReadyState.Complete once all Ajax and content has completed loading.

Winforms: How to display a "loading" form?

I have a grid and when a row is double clicked a form is loaded. However a lot of data must be loaded, so I'd like to display a simple form with the text 'loading, please wait..'. And when all loading is finished, the form must disappear.
This is what I have right now, but it doesn't work:
Code that invokes the form with lots of data:
FormWithLotData form = new FormWithLotData();
form.ShowDialog(this);
Constructor of FormWithLotData:
// Show load form
FormIsLoading frm = new FormIsLoading();
_CloseLoadForm closeForm = new _CloseLoadForm(frm.Close);
System.Threading.Thread thread = new System.Threading.Thread(frm.Show);
thread.Start();
InitializeComponent();
this.Visible = false;
LoadAllData();
this.Visible = true;
// Close load form
Invoke(closeForm);
Hope you can help me out.
EDIT:
I'd like to show an animated gif on the loading form.
SOLUTION:
I've created a background worker. The DoWork event handles all the loading and by using the invoke() method I add nodes to the treeview. Now, the GUI doesn't hang and the user don't have the idea that the application is hanging.
You need to reverse your code.
The constructor of FormWithLotData is running in the UI thread. This is the thread that must show your FormIsLoading form. So instead of trying to display this form using the new Thread, do your data loading with it.
The DoEvents way others have suggested is the easiest to implement and (possibly? never done it myself) may work well.
The better pattern to use is to do your data loading on a worker thread. Before you show your FormWithLotData, Begin loading data on a background thread and show your Loading dialog. The method that loads the data should have a callback method into the Loading dialog to signal when it should Close(). Once it closes you can then construct a new FWLD, pass it the already loaded data, and Show it.
Trying to load your data after the form has already been invoked mixes your UI with your data operations, forcing your form to not only be in charge of the UI but also be in charge of data retrieval. Bad for KISS and Single Responsibility, imho.
After your update, it seems like DoEvents is going to be the only real answer to your question, but with some caveats.
You will not be able to show another form MODALLY while you construct your tree. You will still have to do your heavy work within your form's constructor. You will still have to hide your main form and Show() (not ShowDialog) your loading form. You will also have to call DoEvents at every single possible moment while constructing your tree. Its not exactly an elegant solution, but it will probably be your best bet at this point.
how about ...
FormIsLoading frm = new FormIsLoading();
frm.Show();
Application.DoEvents();
// ... load data ...
frm.Close();
In the Form_Load event, add the following:
this.Visible = true;
Application.DoEvents();
before any other processing occurs. The Application.DoEvents caused the UI to show the form at the current state, where normally the UI thread is locked while you other processing is taking place.
Don't do your LoadAllData() directly in a UI thread, instead start up a background thread to do it. The in your Form_Loaded event handler, use an AutoResetEvent and wait till it becomes signalled by the background data retrieving thread. Once it is signalled you can then continue to do whatever you need to do with the data, like bind it into the UI.
This method is still a little clunky for various reasons, but it will get you started.
Edit: i was being lazy with my answer above... a better option is to pass a delegate (callback) to the background thread, when the delegate is invoked (upon completion of the data retrieval) it marshals itself back on to the UI thread, and starts doing the required work with the data.
Inside your form, you can use a Timer control to simulate loading
using ProgressBar when it reaches 100 it unloads the form or any
kind of animation.
for progress-bar code just add the control from the toolbox and then
write the following code:
ProgressBar1.Value = ProgressBar1.Value+1;
if(ProgressBar1.Value == 100)
{
timer1.Enabled = false; this.Hide();
MessageBox.Show("Complete Loading...");
}

Delay loading of combobox when form loads

I've got a Windows Forms (C#) project with multiple comboboxes/listboxes etc that are populated when the form loads.
The problem is that the loading of the comboboxes/listboxes is slow, and since the loading is done when the form is trying to display the entire form isn't shown until all the controls have been populated. This can in some circumstances be 20+ seconds.
Had there been a Form_finished_loaded type of event I could have put my code in there, but I can't find an event that is fired after the form is done drawing the basic controls.
I have one requirement though - the loading has to be done in the main thread (since I get the items from a non-threading friendly COM-application).
I have found one potential solution, but perhaps there is a better way?
I can create a System.Timer.Timer when creating the form, and have the first Tick be called about 1 second later, and then populate the lists from that tick. That gives the form enough time to be displayed before it starts filling the lists.
Does anyone have any other tips on how to delay the loading of the controls?
There is the Shown event that "occurs whenever the form is first displayed.". Also you may want to use the BeginUpdate and EndUpdate functions to make the populating of your combobox faster.
It has that certain smell of workaround, but this approach should fulfil your needs:
private bool _hasInitialized = false;
private void Form1_Shown(object sender, EventArgs e)
{
if (!_hasInitialized)
{
ThreadPool.QueueUserWorkItem(state =>
{
Thread.Sleep(200); // brief sleep to allow the main thread
// to paint the form nicely
this.Invoke((Action)delegate { LoadData(); });
});
}
}
private void LoadData()
{
// do the data loading
_hasInitialized = true;
}
What it does is that it reacts when the form is shown, checks if it has already been initialized before, and if not it spawns a thread that will wait for a brief moment before calling the LoadData method on the main thread. This will allow for the form to get painted properly. The samething could perhaps be achieve by simply calling this.Refresh() but I like the idea of letting the system decide how to do the work.
I would still try to push the data loading onto a worker thread, invoking back on the main thread for populating the UI (if it is at all possible with the COM component).
Can you get your data from a web service that calls the COM component?
That way, you can display empty controls on a Locked form at the start, make Asynchronous calls to get the data, and on return populate the respective combos, and once all of them are loaded, you can unlock the form for the user to use.
You could listen for the VisibleChanged event and the first time it's value is true you put your initialization code.
Isn't FormShown the event you're looking for?
When you say that you cannot use a background thread because of COM what do you mean? I am using many COM components within my apps and running them on background threads.
If you create a new thread as an STAThread you can probably load the ComboBox/ListBox on a Non-UI thread. IIRC the ThreadPool allocates worker threads as MTAThread so you'll need to actually create a thread manually instead of using ThreadPool.QueueUserWorkItem.

Categories

Resources