一、文档视图形式,以一个视图cview(chtmlview)作为首页界面
转自:http://liuxiang031130.blog.163.com/blog/static/1176665520111117914849/
用过 Outlook Express 的很多人都对其第一页的 HTML 界面感到新奇,很明显这是使用 DHTML 技术,加入了一些 Java Script 的一个网页,但它能够和应用程序进行交互操作。其实利用 VC6.0 的新加入的 MFC 类 CHtmlView ,你也可以实现这样一个令人激动的程序界面。这个界面可以利用 HTML ,这是很有意义的,想象一下,你在 HTML 中实现的效果,全部可以放在程序的界面中,而你所做的只是写了一个 HTML 文件和少量的编程。
CHtmlView 是 MFC 新加入的一个类,如果你看一下 MFC 关于这个类的源代码,就会发现在其内部封装了接口 IWebBrowser2 。这个接口实际上是 IE 的接口。也就是说,你可以通过这个类来调用强大的 IE 来显示 HTML 页面,每个人都可以利用这个类,轻松的写出一个浏览器。 VC 中也带了一个使用这个类写的浏览器的例子 MFCIE ,可以参考。
利用 CHtmlView 显示页面是很简单的。你只要在资源中加入 HTML 页面资源,程序中加入下面一句语句就可以实现。
LoadFromResource(ID_XXX);//ID_XXX 是资源的定义
解决这个问题的核心是如何利用 CHtmlView 把用户对 HTML 页面的操作传送给应用程序。这里看似很神秘,但实际上有一个技巧,可以截获用户的输入。在类 CHtmlView 中有一个事件 OnBeforeNavigate2 ,当浏览器被重新导向之前,会激活这个事件。比如说每当用户按下了 HTML 中的超级链接,或者用户在地址栏输入新的地址,还有程序员调用接口的 Navigate 方法,浏览器要转向新地址的时候,都会激活这个事件。而在这里,当你实现 HTML 界面的时候,用户通过点击页面上的链接来激活命令,所以我们可以在这个事件里做一些处理,这个事件的参数中有两个比较重要, lpszURL 就是在 HTML 页面中 href 指定的地址,你可以给各个链接设置相应的地址,通过这个参数的内容就可以识别用户点击的链接。而 pbCancel 可以指定是否取消导向,只要写入 *pbCancel = TRUE ,导向就被取消,不会发生了, CHtmlView 显示的还是现在的页面。让我们来看一下我写的例子程序 HtmlGUI 。
void CHtmlGUIView::OnBeforeNavigate2(LPCTSTR lpszURL, DWORD nFlags, LPCTSTR lpszTargetFrameName, CByteArray& baPostedData, LPCTSTR lpszHeaders, BOOL* pbCancel)
{
// TODO: Add your specialized code here and/or call the base class
if (ProcessCommand(lpszURL))
{
//URL was processed by the programmer defined code.
//cancel navigation
*pbCancel = TRUE;
}
else
{
CHtmlView::OnBeforeNavigate2(lpszURL, nFlags, lpszTargetFrameName, baPostedData, lpszHeaders, pbCancel);
}
}
BOOL CHtmlGUIView::ProcessCommand(LPCTSTR lpszURL)
{
if ( _tcscmp(lpszURL, _T("app:link1")) == 0 )
{
AfxMessageBox("Link1 was pressed, you can process your command here!");
return TRUE;
}
if ( _tcscmp(lpszURL, _T("app:link2")) == 0 )
{
AfxMessageBox("Link2 was pressed, you can process your command here!");
return TRUE;
}
//NOT processed by me.
if ( _tcscmp(lpszURL, _T("app:about")) == 0 )
{
::SendMessage(AfxGetMainWnd()->GetSafeHwnd(), WM_MAINAPP_ABOUT,0,0);
return TRUE;
}
return FALSE;
}
这里面我写了一个函数 BOOL CHtmlGUIView::ProcessCommand(LPCTSTR lpszURL) 来处理命令的映射,如果被导向的地址应该映射到程序的命令,则作相应的处理,并返回 TRUE ,表示已经被处理了,取消导向。否则返回 FALSE ,则调用 MFC 父类的函数,进行正常的操作。
为了不露出破绽,最好屏蔽掉鼠标右键,所以还应该重载 PreTranslateMessage ,并加入如下代码。
BOOL CHtmlGUIView::PreTranslateMessage(MSG* pMsg)
{
// TODO: Add your specialized code here and/or call the base class
// Do not let View get Right button Message.
if ((pMsg->message == WM_RBUTTONDOWN) ||
(pMsg->message == WM_RBUTTONDBLCLK))
return TRUE;
else
return CHtmlView::PreTranslateMessage(pMsg);
}
当用户按下右键或者双击右键的时候,返回 TRUE ,这样 CHtmlView 窗口就不会得到这两条消息了。当然你也可以利用页面中的 Script 来实现这个功能。
另外在写 HTML 页面时有一些东西要注意。微软为了使资源中的 HTML 文件能够得到访问,为 IE 定义了一个可以称为 res 协议的东西。你可以在 IE 的地址栏中输入 res://x:\xxx\htmlgui.exe/gui.htm ( x:\xxx\ 为路径), IE 中应该显示出我的程序的 HTML 文件。其实当你用 IE 访问页面出错的的时候, IE 显示的出错信息也是放在 C:\WINDOWS\SYSTEM\SHDOCLC.DLL 中的。 HTML 中用到的图片,声音等等,显然也要放入程序资源中才比较好。所以这些东西要全部当作 HTML 加入到资源中。而且,最好直接使用文件名用作资源 ID ,在资源文件中按下面格式加入定义。
// HTML
GUI.HTM HTML DISCARDABLE "res\\gui.htm"
PIC1.JPG HTML DISCARDABLE "res\\pic1.jpg"
WRITE.GIF HTML DISCARDABLE "res\\write.gif"
CHIMES.WAV HTML DISCARDABLE "res\\Chimes.wav"
【注意,提前把相应的htm和gif、jpg、wav等文件拷贝到工程的res文件夹下,然后在资源文件(以代码形式打开查看)中写入上述语句即可。】
虽然 VC 的资源编辑器会把他们当作文本来显示,但是不要担心。只要你不要按文本去编辑他们就行了, CHtmlView 在加载页面的时候会正确显示他们的。不过千万不要把他们当作自定义或其他资源来加入,如果你那样做了, CHtmlView 加载时反而会不认识他们。为了确保 HTML 被正确显示,所有的图片和声音能被找到,所以在 HTML 文件中加入下面一行。
< BASE url="res://HtmlGUI.exe/GUI.HTM">
CHtmlView 还有一个函数 CHtmlView::GetHtmlDocument 可以得到 Ative Document 对象的 IDispatch 接口,然后你应该可以利用这个接口 QueryInterface 出其他的接口,利用这些接口你可以在程序中动态地控制 HTML 页面的内容。如果有兴趣,好好看看 MSDN 联机帮助,研究一下吧。
HTML 界面是一种快速,方便,效果独特,容易排错的全新的界面技术,而由于微软封装了 OLE 接口 IWebBrowser2 ,使它的实现的技术细节变得这么简单,让我们来创造更新更酷的界面吧,祝编程愉快!
【补充:HTMLVIEW中打开网页,填表提交后 出现脚本错误提示 “当前的网页脚本发生错误”请问如何不让这种提示出现】
第一个方案
SetSilent(TRUE);
如果第一个方案不行
上这段代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
//屏蔽IE的错误窗口
void
CSimOOView::OnNavigateComplete2(
LPCTSTR
strURL)
{
// TODO: 在此添加专用代码和/或调用基类
CComPtr<IDispatch> spDisp = GetHtmlDocument();
if
(spDisp != NULL)
{
CComPtr<IHTMLDocument2> doc;
spDisp->QueryInterface(IID_IHTMLDocument2,
reinterpret_cast
<
void
**>(&doc));
if
(doc != NULL)
{
IHTMLWindow2 * pIhtmlwindow2 = NULL;
doc->get_parentWindow(&pIhtmlwindow2);
if
(pIhtmlwindow2 != NULL)
{
//屏蔽javascript脚本错误的javascript脚本
//CString strJavaScriptCode = "function fnOnError(msg,url,lineno){alert('script error:\\n\\nURL:'+url+'\\n\\nMSG:'+msg +'\\n\\nLine:'+lineno);return true;}window.onerror=fnOnError;";
//CString strJavaScriptCode = "function fnOnError(msg,url,lineno){alert('script error:\\n\\nURL:'+url+'\\n\\nMSG:'+msg +'\\n\\nLine:'+lineno);return true;}";
CString strJavaScriptCode =
"function fnOnError(msg,url,lineno){return true;}window.onerror=fnOnError;"
;
//TRACE("%s\n" , strJavaScriptCode);
BSTR bstrScript = strJavaScriptCode.AllocSysString();
CString strLanguage(
"JavaScript"
);
BSTR bstrLanguage = strLanguage.AllocSysString();
long
lTime = 1 * 1000;
long
lTimeID = 0;
VARIANT varLanguage;
varLanguage.vt = VT_BSTR;
varLanguage.bstrVal = bstrLanguage;
VARIANT pRet;
//把window.onerror函数插入入当前页面中去
pIhtmlwindow2->execScript(bstrScript, bstrLanguage, &pRet);
::SysFreeString(bstrScript);
::SysFreeString(bstrLanguage);
pIhtmlwindow2->Release();
}
}
}
CHtmlView::OnNavigateComplete2(strURL);
}
|
具体使用时,在具体CView::OnInitialUpdate()函数中添加如下代码,有三种形式,任选一种即可
法1:Navigate2(_T("www.google.com"),NULL,NULL); //此法是直接打开远程网页
法2:this->LoadFromResource(_T("firstpage.htm")); //此法是打开存放在exe中的二进制文件firstpage.hm。
法3:Navigate2(_T("WebPage\\FirstPage.htm"),NULL,NULL); //此法可以打开存放在工作目录下的WebPage目录中的FirstPage.htm文件,而且htm文件不必是二进制文件,甚至htm里面使用的图片等都可以不必是二进制文件,就像正常网页编辑那样使用,最重要的是法2不能使用外嵌的js文件,而法3想正常网页那样编辑然后嵌入到view中,没有法2那样的限制。
二、对话框形式,使用自定义的chtmlctrl控件
(1)转化htmlview为htmlctrl
转自:http://www.vckbase.com/index.php/wv/335
Wow!! 几篇让人拍案的文章,啃完之后大呼过瘾!想不到微软也有如此精通windows编程的家伙?! 此时此刻,俺想到的是分享给KBASE里的兄弟们啊! 没的说,掌声伺候!!!!
[NOTE]:
罗头说了,最好不要把Frame/Doc/View拆的妻离子散。是啊,本来好好的一家人,谁会那么残忍呢!? 嘿嘿,偶只是给他们弄了个远房的亲戚。:)
Now, Stop 费话ing!! Let''s go on the stuff…
首先,这里有两个难点需要解决! 一是:既然最后的产物是CHtmlCtrl,如何能象其他控件(比如Button)随意的丢到对话框里呢? COM->ActiveX?? 你说的,你自己做去吧!偶可是个COM稀里糊涂者!! 偶要比你想象地懒的多(鼓励程序员锻炼一下这种惰性! 好处多多)。 偶想,何不拉个替死鬼呢? 对了,CStatic不是可以随便被嗲来嗲去吗? 嗯,给它套上个SubclassDlgItem不就可以当成我们的CHtmlCtrl用了嘛! 有道理!! 然后是:View的确和Frame有着千丝万缕的联系。MFC是个半定制的框架,微软已做了很多手脚,说不定你在View里啪啪点几下,就有几个类似WM_MICROSPACE这样的消息传到了Frame里。然而控件是没有Frame可言的,而且控件也从不需要知道自己被放到了哪个容器里!!
所以,为了不至于编译器当啊当的乱叫,我们还要小心伺候着!:)
在继续往下做之前,你还要明确CHtmlView和我们最终生成的CHtmlCtrl到底有什么区别?
其实,区别仅仅是它们被使用的方法不同。控件通常是对话框里的子窗口---当然你可以把它作为任何窗口的子窗口。然而View却是专门为了实现MFC 文档视图结构而设计的。一个View有一个指向Document的指针并且被固定在一个特别的窗口里---人称:框架窗口(CFrameWnd)。对于Document来说,CView是它可以从形态上被表现的场作。但,指向Document的指针m_pDocument可能是NULL,所以每当我们在View里处理Document的时候,这么做是明智的:
2.
{
3.
// Do something here!
4.
}
所以,View并不正真的需要一个Document,CHtmlView也不需要。你可能认为在CHtmlView里的Document就是一个HTML文件,但实际上,CHtmlView是使用IWebBrowser2实现的,而且它对MFC的文档视图结构一无所知。
那么需要一个Frame吗? 如果你仔细研究过相关的代码,就会发现:只是在极少地方,View知道自己属于一个Frame。大多数文档视图结构是在更高一级的类比如Frame本身和CDocTemplate里实现的,它们把Frame,View,Document紧紧的粘在了一起。View并不知道外面发生了什么,这样设计的文档视图系统有很多优点。理论上来说,View是被动地受Frame控制,而且没有其他途径,所以认为View知道它自己的父窗口是错误的。有两处CView( 也是CHtmlView的父类 )会假想自己在一个Frame里。第一处是CView::OnMouseActive,它是WM_MOUSEACTIVE消息的处理函数,当你在View里点击鼠标以后,它会做很多细致的工作。这些细致的工作是不重要的,但重要的是View调用了GetParentFrame以得到它的父窗口框架,然后CFrameWnd::GetActiveView激活当前的活动视图---所有的这一切,都建立在假设View是CFrameWnd的一个子窗口的基础之上。
另外一处是在CView::OnDestroy里:
2.
{
3.
CframeWnd* pFrame = GetParentFrame();
4.
If(pFrame!=NULL&&pFrame->GetActiveView==
this
)
5.
// deactive during death
6.
pFrame->SetActiveView(NULL);
7.
CWnd::OnDestroy();
8.
}
在这里,View先让自己处于非活动状态。从另一个角度考虑,我们完全可以通过向父窗口发通知消息(Notification)代替C++方法调用从而回避掉View对Frame的依赖!! GetParentFrame可以返回一个CWnd,而非CFrameWnd,因为该函数只是强调了父窗口,而不是一定要返回一个特定的类。View可以通过发送一个类似WM_MICROSPACE的通知消息取代对CFrameWnd的方法调用,这些Notification会被"Frame"(或是CFrameWnd或是CfooWnd)正确的响应。 但是,无论如何,你必须清楚:当View被激活或进入非活动状态时,是Frame决定该如何响应,而不是View本身。任何系统都是如此;函数向下调用(从父到子),事件向上传递(从子到父)。一个子类从来都不会知道自己所在的容器!! 生活本身就不是完美的! 但幸运的是我们将会很容易的克服MFC给我们带来的难题! CHtmlCtrl类( here! )正是我们需要的:一个HTML "View" ,你可以在对话框或任何窗口里使用。CHtmlCtrl重载了OnMouseActive和OnDestroy从而回避CView那些给我们惹麻烦的代码。
02.
{
03.
// bypass CView doc/frame stuff
04.
return
CWnd::OnMouseActive(…);
05.
}
06.
void
CHtmlCtrl::OnDestroy()
07.
{
08.
// bypass CView doc/frame stuff
09.
CWnd::OnDestroy();
10.
}
除此之外,CHtmlCtrl也重载了PostNcDestroy
2.
{
3.
// do nothing
4.
}
CView正常的PostNcDestroy实现是使用delete this销毁View。对于Views来说,这是正常的处理方式,因为View是直接在堆里建立的。但是,控件一般是作为另一个窗口对象的成员存在的。这时你就不要delete了,它会被父对象delete掉。 通过以上的修改(OnMouseActive,OnDestroy和PostNcDestroy),CHtmlCtrl能顺利的在对话框里工作了!! 为此,偶写了个小程序:AboutHtml( 见源代码)显示一个About框(如图1)。那是完全用HTML写的。 HTML的资源:图片,音频文件(对了!甚至可以有声音)都被存储到EXE文件里。
2.
ABOUT.HTML HTML DISCARDABLE
"res\\about.htm"
3.
VCKBCOM.GIF HTML DISCARDABLE
"res\\vckcom.gif"
4.
OKUP.GIF HTML DISCARDABLE
"res\\okup.gif"
5.
OKDN.GIF HTML DISCARDABLE
"res\\okdn.gif"
6.
MOZART.WAV HTML DISCARDABLE
"res\\mozart.wav"
在一般的Web页面里,如果你这么做:< img src="vckcom.gif" / > 就需要把vckcom.gif放到当前目录下。对于访问一个存储在EXE文件里的资源,同样也要这样。这种情况下,你必须使用下面的代码帮助浏览器寻找你的HTML元素:
浏览器就会知道当前的"目录"是res://AboutHtml.exe,所以当它遇到< img src="vckcom.gif" / >,它会自动找到res://AboutHtml.exe/vckcom.gif,否则,它将在HTML文件所在的当前目录下寻找。
图一
一般来说,你可以使用res://modulename访问任何存储在EXE和DLL里的资源。res:是一个类似http:,ftp:,file:,或mailto:的协议。它告诉浏览器资源的路径和名字,细节工作浏览器知道如何去做!:) 为实现About对话框,偶写了个类,CAboutDialog,它有个CHtmlCtrl类型的成员m_page。我们来看看CaboutDialog的初始化过程:
2.
{
3.
VERIFY(Cdialog::OnInitDialog());
4.
VERIFY(m_page.CreateFromStatic(IDC_HTMLVIEW,
this
));
5.
m_page.LoadFromResource(_T(
"about.htm"
));
6.
return
TRUE;
7.
}
你可能对CHtmlCtrl::CreateFromStatic有点迷惑。 还记得我们刚开始谈到的CStatic吗? 我们打算用它来代表CHtmlCtrl控件,它将从CStatic建立一个CHtmlCtrl控件对象,这是一个子类化的过程,该对象将和CStatic有同样的ID,大小和位置。这么做很方便,很有效!:)
02.
{
03.
CStatic wndStatic;
04.
if
(!wndStatic.SubclassDlgItem(nID, pParent))
05.
return
FALSE;
06.
// Get static control rect
07.
CRect rc;
08.
wndStatic.GetWindowRect(&rc);
09.
pParent->ScreenToClient(&rc);
10.
wndStatic.DestroyWindow();
11.
// create HTML control (CHtmlView)
12.
return
Create(NULL,
// class name
13.
NULL,
// title
14.
(WS_CHILD | WS_VISIBLE ),
// style
15.
rc,
// rectangle
16.
pParent,
// parent
17.
nID,
// control ID
18.
NULL);
// frame/doc context not used
19.
}
然后是使用CHtmlCtrl::LoadFromResource打开页面,它从CHtmlView继承而来。当然偶也可以这样打开页面:res://AboutHtml.exe/about.html OK! 偶已经向你展示了CHtmlCtrl如何通过回避CView而顺利代替frame在dialog里显示!。偶也介绍了如何如何在资源文件里定位HTML文件和图象文件。并且告诉你如何打开一个Web页面。 但还有一个极为精彩的处理没有告诉你!:) ,能猜到是什么吗? 哇哈哈哈!!! 看到About对话框里的OK按钮了吧? 它并不是一个按钮!!仅仅是HTML文件里的一副图片! 偶使用了Javascript使得它在被单击时有up和down两种状态,但是它是如何和我们的对话框程序通讯的呢??? 你说好玩不?
如果你搞过DHTML,你可能会想到DHTML文档层可使用COM发现IMG元素然后监听它的OnClick事件。但是那样做对于偶这样的COM半文盲是way,way,way,WAY痛苦和麻烦的工作!!:( 其实,有一个更为简单的方法。假设你让这个"button"链接到一个叫做ok的文档: 现在,当用户单击它,浏览器将转到ok文件。但在它这么做以前,控制权交给 CHtmlCtrl::OnBeforeNavigate2。这时CHtmlCtrl可以做任何合法的事情:
02.
LPCTSTR
lpszURL,…,
BOOL
*pbCancel)
03.
{
04.
if
(_tcscmp(lpszURL,_T(
"ok"
))==0)
05.
{
06.
//"ok" clicked
07.
*pbCancle=TRUE;
// abort
08.
// will close dialog
09.
GetParent()->SendMessage(WM_COMMAND,IDOK);
10.
}
11.
}
[这是多么振奋人心的消息?? 想一想,我们几乎可以让对话框做几乎所有能做的事情! 而且我们可以将Web页面处理的更为美观!!:]] 所以,ok并不正真的是另一个文件,而CHtmlCtrl正是利用它来解释OK按钮!! 太完美了! 为了让这个想法更紧凑,偶引入了一个伪协议! 叫做:app:。用它来代替使用ok,在about.htm里正真的链接是app:ok。当CHtmlCtrl发现浏览器试图导航到app:somewhere时,它调用一个新的虚函数,CHtmlCtrl::OnAppCmd,它用somewhere作为参数,并cancels调航(navigation),所以CmyHtmlCtrl并没有重载OnNavigate2,而是重载了OnAppCmd:
2.
{
3.
if
(_tcsicmp(lpszWhere,_T(
"ok"
))==0)
4.
{
5.
GetParent()->SendMessage(WM_COMMAND,IDOK);
6.
}
7.
}
你可以在HTML文件里使用其他链接,比如app:cancel,app:refresh,或者app:whatever!:) 并同时使用OnAppCmd函数寻找相应的字串,"cancel","refresh",以及"whatever"。 好了!! 可以做你自己想做的事去了!…在你疯狂的code之前,提醒几句:加载IE DLLs需要极少的等待,但是如果加载About对话框超过10秒并且搬出来个沙漏晃来晃去,用户将感到非常不舒服!:)。 最后,当你在About对话框里单击鼠标右键,会弹出个标准的浏览器快捷菜单,你可能觉得这是多余的,或者出于保护你的源代码的目的,你会买力的屏蔽调右键的功能。其实这很简单,你仅仅需要在HTML的 标签里加入一句脚本代码……但我们毕竟是在玩VC。所以,尽管麻烦,我们还是很乐意尝试。 这个也不难!! 我们同样使用子类化的原理就能实现。但不是现在,而是将来的某个时候!! :)
(2)交互深入
转自:http://www.vckbase.com/index.php/wv/686 可以下载源码
在这之前补充我发现的一个问题:
void CHtmlCtrl::OnDestroy()
{
// This is probably unecessary since ~CHtmlView does it, but
// safer to mimic CHtmlView::OnDestroy.
if (m_pBrowserApp) {
m_pBrowserApp->Release();
m_pBrowserApp = NULL;
}
CWnd::OnDestroy(); // bypass CView doc/frame stuff
}
【上面代码是CHtmlCtrl.cpp中的,但不能通过vs2008编译,原因是下面讲的是vc6环境下的。可能是vs2008使用的是atl3.0以上的版本的缘故。解决方法是把函数内部的东西都注释掉,只留下最后一句CWnd::OnDestroy();即可。不可只注释掉出错的m_pBrowserApp->Release();这一句,否则表面没错,可能影响整个程序,导致崩溃。】
译者注&#