Out With the Old and in With the New

farcall發表於2011-04-22

.NET Framework 3.0

Mark Davis, Heidi Housten, Dan Mohr, and Kusuma Vellanki
Microsoft Corporation

February 5, 2001

Fanfare, please! Allow us to introduce our new team members, Mark Davis and Dan Mohr. They'll have a hard time filling Tom and Rafael's shoes, but after giving them clothes pegs for their noses, we set them loose. Seriously, Tom and Rafael have set a high bar for us all, and they will be greatly missed on the team. After all, no one else can do dumb jokes quite like them. Thanks guys, and welcome to the new kids.

We have a good collection for you this month, so with no further ado, read on, friends.

Contents

Firing 'em all—Doing it the right way
Bridging the generation gap—Parent and child communication
Keep passing the open windows—New windows in custom Web browsers
Scrolling in sweet synchrony—Taking a little scroll together

The Web Team in Short

Firing 'em All

Dear Web Team:

There is lots of info and samples on how to capture events such as mouse clicks in IE from a C++ program but we are drawing a blank when trying to do the reverse. We want to be able to simulate user actions such as a user clicking a button from a C++ program and would imagine there would be a method such as Fire(action = click clientX=xx clientY=yy). However we cannot find anything that provides this function. The FireEvent of IHTMLDOCUMENT4 looks promising but all docs indicate that the event object is used to receive not set values. Is this a limitation of the programming model? I would be surprised as the model is so rich in other respects.

Thanks
John Mcfetridge

The Web Team replies:

We know all about firing; we do mean events, of course. Logically, you would want to simulate the action, not the event. All you need to do is call the related method on the object. For example, if you want to fire the onclick event for a button, you can call its click method and that will automatically fire the event for you. Your main question was firing events with some custom values, so this is probably not the answer you were looking for. Patience my friend, we are paving the way to enlightenment.

As a matter of fact, you were really close with your guess. You do need to use the FireEvent method of IHTMLDocument4, but you have to create an event object initially with the values that you want, then use this event object to fire the event. You cannot set the event.srcElement and event.typeexplicitly. By default, the srcElement is set to the element with which you call FireEvent, and the type is the eventname you pass as the first parameter to the FireEvent method. We usually find it much easier to experiment with script, then translate into C++.

Here are two samples that show how to fire the onclick event for a button with custom clientX and clientY values—first in HTML, then in C++.

<HTML>
<HEAD>
<SCRIPT>
function firebuttonclick()
{
   var neweventobj = document.createEventObject();
   neweventobj.clientX = 100;
   neweventobj.clientY = 100;
   button2.fireEvent("onclick", neweventobj);
}
function handleclick()
{
   alert("Received " + window.event.type + " event on " + 
window.event.srcElement.id + " with clientX: " + window.event.clientX + " 
clientY: " + window.event.clientY);
}
</SCRIPT>
</HEAD>
<BODY BGCOLOR="#000000" TEXT="#008080">
<INPUT TYPE=button VALUE="Button 1" NAME=button1 ID=button1 
ONCLICK=firebuttonclick()><b> - Click here to fire a click event on the 
second button below. </b>
<BR><BR>
<INPUT TYPE=button VALUE="Button 2" NAME=button2 ID=button2 
ONCLICK=handleclick()><b> - Or click this directly.
</BODY>
</HTML>

You will need the latest Internet Explorer 5.5 headers and libraries to compile the code below, which demonstrates how to fire the onclick event of the second button in the HTML sample mentioned above.

#include <atlbase.h>
#define CHECKPTR(ptr) if ((ptr) == NULL) goto cleanup;

void CMyView::OnFireEvent() 
{
    HRESULT         hr = S_OK;
    BSTR            eventName = NULL;
    VARIANT         name, index, eventobj;
    VARIANT_BOOL    vBool;    

    CComPtr<IDispatch>              pDisp = NULL;
    CComPtr<IDispatch>              pEltDisp = NULL;
    CComPtr<IHTMLElementCollection> pEltColl = NULL;
    CComPtr<IHTMLEventObj>          pEvent = NULL;

    CComQIPtr<IHTMLDocument2, &IID_IHTMLDocument2>  pDoc2;
    CComQIPtr<IHTMLDocument4, &IID_IHTMLDocument4>  pDoc4;
    CComQIPtr<IHTMLEventObj2, &IID_IHTMLEventObj2>  pEvent2;
    CComQIPtr<IHTMLElement3, &IID_IHTMLElement3>    pElt3;
    CComQIPtr<IDispatch, &IID_IDispatch>            pEventDisp;
    
      // m_webBrowser is the IWebBrowser2 ptr of
      // the Web browser control you host.
    CHECKPTR(pDisp = m_webBrowser.GetDocument());    
    CHECKPTR(pDoc2 = pDisp);

    //Get the element you want to fire the event on.
    hr = pDoc2->get_all(&pEltColl);
    if (FAILED(hr) || !pEltColl)
        goto cleanup;

    VariantInit(&name);
    V_VT(&name) = VT_BSTR;
    V_BSTR(&name) = SysAllocString(L"button2");
    VariantInit(&index);
    V_VT(&index) = VT_I2;
    V_I2(&index) = 0;
    
    // We are assuming there's only one element with that name on the page,
    // else you must expect a collection instead of a single element.
    hr = pEltColl->item(name, index, &pEltDisp);
    if (FAILED(hr) || !pEltDisp)
        goto cleanup;

    CHECKPTR(pElt3 = pEltDisp);

    // Create the event object and populate it.
    CHECKPTR(pDoc4 = pDoc2);

    hr = pDoc4->createEventObject(NULL, &pEvent);
    if (FAILED(hr) || !pEvent)
        goto cleanup;

    CHECKPTR(pEvent2 = pEvent);
    CHECKPTR(pEventDisp = pEvent2);

    pEvent2->put_clientX(100);
    pEvent2->put_clientY(100);
    
    // Fire the event.
    eventName = SysAllocString(L"onclick");
    VariantInit(&eventobj);
    V_VT(&eventobj) = VT_DISPATCH;
    V_DISPATCH(&eventobj) = pEventDisp;

    hr = pElt3->fireEvent(eventName, &eventobj, &vBool);

cleanup:
    if (V_BSTR(&name))
        SysFreeString(V_BSTR(&name));
    if (eventName)
        SysFreeString(eventName);
}

Bridging the Generation Gap

Dear Web Team:

I was wondering how to refer to an HTA from a window opened from it. I thought window.opener would work, but unfortunately it hasn't. Any ideas?

Yours sincerely
Chris O'Brien

The Web Team replies:

Window.opener should do the magic for you. If not, then you are probably running into one of these scenarios:

If window.open opens a new window outside your .hta file, then the domains of your .hta and the child window must match, or you will run into all the usual cross-domain security issues.

If you are targeting a frame name within your .hta using window.open, then you need to use the parent or top property, depending on how deeply your frame is nested. You also need to specify the APPLICATION=yes attribute on the frame itself.

If you target a frame in another window, you will not be able to use window.opener since it's a frame and not a child window, per se. You will not be able to use the parent property since the frame is part of a different object model tree. You can set a variable in the child window from the .hta with thewindow property of the .hta, which can then be used to talk back to the .hta. Again, this is possible only as long as the frame page and the .hta are from the same domain. Below is a code snippet to demonstrate how to do this. Three things to watch out for:

  • Make sure the child window is completely loaded before you try to access its object model to set the parent.
  • Make sure the parent variable is set correctly in the child window before you use it.
  • Reset these variables when either the parent or child window is closed. The last part is left as an exercise for our readers.

Copy these files to your Web root or anywhere on your hard drive. Open the TestFrm.htm and then Test.hta. Click Open a new window. In the TestFrm.htm, click Access parent set through script.

TestFrm.htm

<HTML>
<BODY>
<P>This is a test page which contains a frame with the frame name<B>test</B>.</P>
<BR><BR>
<INPUT TYPE=text ID=input1 NAME=input1 VALUE="frmparent">
<BR><BR>
<IFRAME ID=test NAME=test SRC="about:blank"></IFRAME>
</BODY>
</HTML>

Test.hta

<HTML>
<HEAD>
<HTA:APPLICATION>
<SCRIPT>
var childwin;
function OpenWindow()
{   
   childwin = window.open('test.htm','test'); 
   window.setTimeout(SetParent, 1);
}
function SetParent()
{
   if (childwin.document.readyState == "complete")
   {
      childwin.setParent(window);
   }
   else
      window.setTimeout(SetParent, 1);
}
</SCRIPT>
</HEAD>
<BODY>
<INPUT TYPE=button VALUE="Open a new window" ONCLICK=OpenWindow()>
<BR>
<INPUT TYPE=text ID=input1 NAME=input1 value="hta">
</BODY>
</HTML>

Test.htm

<HTML>
<HEAD>
<SCRIPT>
var newparent;
function setParent(newwin)
{
newparent = newwin;
}
function accessParent()
{
   if (newparent!=null && newparent != "undefined") 
      alert(newparent.input1.value) 
   else 
      alert("parent var not set");
}
</SCRIPT>
</HEAD>
<BODY>
<INPUT TYPE=button VALUE="Access parent" ONCLICK="alert(window.parent.input1.value)">
<BR><BR>
<INPUT TYPE=button VALUE="Access parent set thru script" 
onclick=accessParent()>
</BODY>
</HTML>

Keep Passing the Open Windows

Dear Web Team:

I am developing a custom browser using the Microsoft Foundation Class (MFC) CHtmlView class. I want to make all windows open within my custom browser, and not in Microsoft Internet Explorer.

Dhiren Vyas

The Web Team replies:

As you probably already know, developing your own custom browser gives you complete control over your user interface whilst making use of the incredible technology that Internet Explorer has to offer. You get to choose what your browser looks like and what it can do for your customers. Microsoft Visual C++ makes this task even easier by providing an MFC class, CHtmlView, that implements a view based on the WebBrowser control—a reusable component of Internet Explorer.

There are obstacles, though. When a Web page opens a window (by calling window.open, for example) an Internet Explorer object is created. This means that the Web page will appear in an Internet Explorer window. If you want all windows to appear in your application, you can use the NewWindow2 event, available on the DWebBrowserEvents2 interface. We'll demonstrate how this event can be used in a multiple document interface (MDI) MFC application. We'll be brief because this subject is covered in detail in the Knowledge Base article HOWTO: Use the WebBrowser Control NewWindow2 Event (Q184876).

First let's create an MDI MFC application using the AppWizard. During the final page of the wizard, select a view class that is derived from CHtmlView. Then use the ClassWizard to add an event handler for the NewWindow2 event. Finally, add code to your NewWindow2 event handler function:

void CMyHtmlView::OnNewWindow2(LPDISPATCH* ppDisp, BOOL* Cancel) 
{
   // Get a pointer to the application object.
   CWinApp* pApp = AfxGetApp();

   // Get the correct document template.
   POSITION pos = pApp->GetFirstDocTemplatePosition();
   CDocTemplate* pDocTemplate = pApp->GetNextDocTemplate( pos );

   // Create a new frame.
   CFrameWnd* pFrame = pDocTemplate->CreateNewFrame(
                                          GetDocument(),
                                          (CFrameWnd*)AfxGetMainWnd() );

   // Activate the frame.
   pDocTemplate->InitialUpdateFrame( pFrame, NULL );
   CNewWindow2View* pView = (CNewWindow2View*)pFrame->GetActiveView();

   // Pass pointer of WebBrowser object.
   pView->SetRegisterAsBrowser( TRUE );
   *ppDisp = pView->GetApplication();   
}

Navigate to a Web page that opens a new window and you'll see the Web page displayed in your application. This works because your application intercepts the NewWindow2 event, creates a new document/frame/view combination, and passes the IDispatch for the WebBrowser object. This causes your CHtmlView-derived class to be used to display the Web page. Note that the WebBrowser object must be created each time and not have navigated to a URL, or this won’t work.

You'll probably want to make this more useful by obtaining the window information, such as the height and width, so that you can modify your view accordingly. A simple approach to this is to handle the BeforeNavigate2 event that is fired after the new window is created. Add the following code to your application to resize the view to the size of the new window. Note that this is an overridden method that allows access to the WebBrowser object, and that an additional Boolean member, m_bResizeWindow, is required. Set this to false in the constructor and true in the NewWindow2 event handler to ensure this code is only called for new windows.

void CMyHtmlView::BeforeNavigate2(LPDISPATCH pDisp, VARIANT* URL,
      VARIANT* Flags, VARIANT* TargetFrameName,
      VARIANT* PostData, VARIANT* Headers, BOOL* Cancel)
{
   if ( m_bResizeWindow )
   {
   IWebBrowser2*   pWB = NULL;

      // QI the dispatch for WebBrowser control.
      HRESULT hr = pDisp->QueryInterface( IID_IWebBrowser2,
                                          (void**)&pWB );
      if ( SUCCEEDED(hr) )
      {
      long x,y;

         // Get dimensions.
         pWB->get_Width( &x );
         pWB->get_Height( &y );

         // Resize frame.
         SetScrollSizes( MM_TEXT, CSize(x,y) );
         ResizeParentToFit();
         pWB->Release();
      }
      m_bResizeWindow = false;
   }
}

Scrolling in Sweet Synchrony

Dear Web Team:

Hi. I need to scroll two vertically placed frames together, i.e. when one is scrolled, the other should also scroll.

Prithu Nath

The Web Team replies:

Good question. This would provide a great way to compare Web pages, cross-reference parts of a long document, and provide other useful viewing features.

A simple way to provide this feature is to modify the onscroll event property. We'll demonstrate how to do this using Dynamic HTML and two IFRAMEelements.

Here's a quick run-down of what we'll be doing. The content body of each frame fires an onscroll event whenever the user scrolls the content. So we'll capture that event by setting the onscroll event property to a JScript function that will position the content of the other frame to the same scroll position (provided by the scrollTop property).

Now, when accessing the content of frames, there are some rules to consider. Internet Explorer prevents Web pages from snooping around content that doesn't belong to them, so your frames should host pages on the same domain as the parent page. If you attempt to access a frame that is displaying a page hosted on a different domain, you'll get an "Access is denied" scripting error. You've been told. If you really need to do this, you could use an HTML Application, where security is less restrictive.

Here is some code that will enable two frames to scroll together vertically. For your homework, modify this code to also synchronize horizontal scrolling, turn on/off synchronization, and allow for differences between the scroll positions of each page.

<HTML>
<HEAD><TITLE>Synchro</TITLE>
<SCRIPT LANGUAGE="JScript">
var right, left;

function init()
{
  right = document.frames.frmRight;
  left = document.frames.frmLeft;
  left.document.body.onscroll = scrollRight;
  right.document.body.onscroll = scrollLeft;
}

function scrollLeft()
{
  left.document.body.scrollTop = right.document.body.scrollTop;
}

function scrollRight()
{
  right.document.body.scrollTop = left.document.body.scrollTop;
}
</SCRIPT>
</HEAD>
<BODY ONLOAD="init()">
<IFRAME ID="frmLeft" SRC="http://msdn.microsoft.com/" 
STYLE="height:400px;left:10px;position:absolute;top:10px;width:300px">
</IFRAME>
<IFRAME ID="frmRight" SRC="http://msdn.microsoft.com/" 
STYLE="height:400px;left:320px;position:absolute;top:10px;width:300px">
</IFRAME>
</BODY>
</HTML>

相關文章