천객만래 [千客萬來] (It has an interminable succession of visitors)

[VC] IE에 사용자 툴바를 설치후에 기본으로 보이게 하는 방법





IE에 새 툴바밴드를 설치하고 새 IE를 실행했을때 기본으로 툴바가 IE에 보여지도록 하는 방법에 대한 문서이다. 혹자는 이것이 왜 필요하냐고 물을수도 있는데, 한마디로 필요하다. 

왜냐하면 설치를 했다면 당연히 눈여보여서 동작하는 것이 사용자의 기대동작이기 때문이다. 만약 IE에서 툴바를 켜는 방법을 모르는 사용자가 있다면 어떨까? 설치를 한 의미가 없어진다. 사용을 촉진시키고, UI를 보여줌으로써 관심을 일으키는 것이 마케팅일것이다.

1 툴바 DLL의 기본 설정을 이용하는 방법 

이 방법을 알아낸다면 밑에 있는 방법은 다 없어도 된다. 이 방법은 웹 검색과 구글검색으로도 쉽게 알아내지 못했다. IE의 툴바 레이아웃은 
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Toolbar
There are 3 sub keys: Explorer, ShellBrowser, WebBrowser'
In each of these sub-keys is the entry for ITBarLayout 
For IE Toolbars: The WebBrowser Sub-key directly affects IE.
For Windows Explorer Toolbars: The Explorer Sub-key directly affects Explorer.
The ShellBrowser sub-key may affect IE as well. Its effect on IE and Windows Explorer is unknown at this time, so if the first option doesn't work, delete the ITBarLayout value here as well.
Choose the relavent sub-key and right click where it says ITBarLayout and select Delete. 
The deleted value will automatically be recreated for you.

이런식으로 저장이 된다. 문제는 ITBarLayout에 레이아웃 구조체가 저장되는데 이 키를 지우면 모든 레이아웃 정보가 사라지고 IE가 디폴트로 툴바 레이아웃을 생성하는데, 이때 특정 툴바는 기본으로 뜬다는 것이다. 설치하고 나서 기본으로 뜨고 사용자가 안 보이게 하면 그때 부터 안 보인다. 이런 설정은 어떻게 하는 것일까? 만들때 어떤 옵션을 주는것 같기도 하고. 만약 방법을 찾게 되면 이 페이지에서 가장 강조되는 블럭으로 말들어 놓겠다.

2 html을 이용하는 방법 

이 방법은 해당 툴바를 Object 태그로 HTML에 삽입하는 방법이다. 다시 말해서 해당 COM객체를 IE에서 실행하라고 알리는 것이다. 설치가 끝나고 나서 인스톨러가 설치가 완료되었다는 깜찍하고 작은 IE를 띄우는것은 애교로 봐 줄만하다. 그 IE가 띄우는 페이지에 저 Object에 툴바의 클래스 아이디를 넣는다는 얘기이다. HTML에서는 COM객체를 로드할 것이고, 그 객체는 툴바DLL이기 때문에 당연히 IE에 리바영역에 나타날 것이다. 

문제점이있다. 어떤 툴바는 로드되지 않는다. 내가 뜨기를 원하는 툴바에서는 안 뜨고 다른 툴바는 성공적으로 뜨는 바람에 바로 다른 방법을 강구하기 시작했는데, 자기가 배포하려는 툴바가 이 방법으로 잘 되는지 확인해 볼 필요가 있다. 

주의할 점은 winxp sp2에서는 기본 보안설정으로는 DLL이 로드되지 않는다는 점이다., IE의 보안레벨에 의존적이다.

3 IE 메뉴를 이용하는 방법 

새로운 IE를 실행하면서 그 IE의 IEFrame핸들을 얻을 수 있는데 그 윈도우 핸들에 WM_COMMAND를 보내는 방법이다. 이 경우, 예를들면 Ctrl+N과 같은 IE의 기본적인 값은 spy++로 확인해 보면 항상 같다는 점을 알 수 있다. 그래서 테스트 결과 WM_COMMAND로 wParam에 그 값을 넣어서 보내면 정확히 그 메뉴가 실행된다. 이 점을 응용하면 보기-툴바에서 우리 툴바의 메뉴아이템의 ID를 구하기만 하면 되는 것이다. 그 ID는 항상 동적이다. 설치된 툴바의 갯수나 설치된 순서에 따라 다르기 때문이다. 그래서 IE의 메인메뉴를 HMENU를 구하고 거기서 모든 메뉴 아이템을 얻어서 우리가 원하는 메뉴 캡션을 찾아내면 그 메뉴 아이템의 ID가 우리가 찾는 ID가 되는 것이다.

[치명적 문제점]
메인메뉴의 HMENU 값을 구할 수가 없다? 일단 GetMenu(HWND) 는 메뉴가 없다. 왜 그럴까? IE의 메인메뉴는 메뉴바라고 하는 툴바에 올라가 있고, 팝업메뉴로 나타나기 때문이다. HMENU만 알아낸다면 확실히 효율적이고 안정적인 좋은 방법이다.

4 IWebBrowser2를 이용하는 방법 




STDMETHODIMP CShowBarObj::SetSite(IUnknown *pUnkSite)

(

 if (NULL != pUnkSite)

 {

  IWebBrowser2 *pBrowser = NULL;

  // Ensure that our site is an browser window

  HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void **) &pBrowser);

  if (SUCCEEDED(hr))

  {

   // Display the band object (the Search bar in this case)

   VARIANT vtBandGUID, vtShow;

   vtBandGUID.vt = VT_BSTR;

   vtBandGUID.bstrVal = SysAllocString(OLESTR("{30D02401-6A81-11D0-8274-00C04FD5AE38}"));

   vtShow.vt = VT_BOOL;

   vtShow.boolVal = true;

   VARIANT vret;

   pBrowser->ShowBrowserBar(&vtBandGUID, &vtShow, &vret);

   SysFreeString(vtBandGUID.bstrVal);

   pBrowser->Release();

  }

 }

 return S_OK;

}




이 코드인데 이 코드를 툴바(툴바는 문제가 있는데 툴바는 켜지 않으면 DLL을 로드하지 않기 때문이다.)나 BHO에 넣으면 잘 동작한다. 그러나 IE를 실행하고 그 실행한 IE의 IWebBrowser2의 ShowBrowserBar를 호출하면 절대로 동작하지 않는다. 그것이 문제이다. 일단 그 이유를 모르겠다. 한가지 생각나는 이유라고는 TopLevel의 IWebBrowser2가 아니기 때문이 아닐까 싶기도 한데, 사실상 그런 이유때문은 아닌것 같다. 

아래 코드는 Activex에서 탑 레베의 IWebBrowser2를 얻는 코드이다. 나중에 참조해 볼 것.





#include <SHLGUID.h>

#define COMRELEASE(ptr)

if (ptr != NULL) 

{

ptr->Release();

   ptr = NULL;

}

IWebBrowser2 *browser = NULL;

STDMETHODIMP SetClientSite(IOleClientSite *pClientSite) 

{

  HRESULT hr = S_OK;

   IServiceProvider *isp, *isp2 = NULL;

    if (!pClientSite)

{

 COMRELEASE(browser);


else

{

hr = pClientSite->QueryInterface(IID_IServiceProvider, reinterpret_cast<void **>(&isp));

if (FAILED(hr)) 

{

hr = S_OK;

goto cleanup;

}

hr = isp->QueryService(SID_STopLevelBrowser, IID_IServiceProvider, reinterpret_cast<void **>(&isp2));

if (FAILED(hr))

{

hr = S_OK;

goto cleanup;

}

hr = isp2->QueryService(SID_SWebBrowserApp, IID_IWebBrowser2, reinterpret_cast<void **>(&browser));

if (FAILED(hr)) 

{

hr = S_OK;

goto cleanup;

}

cleanup:

// Free resources.

COMRELEASE(isp);

COMRELEASE(isp2);

return hr;

}

}


5 BHO를 이용하는 방법 

이 방법이 그나마 안정적이고 좋은방법 같다. ShowBrowserBar를 BHO에서 동작하도록 하는 것이다. BHO의 SetSite에서 우리가 원하는 툴바를 보이게 만들고 DLL을 등록해제해서 (레지스트리 삭제) 다음 IE에서는 이 DLL을 다시 로드하지 않도록 하는 방법이다. 

bho등록
IE를 안 보이게 실행
로드된 bho에서 우리가 원하는 툴바를 보이게 함
IE를 종료함 WM_CLOSE를 보내거나 ->Quit를 호출함
bho등록해제
bho dll삭제

와 같은 순서로 동작한다.

6 레이아웃 레지스트리를 조작하는 방법

레지스트리는 바이네리 데이타이다. 상식적으로 생각했을때 그건 IE내부에서만 사용되는 어떤 구조체일 것이고, 그 내막은 파악하기는 소스가 없다면 리버스엔지니어링이라고 불리는 삽질을 하는 수 밖에 없는 법이다. 당행스럽게도 나에겐 win2k src가 있었는데 없었다면 감히 삽질하려고 시도조차 하지 않았을 것이다. 그런데 그런 시도를 이미 한 사람이 있었으니 내가 감탄하지 않을 수 없었다. 구조체는


typedef struct tagCOOLBARSAVE
{
  UINT cbVer;
   UINT uiMaxTBWidth;
    UINT uiMaxQLWidth;
#ifdef UNIX
    BITBOOL fUnUsed : 28; // unused
#endif
  BITBOOL fVertical : 1; // The bar is oriented vertically
   BITBOOL fNoText :1; // "NoText"
    BITBOOL fList : 1; 
// toolbar is TBSTYLE_LIST (text on right) + TBSTYLE_EX_MIXEDBUTTONS
BITBOOL fAutoHide : 1; // Auto hide toolbar in theater mode
    BITBOOL fStatusBar : 1; // Status bar in theater mode
BITBOOL fSaveInShellIntegrationMode : 1; 
// Did we save in shell integration mode?
UINT uiVisible; // "Visible bands"
UINT cyRebar;
BANDSAVE bsCBANDSMAX;
CLSID aclsidExternalBands[ MAXEXTERNALBANDS ]; // Check classid
CLSID clsidVerticalBar; //clsid of bar persisted within vertical band
CLSID clsidHorizontalBar;
} COOLBARSAVE, *LPCOOLBARSAVE;




이렇다. 소스 코드를 분석해 본 결과 상당히 복잡한 방법으로 bs 멤버와 aclsidExternalBands에 저장되는 순서가 어떤 함수로 관계지어지고 uiVisible에 교묘한 방법으로 계산한 bit를 OR시켜야 한다. 등등 상당히 복잡한 방법으로 저장이 되고 있다는 점이다. 내가 수십시간에 걸쳐 분석하고 완성한 소스가 있지만 그것조차 100% 완벽하다고 장담하지 못하겠다. 솔직히 MS얘네들 간단한 내용을 왜 이렇게 복잡하게 만들었는지 모르겠다. 그냥 최대 15개의 툴바를 지원하므로 15개의 툴바에 구조체를 할당하고 보임 안보임 BOOL 변수와, 스타일 변수, 위치변수 3개만 있으면 다 될것 같은데, 뭐가 그리 복잡하게 만드어 놨는지. 간단하고 읽기좋고 단순한 알고리즘으로 충분히 리팩토링이 필요한 소스가 아닌가 하는 개인적인 불만이 있다.

7 가장 확실한 방법 



#include <mshtml.h>
void CDeletmeDlg::OnButton1()
{
 CoInitialize(NULL);
 IWebBrowser2 *pIE;
 HRESULT hr =  CoCreateInstance(CLSID_InternetExplorer, NULL,
  CLSCTX_LOCAL_SERVER,
  IID_IWebBrowser2,(void**)&pIE);
 pIE->put_Visible(VARIANT_TRUE);
 VARIANT vtBandGUID;
 VARIANT vtShow;
 VARIANT vtRet;
 vtBandGUID.vt = VT_BSTR;
 vtBandGUID.bstrVal = ::SysAllocString( L"{5258771B-ACBE-4974-8ABC-AE4969A2A5CD}" );
 vtShow.vt = VT_BOOL; 
 vtShow.boolVal = VARIANT_TRUE; //** 주의 true가 아니라 VARIANT_TRUE이다.
 vtRet.vt = VT_INT;
 vtRet.intVal = 1;
 //**주의 vtRet는 리턴값이므로 반드시 읽어야 한다.
 //NULL을 넣으면 제대로 동작하지 않는다..
 hr = pIE->ShowBrowserBar(&vtBandGUID, &vtShow, &vtRet); 
 SysFreeString(vtBandGUID.bstrVal);
 //주소바가 보이도록 설정되 있으면
 VARIANT_BOOL b;
 pIE->get_AddressBar(&b);
 if(b == VARIANT_TRUE)
 {    
  HWND hWnd=0;
  pIE->get_HWND((long*)&hWnd);
  if(::IsWindow(hWnd))
  {
   //수동으로 안 보이게한다.
   //put_AddressBar는 현재창에서는 되지만 종료하고 닫으면 주소바가 다시 보이게 되어 있다.
   //따라서 사용자가 메뉴를 누른것처럼 동작하도록 한 다면 리바의 위치정보를 즉시 다시 기록하게 된다.
   ::PostMessage(hWnd, WM_COMMAND, 41477, 0);
  }
 }
 pIE->Release();
}



내가 미처 몰랐는데 이 방식이 있다. 주의라고 표시되지 않은 부분에서 내가 실수를 해서 안 되었던 것이고, 이제 하니 잘 된다. 툴바를 새 줄로 놓는다면 그건 DLL에서 GetHostInfo인가 하는 함수에서 _BREAK을 제거하면 된다.




Posted by SB패밀리

[개발/VC++] WebBrowser Control 이벤트 연결 - C++



WebBrowser Control
IDispatch 
IWebBrowser2
    .ReadyState
        READYSTATE_COMPLETE

    .Navigate2()
    .get_Document() // IDispatch -> IHTMLDocument2
    .get_StatusText()

    on DownloadBegin
    on DocumentComplete
    on DownloadComplete

IOleObject

 

IHTMLWindow2

IHTMLDocument2
    .get_all()
    .get_parentWindow() // IHTMLWindow2

IHTMLElementCollection
    .item()
    .tags // IHTMLElementCollection

IHTMLElement

a connectable object
    IConnectionPointContainer
        .FindConnectionPoint() // DHTMLElementEvents2

DHTMLElementEvents2
    .Advise()

outgoing HTMLElementEvents2 interface // event sink object // the client event sink 
    IDispatch
        .Invoke()

----------------------------------------------------------------------------------------


class CTryDlg : public CDialog
{
// Construction
public:
    CTryDlg(CWnd* pParent = NULL);    // standard constructor

// Dialog Data
    //{{AFX_DATA(CTryDlg)
    enum { IDD = IDD_TRY_081209_DIALOG };
    CWebBrowser2    m_webBrowser;
    //}}AFX_DATA
...
    // Generated message map functions
    //{{AFX_MSG(CTryDlg)
    ...
    afx_msg void OnDocumentCompleteExplorer1(LPDISPATCH pDisp, VARIANT FAR* URL);
    DECLARE_EVENTSINK_MAP()
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

BOOL CTryDlg::OnInitDialog()
{
    ...
    SetIcon(m_hIcon, FALSE);        // Set small icon
    
    // TODO: Add extra initialization here
// {
    COleVariant     szURL       = "http://www.google.com";
    m_webBrowser.Navigate2(szURL, NULL, NULL, NULL, NULL);
// }    
    return TRUE;  // return TRUE  unless you set the focus to a control
}
...
BEGIN_EVENTSINK_MAP(CTryDlg, CDialog)
    //{{AFX_EVENTSINK_MAP(CTryDlg)
    ON_EVENT(CTryDlg, IDC_EXPLORER1, 259 /* DocumentComplete */, OnDocumentCompleteExplorer1, VTS_DISPATCH VTS_PVARIANT)
    //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

void CTryDlg::OnDocumentCompleteExplorer1(LPDISPATCH lpDisp, VARIANT FAR* URL) 
{
    // TODO: Add your control notification handler code here
   IUnknown*  pUnk;
   LPDISPATCH lpWBDisp;
   HRESULT    hr;

   pUnk = m_webBrowser.GetControlUnknown();
   ASSERT(pUnk);

   hr = pUnk->QueryInterface(IID_IDispatch, (void**)&lpWBDisp);
   ASSERT(SUCCEEDED(hr));

   if (lpDisp == lpWBDisp )
   {
      // Top-level Window object, so document has been loaded
      TRACE("Web document is finished downloading\n");
   }

  lpWBDisp->Release();
}
----------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------
m_pBrowser // IWebBrowser2

// DWebBrowserEvents2::DocumentComplete
void CMyClass::DocumentComplete(LPDISPATCH pDisp, VARIANT* URL)
{
    HRESULT hr;
    IUnknown* pUnkBrowser = NULL;
    IUnknown* pUnkDisp = NULL;
    IDispatch* pDocDisp = NULL;
    IHTMLDocument2* pDoc = NULL;

    // Is this the DocumentComplete event for the top frame window?
    // Check COM identity: compare IUnknown interface pointers.
    hr = m_pBrowser->QueryInterface(IID_IUnknown, (void**)&pUnkBrowser);

    if (SUCCEEDED(hr))
    {
        hr = pDisp->QueryInterface(IID_IUnknown, (void**)&pUnkDisp);

        if (SUCCEEDED(hr))
        {
            if (pUnkBrowser == pUnkDisp)
            {
                // This is the DocumentComplete event for the top frame.
                // This page is loaded, so we can access the DHTML Object Model.
                hr = m_pBrowser->get_Document(&pDocDisp);

                if (SUCCEEDED(hr))
                {
                    // Obtained the document object.
                    pDocDisp->QueryInterface(IID_IHTMLDocument2, (void**)&pDoc);
                    if (SUCCEEDED(hr))
                    {
                        // Obtained the IHTMLDocument2 interface for the document object
                        ProcessDocument(pDoc);
                    }

                    pDocDisp->Release();
                }
            }

            pUnkDisp->Release();
        }

        pUnkBrowser->Release();
    }
}

// IHTMLDocument2
void CMyClass::ProcessDocument(IHTMLDocument2* pDoc)
{
    IHTMLElementCollection* pElemColl = NULL;

    hr = pDoc->get_all(&pElemColl);
    if (SUCCEEDED(hr))
    {
        // Obtained element collection.
        ProcessElementCollection(pElemColl);
        pElemColl->Release();
    }
}

void CMyClass::ProcessElementCollection(IHTMLElementCollection* pElemColl)
{
    IDispatch* pElemDisp = NULL;
    IHTMLElement* pElem = NULL;
    _variant_t varID("myID", VT_BSTR);
    _variant_t varIdx(0, VT_I4);

    hr = pElemColl->item(varID, varIdx, &pElemDisp);

    if (SUCCEEDED(hr))
    {
        hr = pElemDisp->QueryInterface(IID_IHTMLElement, (void**)&pElem);

        if (SUCCEEDED(hr))
        {
            // Obtained element with ID of "myID".
            ConnectEvents(pElem);
            pElem->Release();
        }

        pElemDisp->Release();
    }
}


void CMyClass::ConnectEvents(IHTMLElement* pElem)
{
    HRESULT hr;
    IConnectionPointContainer* pCPC = NULL;
    IConnectionPoint* pCP = NULL;
    DWORD dwCookie;

    // Check that this is a connectable object.
    hr = pElem->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPC);

    if (SUCCEEDED(hr))
    {
        // Find the connection point.
        hr = pCPC->FindConnectionPoint(DIID_HTMLElementEvents2, &pCP);

        if (SUCCEEDED(hr))
        {
            // Advise the connection point.
            // pUnk is the IUnknown interface pointer for your event sink
            hr = pCP->Advise(pUnk, &dwCookie);

            if (SUCCEEDED(hr))
            {
                // Successfully advised
            }

            pCP->Release();
        }

        pCPC->Release();
    }
}


STDMETHODIMP CEventSink::Invoke(DISPID dispidMember,
                                REFIID riid,
                                LCID lcid,
                                WORD wFlags,
                                DISPPARAMS* pdispparams,
                                VARIANT* pvarResult,
                                EXCEPINFO* pexcepinfo,
                                UINT* puArgErr)
{
    switch (dispidMember)
    {
        case DISPID_HTMLELEMENTEVENTS2_ONCLICK:
        OnClick();
        break;

        default:
        break;
    }

    return S_OK;
}

----------------------------------------------------------------------------------------

IOleInPlaceFrame
IOleClientSite
IOleInPlaceSite

EmbedBrowserObject()
    window handle
        _IOleClientSiteEx    // USERDATA
            IOleInPlaceFrame
            IOleClientSite
            IOleInPlaceSite
            IDocHostUIHandler

    CoCreateInstance()
        CLSID_WebBrowser
        IWebBrowser2
            .put_Left()
            .put_Top()
            .put_Width()
            .put_Height()
            .Navigate2()

        IOleObject
            .Close()
            .SetClientSite() // IOleClientSite
            .SetHostNames()
            .DoVerb()

UnEmbedBrowserObject()

ResizeBrowser()

DisplayHTMLPage()

DoPageAction()

GetWebPtrs()
    IHTMLDocument2

IHTMLElementCollection

GetWebElement()
    IHTMLElement

CreateWebEvtHandler()

FreeWebEvtHandler()

WaitOnReadyState()

더보기


extern "C" {

HINSTANCE        cwebdll;

long WINAPI EmbedBrowserObject(HWND hwnd);
void WINAPI UnEmbedBrowserObject(HWND hwnd);
long WINAPI DisplayHTMLPage(HWND hWnd, const char *url);
void WINAPI ResizeBrowser(HWND hWnd, DWORD x, DWORD y);

}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
     // TODO: Place code here.
// {
    // Load our DLL containing the OLE/COM code. We do this once-only. It's named "cwebpage.dll"
    if (!(cwebdll = LoadLibrary("cwebpage.dll")))
    {
        MessageBox(0, "Can't open cwebpage.dll!", "ERROR", MB_OK);
        return(-1);
    }
// }
    MSG msg;
    HACCEL hAccelTable;

    // Initialize global strings
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    ...
}
...
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) 
    {
// {
        case WM_CREATE:
            // Embed the browser object into our host window. We need do this only
            // once. Note that the browser object will start calling some of our
            // IOleInPlaceFrame and IOleClientSite functions as soon as we start
            // calling browser object functions in EmbedBrowserObject().
            if (EmbedBrowserObject(hWnd)) return(-1);
            DisplayHTMLPage(hWnd, "http://www.microsoft.com");
            break;
// }
        case WM_DESTROY:
// {
            // Detach the browser object from this window, and free resources.
            UnEmbedBrowserObject(hWnd);
// }
            PostQuitMessage(0);
            break;
        case WM_COMMAND:
            ...
            break;
        case WM_SIZE:
            // Resize the browser object to fit the window
            ResizeBrowser(hWnd, LOWORD(lParam), HIWORD(lParam));
            break;
        ...
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}


참조 사이트:

http://support.microsoft.com/kb/315617/en-us/
http://msdn.microsoft.com/en-us/library/bb508508(VS.85).aspx
http://support.microsoft.com/kb/194179/en-us/
http://www.codeproject.com/KB/shell/dlgdhtmlevents.aspx?display=Print
http://msdn.microsoft.com/en-us/library/aa293017(VS.71).aspx
http://urassa.tistory.com/entry/Web-Browser-Control-FAQ

http://www.codeproject.com/KB/COM/cwebpage.aspx?display=Print
http://jjjryu.tistory.com/entry/IE-1
http://support.microsoft.com/kb/196339/en-us/
http://jjjryu.tistory.com/entry/ActiveX-%EC%BB%A8%ED%8A%B8%EB%A1%A4-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8
http://www.codeproject.com/KB/shell/AutomateShellWindow.aspx?display=Print
http://www.codeproject.com/KB/shell/iehelper.aspx?display=Print


출처 : http://jjjryu.tistory.com/333

Posted by SB패밀리

[개발/VC++] WebBrowser HTML Element Events  핸들링




IDispEventSimpleImpl 의 SinkEvent 관련

[[ C++ - Header ]]
extern _ATL_FUNC_INFO OnClickEventInfo;
class CEventSink : public IDispEventSimpleImpl<1, CVCMapEventSink, &DIID__EventInterface>
{
    BEGIN_SINK_MAP(CEventSink )
        SINK_ENTRY_INFO(1,DIID__EventInterface, 1, OnClickEvent, &OnClickEventInfo)
    END_SINK_MAP()

    ...


public:
    void Start()
    {
        DispEventAdvise((IUnkown*) pClass);
    }
    void Stop();
    {
        DispEventUnadvise((IUnknown*) pClass);
    }

    void __stdcall OnClickEvent(int ID, bool selected);

private:
    _COMClassPtr pClass;
    CWnd* m_pTargetWnd;
}

[[ C++ - Cpp ]] 
_ATL_FUNC_INFO OnClickEventInfo= {CC_STDCALL, VT_EMPTY, 2, {VT_INT,VT_BOOL}};

void CEventSink::OnSelectedTeacher(int ID, bool selected)
{
    ...
}

/// main
void main()
{
    ...

    CEventSink  sink = new CEventSink(this);
    sink->Start();
    
    ...

    /// release
    sink->Stop();
    delete sink;    
    ...
}

Posted by SB패밀리

[개발/IE] 툴바 상태 레지스트리에서 확인


IE 툴바를 설치한 후 사용자들이 '사용안함' 상태로 설정하면

사용자가 '사용함'으로 설정할 때까지 영영 실행되지 않는다.

그렇다면 IE툴바의 상태 여부를 확인할 방법이 없을까?

방법은 레지스트리에서 찾을 수 있다.

IE툴바의 상태여부는 레지스트리에서 기록되기 때문이다.

IE툴바 '사용함' 상태일 때와 '사용안함' 상태일 때의 차이를 기준으로 살펴보자.


'사용함' 상태

- HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Ext\Stats\{CLSID}\iexplore
키에서 Flags 값이 Flags=0으로 설정되어 있다.

'사용안함' 상태
- HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Ext\Settings\{CLSID} 가 존재한다.
Version 네임값에 '*'값이면 disabled 임

- HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Ext\Stats\{CLSID}\iexplore
키에서 Flags 네임값이 Flags=4으로 설정되어 있다.

위의 차이를 활용하면 툴바의 상태를 알수 있다.

 

 

Posted by SB패밀리
[개발/VC] ActiveX 컨트롤에서 자신을 로딩한 웹브라우저 포인터 구하기 

웹브라우저 컨트롤의 포인터를 넘깁니다..


    IWebBrowser2* CActiveXGetIETestCtrl::GetIWebPointer()
    {
        // TODO: Replace the following code with your own drawing code.
        HRESULT hr ;
        IOleContainer *pIContainer = NULL ;
        IWebBrowser2 *pIWeb = NULL ;
        IServiceProvider *pISP = NULL ;
        
        // Get IOleClientSite interface pointer.
        LPOLECLIENTSITE pIClientSite = GetClientSite() ;
        
        // Get IOleContainer interface poineter.
        hr = pIClientSite->GetContainer(&pIContainer) ;
        if (hr != S_OK) {
            pIClientSite->Release() ;
            return NULL ;
        }
        
        // Get IServiceProvider interface pointer.
        hr = pIClientSite->QueryInterface(IID_IServiceProvider,(void **)&pISP) ;
        if (hr != S_OK) {
            pIContainer->Release() ;
            pIClientSite->Release() ;
            return NULL ;
        }

        // Get IWebBrowser2 interface pointer.
        hr = pISP->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, (void**)&pIWeb) ;
        if (hr != S_OK) {
            pIContainer->Release() ;
            pIClientSite->Release() ;
            pISP->Release() ;
            return NULL ;
        }
        
        // release interface.
        pIContainer->Release() ;
        pIClientSite->Release() ;
        pISP->Release() ;
        return pIWeb ;
    }



    여기서 얻어온 인터페이스 객체를 release 해주지 않았으므로
    ActiveX 컨트롤이 소멸될 때 반드시 해줘야 합니다..

    void CActiveXGetIETestCtrl::OnDestroy() 
    {
        COleControl::OnDestroy();
        
        // TODO: Add your message handler code here
        if (m_pIWeb)
            m_pIWeb->Release() ;
    }



    만약 포인터를 구해오는 멤버함수를 2번 이상 호출한다면
    호출한 횟수만큼 release를 호출해 줘야 합니다..

    제 예제에서는 1번만 호출하고 포인터를 멤버변수로 가지고 있으므로
    종료시 한번만 release 합니다..


    이렇게 얻어온 포인터를 활용해서 다른 URL로 이동,
    웹브라우저를 닫는 함수를 만들어봤습니다..

    IWebBrowser2 에서 지원하는 모든 메소드들은 다 호출할 수 있습니다..
    툴바를 숨긴다던지.. 뭐 맘대로 되겠죠..


    void CActiveXGetIETestCtrl::OnNavigate()
    {
        if (m_pIWeb)
        {
            CString sURL ;
            m_editURL.GetWindowText(sURL) ;
            COleVariant vaURL(sURL) ;
            m_pIWeb->Navigate2(vaURL,NULL,NULL,NULL,NULL) ;
        }
    }

    void CActiveXGetIETestCtrl::OnIEClose() 
    {
        if (m_pIWeb)
        {
            m_pIWeb->Quit() ;
        }
    }
Posted by SB패밀리
HRESULT hr;
IWebBrowser2* pWebBrowser = NULL;
hr = CoCreateInstance (CLSID_InternetExplorer, NULL, 
  CLSCTX_SERVER, IID_IWebBrowser2, (LPVOID*)&pWebBrowser);
    
if (SUCCEEDED (hr) && (pWebBrowser != NULL))
{
    m_pWebBrowser->put_Visible (VARIANT_TRUE);
    // OK, we created a new IE Window and made it visible

    // You can use pWebBrowser object to do whatever you want to do!

}
else
{
    // Failed to create a new IE Window.

    // Check out pWebBrowser object and

    // if it is not NULL (should never happen), the release it!

    if (pWebBrowser)
        pWebBrowser->Release ();
}
Posted by SB패밀리
[개발/IE] 툴바 상태 레지스트리에서 확인

IE 툴바를 설치한 후 사용자들이 '사용안함' 상태로 설정하면
사용자가 '사용함'으로 설정할 때까지 영영 실행되지 않는다.

그렇다면 IE툴바의 상태 여부를 확인할 방법이 없을까?
방법은 레지스트리에서 찾을 수 있다.

IE툴바의 상태여부는 레지스트리에서 기록되기 때문이다.

IE툴바 '사용함' 상태일 때와 '사용안함' 상태일 때의 차이를 기준으로 살펴보자.

'사용함' 상태

- HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Ext\Stats\{CLSID}\iexplore
 키에서 Flags 값이 Flags=0으로 설정되어 있다.

'사용안함' 상태
- HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Ext\Settings\{CLSID} 가 존재한다.
  Version 네임값에 '*'값이면 disabled 임

- HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Ext\Stats\{CLSID}\iexplore
 키에서 Flags 네임값이 Flags=4으로 설정되어 있다.

위의 차이를 활용하면 툴바의 상태를 알수 있다.
Posted by SB패밀리



[개발/VC++] WebBrowser Control 이벤트 연결 - C++


WebBrowser Control
IDispatch
IWebBrowser2
    .ReadyState
        READYSTATE_COMPLETE

    .Navigate2()
    .get_Document() // IDispatch -> IHTMLDocument2
    .get_StatusText()

    on DownloadBegin
    on DocumentComplete
    on DownloadComplete

IOleObject

 

IHTMLWindow2

IHTMLDocument2
    .get_all()
    .get_parentWindow() // IHTMLWindow2

IHTMLElementCollection
    .item()
    .tags // IHTMLElementCollection

IHTMLElement

a connectable object
    IConnectionPointContainer
        .FindConnectionPoint() // DHTMLElementEvents2

DHTMLElementEvents2
    .Advise()

outgoing HTMLElementEvents2 interface // event sink object // the client event sink
    IDispatch
        .Invoke()

----------------------------------------------------------------------------------------


class CTryDlg : public CDialog
{
// Construction
public:
    CTryDlg(CWnd* pParent = NULL);    // standard constructor

// Dialog Data
    //{{AFX_DATA(CTryDlg)
    enum { IDD = IDD_TRY_081209_DIALOG };
    CWebBrowser2    m_webBrowser;
    //}}AFX_DATA
...
    // Generated message map functions
    //{{AFX_MSG(CTryDlg)
    ...
    afx_msg void OnDocumentCompleteExplorer1(LPDISPATCH pDisp, VARIANT FAR* URL);
    DECLARE_EVENTSINK_MAP()
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

BOOL CTryDlg::OnInitDialog()
{
    ...
    SetIcon(m_hIcon, FALSE);        // Set small icon
   
    // TODO: Add extra initialization here
// {
    COleVariant     szURL       = "http://www.google.com";
    m_webBrowser.Navigate2(szURL, NULL, NULL, NULL, NULL);
// }   
    return TRUE;  // return TRUE  unless you set the focus to a control
}
...
BEGIN_EVENTSINK_MAP(CTryDlg, CDialog)
    //{{AFX_EVENTSINK_MAP(CTryDlg)
    ON_EVENT(CTryDlg, IDC_EXPLORER1, 259 /* DocumentComplete */, OnDocumentCompleteExplorer1, VTS_DISPATCH VTS_PVARIANT)
    //}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

void CTryDlg::OnDocumentCompleteExplorer1(LPDISPATCH lpDisp, VARIANT FAR* URL)
{
    // TODO: Add your control notification handler code here
   IUnknown*  pUnk;
   LPDISPATCH lpWBDisp;
   HRESULT    hr;

   pUnk = m_webBrowser.GetControlUnknown();
   ASSERT(pUnk);

   hr = pUnk->QueryInterface(IID_IDispatch, (void**)&lpWBDisp);
   ASSERT(SUCCEEDED(hr));

   if (lpDisp == lpWBDisp )
   {
      // Top-level Window object, so document has been loaded
      TRACE("Web document is finished downloading\n");
   }

  lpWBDisp->Release();
}
----------------------------------------------------------------------------------------

----------------------------------------------------------------------------------------
m_pBrowser // IWebBrowser2

// DWebBrowserEvents2::DocumentComplete
void CMyClass::DocumentComplete(LPDISPATCH pDisp, VARIANT* URL)
{
    HRESULT hr;
    IUnknown* pUnkBrowser = NULL;
    IUnknown* pUnkDisp = NULL;
    IDispatch* pDocDisp = NULL;
    IHTMLDocument2* pDoc = NULL;

    // Is this the DocumentComplete event for the top frame window?
    // Check COM identity: compare IUnknown interface pointers.
    hr = m_pBrowser->QueryInterface(IID_IUnknown, (void**)&pUnkBrowser);

    if (SUCCEEDED(hr))
    {
        hr = pDisp->QueryInterface(IID_IUnknown, (void**)&pUnkDisp);

        if (SUCCEEDED(hr))
        {
            if (pUnkBrowser == pUnkDisp)
            {
                // This is the DocumentComplete event for the top frame.
                // This page is loaded, so we can access the DHTML Object Model.
                hr = m_pBrowser->get_Document(&pDocDisp);

                if (SUCCEEDED(hr))
                {
                    // Obtained the document object.
                    pDocDisp->QueryInterface(IID_IHTMLDocument2, (void**)&pDoc);
                    if (SUCCEEDED(hr))
                    {
                        // Obtained the IHTMLDocument2 interface for the document object
                        ProcessDocument(pDoc);
                    }

                    pDocDisp->Release();
                }
            }

            pUnkDisp->Release();
        }

        pUnkBrowser->Release();
    }
}

// IHTMLDocument2
void CMyClass::ProcessDocument(IHTMLDocument2* pDoc)
{
    IHTMLElementCollection* pElemColl = NULL;

    hr = pDoc->get_all(&pElemColl);
    if (SUCCEEDED(hr))
    {
        // Obtained element collection.
        ProcessElementCollection(pElemColl);
        pElemColl->Release();
    }
}

void CMyClass::ProcessElementCollection(IHTMLElementCollection* pElemColl)
{
    IDispatch* pElemDisp = NULL;
    IHTMLElement* pElem = NULL;
    _variant_t varID("myID", VT_BSTR);
    _variant_t varIdx(0, VT_I4);

    hr = pElemColl->item(varID, varIdx, &pElemDisp);

    if (SUCCEEDED(hr))
    {
        hr = pElemDisp->QueryInterface(IID_IHTMLElement, (void**)&pElem);

        if (SUCCEEDED(hr))
        {
            // Obtained element with ID of "myID".
            ConnectEvents(pElem);
            pElem->Release();
        }

        pElemDisp->Release();
    }
}


void CMyClass::ConnectEvents(IHTMLElement* pElem)
{
    HRESULT hr;
    IConnectionPointContainer* pCPC = NULL;
    IConnectionPoint* pCP = NULL;
    DWORD dwCookie;

    // Check that this is a connectable object.
    hr = pElem->QueryInterface(IID_IConnectionPointContainer, (void**)&pCPC);

    if (SUCCEEDED(hr))
    {
        // Find the connection point.
        hr = pCPC->FindConnectionPoint(DIID_HTMLElementEvents2, &pCP);

        if (SUCCEEDED(hr))
        {
            // Advise the connection point.
            // pUnk is the IUnknown interface pointer for your event sink
            hr = pCP->Advise(pUnk, &dwCookie);

            if (SUCCEEDED(hr))
            {
                // Successfully advised
            }

            pCP->Release();
        }

        pCPC->Release();
    }
}


STDMETHODIMP CEventSink::Invoke(DISPID dispidMember,
                                REFIID riid,
                                LCID lcid,
                                WORD wFlags,
                                DISPPARAMS* pdispparams,
                                VARIANT* pvarResult,
                                EXCEPINFO* pexcepinfo,
                                UINT* puArgErr)
{
    switch (dispidMember)
    {
        case DISPID_HTMLELEMENTEVENTS2_ONCLICK:
        OnClick();
        break;

        default:
        break;
    }

    return S_OK;
}

----------------------------------------------------------------------------------------

IOleInPlaceFrame
IOleClientSite
IOleInPlaceSite

EmbedBrowserObject()
    window handle
        _IOleClientSiteEx    // USERDATA
            IOleInPlaceFrame
            IOleClientSite
            IOleInPlaceSite
            IDocHostUIHandler

    CoCreateInstance()
        CLSID_WebBrowser
        IWebBrowser2
            .put_Left()
            .put_Top()
            .put_Width()
            .put_Height()
            .Navigate2()

        IOleObject
            .Close()
            .SetClientSite() // IOleClientSite
            .SetHostNames()
            .DoVerb()

UnEmbedBrowserObject()

ResizeBrowser()

DisplayHTMLPage()

DoPageAction()

GetWebPtrs()
    IHTMLDocument2

IHTMLElementCollection

GetWebElement()
    IHTMLElement

CreateWebEvtHandler()

FreeWebEvtHandler()

WaitOnReadyState()

더보기


extern "C" {

HINSTANCE        cwebdll;

long WINAPI EmbedBrowserObject(HWND hwnd);
void WINAPI UnEmbedBrowserObject(HWND hwnd);
long WINAPI DisplayHTMLPage(HWND hWnd, const char *url);
void WINAPI ResizeBrowser(HWND hWnd, DWORD x, DWORD y);

}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR     lpCmdLine,
                     int       nCmdShow)
{
     // TODO: Place code here.
// {
    // Load our DLL containing the OLE/COM code. We do this once-only. It's named "cwebpage.dll"
    if (!(cwebdll = LoadLibrary("cwebpage.dll")))
    {
        MessageBox(0, "Can't open cwebpage.dll!", "ERROR", MB_OK);
        return(-1);
    }
// }
    MSG msg;
    HACCEL hAccelTable;

    // Initialize global strings
    LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    ...
}
...
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
// {
        case WM_CREATE:
            // Embed the browser object into our host window. We need do this only
            // once. Note that the browser object will start calling some of our
            // IOleInPlaceFrame and IOleClientSite functions as soon as we start
            // calling browser object functions in EmbedBrowserObject().
            if (EmbedBrowserObject(hWnd)) return(-1);
            DisplayHTMLPage(hWnd, "http://www.microsoft.com");
            break;
// }
        case WM_DESTROY:
// {
            // Detach the browser object from this window, and free resources.
            UnEmbedBrowserObject(hWnd);
// }
            PostQuitMessage(0);
            break;
        case WM_COMMAND:
            ...
            break;
        case WM_SIZE:
            // Resize the browser object to fit the window
            ResizeBrowser(hWnd, LOWORD(lParam), HIWORD(lParam));
            break;
        ...
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}


참조 사이트:

http://support.microsoft.com/kb/315617/en-us/
http://msdn.microsoft.com/en-us/library/bb508508(VS.85).aspx
http://support.microsoft.com/kb/194179/en-us/
http://www.codeproject.com/KB/shell/dlgdhtmlevents.aspx?display=Print
http://msdn.microsoft.com/en-us/library/aa293017(VS.71).aspx
http://urassa.tistory.com/entry/Web-Browser-Control-FAQ

http://www.codeproject.com/KB/COM/cwebpage.aspx?display=Print
http://jjjryu.tistory.com/entry/IE-1
http://support.microsoft.com/kb/196339/en-us/
http://jjjryu.tistory.com/entry/ActiveX-%EC%BB%A8%ED%8A%B8%EB%A1%A4-%ED%81%B4%EB%9D%BC%EC%9D%B4%EC%96%B8%ED%8A%B8
http://www.codeproject.com/KB/shell/AutomateShellWindow.aspx?display=Print
http://www.codeproject.com/KB/shell/iehelper.aspx?display=Print


출처 : http://jjjryu.tistory.com/333

Posted by SB패밀리