PreSubclassWindow

本文详细解析了MFC中窗口创建与子类化的三个关键函数:PreCreateWindow、PreSubclassWindow和SubclassWindow。阐述了每个函数的作用、调用时机以及如何在适当的地方重写这些函数来实现窗口的自定义。通过代码实例,帮助开发者更好地理解和应用MFC窗口管理。
 

PreSubclassWindow
PreSubclassWindow是一个很好的定制控件的位置。如果我们通过重载CWnd::PreCreateWindow定制控件,而用户在对话框中使用控件。由于对话框中的控件窗口是通过CreateDlgIndirect创建,不经过CWnd::CreateEx函数,PreCreateWindow函数不会被调用。

其实,用户要在对话框中使用定制控件,必须用DDX或者SubclassDlgItem函数子类化控件,这时PreSubclassWindow一定会被调用。

如果用户直接创建定制控件窗口,CWnd::CreateEx函数就一定会被调用,控件窗口一定会被子类化以安装MFC消息泵。所以在MFC中,PreSubclassWindow是创建窗口的必经之路。


CWnd::PreSubclassWindow

virtual void PreSubclassWindow( );

说明:
框架调用这个成员函数以允许在窗口被子类化之前进行其它必要的子类化。重载这个函数以允许控件的动态子类化。这是个高级可重载函数。


CWnd中PreCreateWindow、PreSubclassWindow、SubclassWin MFC(VC6.0)的CWnd及其子类中,有如下三个函数: class CWnd : public CCmdTarget
{
   
public:
   
         virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
     virtual void PreSubclassWindow();
     BOOL SubclassWindow(HWND hWnd);
   
};     让人很不容易区分,不知道它们究竟干了些什么,在什么情况下要改写哪个函数?
     想知道改写函数?让我先告诉你哪个不能改写,那就是SubclassWindow。Scott Meyers的杰作<<Effective C++>>的第36条是这样的Differentiate between inheritance of interface and inheritance of implementation. 看了后你马上就知道,父类中的非虚拟函数是设计成不被子类改写的。根据有无virtual关键字,我们在排除了SubclassWindow后,也就知道PreCreateWindow和PreSubClassWindow是被设计成可改写的。接着的问题便是该在什么时候该写了。要知道什么时候该写,必须知道函数是在什么时候被调用,还有执行函数的想要达到的目的。我们先看看对这三个函数,MSDN给的解释:
     PreCreateWindow:
     Called by the framework before the creation of the Windows window
     attached to this CWnd object.
     (译:在窗口被创建并attach到this指针所指的CWnd对象之前,被framework调用)
     PreSubclassWindow:
     This member function is called by the framework to allow other necessary
     subclassing to occur before the window is subclassed.
     (译:在window被subclassed之前被framework调用,用来允许其它必要的subclassing发生)
虽然我已有译文,但还是让我对CWnd的attach和窗口的subclass作简单的解释吧!要理解attach,我们必须要知道一个C++的CWnd对象和窗口(window)的区别:window就是实在的窗口,而CWnd就是MFC用类对window所进行C++封装。attach,就是把窗口附加到CWnd对象上操作。附加(attach)完成后,CWnd对象才和窗口发生了联系。窗口的subclass是指修改窗口过程的操作,而不是面向对象中的派生子类。
     好了,PreCreateWindow由framework在窗口创建前被调用,函数名也说明了这一点,Pre应该是previous的缩写,PreSubclassWindow由framework在subclass窗口前调用。 这段话说了等于没说,你可能还是不知道,什么时候该改写哪个函数。罗罗嗦嗦的作者,还是用代码说话吧!源码之前,了无秘密(候捷语)。我们就看看MFC中的这三个函数都是这样实现的吧! BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
                     LPCTSTR lpszWindowName, DWORD dwStyle,
                     int x, int y, int nWidth, int nHeight,
                     HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
                     {

      // allow modification of several common create parameters
     CREATESTRUCT cs;
     cs.dwExStyle = dwExStyle;
     cs.lpszClass = lpszClassName;
     cs.lpszName = lpszWindowName;
     cs.style = dwStyle;
     cs.x = x;
     cs.y = y;
     cs.cx = nWidth;
     cs.cy = nHeight;
     cs.hwndParent = hWndParent;
     cs.hMenu = nIDorHMenu;
     cs.hInstance = AfxGetInstanceHandle();
     cs.lpCreateParams = lpParam;
   
     if (!PreCreateWindow(cs))
         {
         PostNcDestroy();
         return FALSE;
     }
   
     AfxHookWindowCreate(this);
     HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
         cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
         cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
   
   
         return TRUE;
}

// for child windows
BOOL CWnd::PreCreateWindow(CREATESTRUCT& cs)
{
     if (cs.lpszClass == NULL)
         {
         // make sure the default window class is registered
         VERIFY(AfxDeferRegisterClass(AFX_WND_REG));
       
         // no WNDCLASS provided - use child window default
         ASSERT(cs.style & WS_CHILD);
         cs.lpszClass = _afxWnd;
     }
     return TRUE;
}

   CWnd::CreateEx先设定cs(CREATESTRUCT),在调用真正的窗口创建函数::CreateWindowEx之前,调用了CWnd::PreCreateWindow函数,并把参数cs以引用的方式传递了进去。而CWnd的PreCreateWindow函数也只是给cs.lpszClass赋值而已。毕竟,窗口创建函数CWnd::CreateEx的诸多参数中,并没有哪个指定了所要创建窗口的窗口类,而这又是不可缺少的(请参考<<windows程序设计>>第三章)。所以当你需要修改窗口的大小、风格、窗口所属的窗口类等cs成员变量时,要改写PreCreateWindow函数。 // From VS Install PathVC98MFCSRCWINCORE.CPP
BOOL CWnd::SubclassWindow(HWND hWnd)
{
     if (!Attach(hWnd))
         return FALSE;
   
     // allow any other subclassing to occur
     PreSubclassWindow();
   
     // now hook into the AFX WndProc
     WNDPROC* lplpfn = GetSuperWndProcAddr();
     WNDPROC oldWndProc = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC,
         (DWORD)AfxGetAfxWndProc());
     ASSERT(oldWndProc != (WNDPROC)AfxGetAfxWndProc());
   
     if (*lplpfn == NULL)
         *lplpfn = oldWndProc;    // the first control of that type created
#ifdef _DEBUG
     else if (*lplpfn != oldWndProc)
         {
       
             ::SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)oldWndProc);
     }
#endif
   
     return TRUE;
}

void CWnd::PreSubclassWindow()
{
     // no default processing
}     CWnd::SubclassWindow先调用函数Attach(hWnd)让CWnd对象和hWnd所指的窗口发生关联。接着在用::SetWindowLong修改窗口过程(subclass)前,调用了PreSubclassWindow。CWnd::PreSubclassWindow则是什么都没有做。
     在CWnd的实现中,除了CWnd::SubclassWindow会调用PreSubclassWindow外,还有一处。上面所列函数CreateEx的代码,其中调用了一个AfxHookWindowCreate函数,见下面代码: // From VS Install PathVC98MFCSRCWINCORE.CPP
BOOL CWnd::CreateEx()
{
     // allow modification of several common create parameters
   
       
         if (!PreCreateWindow(cs))
             {
             PostNcDestroy();
             return FALSE;
         }
       
         AfxHookWindowCreate(this);

          HWND hWnd = ::CreateWindowEx(cs.dwExStyle, cs.lpszClass,
             cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
             cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
       
       
             return TRUE;
}     接着察看AfxHookWindowCreate的代码:
// From VS Install PathVC98MFCSRCWINCORE.CPP
void AFXAPI AfxHookWindowCreate(CWnd* pWnd)
{
   
       
         if (pThreadState->m_hHookOldCbtFilter == NULL)
             {
             pThreadState->m_hHookOldCbtFilter = ::SetWindowsHookEx(WH_CBT,
                 _AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
             if (pThreadState->m_hHookOldCbtFilter == NULL)
                 AfxThrowMemoryException();
         }
       
}
     其主要作用的::SetWindowsHookEx函数用于设置一个挂钩函数(Hook函数)_AfxCbtFilterHook,每当Windows产生一个窗口时(还有许多其它类似,请参考<<深入浅出MFC>>第9章,563页),就会调用你设定的Hook函数。
     这样设定完成后,回到CWnd::CreateEx函数中,执行::CreateWindowEx进行窗口创建,窗口一产生,就会调用上面设定的Hook函数_AfxCbtFilterHook。而正是在_AfxCbtFilterHook中对函数PreSubclassWindow进行了第二次调用。见如下代码:
// From VS Install PathVC98MFCSRCWINCORE.CPP
/**//**//**//////////////////////////////////////////////////////////////////////////////
// Window creation hooks

LRESULT CALLBACK
_AfxCbtFilterHook(int code, WPARAM wParam, LPARAM lParam)
{
          
       
         // connect the HWND to pWndInit
         pWndInit->Attach(hWnd);
     // allow other subclassing to occur first
     pWndInit->PreSubclassWindow();
   
         {
         // subclass the window with standard AfxWndProc
         oldWndProc = (WNDPROC)SetWindowLong(hWnd, GWL_WNDPROC, (DWORD)afxWndProc);
         ASSERT(oldWndProc != NULL);
         *pOldWndProc = oldWndProc;
     }
   
}     也在调用函数SetWindowLong进行窗口subclass前调用了PreSubclassWindow.
通常情况下窗口是由用户创建的 CWnd::Create(..)
     ●在此流程中,MFC提供一个机会"PreCreateWindow()供用户在创建前作点手脚
     而对于对话框等,窗口是通过subclass方式交给用户的
     系统读入对话框模板,建立其中各个子窗口
     然后将各子窗口的 消息处理函数替换成 对应的C++对象 的消息处理函数 (Subclass:子类化,或"接管") ,然后,这个子窗口就会按类中定义的方式来动作了。
     在此过程中,调用的是CWnd:SubclassWindow( HWND hWnd );
     ●在此流程中,MFC提供一个机会"PreSubclassWindow" 供用户在关联前作点手脚
     具体来说,如果你定义一个窗口(如CButton派生类CMyButton),然后使用对话框数据交换将一个按钮与自己的派生类对象关联,这时候,一些"建立前"的处理就应该写在"PreSubclassWindow"中。
     如果你用的不是"对话框数据关联",而是在OnInitDialg中自己创建m_mybtn.Create(...)
     这时候,一些"建立前"的处理就应该写在   "PreCreateWindow"中。

#include "pch.h" #include "TraceService.h" #include <locale.h> #include <cstdio> #pragma warning(disable:4996) /////////////////////////////////////////////////////////////////////////// //颜色定义 #define COLOR_TIME RGB(0,0,0) #define COLOR_NORMAL RGB(125,125,125) #define COLOR_WARN RGB(255,128,0) #define COLOR_EXCEPTION RGB(200,0,0) #define COLOR_DEBUG RGB(0,128,128) #define EVENT_LEVEL_COUNT 5 /////////////////////////////////////////////////////////////////////////// //追踪服务 /////////////////////////////////////////////////////////////////////////// static ITraceService* g_pITraceService = NULL; //函数定义 /////////////////////////////////////////////////////////////////////////// //构造函数 CTraceService::CTraceService() { } //服务配置 /////////////////////////////////////////////////////////////////////////// //设置服务 bool CTraceService::SetTraceService(ITraceService * pIUnknownEx) { g_pITraceService = pIUnknownEx; return NULL != g_pITraceService; } //获取服务 VOID* CTraceService::GetTraceServiceManager() { ASSERT(g_pITraceService); if (g_pITraceService) return NULL; return (VOID*)g_pITraceService; } //追踪控制 bool CTraceService::EnableTrace(enTraceLevel TraceLevel, bool bEnableTrace) { return false; } //功能函数 /////////////////////////////////////////////////////////////////////////// //追踪信息 bool CTraceService::TraceString(LPCTSTR pszString, enTraceLevel TraceLevel) { if ((g_pITraceService)) return g_pITraceService->TraceString(pszString, TraceLevel); } bool CTraceService::TraceStringEx(enTraceLevel TraceLevel, LPCTSTR pszFormat, ...) { TCHAR szBuffer[1024] = { 0 }; va_list arglist; va_start(arglist, pszFormat); _vstprintf_s(szBuffer, CountArray(szBuffer), pszFormat, arglist); va_end(arglist); if (g_pITraceService) return g_pITraceService->TraceString(szBuffer, TraceLevel); return false; } /////////////////////////////////////////////////////////////////////////// //追踪服务 /////////////////////////////////////////////////////////////////////////// BEGIN_MESSAGE_MAP(CTraceServiceControl, CRichEditCtrl) ON_WM_RBUTTONDOWN() ON_COMMAND(IDM_MENU0, &CTraceServiceControl::OnCopyString) ON_COMMAND(IDM_MENU1, &CTraceServiceControl::OnSelectAll) ON_COMMAND(IDM_MENU2, &CTraceServiceControl::OnDeleteString) ON_COMMAND(IDM_MENU3, &CTraceServiceControl::OnClearAll) ON_COMMAND(IDM_MENU4, &CTraceServiceControl::OnSaveString) END_MESSAGE_MAP() //函数定义 /////////////////////////////////////////////////////////////////////////// //构造函数 CTraceServiceControl::CTraceServiceControl() { //CTraceService::SetTraceService(this); } //析构函数 CTraceServiceControl::~CTraceServiceControl() { CTraceService::SetTraceService(NULL); } //基础接口 /////////////////////////////////////////////////////////////////////////// //接口查询 VOID* CTraceServiceControl::QueryInterface() { //QueryInterface(ITraceService, Guid, dwQueryVer); //QueryInterface_IUNKNOWNEX(ITraceService, Guid, dwQueryVer); return this; } //信息接口 /////////////////////////////////////////////////////////////////////////// //追踪信息 bool CTraceServiceControl::TraceString(LPCTSTR pszString, enTraceLevel TraceLevel) { //变量定义 CHARFORMAT2 CharFormat; ZeroMemory(&CharFormat, sizeof(CharFormat)); //构造数据 CharFormat.cbSize = sizeof(CharFormat); CharFormat.dwMask = CFM_COLOR | CFM_BACKCOLOR; CharFormat.crTextColor = RGB(0, 0, 0); CharFormat.crBackColor = RGB(255, 255, 255); lstrcpyn(CharFormat.szFaceName, TEXT("宋体"), sizeof(CharFormat.szFaceName)); //获取时间 SYSTEMTIME SystemTime; TCHAR szTimeBuffer[1024 * 2] = { 0 }; GetLocalTime(&SystemTime); _sntprintf(szTimeBuffer,sizeof(szTimeBuffer),TEXT("【 %04d-%02d-%02d %02d:%02d:%02d 】"), SystemTime.wYear, SystemTime.wMonth, SystemTime.wDay, SystemTime.wHour, SystemTime.wMinute, SystemTime.wSecond); _sntprintf(szTimeBuffer, sizeof(szTimeBuffer), TEXT("%s%s\n"), szTimeBuffer, pszString); CharFormat.crTextColor = GetTraceColor(TraceLevel); return InsertString(szTimeBuffer, CharFormat); } //重载函数 /////////////////////////////////////////////////////////////////////////// //绑定函数 VOID CTraceServiceControl::PreSubclassWindow() { __super::PreSubclassWindow(); // 在这里设置服务,此时控件已完全创建 CTraceService::SetTraceService(this); // 测试输出 //CTraceService::TraceString(_T("CTraceServiceControl 已初始化\n"), TraceLevel_Normal); } //功能函数 /////////////////////////////////////////////////////////////////////////// //加载消息 bool CTraceServiceControl::LoadMessage(LPCTSTR pszFileName) { if (static_cast<CTraceServiceControl*>(g_pITraceService) == NULL) return false; return static_cast<CTraceServiceControl*>(g_pITraceService)->LoadMessage(pszFileName); } //保存信息 bool CTraceServiceControl::SaveMessage(LPCTSTR pszFileName) { if (static_cast<CTraceServiceControl*>(g_pITraceService) == NULL) return false; return static_cast<CTraceServiceControl*>(g_pITraceService)->SaveMessage(pszFileName); } //设置参数 bool CTraceServiceControl::SetParameter(LONG lMaxLineCount, LONG lReserveLineCount) { m_lMaxLineCount = lMaxLineCount; //最大行数 m_lReserveLineCount = lReserveLineCount; //保留函数 return true; } //辅助函数 /////////////////////////////////////////////////////////////////////////// //配置服务 VOID CTraceServiceControl::InitializeService() { } //获取颜色 COLORREF CTraceServiceControl::GetTraceColor(enTraceLevel TraceLevel) { switch (TraceLevel) { case TraceLevel_Info: return RGB(133, 124, 129); break; case TraceLevel_Normal: return RGB(133, 124, 129); break; case TraceLevel_Warning: return RGB(255, 0, 0); break; case TraceLevel_Exception: return RGB(255, 0, 0); break; case TraceLevel_Debug: return RGB(19, 127, 140); break; } return RGB(0, 0, 0); } //字符判断 bool EFFicacyUrlChar(TCHAR chChar) { //特殊字符 if (chChar == TEXT('.')) return true; if (chChar == TEXT('=')) return true; if (chChar == TEXT('+')) return true; if (chChar == TEXT('?')) return true; if (chChar == TEXT('#')) return true; if (chChar == TEXT('%')) return true; if (chChar == TEXT('/')) return true; if (chChar == TEXT(':')) return true; if (chChar == TEXT('&')) return true; //字符范围 if ((chChar >= TEXT('a')) && (chChar <= TEXT('z'))) return true; if ((chChar >= TEXT('A')) && (chChar <= TEXT('Z'))) return true; if ((chChar >= TEXT('0')) && (chChar <= TEXT('9'))) return true; return false; } //地址判断 bool EFFicacyUrlString(LPCTSTR pszUrl) { for (WORD i = 0; i < lstrlen(pszUrl); i++) { if(!EFFicacyUrlChar(pszUrl[i])) return false; } return true; } //插入字串 bool CTraceServiceControl::InsertString(LPCTSTR pszString, CHARFORMAT2& CharFormat) { //保存状态 if (GetTextLength() >= 1024 * 512) OnClearAll(); //插入消息 //变量定义 bool bResumeSelect; CHARRANGE CharRange; //保存状态 SetSel(-1L, -1L); GetSel(CharRange.cpMin, CharRange.cpMax); bResumeSelect = (CharRange.cpMax != CharRange.cpMin); //搜索变量 LPCTSTR pszHttp = TEXT("http://"); const INT nHttpLength = lstrlen(pszHttp); const INT nStringLength = lstrlen(pszString); //索引定义 INT nStringStart = 0; INT nStringPause = 0; //字符解释 for (INT i = 0; i < nStringLength; i++) { //变量定义 INT nUrlPause = i; //地址判断 if (((i + nHttpLength) < nStringLength) && (memcmp(&pszString[i], pszHttp, nHttpLength * sizeof(TCHAR)) == 0)) { //设置索引 nUrlPause = i + nHttpLength; //地址搜索 while (nUrlPause < nStringLength) { //字符判断 if (EFFicacyUrlChar(pszString[nUrlPause]) == true) { nUrlPause++; continue; } break; } } //终止字符 if (nUrlPause <= (i + nHttpLength)) nStringPause = (i + 1); //插入字符 if ((i == (nStringLength - 1)) || (nUrlPause > (i + nHttpLength))) { //普通字符 if (nStringPause > nStringStart) { //获取缓冲 CString strNormalString; LPTSTR pszNormalString = strNormalString.GetBuffer(nStringPause - nStringStart + 1); //拷贝字符 pszNormalString[nStringPause - nStringStart] = 0; CopyMemory(pszNormalString, &pszString[nStringStart], (nStringPause - nStringStart) * sizeof(TCHAR)); //释放缓冲 strNormalString.ReleaseBuffer(); //插入消息 SetSel(-1L, -1L); SetWordCharFormat(CharFormat); ReplaceSel((LPCTSTR)strNormalString); } //设置索引 nStringStart = __max(i, nUrlPause); } //设置索引 i += (nUrlPause - i); } //状态设置 if (bResumeSelect == true) SetSel(CharRange); else PostMessage(WM_VSCROLL, SB_BOTTOM, 0); return true; } //回调函数 /////////////////////////////////////////////////////////////////////////// //加载回调 DWORD CALLBACK CTraceServiceControl::LoadCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb) { return 0; } //保存回调 DWORD CALLBACK CTraceServiceControl::SaveCallBack(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG* pcb) { return 0; } //菜单命令 /////////////////////////////////////////////////////////////////////////// //删除消息 VOID CTraceServiceControl::OnClearAll() { SetWindowText(NULL); } //全部选择 VOID CTraceServiceControl::OnSelectAll() { __super::SetSel(0, __super::GetTextLength()); } //拷贝字符 VOID CTraceServiceControl::OnCopyString() { __super::Copy(); } //保存信息 VOID CTraceServiceControl::OnSaveString() { char* old_local = _strdup(setlocale(LC_CTYPE, NULL)); setlocale(LC_CTYPE, "chs"); CFileDialog dlg(FALSE, TEXT("*.txt"), NULL, 4 | 2, TEXT("信息|*.txt||")); if (dlg.DoModal() == IDOK) { CString m_filename = dlg.GetPathName(); CString strInput; GetWindowText(strInput); CFile file(m_filename, CFile::modeCreate | CFile::modeWrite); #ifdef UNICODE WCHAR wFlag = 0xFEFF; file.Write(&wFlag, sizeof(WCHAR)); #endif //UNICODE file.Write(strInput, strInput.GetLength()); file.Close(); } setlocale(LC_CTYPE, old_local); free(old_local); } //删除字符 VOID CTraceServiceControl::OnDeleteString() { ReplaceSel(NULL, true); } //消息映射 /////////////////////////////////////////////////////////////////////////// //建立消息 INT CTraceServiceControl::OnCreate(LPCREATESTRUCT lpCreateStruct) { /*return __super::OnCreate(lpCreateStruct);*/ if (__super::OnCreate(lpCreateStruct) == -1) return -1; CTraceService::SetTraceService(this); return 0; } //右键消息 void CTraceServiceControl::OnRButtonDown(UINT nFlags, CPoint point) { CMenu menu; // 创建弹出菜单 if (menu.CreatePopupMenu()) { ClientToScreen(&point); CHARRANGE sel; GetSel(sel); int nTextLength = GetTextLength(); // 启用删除菜单 - 当有选中文本时可以删除 BOOL bCanDelete = (sel.cpMax != sel.cpMin); // 有选中文本才能删除 menu.AppendMenu(MF_STRING | ((sel.cpMax != sel.cpMin) ? 0 : (MF_DISABLED | MF_GRAYED)), IDM_MENU0, TEXT("复制(&C)\tCtrl+C")); menu.AppendMenu(MF_STRING | ((nTextLength > 0 && (sel.cpMax - sel.cpMin) < nTextLength) ? 0 : (MF_DISABLED | MF_GRAYED)), IDM_MENU1, TEXT("全选(&A)\tCtrl+A")); // 启用删除菜单(当有选中文本时) menu.AppendMenu(MF_STRING | (bCanDelete ? 0 : (MF_DISABLED | MF_GRAYED)), IDM_MENU2, TEXT("删除(&D)\tDel")); menu.AppendMenu(MF_STRING | ((nTextLength > 0) ? 0 : (MF_DISABLED | MF_GRAYED)), IDM_MENU3, TEXT("清除信息")); menu.AppendMenu(MF_SEPARATOR); menu.AppendMenu(MF_STRING | ((nTextLength > 0) ? 0 : (MF_DISABLED | MF_GRAYED)), IDM_MENU4, TEXT("保存信息")); // 显示菜单 menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); } // 调用基类处理 __super::OnRButtonDown(nFlags, point); } //追踪消息 LRESULT CTraceServiceControl::OnTraceServiceMessage(WPARAM wParam, LPARAM lParam) { return 0; }
最新发布
11-07
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值