cocos2d-x-触屏(Touch)事件详解
来源:互联网 发布:手机号码检测软件 编辑:程序博客网 时间:2024/09/21 09:24
1.首先来了解一下相关的几个类、处理触屏事件时操作和执行的流程
CCTouch:它封装了触摸点,可以通过locationInView函数返回一个CCPoint。CCTouchDelegate:它是触摸事件委托,就是系统捕捉到触摸事件后交由它或者它的子类处理,所以我们在处理触屏事件时,必须得继承它。它封装了下面这些处理触屏事件的函数:
virtual void ccTouchesBegan(CCSet *pTouches, CCEvent *pEvent);virtual void ccTouchesMoved(CCSet *pTouches, CCEvent *pEvent);virtual void ccTouchesEnded(CCSet *pTouches, CCEvent *pEvent);virtual void ccTouchesCancelled(CCSet *pTouches, CCEvent *pEvent); virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent);virtual void ccTouchMoved(CCTouch *pTouch, CCEvent *pEvent);virtual void ccTouchEnded(CCTouch *pTouch, CCEvent *pEvent);virtual void ccTouchCancelled(CCTouch *pTouch, CCEvent *pEvent);
ccTouchesCancelled和ccTouchCancelled函数很少用,在接到系统中断通知,需要取消触摸事件的时候才会调用此方法。
如:应用长时间无响应、当前view从window上移除、触摸的时候来电话了等。
CCTargetedTouchDelegate和CCStandardTouchDelegate是CCTouchDelegate的子类,类结构图如下:
CCTouchDispatcher:实现触摸事件分发,它封装了下面这两个函数,
可以把CCStandardTouchDelegate和CCTargetedTouchDelegate添加到分发列表中:
void addStandardDelegate(CCTouchDelegate *pDelegate, int nPriority);void addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches);
CCTouchHandler:封装了CCTouchDelegate和其对应的优先级,优先级越高,分发的时候越容易获得事件处理权,CCStandardTouchHandler和CCTargetedTouchHandler是它的子类。
下面分析一下触屏事件处理和执行流程:
用户自定义类继承CCTouchDelegate,重写触屏事件处理函数和registerWithTouchDispatcher函数,在init或者onEnter函数中调用registerWithTouchDispatcher函数,如:
void GameLayer::registerWithTouchDispatcher(){ cocos2d::CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, 0, true);}
把相应的CCTouchDelegate添加到CCTouchDispatcher的分发列表中。addTargetedDelegate函数会创建CCTouchDelegate对应
的CCTouchHandler对象并添加到CCArray m_pTargetedHandlers中,看源码:
void CCTouchDispatcher::addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches){ CCTouchHandler *pHandler = CCTargetedTouchHandler::handlerWithDelegate(pDelegate, nPriority, bSwallowsTouches); if (! m_bLocked) { forceAddHandler(pHandler, m_pTargetedHandlers); } else { /**....*/ }} void CCTouchDispatcher::forceAddHandler(CCTouchHandler *pHandler, CCMutableArray *pArray){ unsigned int u = 0; CCMutableArray::CCMutableArrayIterator iter; for (iter = pArray->begin(); iter != pArray->end(); ++iter) { CCTouchHandler *h = *iter; if (h) { if (h->getPriority() < pHandler->getPriority()) { ++u; } if (h->getDelegate() == pHandler->getDelegate()) { CCAssert(0, ""); return; } } } pArray->insertObjectAtIndex(pHandler, u);}
注意forceAddHandler()函数中,pHandler是被添加的对象:pHandler->getPriority()的值越小u的值就越小,
因此插入到目标容器中的位置也就越靠前,说明优先级的值越小优先级反而越高,
也就能先响应事件(CCMenu的默认值是-128)。
前面事件分发时就是从m_pTargetedHandlers中取出CCXXXTouchHandler,然后调用handler对应的delegate的:
pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent);执行的是CCTouchDispatcher的touches函数.
//// dispatch events//void CCTouchDispatcher::touches(CCSet *pTouches, CCEvent *pEvent, unsigned int uIndex){ CCAssert(uIndex >= 0 && uIndex < 4, ""); CCSet *pMutableTouches; m_bLocked = true; // optimization to prevent a mutable copy when it is not necessary unsigned int uTargetedHandlersCount = m_pTargetedHandlers->count(); unsigned int uStandardHandlersCount = m_pStandardHandlers->count(); bool bNeedsMutableSet = (uTargetedHandlersCount && uStandardHandlersCount); pMutableTouches = (bNeedsMutableSet ? pTouches->mutableCopy() : pTouches); struct ccTouchHandlerHelperData sHelper = m_sHandlerHelperData[uIndex]; // // process the target handlers 1st // if (uTargetedHandlersCount > 0) { CCTouch *pTouch; CCSetIterator setIter; for (setIter = pTouches->begin(); setIter != pTouches->end(); ++setIter) { pTouch = (CCTouch *)(*setIter); CCTargetedTouchHandler *pHandler = NULL; CCObject* pObj = NULL; CCARRAY_FOREACH(m_pTargetedHandlers, pObj) { pHandler = (CCTargetedTouchHandler *)(pObj); if (! pHandler) { break; } bool bClaimed = false; if (uIndex == CCTOUCHBEGAN) { bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent); if (bClaimed) { pHandler->getClaimedTouches()->addObject(pTouch); } } else if (pHandler->getClaimedTouches()->containsObject(pTouch)) { // moved ended canceled bClaimed = true; switch (sHelper.m_type) { case CCTOUCHMOVED: pHandler->getDelegate()->ccTouchMoved(pTouch, pEvent); break; case CCTOUCHENDED: pHandler->getDelegate()->ccTouchEnded(pTouch, pEvent); pHandler->getClaimedTouches()->removeObject(pTouch); break; case CCTOUCHCANCELLED: pHandler->getDelegate()->ccTouchCancelled(pTouch, pEvent); pHandler->getClaimedTouches()->removeObject(pTouch); break; } } if (bClaimed && pHandler->isSwallowsTouches()) { if (bNeedsMutableSet) { pMutableTouches->removeObject(pTouch); } break; } } } } // // process standard handlers 2nd // if (uStandardHandlersCount > 0 && pMutableTouches->count() > 0) { CCStandardTouchHandler *pHandler = NULL; CCObject* pObj = NULL; CCARRAY_FOREACH(m_pStandardHandlers, pObj) { pHandler = (CCStandardTouchHandler*)(pObj); if (! pHandler) { break; } switch (sHelper.m_type) { case CCTOUCHBEGAN: pHandler->getDelegate()->ccTouchesBegan(pMutableTouches, pEvent); break; case CCTOUCHMOVED: pHandler->getDelegate()->ccTouchesMoved(pMutableTouches, pEvent); break; case CCTOUCHENDED: pHandler->getDelegate()->ccTouchesEnded(pMutableTouches, pEvent); break; case CCTOUCHCANCELLED: pHandler->getDelegate()->ccTouchesCancelled(pMutableTouches, pEvent); break; } } } if (bNeedsMutableSet) { pMutableTouches->release(); } // // Optimization. To prevent a [handlers copy] which is expensive // the add/removes/quit is done after the iterations // m_bLocked = false; if (m_bToRemove) { m_bToRemove = false; for (unsigned int i = 0; i < m_pHandlersToRemove->num; ++i) { forceRemoveDelegate((CCTouchDelegate*)m_pHandlersToRemove->arr[i]); } ccCArrayRemoveAllValues(m_pHandlersToRemove); } if (m_bToAdd) { m_bToAdd = false; CCTouchHandler* pHandler = NULL; CCObject* pObj = NULL; CCARRAY_FOREACH(m_pHandlersToAdd, pObj) { pHandler = (CCTouchHandler*)pObj; if (! pHandler) { break; } if (dynamic_cast<CCTargetedTouchHandler*>(pHandler) != NULL) { forceAddHandler(pHandler, m_pTargetedHandlers); } else { forceAddHandler(pHandler, m_pStandardHandlers); } } m_pHandlersToAdd->removeAllObjects(); } if (m_bToQuit) { m_bToQuit = false; forceRemoveAllDelegates(); }}
该函数首先会先处理targeted 再处理standard,所以CCTargetedTouchDelegate比
CCStandardTouchDelegate优先级高。
那什么时候触发执行touches函数呢?CCTouchDispatcher继承了EGLTouchDelegate类,EGLTouchDelegate类源码:
class CC_DLL EGLTouchDelegate{public: virtual void touchesBegan(CCSet* touches, CCEvent* pEvent) = 0; virtual void touchesMoved(CCSet* touches, CCEvent* pEvent) = 0; virtual void touchesEnded(CCSet* touches, CCEvent* pEvent) = 0; virtual void touchesCancelled(CCSet* touches, CCEvent* pEvent) = 0; virtual ~EGLTouchDelegate() {}};
CCTouchDispatcher中实现了这四个函数,正是在这四个函数中调用了touches函数:
void CCTouchDispatcher::touchesBegan(CCSet *touches, CCEvent *pEvent){ if (m_bDispatchEvents) { this->touches(touches, pEvent, CCTOUCHBEGAN); }}/**其他三个方法类似 **/
这几个触屏处理函数是由具体平台底层调用的,在AppDelegate.cpp中有这段代码:
CCDirector *pDirector = CCDirector::sharedDirector();pDirector->setOpenGLView(&CCEGLView::sharedOpenGLView());
继续跟进setOpenGLView函数,发现了这段代码:
CCTouchDispatcher *pTouchDispatcher = CCTouchDispatcher::sharedDispatcher();m_pobOpenGLView->setTouchDelegate(pTouchDispatcher);pTouchDispatcher->setDispatchEvents(true);
调用了具体平台下的CCEGLView类中的setTouchDelegate函数。由于是在windows平台下,
所以CCEGLView此时对应CCEGLView_win32.h文件的CCEGLView类,对应的setTouchDelegate函数为:
void setTouchDelegate(EGLTouchDelegate * pDelegate);
系统最终通过CCEGLView类的WindowProc函数处理鼠标在Windows窗口的DOWN、MOVE、UP事件,
通过pDelegate分别调用touchesBegan、touchesMoved、touchesEnded函数。
LRESULT CCEGLView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam){ BOOL bProcessed = FALSE; switch (message) { case WM_LBUTTONDOWN:#if(_MSC_VER >= 1600) // Don't process message generated by Windows Touch if (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;#endif /* #if(_MSC_VER >= 1600) */ if (m_pDelegate && MK_LBUTTON == wParam) { POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)}; CCPoint pt(point.x, point.y); pt.x /= m_fFrameZoomFactor; pt.y /= m_fFrameZoomFactor; CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y); if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp)) { m_bCaptured = true; SetCapture(m_hWnd); int id = 0; handleTouchesBegin(1, &id, &pt.x, &pt.y); } } break; case WM_MOUSEMOVE:#if(_MSC_VER >= 1600) // Don't process message generated by Windows Touch if (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;#endif /* #if(_MSC_VER >= 1600) */ if (MK_LBUTTON == wParam && m_bCaptured) { POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)}; CCPoint pt(point.x, point.y); int id = 0; pt.x /= m_fFrameZoomFactor; pt.y /= m_fFrameZoomFactor; handleTouchesMove(1, &id, &pt.x, &pt.y); } break; case WM_LBUTTONUP:#if(_MSC_VER >= 1600) // Don't process message generated by Windows Touch if (m_bSupportTouch && (s_pfGetMessageExtraInfoFunction() & MOUSEEVENTF_FROMTOUCH) == MOUSEEVENTF_FROMTOUCH) break;#endif /* #if(_MSC_VER >= 1600) */ if (m_bCaptured) { POINT point = {(short)LOWORD(lParam), (short)HIWORD(lParam)}; CCPoint pt(point.x, point.y); int id = 0; pt.x /= m_fFrameZoomFactor; pt.y /= m_fFrameZoomFactor; handleTouchesEnd(1, &id, &pt.x, &pt.y); ReleaseCapture(); m_bCaptured = false; } break;#if(_MSC_VER >= 1600) case WM_TOUCH:{ BOOL bHandled = FALSE; UINT cInputs = LOWORD(wParam); PTOUCHINPUT pInputs = new TOUCHINPUT[cInputs]; if (pInputs) { if (s_pfGetTouchInputInfoFunction((HTOUCHINPUT)lParam, cInputs, pInputs, sizeof(TOUCHINPUT))) { for (UINT i=0; i < cInputs; i++) { TOUCHINPUT ti = pInputs[i]; POINT input; input.x = TOUCH_COORD_TO_PIXEL(ti.x); input.y = TOUCH_COORD_TO_PIXEL(ti.y); ScreenToClient(m_hWnd, &input); CCPoint pt(input.x, input.y); CCPoint tmp = ccp(pt.x, m_obScreenSize.height - pt.y); if (m_obViewPortRect.equals(CCRectZero) || m_obViewPortRect.containsPoint(tmp)) { pt.x /= m_fFrameZoomFactor; pt.y /= m_fFrameZoomFactor; if (ti.dwFlags & TOUCHEVENTF_DOWN) handleTouchesBegin(1, reinterpret_cast<int*>(&ti.dwID), &pt.x, &pt.y); else if (ti.dwFlags & TOUCHEVENTF_MOVE) handleTouchesMove(1, reinterpret_cast<int*>(&ti.dwID), &pt.x, &pt.y); else if (ti.dwFlags & TOUCHEVENTF_UP) handleTouchesEnd(1, reinterpret_cast<int*>(&ti.dwID), &pt.x, &pt.y); } } bHandled = TRUE; } delete [] pInputs; } if (bHandled) { s_pfCloseTouchInputHandleFunction((HTOUCHINPUT)lParam); }} break;#endif /* #if(_MSC_VER >= 1600) */ case WM_SIZE: switch (wParam) { case SIZE_RESTORED: CCApplication::sharedApplication()->applicationWillEnterForeground(); break; case SIZE_MINIMIZED: CCApplication::sharedApplication()->applicationDidEnterBackground(); break; } break; case WM_KEYDOWN: if (wParam == VK_F1 || wParam == VK_F2) { CCDirector* pDirector = CCDirector::sharedDirector(); if (GetKeyState(VK_LSHIFT) < 0 || GetKeyState(VK_RSHIFT) < 0 || GetKeyState(VK_SHIFT) < 0) pDirector->getKeypadDispatcher()->dispatchKeypadMSG(wParam == VK_F1 ? kTypeBackClicked : kTypeMenuClicked); } else if (wParam == VK_ESCAPE) { CCDirector::sharedDirector()->getKeypadDispatcher()->dispatchKeypadMSG(kTypeBackClicked); } if ( m_lpfnAccelerometerKeyHook!=NULL ) { (*m_lpfnAccelerometerKeyHook)( message,wParam,lParam ); } break; case WM_KEYUP: if ( m_lpfnAccelerometerKeyHook!=NULL ) { (*m_lpfnAccelerometerKeyHook)( message,wParam,lParam ); } break; case WM_CHAR: { if (wParam < 0x20) { if (VK_BACK == wParam) { CCIMEDispatcher::sharedDispatcher()->dispatchDeleteBackward(); } else if (VK_RETURN == wParam) { CCIMEDispatcher::sharedDispatcher()->dispatchInsertText("\n", 1); } else if (VK_TAB == wParam) { // tab input } else if (VK_ESCAPE == wParam) { // ESC input //CCDirector::sharedDirector()->end(); } } else if (wParam < 128) { // ascii char CCIMEDispatcher::sharedDispatcher()->dispatchInsertText((const char *)&wParam, 1); } else { char szUtf8[8] = {0}; int nLen = WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)&wParam, 1, szUtf8, sizeof(szUtf8), NULL, NULL); CCIMEDispatcher::sharedDispatcher()->dispatchInsertText(szUtf8, nLen); } if ( m_lpfnAccelerometerKeyHook!=NULL ) { (*m_lpfnAccelerometerKeyHook)( message,wParam,lParam ); } } break; case WM_PAINT: PAINTSTRUCT ps; BeginPaint(m_hWnd, &ps); EndPaint(m_hWnd, &ps); break; case WM_CLOSE: CCDirector::sharedDirector()->end(); break; case WM_DESTROY: destroyGL(); PostQuitMessage(0); break; default: if (m_wndproc) { m_wndproc(message, wParam, lParam, &bProcessed); if (bProcessed) break; } return DefWindowProc(m_hWnd, message, wParam, lParam); } if (m_wndproc && !bProcessed) { m_wndproc(message, wParam, lParam, &bProcessed); } return 0;}
现在应该明白了触屏操作相关函数的执行过程了,在其他平台下应该类似。
2.实现触屏事件处理
知道了原理之后,实现起来就很简单了:定义一个CCTouchDelegate(或者其子类CCTargetedTouchDelegate/CCStandardTouchDelegate),
然后重写那几个处理函数(began、move、end),并把定义好的CCTouchDelegate添加到分发列表中,在onExit函数中实现从分发列表中删除。
在平常的开发中,一般有两种方式:(1)继承CCLayer,在层中处理触屏函数。
(2)继承CCSprite和CCTouchDelegate(或者其子类)。
上面两种方式,从原理上来说是一样的。
1. 下面是采用继承CCLayer的方式处理触屏事件。
(1)CCStandardTouchDelegate
添加CCStandardTouchDelegate是非常简单的,只需要重写触屏处理函数和调用setTouchEnabled(true)。主要代码如下:
//init函数中this->setIsTouchEnabled(true); void GameLayer::ccTouchesBegan(CCSet* pTouches,CCEvent* pEvent){ CCSetIterator it = pTouches->begin(); CCTouch* touch = (CCTouch*)(*it); /** .... **/}
这里为什么没有把CCStandardTouchDelegate添加进分发列表和从分发列表删除的操作呢,因为函数 setTouchEnabled已经帮我们做了,看源码:
void CCLayer::setIsTouchEnabled(bool enabled){ if (m_bIsTouchEnabled != enabled) { m_bIsTouchEnabled = enabled; if (m_bIsRunning) { if (enabled) { this->registerWithTouchDispatcher(); } else { // have problems? CCTouchDispatcher::sharedDispatcher()->removeDelegate(this); } } }} void CCLayer::registerWithTouchDispatcher(){ /** .... **/ CCTouchDispatcher::sharedDispatcher()->addStandardDelegate(this,0);} void CCLayer::onExit(){ if( m_bIsTouchEnabled ) { CCTouchDispatcher::sharedDispatcher()->removeDelegate(this); unregisterScriptTouchHandler(); } CCNode::onExit();}
(2) CCTargetedTouchDelegate
直接看cocos2d-x中的CCMenu(菜单)类,它是继承CCLayer的。部分源码如下:
class CC_DLL CCMenu : public CCLayer, public CCRGBAProtocol { /** .... */ virtual void registerWithTouchDispatcher(); /** @brief For phone event handle functions */ virtual bool ccTouchBegan(CCTouch* touch, CCEvent* event); virtual void ccTouchEnded(CCTouch* touch, CCEvent* event); virtual void ccTouchCancelled(CCTouch *touch, CCEvent* event); virtual void ccTouchMoved(CCTouch* touch, CCEvent* event); /** @since v0.99.5 override onExit */ virtual void onExit(); /** .... */ };} //Menu - Events,在CCLayer的onEnter中被调用 void CCMenu::registerWithTouchDispatcher() { CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, kCCMenuTouchPriority, true); } bool CCMenu::ccTouchBegan(CCTouch* touch, CCEvent* event) { /** .... */ } void CCMenu::onExit() { /** .... */ CCLayer::onExit(); }
2.下面实现继承CCSprite的方式
定义一个Ball类继承CCSprite和CCTargetedTouchDelegate。源码如下:
class Ball : public CCSprite, public CCTargetedTouchDelegate{public: Ball(void); virtual ~Ball(void); virtual void onEnter(); virtual void onExit(); virtual bool ccTouchBegan(CCTouch* touch, CCEvent* event); virtual void ccTouchMoved(CCTouch* touch, CCEvent* event); virtual void ccTouchEnded(CCTouch* touch, CCEvent* event);/** .... */ }; void Ball::onEnter(){ CCTouchDispatcher::sharedDispatcher()->addTargetedDelegate(this, 0, true); CCSprite::onEnter();} void Ball::onExit(){ CCTouchDispatcher::sharedDispatcher()->removeDelegate(this); CCSprite::onExit();} bool Ball::ccTouchBegan(CCTouch* touch, CCEvent* event){ CCPoint touchPoint = touch->locationInView( touch->view() ); touchPoint = CCDirector::sharedDirector()->convertToGL( touchPoint ); /** .... */ return true;}
注意:virtual bool ccTouchBegan(CCTouch *pTouch, CCEvent *pEvent)的返回值对触屏消息是有影响的。
如果返回false,表示不处理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法,而交由后面接收触屏消息的对象处理;
如果返回true,表示会处理ccTouchMoved(),ccTouchEnded(),ccTouchCanceld()方法。
请看CCTouchDispatcher.cpp的touches函数部分源码,它是用来分发事件的:
bool bClaimed = false; if (uIndex == CCTOUCHBEGAN) { bClaimed = pHandler->getDelegate()->ccTouchBegan(pTouch, pEvent); if (bClaimed) { pHandler->getClaimedTouches()->addObject(pTouch); } } else if (pHandler->getClaimedTouches()->containsObject(pTouch)) { // moved ended canceled bClaimed = true; switch (sHelper.m_type) { case CCTOUCHMOVED: pHandler->getDelegate()->ccTouchMoved(pTouch, pEvent); break; case CCTOUCHENDED: pHandler->getDelegate()->ccTouchEnded(pTouch, pEvent); pHandler->getClaimedTouches()->removeObject(pTouch); break; case CCTOUCHCANCELLED: pHandler->getDelegate()->ccTouchCancelled(pTouch, pEvent); pHandler->getClaimedTouches()->removeObject(pTouch); break; } } if (bClaimed && pHandler->isSwallowsTouches()) { if (bNeedsMutableSet) { pMutableTouches->removeObject(pTouch); } break; }
如果返回true,并且addTargetedDelegate(CCTouchDelegate *pDelegate, int nPriority, bool bSwallowsTouches),
bSwallowsTouches为true,则表示消耗掉此触屏消息,后面需要接收触屏消息的对象就接收不到触屏消息了。
把该触摸对象CCTouch从数组pMutableTouches中移除了,并且跳出当前for循环,而CCStandardTouchHandler需要从
pMutableTouches取出触摸对象进行处理的,这样后面的CCTargetedTouchHandler和CCStandardTouchHandler就都处理不了。
0 0
- cocos2d-x-触屏(Touch)事件详解
- cocos2d-x touch事件
- cocos2d-x touch事件
- cocos2d-x touch事件
- Cocos2d-x---Touch事件
- Cocos2d-x Touch事件处理
- Cocos2D-X 之 Touch事件
- cocos2d-x注册touch事件
- Cocos2d-x之Touch事件
- cocos2d-x Touch事件处理机制
- cocos2d-x Touch事件处理机制
- cocos2d-x Touch事件处理机制
- cocos2d-x 屏蔽场景的Touch事件
- cocos2d-x Touch事件处理机制
- cocos2d-x Touch事件处理机制
- cocos2d-x里面touch事件传递机制
- cocos2d-x Touch触屏事件
- cocos2d-x Touch事件处理机制
- IP SAN (iscsi)
- linux下etc/fstab
- Oracle 登录
- 常用git命令整理
- Deformable Part Model的学习
- cocos2d-x-触屏(Touch)事件详解
- 玩转Google开源C++单元测试框架Google Test系列(gtest)之七 - 深入解析gtest
- 在Ubuntu上编写Qt Helloworld程序
- 我的WCF4 Rest Service及Entity Framework with POCO之旅(二)——选择请求/返回格式
- 信号相关函数
- poj 2250 Compromise
- 我的WCF4 Rest Service及Entity Framework with POCO之旅(三)——用Entity Framework和POCO Template实现数据模型及存储
- NPAPI插件开发学习:NPAPI和NPRuntime的介绍
- 抽象编程