Web Browser Control FAQ [문서파일]
다음의 FAQ(FAQ라고 하기도 뭐 합니다만.. )는 제가 VC++ Q&A 에서 그 동안 봐왔던 Web Browser 컨트롤과 관련 질문들에 대한 답 글과 KB, MSDN 링크 모음 들 입니다. 그 동안 집 컴퓨터에 정리만 해 놓고 필요할 때 종종 참고 했었는데 얼마 전 노트북 하드가 사망 하신 후에 복구한 내용을 팁 게시판에 올리는게 낫겠다고 생각 되어 올립니다. 답이 불 충분 한 경우도 있고 제가 미처 파악하지 못한 다른 좋은 해결 방법이 있을 수 있습니다. 더 좋은 방법을 알고 계시면 밑에 답 글을 써 주시고 다른 내용들도 추가 해주시면 감사하겠습니다.
1. API만 사용하는 내 프로그램에 HTML 컨트롤을 사용하고 싶어요.
이런 경우는 많지 않겠지만 MFC나 ATL 같은 Framework를 사용하지 않고 직접 API만을 이용해 구현 하는 경우엔 Web Browser 컨트롤을 사용할 수 있게 ActiveX 컨트롤 Host를 직접 만들어 줘야 합니다.
http://www.codeproject.com/com/cwebpage.asp?print=true
위 예제 코드에 그 내용이 설명 되어 있으니 참고 해보세요.
2. Dialog Based MFC Application 에서 CHtmlView를 사용하려고 합니다. 어떻게 하나요?
CHtmlView는 CView를 상속 받는 클래스로서 일반 적인 Doc/View 구조에서 사용할 수 있도록 만들어 져있으나 Document가 없이도 사용가능 하도록 되어 있습니다. 그러나 일반 적인 MFC Application에서 볼 수 있는 Frame (MainFrame) 윈도에 대한 처리 부분이 있는데 이를 CView의 함수들 대신 CWnd 함수들을 호출 하도록 변경 해주시면 됩니다.
우선 CHtmlView 클래스를 상속 받는 클래스를 만드시고 다음 함수들을 Override 하고 다음과 같이 해주시면 됩니다.
int CHtmlXXX::OnMouseActivate(…){
// bypass CView doc/frame stuff
return CWnd::OnMouseActivate(…);
}
void CHtmlXXX::OnDestroy() {
// bypass CView doc/frame stuff
CWnd::OnDestroy();
}
void CHtmlXXX::PostNcDestroy() {
// Do nothing. Don’t let CView get it.
}
http://www.microsoft.com/msj/0100/c/c0100.aspx
3. 쿠키를 지정 하고 싶은데 어떻게 하면 되나요?
InternetSetCookie()를 이용하시면 됩니다.
4. 여러 개의 Frame으로 이루어진 HTML 페이지에서 페이지가 로드 완료 되었는지 확인 하는 방법
여러개의 Frame이 존재 하는 HTML 페이지에서 DWebBrowserEvents::OnDocumentComplete 를 이용해 페이지 로딩의 끝을 감시 하는 경우엔 DocumentComplete Event가 Frame의 갯수 만큼 발생 되기 때문에 모든 Frame이 완전히 로드 다 됐다는 것을 검사 하기 위해서는 DocumentComplete 이벤트를 통해 넘겨 받는 IDispatch 인터페이스의 IUnknown과 웹브라우저 컨트롤의 IUnknown 인터페이스를 배교해같으면 최상위 레벨 프레임의 DocumentComplete 이벤트로 판단하고 모든 내용이 다운로드 되었다고 판단 하면 됩니다.
void CMfcWebHostView::OnDocumentComplete(LPDISPATCH lpDispatch,
VARIANT FAR* URL)
{
HRESULT hr;
LPUNKNOWN lpUnknown;
LPUNKNOWN lpUnknownWB = NULL;
LPUNKNOWN lpUnknownDC = NULL;
lpUnknown = m_webBrowser.GetControlUnknown();
ASSERT(lpUnknown);
if (lpUnknown)
{
// Get the pointer to the IUnknown interface of the WebBrowser
// control being hosted. The pointer to the IUnknown returned from
// GetControlUnknown is not the pointer to the IUnknown of the
// WebBrowser control. It’s actually a pointer to the IOleObject.
//
hr = lpUnknown->QueryInterface(IID_IUnknown,
(LPVOID*)&lpUnknownWB);
ASSERT(SUCCEEDED(hr));
if (FAILED(hr))
return;
// Get the pointer to the IUnknown of the object that fired this
// event.
//
hr = lpDispatch->QueryInterface(IID_IUnknown,
(LPVOID*)&lpUnknownDC);
ASSERT(SUCCEEDED(hr));
if (SUCCEEDED(hr) && lpUnknownWB == lpUnknownDC)
{
// The document has finished loading.
//
MessageBox(“The document has finished loading.”);
}
if (lpUnknownWB)
lpUnknownWB->Release();
if (lpUnknownDC)
lpUnknownDC->Release();
}
}
http://support.microsoft.com/kb/q180366/
4. 키 입력이 제대로 안 되는 경우가 있는데 이 문제는 어떻게 해결 하나요?
Web Browser 컨트롤이나 IE 에서 사용되는 ActiveX의 경우 Backspace 나 기타 몇몇 키 들에 대한 이벤트가 전달 되지 않아 처리를 못하는 경우가 있습니다. 이에 대한 해결은 MFC의 사용 여부 IE의 버전 차이 등 여러 가지 변수가 있어 이 FAQ 에서 다 설명 드리긴 힘들 것 같아 관련 글에 대한 Link를 올립니다.
MFC, ATL 등의 경우에 대한 글:
CView/CWnd 사용시에 대한 글:
WebBand 등의 ActiveX 컨트롤 사용시에 대한 글:
5. Web Browser 컨트롤이 띄우는 Message Box 내 입 맛 대로 Customizing 하는 방법
Web Browser 컨트롤이 MessageBox 를 띄울 때 마다 Web Browser 컨트롤은 자신의 Control Host의 IDocHostShowUI::ShowMessage()를 호출 합니다. Control Host 구현의 이 메소드를 override 하시고 원하시는 코드를 넣으시면 됩니다. 다음 예제는 Web Browser 컨트롤 창이 닫힐 때 창을 닫겠냐고 묻는 창이면 이를 보여주지 않고 바로 닫히게금 하는 코드 입니다.
STDMETHOD(ShowMessage)(HWND hwnd, LPOLESTR lpstrText, LPOLESTR lpstrCaption, DWORD dwType, LPOLESTR lpstrHelpFile, DWORD dwHelpContext, LRESULT *plResult )
{
USES_CONVERSION;
//
// If ShowMessage is called when you have specified no execution of ActiveX
// controls and there is an ActiveX control on the page, plResult is NULL
//
LPCTSTR lpszText = OLE2T(lpstrText);
#define IDS_MESSAGE_BOX_BROWSER_CLOSE 12341
HINSTANCE hinstSHDOCLC = LoadLibrary(TEXT(“SHDOCLC.DLL”));
if( hinstSHDOCLC )
{
TCHAR pBuffer[100];
LoadString(hinstSHDOCLC, IDS_MESSAGE_BOX_BROWSER_CLOSE, pBuffer, 100);
FreeLibrary(hinstSHDOCLC);
if( lstrcmpi(pBuffer, lpszText) == 0 )
{
*plResult = IDYES;
return S_OK;
}
}
return S_FALSE;
}
6. HTML Document 의 전체 스크롤 사이즈 나 content height, width를 얻는 방법
7. Web Browser 컨트롤이 종료 될 때 이에 대한 이벤트를 받고 싶습니다. 어떻게 하나요?
DWebBrwoserEvents2::OnQuit() 이벤트를 사용하시는 방법이 있고 이 방법 외에 WM_PARENTNOTIFY 메시지를 이용한 방법이 있습니다. 다음은 ATL 에서 이 메시지를 사용한 예 입니다.
LRESULT OnParentNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
if ((LOWORD(wParam) == WM_DESTROY) && ( (HWND)lParam == m_hWndIE ) )
{
ATLTRACE(“Destroying xWebCtrln”);
// Close the parent frame window.
OnQuit();
}
else
{
bHandled = FALSE;
}
return 0;
}
8. 실행 중인 IE의 리스트 얻는 방법
Browser Helper Object를 사용하시거나 ShellWindows 인터페이스를 통해 현재 실행되고 있는 ShellWindow 들 중에 IWebBrowser2 인터페이스를 지원하는 ShellWindow를 찾아 내는 방법이 있습니다.
BHO 사용 예제: http://www.codeguru.com/ieprogram/connect2IE4.shtml
ShellWindows 인터페이스 사용예제: http://www.codeguru.com/ieprogram/enumIE.html
ShellWindows를 사용한 방법은 앞으로 출시 될 Windows Vista 에선 보안 문제로 사용이 어려울 수 있습니다.
9. 소스 보기 막기
대략 두 가지 방법이 있겠습니다. 오른쪽 마우스 메뉴를 완전 막는 방법과 그 소스보기만 제거하는 방법입니다. 오른쪽 마우스 메뉴를 완전 막는 방법은 HtmlView의 PreTranslateMessage에서 WM_CONTEXTMENU, WM_RBUTTONDOWN, 그리고 WM_RBUTTONDBLCLK의 메시지에 바로 TRUE를 반환 하시면 됩니다. 소스보기만을 제거 하시려면 IDocHostUIHandler::ShowContextMenu() 를 구현 하시고 Web Browser 컨트롤에 등록 하시면 됩니다. 아 그리고 웹 페이지 내 스크립트로 막는 방법도 있겠네요.
10. 브라우저에서 자바 스크립트 호출 방법
다음의 KB 문서를 참고해 보시기 바랍니다.
http://support.microsoft.com/default.aspx?scid=kb;en-us;185127
이 방법 외에 IHTMLWindow2::execScript() 메소드를 사용하는 방법도 있습니다.
11. 스크립트 에러 창을 안나오게 하는 방법
다음의 KB 문서를 참고하세요.
http://support.microsoft.com/kb/261003
12. MFC에서 IDocHostShowUI, IDocHostUIHandler 를 직접 구현 하는 방법
IDocHostShowUI, IDocHostUIHandler 등의 인터페이스는 Control Host 에서 구현 하는 인터페이스 입니다. 그러나 MFC는 CWnd 를 통해 기본적인 ActiveX Control Host 구현을 제공 합니다. 이 기본으로 제공 하는 구현을 사용하는 대신 직접 구현한 클래스를 만들어야 합니다.
이에 대한 설명이 나온 글의 링크입니다.
http://www.codeproject.com/com/MFCClientSite.asp
13. ATL 에서 IDocHostShowUI, IDocHostUIHandler 를 구현 하는 방법
ATL에서는 MFC와는 달리 구현하고자 하는 인터페이스를 그냥 상속 받아 해당 메소드를 구현 해주시면 됩니다.
이 예제에 이 방법이 나와 있습니다.
14. DWebBrowserEvents2 구현 하는 방법
MFC 에서는 Class Wizard 에서 필요한 메소드를 Override 하는 방법으로 간단히 구현 가능 합니다.
그러나 IE 5.5 에서 추가된 WindowClosing, WindowSetHeight 등의 이벤트는 직접 추가 해주셔야 합니다.
DWebBrowserEvents2::WindowClosing 이벤트 추가에 대한 간단한 예제 입니다.
ON_EVENT() 매크로에 사용하는 인자에 대한 사용의 예제는 MSDN 과 MFC 소스코드의
CHtmlView의 코드인 C…VC98MFCSRCVIEWHTML.CPP 의 36 라인 부터 참고해보시기 바랍니다.
(앞의 경로는 VC6 기준 입니다.)
//헤더 파일에 다음을 추가 합니다.
#include “ExDispid.h” // IE5.5 이상이 지원 되는 헤더가 필요 합니다.
class CMFCIEView : public CHtmlView
{
protected: // create from serialization only
CMFCIEView();
DECLARE_DYNCREATE(CMFCIEView)
// …
DECLARE_EVENTSINK_MAP()
void OnWindowClosing(BOOL bIsChildWindow, BOOL *pbCancel);
void OnWindowSetHeight(long lHeight);
// …
};
// cpp 구현 파일에 다음 처럼 추가 합니다.
BEGIN_EVENTSINK_MAP(CMFCIEView, CHtmlView)
ON_EVENT(CMFCIEView, AFX_IDW_PANE_FIRST, DISPID_WINDOWCLOSING, OnWindowClosing, VTS_BOOL VTS_PBOOL)
ON_EVENT(CMFCIEView, AFX_IDW_PANE_FIRST, DISPID_WINDOWCLOSING, OnWindowSetHeight, VTS_I4)
END_EVENTSINK_MAP()
// 그리고 OnWindowClosing, OnWindowSetHeight 함수를 추가 합니다.
void CMFCIEView::OnWindowClosing(BOOL bIsChildWindow, BOOL *pbCancel)
{
//무언가 처리 합니다.
}
void CMFCIEView::OnWindowSetHeight(long lHeight)
{
//무언가 처리 합니다.
}
위의 빨간 색 부분에 이벤트 핸들러 함수가 받는 인자의 타입에 맞는 VTS_XXX 를 나열 해주시면 됩니다.
ATL에서 는 IDispEventImpl 클래스를 사용하는 방법이 있습니다.
class CxWebCtrl :
public CWindowImpl<CxWebCtrl>,
public IDispEventImpl<0, CxWebCtrl, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 0 >
{
public:
// …
BEGIN_SINK_MAP(CxWebCtrl)
SINK_ENTRY_EX( 0, DIID_DWebBrowserEvents2, DISPID_BEFORENAVIGATE2, BeforeNavigate2)
SINK_ENTRY_EX( 0, DIID_DWebBrowserEvents2, DISPID_COMMANDSTATECHANGE, CommandStateChange)
SINK_ENTRY_EX( 0, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete)
SINK_ENTRY_EX( 0, DIID_DWebBrowserEvents2, DISPID_DOWNLOADBEGIN, DownloadBegin)
SINK_ENTRY_EX( 0, DIID_DWebBrowserEvents2, DISPID_DOWNLOADCOMPLETE, DownloadComplete)
SINK_ENTRY_EX( 0, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2, NavigateComplete2)
SINK_ENTRY_EX( 0, DIID_DWebBrowserEvents2, DISPID_NEWWINDOW2, NewWindow2 )
SINK_ENTRY_EX( 0, DIID_DWebBrowserEvents2, DISPID_ONFULLSCREEN, OnFullScreen )
SINK_ENTRY_EX( 0, DIID_DWebBrowserEvents2, DISPID_ONMENUBAR, OnMenuBar )
SINK_ENTRY_EX( 0, DIID_DWebBrowserEvents2, DISPID_ONQUIT, OnQuit )
END_SINK_MAP()
//…
};
IDispEventImpl 에 대해서는 http://support.microsoft.com/kb/q194179/ 글을 참고해보세요.
15. 새 Popup 창 제어
IE 컨트롤의 DWebBrowseEvents2 이벤트 중 NewWindow2 이벤트를 사용하시면 됩니다.
DWebBrowseEvents2::NewWindow2(IDispatch **ppDisp, VARIANT_BOOL *Cancel) 에서
ppDisp 에 직접 생성한 IE나 WebBrowser 컨트롤의 IDispatch 인터페이스를 넣어 주시면 새로 창이 뜨는 대신 내가 생성한 IE 나 WebBrowser 컨트롤이 사용됩니다.
Cancel에 VARIANT_TRUE를 넣어 주시면 새 창이 뜨지 않습니다.
16. 사용자가 다녀간 웹 페이지의 히스토리 얻기
ITravelLogStg 인터페이스를 이용해 해당 웹브라우저 컨트롤의 히스트리를 얻을 수 있습니다.
HRESULT hr = S_OK;
IServiceProvider* pISP = NULL;
ITravelLogStg* pTLStg = NULL;
ITravelLogEntry* pTLEntry = NULL;
IEnumTravelLogEntry* pTLEnum = NULL;
if (FAILED(pWB->QueryInterface(IID_IServiceProvider, (void**) &pISP))
|| pISP == NULL)
goto Cleanup;
if (FAILED(pISP->QueryService(SID_STravelLogCursor, IID_ITravelLogStg, (void**) &pTLStg))
|| pTLStg == NULL)
goto Cleanup;
if (SUCCEEDED(pTLStg->EnumEntries(TLEF_RELATIVE_BACK, &pTLEnum)) && pTLEnum)
{
hr = pTLEnum->Next(1, &pTLEntry, NULL);
while (hr != S_FALSE)
{
LPOLESTR szURL;
if (SUCCEEDED(pTLEntry->GetURL(&szURL)) && szURL)
{
// 무언가 하기..
}
pTLEntry->Release();
pTLEntry = NULL;
hr = pTLEnum->Next(1, &pTLEntry, NULL);
}
}
Cleanup:
if (pTLStg)
pTLStg->Release();
if (pTLEnum)
pTLEnum->Release();
17. Web Browser 컨트롤의 Border, Scroll bar 등의 속성 제어 하기
Web Browser 컨트롤이 웹 페이지를 로드 할 때 마다 Control의 Host 쪽에 IDocHostUIHandler::GetHostInfo() 메소드를 호출 합니다. 이 메소드의 인자인 DOCHOSTUIINFO 의 dwFlags에 값을 적절히 넣어 주시면 됩니다.
자세한 예제는 12, 13번 글을 참고해보세요.