MFC界面--利用CHtmlView和HTML制作新风格的界面(包括CView和Dialog)

本文详细介绍了如何在MFC环境中利用CHtmlView和HTML创建具有交互性的程序界面,包括两种形式:文档视图和对话框形式。在文档视图中,通过重载CHtmlView的事件,如OnBeforeNavigate2,可以处理用户在HTML页面上的操作,如点击链接。此外,通过使用‘app:’伪协议,可以创建自定义命令处理。对话框形式中,通过自定义CHtmlCtrl控件,实现类似CHtmlView的功能,可以避免文档视图的限制。文章还讨论了如何禁用右键菜单,动态加载HTML内容,以及如何处理JavaScript与应用程序的交互,展示了创建新颖界面的可能性。
摘要由CSDN通过智能技术生成

一、文档视图形式,以一个视图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);
如果第一个方案不行
上这段代码

C/C++ code
?
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的时候,这么做是明智的:

1. If(m_pDocument!=NULL)
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里:

1. voidCView::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那些给我们惹麻烦的代码。

01. intCHtmlCtrl::OnMouseActive(…)
02. {
03.     // bypass CView doc/frame stuff
04.     returnCWnd::OnMouseActive(…);
05. }
06. voidCHtmlCtrl::OnDestroy()
07. {
08.     // bypass CView doc/frame stuff
09.     CWnd::OnDestroy();
10. }

除此之外,CHtmlCtrl也重载了PostNcDestroy

1. voidCHtmlCtrl::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文件里。

1. // in AboutHtml.rc
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的初始化过程:

1. BOOLCaboutDialog::OnInitDialog()
2. {
3.     VERIFY(Cdialog::OnInitDialog());
4.     VERIFY(m_page.CreateFromStatic(IDC_HTMLVIEW,this));
5.     m_page.LoadFromResource(_T("about.htm"));
6.     returnTRUE;
7. }

你可能对CHtmlCtrl::CreateFromStatic有点迷惑。 还记得我们刚开始谈到的CStatic吗? 我们打算用它来代表CHtmlCtrl控件,它将从CStatic建立一个CHtmlCtrl控件对象,这是一个子类化的过程,该对象将和CStatic有同样的ID,大小和位置。这么做很方便,很有效!:)

01. BOOLCHtmlCtrl::CreateFromStatic(UINTnID, CWnd* pParent)
02. {
03.     CStatic wndStatic;
04.     if(!wndStatic.SubclassDlgItem(nID, pParent))
05.         returnFALSE;
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.     returnCreate(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可以做任何合法的事情:

1. < A href="ok">< IMG … >< /A >
01. voidCmyHtmlCtrl::OnBeforeNavigate2(
02.         LPCTSTRlpszURL,…,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:

1. voidCmyHtmlCtrl::OnAppCmd(LPCTSTRlpszWhere)
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();这一句,否则表面没错,可能影响整个程序,导致崩溃。】

译者注&#

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值