Watch, Follow, &
Connect with Us

For forums, blogs and more please visit our
Developer Tools Community.


Welcome, Guest
Guest Settings
Help

Thread: Dynamically loading html into TWebBrowser (again)


This question is answered.


Permlink Replies: 12 - Last Post: May 25, 2016 1:44 PM Last Post By: Mike Collins Threads: [ Previous | Next ]
Mike Collins

Posts: 46
Registered: 9/23/05
Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 24, 2016 3:36 PM
Apologies - I know this question has been asked a million times before but...

I have, for some time, used a standard piece of code to dynamically load html from a UnicodeString, generated by TPageProducer's, into a TWebBrowser. I have just come to migrate this code into a project written being built in XE10 (or should I say, Seattle) and it generates an error reporting that IPersistStreamInit::Load() cannot be called with a TStreamAdapter parameter.

I have managed to upgrade this code to use the _di_IStream interface but I have a few questions and queries. My code currently looks like this:

TStringStream  *stsSourceHtml = NULL;
TStreamAdapter *staAdapter    = NULL;
 
try
{
	try
	{
		IPersistStreamInit *psi;
 
		stsSourceHtml = new TStringStream( ppdLocalReportMaster->Content() );
		stsSourceHtml->Seek( 0, 0 );
 
		staAdapter = new TStreamAdapter( stsSourceHtml, soReference );
 
		_di_IStream pAdapter( *staAdapter );
 
		//if ( p_webTarget->ReadyState == READYSTATE_UNINITIALIZED )
		{
			p_webTarget->Navigate( L"about:blank" );
		}
 
		if ( SUCCEEDED( p_webTarget->Document->QueryInterface( IID_IPersistStreamInit,(void **)&psi ) ) )
		{
			psi->Load( pAdapter );
			psi->Release();
			iRtn = 1;  // success
		}
	}
	catch(...) {}
}
__finally
{
	if ( stsSourceHtml )
	{
		delete stsSourceHtml;
		stsSourceHtml = NULL;
	}
}


My first concern is that I call
 staAdapter = new TStreamAdapter( stsSourceHtml, soReference );
but I never free this object. If I try to delete it in the __finally section, my code hangs. If I change the soReference value to soOwned, my code also hangs.

My next query / question is - I thought that it was only necessary to call
 p_webTarget->Navigate( L"about:blank" ); 
once, the first time the function was called, in order to initialise the browser i.e. if its state was READYSTATE_UNINITIALIZED. However, I have found that if I dont call this every time, I get an AV when I call
 p_webTarget->Document->QueryInterface 
as Document is NULL.

Finally, I assumed that if I called
 p_webTarget->Navigate( L"about:blank" ); 
I could determine when the browser was ready by waiting until the ReadyState reached READYSTATE_COMPLETE. However, it never progresses past READYSTATE_LOADING.

Any help, advise or pointers would be gratefully appreciated

Many thanks in advance,

Mike C
Antonio Estevez

Posts: 665
Registered: 4/12/00
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 24, 2016 11:41 PM   in response to: Mike Collins in response to: Mike Collins
El 25/04/2016 a las 0:36, Mike Collins escribió:
My first concern is that I call
 staAdapter = new TStreamAdapter( stsSourceHtml, soReference );
but I never free this object. If I try to delete it in the __finally section, my code hangs. If I change the soReference value to soOwned, my code also hangs.

WebBrowser loads the page asynchronously therefore it is very likely that when you delete stsSourceHtml it has not yet
completed the process.

Try to create staAdapter using the soOwned parameter and do not delete stsSourceHtml in the finally block
    staAdapter = new TStreamAdapter( stsSourceHtml, soOwned );
Mike Collins

Posts: 46
Registered: 9/23/05
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 25, 2016 8:42 AM   in response to: Antonio Estevez in response to: Antonio Estevez
Hi Antonio,

Many thanks for the reply. Surely if I do as you suggest i.e.
staAdapter = new TStreamAdapter( stsSourceHtml, soOwned );


but do not delete the original TStringStream (stsSourceHtml) I will be leaking memory as I have called new but not delete?

Antonio Estevez wrote:
El 25/04/2016 a las 0:36, Mike Collins escribió:
My first concern is that I call
 staAdapter = new TStreamAdapter( stsSourceHtml, soReference );
but I never free this object. If I try to delete it in the __finally section, my code hangs. If I change the soReference value to soOwned, my code also hangs.
Antonio Estevez

Posts: 665
Registered: 4/12/00
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 25, 2016 11:06 AM   in response to: Mike Collins in response to: Mike Collins
El 25/04/2016 a las 17:42, Mike Collins escribió:
Hi Antonio,

Many thanks for the reply. Surely if I do as you suggest i.e.
staAdapter = new TStreamAdapter( stsSourceHtml, soOwned );


but do not delete the original TStringStream (stsSourceHtml) I will be leaking memory as I have called new but not delete?

No, stsSourceHtml is owned by staAdapter and it's freed when staAdapter itself is freed (when its reference count
becomes 0).
Martin van der ...

Posts: 57
Registered: 7/14/02
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 25, 2016 7:35 AM   in response to: Mike Collins in response to: Mike Collins
Mike Collins wrote:
Finally, I assumed that if I called
 p_webTarget->Navigate( L"about:blank" ); 
I could determine when the browser was ready by waiting until the ReadyState reached READYSTATE_COMPLETE. However, it never progresses past READYSTATE_LOADING.

I use the Busy property to check this, try like:

while(p_webTarget->Busy)
Sleep(0);

If you get strange random crashes since upgrading to Seattle, try replacing the TWebBrowser with a TCppWebBrowser. They're supposed to be the same, except the former has some weird bugs in Seattle that makes it crash a lot.
Mike Collins

Posts: 46
Registered: 9/23/05
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 25, 2016 8:45 AM   in response to: Martin van der ... in response to: Martin van der ...
Hi Martin,

Thanks for the update. I confess, I thought TCppWebBrowser has been depreciated or replaced with TWebBrowser. But as you say, its still there. I will look into replacing my current controls and see if a) the ready state is more consistent and if not, b) start using the Busy property.

Cheers

Martin van der Plas wrote:
Mike Collins wrote:
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 25, 2016 10:47 AM   in response to: Mike Collins in response to: Mike Collins
Mike wrote:

Apologies - I know this question has been asked a million times before
but...

And proper examples have been posted a million times before.

I have, for some time, used a standard piece of code to dynamically
load html from a UnicodeString, generated by TPageProducer's, into a
TWebBrowser. I have just come to migrate this code into a project
written being built in XE10 (or should I say, Seattle) and it
generates an error reporting that IPersistStreamInit::Load() cannot be
called with a TStreamAdapter parameter.

IPersistStream...::Load() takes an IStream* as input, and always has. TStreamAdapter
implements IStream, and has a conversion operator defined so a TStreamAdapter
object can be passed where an IStream* is expected.

I have managed to upgrade this code to use the _di_IStream interface

That is what you should be doing, yes. I would suggest also using _di_IPersistStreamInit
(or TComInterface<IPersistStreamInit>) instead of IPersistStreamInit*, and
let it handle the Release() for you.

staAdapter = new TStreamAdapter( stsSourceHtml, soReference );

You really should use soOwned instead, and let the TStreamAdapter free the
TStream when the adapter itself is freed.

p_webTarget->Navigate( L"about:blank" );

if ( SUCCEEDED( p_webTarget->Document->QueryInterface(
IID_IPersistStreamInit,(void **)&psi ) ) )

After navigating to any URL, even a blank page, you need to wait for the
browser document state to be complete before you can safely access its Document
object.

My first concern is that I call
 staAdapter = new
TStreamAdapter( stsSourceHtml, soReference );
but I never free
this object.

It is reference counted object. It will be freed when there are no more
references to it. The _di_IStream will increment the reference count, and
decrement the reference count when it goes out of scope.

My next query / question is - I thought that it was only necessary to
call
 p_webTarget->Navigate( L"about:blank" ); 
once, the
first time the function was called, in order to initialise the browser
i.e. if its state was READYSTATE_UNINITIALIZED.

Whatever gave you that idea?

However, I have found that if I dont call this every time, I get an AV
when I call
 p_webTarget->Document->QueryInterface 

as Document is NULL.

The Document object is only valid when a document has been loaded in the
browser. "about:blank" is still a document, just one with empty content.

Finally, I assumed that if I called
 p_webTarget->Navigate(
L"about:blank" ); 
I could determine when the browser was ready
by waiting until the ReadyState reached READYSTATE_COMPLETE.

Yes.

However, it never progresses past READYSTATE_LOADING.

The load is asynchronous. You need to process new window messages so the
browser can transition from one state to the next. So either

1) start the navigation and then exit, and then wait for the DocumentComplete
event.

2) start the navigation and then run a loop that calls Application->ProcessMessages()
until the ReadyState is complete.

--
Remy Lebeau (TeamB)
Mike Collins

Posts: 46
Registered: 9/23/05
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 25, 2016 1:33 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Hi Remy,

Thank you for taking the time to answer my questions. As usual, you reply is informative. I was reticent to ask the question - I know that this topic has been covered many times before, but I wasn't 100% sure - I don't like just copying code without understanding it.

As I mentioned, passing the TStreamAdapter to IPersistStream...::Load() produced a compile error (which is resolved by using _di_IPersistStreamInit interface).

I also had not read the documentation fully for the TStreamAdapter and therefore did not understand that it took ownership of the passed stream.

Finally, I overlooked the fact that I needed to process messages in order for the TWebBrowser to transition.

Many thanks again

Mike C


Remy Lebeau (TeamB) wrote:
Mike wrote:

Remy Lebeau (TeamB)
Antonio Estevez

Posts: 665
Registered: 4/12/00
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 25, 2016 1:58 PM   in response to: Mike Collins in response to: Mike Collins
El 25/04/2016 a las 22:33, Mike Collins escribió:
I also had not read the documentation fully for the TStreamAdapter and therefore did not understand that it took ownership of the passed stream.

Only if you set soOwned as the second parammeter of the TStreamAdapter constructor
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: Apr 25, 2016 2:41 PM   in response to: Mike Collins in response to: Mike Collins
Mike wrote:

As I mentioned, passing the TStreamAdapter to
IPersistStream...::Load() produced a compile error

You can't pass a TStreamAdapter* pointer where an IStream* pointer is expected,
because TStreamAdapter does not actually derive from IStream, so polymorphism
does not work. You have to dereference the TStreamAdapter pointer to its
IStream* conversion operator, eg:

psi->Load( *pAdapter );


Best to assign the TStreamAdapter to an _di_IStream variable first for reference
counting purposes:

_di_IStream strm = (IStream*) new TStreamAdapter(...);


Or:

_di_IStream strm = *(new TStreamAdapter(...));


Depending on your version of C++Builder. Then pass the _di_IStream to Load():

psi->Load( strm );


--
Remy Lebeau (TeamB)
Mike Collins

Posts: 46
Registered: 9/23/05
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 22, 2016 3:11 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Hi Remy,

Apoligies for the late reply - I took on board what you showed me and thought everything was working. However, I've run into another issue - I decided to reply to this thread instead of starting a post.

So, the long and short is that I have a UI, with a number of different tabsheets shown a variety of different data, via Data-Aware controls. I'm trying to develop a system whereby I can provide the user with a printable version of the information they are seeing. In order to do this, I' am using the various PageProducer components (TPageProducer, TDataSetTableProducer, TDataSetPageProducer) and daisy-chaining them together to generate an HTML report. This is in turn displayed in an embedded Browser window

I have a centralised method, GenerateLocalReport(), that calls these page producers, and then passes the resultant html to an embedded web browser. I supply this method with the target browser control and a record structure that contains details of the source dataset as well as some other information (page title etc).

All works fine the first time I call the method, the chaining works fine, html is generated, this is passed to the web browser and the resultant page is displayed. However, when I call it a second time, I do not see anything displayed in the browser window. I have traced the code and the correct HTML is being generated, but the WebBrowser is not displaying it (if I right-click on the browser windows and select View Source I just see <HTML></HTML>).

int TDMod::GenerateLocalReport(TCppWebBrowser *p_webTarget, PA_LOCAL_REPORT &p_ActiveReport)
{
   // Various validation
   ...
   // Main TPageProducer - load template HTML page
   ppdLocalReportMaster->HTMLFile = UnicodeString::Format( PATH_LOCAL_REPORT_TEMPLATE_HTML, 
                                                        ARRAYOFCONST(( ExtractFilePath( ParamStr(0) ) )) );
 
   try
   {
      TStringStream  *sstmSourceHtml = NULL;
 
      sstmSourceHtml = new TStringStream( ppdLocalReportMaster->Content() );
      sstmSourceHtml->Seek( 0, 0 );
 
      _di_IStream pAdapter = *( new TStreamAdapter( sstmSourceHtml, soOwned ) );
 
      if ( p_webTarget->ReadyState != Shdocvw::READYSTATE_LOADING )
      {
         p_webTarget->Navigate( L"about:blank" );
         while ( p_webTarget->ReadyState < Shdocvw::READYSTATE_LOADING )
         {
            Sleep( 10 );
            Application->ProcessMessages();
         }
      }
 
      _di_IPersistStreamInit psi;
      if ( ( p_webTarget->Document ) && ( SUCCEEDED( p_webTarget->Document->QueryInterface( IID_IPersistStreamInit,(void **)&psi ) ) ) )
      {
         psi->Load( pAdapter );
      }
   }
   catch(...)
   {
	//
   }


I'm see what I'm missing - second time round, everything functions as it should, psi->Load() gets call, just the page doesn't display.

Many thanks in advance,

Mike C
Remy Lebeau (Te...


Posts: 9,447
Registered: 12/23/01
Re: Dynamically loading html into TWebBrowser (again)
Correct
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 23, 2016 11:14 AM   in response to: Mike Collins in response to: Mike Collins
Mike wrote:

if ( p_webTarget->ReadyState != Shdocvw::READYSTATE_LOADING )

Remove that check altogether and unconditionally navigate to the "about:blank"
URL each time.

while ( p_webTarget->ReadyState < Shdocvw::READYSTATE_LOADING

You should be waiting for the READYSTATE_COMPLETE state, not READYSTATE_LOADING.

--
Remy Lebeau (TeamB)
Mike Collins

Posts: 46
Registered: 9/23/05
Re: Dynamically loading html into TWebBrowser (again)  
Click to report abuse...   Click to reply to this thread Reply
  Posted: May 25, 2016 1:44 PM   in response to: Remy Lebeau (Te... in response to: Remy Lebeau (Te...
Thanks Remy, as always, spot on.
Legend
Helpful Answer (5 pts)
Correct Answer (10 pts)

Server Response from: ETNAJIVE02