testWebBrowserDlg.h
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | // testWebBrowserDlg.h : 头文件 // #pragma once #include "explorer1.h" #import "C:\windows\system32\mshtml.tlb" // location of mshtml.tlb #include <map> #include <comdef.h> #include <mshtml.h> #include <mshtmdid.h> /* 标题:测试C++代码与WebBrowser HTML的互动 Author:Kagula Date:2014-08-20 版本号:3 Test Env: Windows8.1、VS2013 Update2 内容: [1]如何拿到html中的elements,取得它的属性! [2]如何响应element激发的事件 [3]如何修改指定element的属性 [4]如何Render内存中的html字符串 参考资料 [1]《MFC中针对WebBrowser控件增加link链接点击事件监控》 http://www.mworkbox.com/wp/work/509.html [2]《IWebBrowser2 interface》 http://msdn.microsoft.com/en-us/library/aa752127(VS.85).aspx [3]《Handling HTML Element Events》 http://msdn.microsoft.com/en-us/library/bb508508(v=vs.85).aspx [4]《如何从 VC web 浏览器应用程序中调用脚本函数》 http://support.microsoft.com/kb/q185127 [5]《Loading HTML content from a Stream》 http://msdn.microsoft.com/en-us/library/ie/aa752047%28v=vs.85%29.aspx [6]《How do I get the font color from a piece of HTML source code?》 http://stackoverflow.com/questions/7402347/how-do-i-get-the-font-color-from-a-piece-of-html-source-code [7]《How to create a sink interface in a MFC-based COM client》 http://support.microsoft.com/default.aspx?scid=kb;en-us;181845 [8]《How To Use the Microsoft WebBrowser Control to Render HTML from Memory》 http://www.nuonsoft.com/blog/2010/03/24/how-to-use-the-microsoft-webbrowser-control-to-render-html-from-memory/comment-page-1/ [9]《How do I get the font color from a piece of HTML source code?》 http://stackoverflow.com/questions/7402347/how-do-i-get-the-font-color-from-a-piece-of-html-source-code [10]《Using the WebBrowser control, simplified》 http://www.codeproject.com/Articles/3919/Using-the-WebBrowser-control-simplified [11]《Microsoft Internet Explorer 5.5 behaviors》 http://msdn.microsoft.com/en-us/magazine/cc301528.aspx [12]《Using IHTMLEditDesigner》 http://www.codeproject.com/Articles/6546/Using-IHTMLEditDesigner [13]《MFC C++ WebBrowser Control load HTML from a string》 http://stackoverflow.com/questions/9179179/mfc-c-webbrowser-control-load-html-from-a-string [14]VC++ webbrowser函数使用范例 [15]《【webbrowser使用】_webbrowser使用的相关文章,教程,源码》 http://www.xuebuyuan.com/zt/12577882.html */ namespace kagula { struct ConnectionInfo { IDispatch* dispatch; IID iid; DWORD cookie; ConnectionInfo() {} ConnectionInfo(IDispatch *dispatch, IID iid, DWORD cookie) { this ->dispatch = dispatch, this ->iid = iid, this ->cookie = cookie; } }; } // CtestWebBrowserDlg 对话框 class CtestWebBrowserDlg : public CDialogEx { // 构造 public : CtestWebBrowserDlg(CWnd* pParent = NULL); // 标准构造函数 // 对话框数据 enum { IDD = IDD_TESTWEBBROWSER_DIALOG }; protected : virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 // 实现 protected : HICON m_hIcon; // 生成的消息映射函数 virtual BOOL OnInitDialog(); afx_msg void OnSysCommand( UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public : CExplorer1 m_webBrowser; void SetElementAttribute(MSHTML::IHTMLDocument2Ptr htmlDoc,CString elementID,CString attributeName,CString value); std::map<IDispatch*, kagula::ConnectionInfo> m_mapElem2EventCookie; //用于释放Connection void ReleaseHTMLConnection(); void DemoGetElement(LPDISPATCH pDisp, VARIANT* URL); void DemoGetAllLinkElement(LPDISPATCH pDisp, VARIANT* URL); void OnClick(MSHTML::IHTMLEventObj *pEvtObj); void OnLostFocus(MSHTML::IHTMLEventObj *pEvtObj); void WriteHTML( const wchar_t * html); afx_msg void OnBnClickedMemoryRender(); //added new three map macros DECLARE_EVENTSINK_MAP() DECLARE_DISPATCH_MAP() DECLARE_INTERFACE_MAP() void BeforeNavigate2Explorer1(LPDISPATCH pDisp, VARIANT* URL, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, BOOL * Cancel); void DocumentCompleteExplorer1(LPDISPATCH pDisp, VARIANT* URL); afx_msg void OnBnClickedBtnSetspecifiedelementattr(); virtual void OnOK(); virtual void OnCancel(); }; |
testWebBrowserDlg.cpp
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 | // testWebBrowserDlg.cpp : 实现文件 // #include "stdafx.h" #include "testWebBrowser.h" #include "testWebBrowserDlg.h" #include "afxdialogex.h" #include <string> #include <afxctl.h> #ifdef _DEBUG #define new DEBUG_NEW #endif // CtestWebBrowserDlg 对话框 CtestWebBrowserDlg::CtestWebBrowserDlg(CWnd* pParent /*=NULL*/ ) : CDialogEx(CtestWebBrowserDlg::IDD, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } void CtestWebBrowserDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_EXPLORER1, m_webBrowser); } BEGIN_MESSAGE_MAP(CtestWebBrowserDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BTN_RENDERSTRING, &CtestWebBrowserDlg::OnBnClickedMemoryRender) ON_BN_CLICKED(IDC_BTN_SETSPECIFIEDELEMENTATTR, &CtestWebBrowserDlg::OnBnClickedBtnSetspecifiedelementattr) END_MESSAGE_MAP() // CtestWebBrowserDlg 消息处理程序 BOOL CtestWebBrowserDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 设置此对话框的图标。 当应用程序主窗口不是对话框时,框架将自动 // 执行此操作 SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, FALSE); // 设置小图标 // TODO: 在此添加额外的初始化代码 EnableAutomation(); //没有这行代码会导致GetIDispatch(FALSE)失败! //m_webBrowser.Navigate(L"D:\\Workspace\\testWebBrowser\\testWebBrowser\\test.html",NULL,NULL,NULL,NULL); m_webBrowser.Navigate(L "about:blank" , NULL, NULL, NULL, NULL); return TRUE; // 除非将焦点设置到控件,否则返回 TRUE } void CtestWebBrowserDlg::OnSysCommand( UINT nID, LPARAM lParam) { CDialogEx::OnSysCommand(nID, lParam); } // 如果向对话框添加最小化按钮,则需要下面的代码 // 来绘制该图标。 对于使用文档/视图模型的 MFC 应用程序, // 这将由框架自动完成。 void CtestWebBrowserDlg::OnPaint() { if (IsIconic()) { CPaintDC dc( this ); // 用于绘制的设备上下文 SendMessage(WM_ICONERASEBKGND, reinterpret_cast < WPARAM >(dc.GetSafeHdc()), 0); // 使图标在工作区矩形中居中 int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // 绘制图标 dc.DrawIcon(x, y, m_hIcon); } else { CDialogEx::OnPaint(); } } //当用户拖动最小化窗口时系统调用此函数取得光标 //显示。 HCURSOR CtestWebBrowserDlg::OnQueryDragIcon() { return static_cast < HCURSOR >(m_hIcon); } //测试:Render内存中的HTML void CtestWebBrowserDlg::OnBnClickedMemoryRender() { m_webBrowser.Navigate(L "app://mymemory.page" ,NULL,NULL,NULL,NULL); } BEGIN_EVENTSINK_MAP(CtestWebBrowserDlg, CDialogEx) ON_EVENT(CtestWebBrowserDlg, IDC_EXPLORER1, 250, CtestWebBrowserDlg::BeforeNavigate2Explorer1, VTS_DISPATCH VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PVARIANT VTS_PBOOL) ON_EVENT(CtestWebBrowserDlg, IDC_EXPLORER1, 259, CtestWebBrowserDlg::DocumentCompleteExplorer1, VTS_DISPATCH VTS_PVARIANT) END_EVENTSINK_MAP() /* 第二步:处理所有种类元素的事件 */ BEGIN_INTERFACE_MAP(CtestWebBrowserDlg, CCmdTarget) INTERFACE_PART(CtestWebBrowserDlg, DIID_HTMLElementEvents2, Dispatch) END_INTERFACE_MAP() /* 第三步: 某种事件(元素类型无关)和哪个响应函数连*/ BEGIN_DISPATCH_MAP(CtestWebBrowserDlg, CCmdTarget) DISP_FUNCTION_ID(CtestWebBrowserDlg, "HTMLELEMENTEVENTS2_ONCLICK" , DISPID_HTMLELEMENTEVENTS2_ONCLICK, CtestWebBrowserDlg::OnClick, VT_EMPTY, VTS_DISPATCH) DISP_FUNCTION_ID(CtestWebBrowserDlg, "HTMLELEMENTEVENTS2_ONFOCUSOUT" , DISPID_HTMLELEMENTEVENTS2_ONFOCUSOUT, CtestWebBrowserDlg::OnLostFocus, VT_EMPTY, VTS_DISPATCH) END_DISPATCH_MAP() void CtestWebBrowserDlg::BeforeNavigate2Explorer1(LPDISPATCH pDisp, VARIANT* URL, VARIANT* Flags, VARIANT* TargetFrameName, VARIANT* PostData, VARIANT* Headers, BOOL * Cancel) { CString strURL(URL->bstrVal); *Cancel = FALSE; if (strURL == _T( "about:blank" )) { *Cancel = FALSE; } else { if (strURL.Find(_T( "ThePageNeverReach.htm" )) > 0 ) { //阻止跳转到指定页面! *Cancel = TRUE; return ; } } if (!(*Cancel)) { //进入新页面之前,先释放掉事件连接 ReleaseHTMLConnection(); } //演示,render内存中的html页面! if (strURL.Find(_T( "app://mymemory.page" )) >= 0) { *Cancel = TRUE; WriteHTML(L "<html><body><h1>My Header</h1><p>Some text below the header</p></body></html>" ); return ; } } void CtestWebBrowserDlg::DocumentCompleteExplorer1(LPDISPATCH pDisp, VARIANT* URL) { //DemoGetElement(pDisp, URL); DemoGetAllLinkElement(pDisp, URL); } //end func //演示:鼠标点击事件 void CtestWebBrowserDlg::OnClick(MSHTML::IHTMLEventObj *pEvtObj) { MSHTML::IHTMLElementPtr elem = pEvtObj->srcElement; CString cstrID = elem->Getid(); CString cstrTag = elem->GettagName(); //标签的名字 if (cstrID.GetLength()<=0) { return ; } _variant_t name = elem->getAttribute(_T( "name" ), 0); CString cstrName; if (name.vt != VT_NULL) { cstrName = name; } _variant_t href = elem->getAttribute(_T( "value" ), 0); CString cstrHref; if (href.vt != VT_NULL) { cstrHref = href.bstrVal; } CString msg; msg.Format(L "[id=%s][name=%s][tag=%s][value=%s]" , cstrID.GetBuffer(), cstrName.GetBuffer(), cstrTag.GetBuffer(), cstrHref.GetBuffer(MAX_PATH)); TRACE(msg); } //演示:响应 标签 失去焦点事件 void CtestWebBrowserDlg::OnLostFocus(MSHTML::IHTMLEventObj *pEvtObj) { MSHTML::IHTMLElementPtr elem = pEvtObj->srcElement; CString cstrID = elem->Getid(); if (cstrID.GetLength()<=0) { return ; } CString msg; msg.Format(L "OnLostFocus cstrID = [%s]" , cstrID.GetBuffer()); AfxMessageBox(msg); } //演示:拿到指定ID的标签元素,并打印它的属性 void CtestWebBrowserDlg::DemoGetElement(LPDISPATCH pDisp, VARIANT* URL) { IWebBrowser2Ptr webBrowser(pDisp); IDispatchPtr htmlDocDisp; (*webBrowser).get_Document(&htmlDocDisp); MSHTML::IHTMLDocument2Ptr htmlDoc(htmlDocDisp); MSHTML::IHTMLElementCollectionPtr elements; (*htmlDoc).get_all(&elements); IDispatchPtr disp; _variant_t index(0L, VT_I4); do { disp = (*elements).item(_variant_t( "myFontTag" ), index); if (disp != NULL) { MSHTML::IHTMLElementPtr element(disp); variant_t vtValue = element->getAttribute( "color" , 0); CString cstr = vtValue; TRACE(L "mytag标签的color属性为%s\n" , cstr.GetBuffer(MAX_PATH)); ++index.lVal; } } while (disp != NULL); } /* 拿到元素,并做链接 [1]《AfxConnectionAdvise》 http://msdn.microsoft.com/en-us/library/b9h84ebk.aspx [2]《How to create a sink interface in a MFC-based COM client》 http://support.microsoft.com/default.aspx?scid=kb;en-us;181845 [3]《同Document建立Connection》 http://www.popkistopki.ru/ch08e.htm */ void CtestWebBrowserDlg::DemoGetAllLinkElement(LPDISPATCH pDisp, VARIANT* URL) { // Get the HTML document. // IWebBrowser2Ptr webBrowser(pDisp); IDispatchPtr htmlDocDisp; (*webBrowser).get_Document(&htmlDocDisp); MSHTML::IHTMLDocument2Ptr htmlDoc(htmlDocDisp); if (htmlDoc == NULL) //URL属性为空 { return ; } //打印HTML页面内容 MSHTML::IHTMLElementPtr body = htmlDoc->Getbody(); variant_t html = body->parentElement->outerHTML; //variant_t bodyHTML = body->GetouterHTML(); CString cstrBodyHTML = html; TRACE(L "cstrBodyHTML.GetBuffer()========\n%s\n" , cstrBodyHTML.GetBuffer()); //取HTML中的元素 DWORD dwCookie = 0; // Get the collection of elements. MSHTML::IHTMLElementCollectionPtr elements; (*htmlDoc).get_all(&elements); IDispatchPtr disp; _variant_t index(0L, VT_I4); do { //Get all elements disp = (*elements).item(index, index); if (disp != NULL) { // Examine their action attribute to determine what should be done. IDispatchPtr element(disp); MSHTML::IHTMLElementPtr elemTag(disp); //第一步:建立Connection DWORD dwCookie = 0; BSTR name = NULL; elemTag->get_tagName(&name); if (name != NULL) { //is link!!!! LPUNKNOWN pUnkSink = GetIDispatch(FALSE); //关联全部类型元素 if (AfxConnectionAdvise(element, DIID_HTMLElementEvents2, pUnkSink, FALSE, &dwCookie)) { kagula::ConnectionInfo ci(element.GetInterfacePtr(), DIID_HTMLElementEvents2, dwCookie); m_mapElem2EventCookie[element.GetInterfacePtr()] = ci; } } //end if ++index.lVal; } } while (disp != NULL); } //释放同HTML的Connection void CtestWebBrowserDlg::ReleaseHTMLConnection() { std::map<IDispatch *, kagula::ConnectionInfo>::iterator itr; for (itr = m_mapElem2EventCookie.begin(); itr != m_mapElem2EventCookie.end(); itr++) { //DIID_HTMLDocumentEvents、DIID_HTMLAnchorEvents2、DIID_HTMLButtonElementEvents AfxConnectionUnadvise(itr->first, itr->second.iid, GetIDispatch(FALSE), FALSE, itr->second.cookie); } m_mapElem2EventCookie.clear(); } //测试,设置当前页面指定元素的属性 void CtestWebBrowserDlg::OnBnClickedBtnSetspecifiedelementattr() { CComPtr<IDispatch> spDisp = m_webBrowser.get_Application(); if (spDisp != NULL) { CComPtr<IWebBrowser2> spWeb; HRESULT hr = spDisp->QueryInterface(IID_IWebBrowser2, ( void **)&spWeb); if (SUCCEEDED(hr)) { IDispatchPtr htmlDocDisp; spWeb->get_Document(&htmlDocDisp); MSHTML::IHTMLDocument2Ptr htmlDoc(htmlDocDisp); if (htmlDoc == NULL) //URL属性为空 { return ; } SetElementAttribute(htmlDoc, L "firstname" , L "value" , L "Marcia" ); SetElementAttribute(htmlDoc, L "lastname" ,L "value" , L "JohnDoe" ); SetElementAttribute(htmlDoc, L "female" ,L "checked" , L "1" ); SetElementAttribute(htmlDoc, L "bike" ,L "checked" , L "" ); SetElementAttribute(htmlDoc, L "car" ,L "checked" , L "1" ); //演示,重定向到其它URL //CComVariant varURL("http://www.intel.com"); //spWeb->Navigate2(&varURL, NULL, NULL, NULL, NULL); } } } void CtestWebBrowserDlg::SetElementAttribute(MSHTML::IHTMLDocument2Ptr htmlDoc, CString elementID, CString attributeName, CString value) { MSHTML::IHTMLElementCollectionPtr elements; htmlDoc->get_all(&elements); IDispatchPtr disp; _variant_t index(0L, VT_I4); do { disp = (*elements).item(_variant_t(elementID.GetBuffer()), index); if (disp != NULL) { MSHTML::IHTMLElementPtr element(disp); element->setAttribute(attributeName.GetBuffer(), value.GetBuffer(),0); ++index.lVal; } } while (disp != NULL); } /*测试render内存中的html*/ void CtestWebBrowserDlg::WriteHTML( const wchar_t * html) { IDispatch* pHtmlDoc = m_webBrowser.get_Document(); /* 在调用这段代码之前,如果你还没有url需要navigate,就必须在 OnInitDialog中插入下面的代码,否则拿不到document! m_webBrowser.Navigate(L"about:blank",NULL,NULL,NULL,NULL); */ if (!pHtmlDoc) return ; CComPtr<IHTMLDocument2> doc2; doc2.Attach((IHTMLDocument2*)pHtmlDoc); if (!doc2) return ; // Creates a new one-dimensional array SAFEARRAY* psaStrings = SafeArrayCreateVector(VT_VARIANT, 0, 1); if (!psaStrings) return ; BSTR bstr = SysAllocString(html); if (bstr) { VARIANT* param; HRESULT hr = SafeArrayAccessData(psaStrings, ( LPVOID *)¶m); if (SUCCEEDED(hr)) { param->vt = VT_BSTR; param->bstrVal = bstr; hr = SafeArrayUnaccessData(psaStrings); if (SUCCEEDED(hr)) { doc2->write(psaStrings); doc2->close(); } } } // SafeArrayDestroy calls SysFreeString for each BSTR! if (psaStrings) SafeArrayDestroy(psaStrings); } //退出前要释放链接 void CtestWebBrowserDlg::OnOK() { ReleaseHTMLConnection(); CDialogEx::OnOK(); } void CtestWebBrowserDlg::OnCancel() { ReleaseHTMLConnection(); CDialogEx::OnCancel(); } |
test.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | < head > < title ></ title > < meta http-equiv="Content-Type" content="text/html; charset=gb2312" /> </ head > < body > < h1 id="myH1Tag">测试从H1标签能不能得到事件</ h1 > < font id="myFontTag" color=#5a6571>测试能不能从font标签得到事件</ font > < br /> < br /> < br /> < a id="idOfA" name="nameOfA" href="file:D:\Workspace\testWebBrowser\testWebBrowser\ThePageNeverReach.htm">测试禁止页面跳转</ a >< br /> < a id="id2OfA" name="name2OfA" href="file:D:\Workspace\testWebBrowser\testWebBrowser\HTMLPage.htm">测试用户点击链接, C++后台得到消息!,并跳转到页面</ a > < form > First name: < input id='firstname' type='text' name='firstname' />< br /> Last name: < input id='lastname' type='text' name='lastname' />< br /> Password: < input id='password' type='password' name='pwd' />< br >< br /> < input type='radio' id='male' name='sex' value='male' />Male< br /> < input type='radio' id='female' name='sex' value='female' />Female< br />< br /> < input type='checkbox' id='bike' name='vehicle' value='Bike' />I have abdsmasterbike< br /> < input type='checkbox' id='car' name='vehicle' value='Car' />I have a car < br />< br /> < input type='button' id='ok' value='OK' />< br /> < input type='button' id='cancel' value='Cancel' />< br />< br /> </ form > </ body > |
转载自:http://blog.csdn.net/lee353086/article/details/38537415
程序员的基础教程:菜鸟程序员
标签:
c++
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现