一种清除windows通知区域“僵尸”图标的方案——XP系统解决方案
XP下“僵尸”图标的解决方案
从《一种清除windows通知区域“僵尸”图标的方案——问题分析》(以后简称《问题分析》)一文中分析的通知区域结构可以看出,XP的通知区域结构是相对简单的。如果我们解决了XP下的问题,那么Win7上的问题至少解决了一半——只有那个隐藏系统通知区域需要研究下。所以,我们先选择XP作为研究对象。(转载请指明出于breaksoftware的csdn博客)
从SPY++抓到的结构可以看出来,通知区域是一个ToolbarWindow32窗口类对象。ToolbarWindow32是windows一个标准的窗口类,虽然我们不能直接调用该类的一些方法来操作元素,但是该类对象响应Toolbar的标准消息。这也是我最开始的解决思路。
获取图标信息
在尝试去掉“僵尸”图标之前,有几个问题摆在我们面前
- 如何获取图标的总数
- 如何枚举到每个图标
- 如何获取图标的信息
- 如何找到我们创建的图标
查阅MSDN后,我发现Toolbar消息中TB_BUTTONCOUNT可以获取到通知区域图标总数。如此,我们便可以一个For循环枚举到所有图标。问题1和2便迎刃而解。那如何找到我们创建的图标呢?在《问题分析》一文中,我们在介绍初始化图标时,特别提出,我给图标Tip取了一个晦涩的名字——“中A英1文”。如此设计,也是因为我试图通过这个特征来识别图标(虽然这种方案存在不严谨性,但是图标的识别不是本文的主要的探讨课题)。
那么我们如何去获取图标的文字呢?查阅MSDN,发现TB_GETBUTTONTEXT这个消息可以获取图标的文字。其参数说明是
wParam
Command identifier of the button whose text is to be retrieved.
lParam
Pointer to a buffer that receives the button text.
这儿要注意几个问题:
- 什么是Command identfier?它和我们For循环传递的递增参数是一致的么?
- lParam指向的地址到底在哪个进程中?因为通知区域的进程载体是Explorer,而Explorer自然不可以访问到我们进程中的空间。如果这段空间在Explorer进程中,我们进程又如何才能读取到Explorer进程中的空间呢?
1 Command identifier of the button whose text is to be retrieved.
2 Zero-based index of the button for which to retrieve information.
可以见得两者是不可以混用的。这两者存在一个推导关系,即可以通过2推导出1。实现这个过程的是TB_GETBUTTON消息。
wParam
Zero-based index of the button for which to retrieve information.
lParam
Pointer to the TBBUTTON structure that receives the button information.
lParam指向一个获取的结构体TBBUTTON信息
typedef struct {int iBitmap;int idCommand;BYTE fsState;BYTE fsStyle;
#ifdef _WIN64BYTE bReserved[6];
#else
#if defined(_WIN32)BYTE bReserved[2];
#endif
#endif DWORD_PTR dwData;INT_PTR iString;
} TBBUTTON, *PTBBUTTON, *LPTBBUTTON;
其中idCommand就是我们之前提到的Command identifier。
问题1我们解决了,那么问题2呢?我们不仅在发送TB_GETBUTTONTEXT消息时遇到这个问题,在发送TB_GETBUTTON消息时也会遇到。这个问题说到底就是跨进程的数据通信,那使用内存映射?No!管道?No!Socket?No!……
#define SENDMSGTIME 100CSendMessageToProcess::CSendMessageToProcess(void)
{
}CSendMessageToProcess::CSendMessageToProcess( HWND hwnd, ENUMWINOWSENDPROC lpFunc )
{m_hWnd = hwnd;m_lpFun = lpFunc;m_stRemoteBuffer.lpBuffer = NULL;m_stRemoteBuffer.dwBufferSize = BUFFERSIZE;m_hProcess = NULL;InitializeRemoteBuffer();
}CSendMessageToProcess::~CSendMessageToProcess(void)
{UnitializeRemoteBuffer();
}BOOL CSendMessageToProcess::InitializeRemoteBuffer()
{if ( NULL != m_stRemoteBuffer.lpBuffer ) {return TRUE;}BOOL bSuc = FALSE;do {if ( 0 == m_stRemoteBuffer.dwBufferSize ) {::SetLastError(ERROR_INVALID_PARAMETER);break;}DWORD dwProcessID = 0;GetWindowThreadProcessId(m_hWnd, &dwProcessID);m_hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ|PROCESS_VM_WRITE , FALSE, dwProcessID);if ( NULL == m_hProcess ) {break;;}m_stRemoteBuffer.lpBuffer = (LPBYTE)VirtualAllocEx(m_hProcess, NULL, m_stRemoteBuffer.dwBufferSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);if ( NULL == m_stRemoteBuffer.lpBuffer ) {break;}bSuc = TRUE;}while (0);return bSuc;
}VOID CSendMessageToProcess::UnitializeRemoteBuffer()
{if ( NULL != m_hProcess ) {VirtualFreeEx(m_hProcess, m_stRemoteBuffer.lpBuffer, 0, MEM_RELEASE);m_stRemoteBuffer.lpBuffer = NULL;m_stRemoteBuffer.dwBufferSize = 0;CloseHandle(m_hProcess);m_hProcess = NULL;}
}BOOL CSendMessageToProcess::SendRemoteMessage( DWORD dwMsgID, DWORD dwIndexOrCmdId, PStBufferInfo lpstLocalBuffer, DWORD& dwRet )
{BOOL bSuc = FALSE;do {if ( NULL == lpstLocalBuffer ) {if ( 0 == ::SendMessageTimeout( m_hWnd, dwMsgID, dwIndexOrCmdId, 0, SMTO_ABORTIFHUNG, SENDMSGTIME, &dwRet ) ) {break;}bSuc = TRUE;break;}if ( NULL == lpstLocalBuffer || NULL == m_stRemoteBuffer.lpBuffer ) {::SetLastError(ERROR_INVALID_PARAMETER);break;}if ( NULL == m_stRemoteBuffer.lpBuffer || 0 == m_stRemoteBuffer.dwBufferSize ) {::SetLastError(ERROR_INVALID_PARAMETER);break;}if ( lpstLocalBuffer->dwBufferSize > m_stRemoteBuffer.dwBufferSize ) {::SetLastError(ERROR_MORE_DATA);break;}DWORD dwWrite = 0;if ( NULL != lpstLocalBuffer->lpBuffer && 0 != lpstLocalBuffer->dwBufferSize ) {// 使用传入的数据初始化数据空间if ( FALSE == WriteProcessMemory(m_hProcess, m_stRemoteBuffer.lpBuffer, lpstLocalBuffer->lpBuffer, lpstLocalBuffer->dwBufferSize , &dwWrite) ) {break;}}if ( 0 == ::SendMessageTimeout( m_hWnd, dwMsgID, dwIndexOrCmdId, (LPARAM)m_stRemoteBuffer.lpBuffer, SMTO_ABORTIFHUNG, SENDMSGTIME, &dwRet ) ) {break;}DWORD dwRead = 0;if ( NULL != lpstLocalBuffer->lpBuffer && 0 != lpstLocalBuffer->dwBufferSize ) {if ( FALSE == ReadProcessMemory( m_hProcess, m_stRemoteBuffer.lpBuffer, lpstLocalBuffer->lpBuffer, lpstLocalBuffer->dwBufferSize, &dwRead)) {break;}}bSuc = TRUE;} while (0);return bSuc;
}
这儿有个特别需要说明的:我使用的是SendMessageTimeout发送消息,而不是SendMessage。因为我们对其他进程发送消息时,我们无法保证其他进程在处理消息时是否会非常费时,或者压根就不返回,从而导致我们发起消息的线程被堵塞。一般来说,比较好的方式是采用PostMessage向其他进程发送消息。但是由于我们的流程需要同步执行,所以在保证同步的情况下,同时兼顾SendMessage执行超时,采用了SendMessageTimeout方式。
BOOL CSendMessageToProcess::EnumChild()
{BOOL bSuc = FALSE;do {DWORD dwChildCount = 0;if ( 0 == ::SendMessageTimeout(m_hWnd, TB_BUTTONCOUNT, 0, 0, SMTO_ABORTIFHUNG, SENDMSGTIME, &dwChildCount) ) {break;}BOOL bContinue = TRUE;for ( DWORD dwIndex = 0; dwIndex < dwChildCount && bContinue; dwIndex++ ) {bContinue = m_lpFun(this, dwIndex);}bSuc = TRUE;} while (0);return bSuc;
}
这儿m_lpFun是一个回调函数,其用来判定和执行清除“僵尸”图标的目的。其回调函数是
static BOOL CALLBACK DealChildComp(CSendMessageToProcess* lpThis, DWORD dwIndex)
{BOOL bContinue = TRUE;do {BYTE byLocalBuffer[BUFFERSIZE] = {0};WCHAR wchBuffer[BUFFERSIZE] = {0};memset(byLocalBuffer, 0, sizeof(byLocalBuffer));TBBUTTON TBButton;memset(&TBButton, 0, sizeof(TBButton));StBufferInfo stLocalBuffer;stLocalBuffer.lpBuffer = &TBButton;stLocalBuffer.dwBufferSize = sizeof(TBBUTTON);DWORD dwSucGetButton = 0;if ( FALSE == lpThis->SendRemoteMessage(TB_GETBUTTON, dwIndex, &stLocalBuffer, dwSucGetButton ) ) {continue;}memset(byLocalBuffer, 0, sizeof(byLocalBuffer));stLocalBuffer.lpBuffer = byLocalBuffer;stLocalBuffer.dwBufferSize = sizeof(byLocalBuffer);DWORD dwRetLenth = 0xFFFFFFFF;if ( FALSE == lpThis->SendRemoteMessage(TB_GETBUTTONTEXTW, TBButton.idCommand, &stLocalBuffer, dwRetLenth ) ) {continue;}if ( 0xFFFFFFFF == dwRetLenth ) {continue;}memset(wchBuffer, 0, sizeof(wchBuffer));memcpy_s( wchBuffer, sizeof(wchBuffer), byLocalBuffer, dwRetLenth * sizeof(WCHAR));std::cout<<wchBuffer<<std::endl;if ( 0 != lstrcmp( L"中A英1文", wchBuffer)) {continue;}g_bFind = TRUE;//bContinue = lpThis->ClearIcon(TBButton.idCommand, FALSE);bContinue = lpThis->ClearIcon(dwIndex, TRUE);} while (0);return bContinue;
}
下一步,我们将要将重心放在如何清除“僵尸”图标上。注意一下ClearIcon这个函数,从我上面列出的代码可以看出,貌似是存在两种方法。是的,的确是两种。之后我将详细介绍这两种方法。
直接删除“僵尸”图标
MSDN上给出了Toolbar消息的所有名称,其中最开始吸引我的是TB_DELETEBUTTON这个消息。在经过上面一系列努力后,我们只要发送这个消息给通知区域便可以干净利索优雅的清除“僵尸”图标。其参数说明是
wParam
Zero-based index of the button to delete.
lParam
Must be zero.
我们的代码是
BOOL CSendMessageToProcess::ClearIcon(DWORD dwIndexOrCmdID, BOOL bByDelete/* = FALSE*/)
{BOOL bContinue = FALSE;do {if ( bByDelete ) {DWORD dwNULL = 0;SendRemoteMessage(TB_DELETEBUTTON, dwIndexOrCmdID, NULL, dwNULL);bContinue = TRUE;break;}
这样看似没什么问题了,但是我们看下执行的结果
看下红色框住的区域(非水印内容),“僵尸”图标的确是被删除了,但是任务栏的长度却没有变化!这是这种最优雅的方法的最失败的地方,也正是这个缺陷促使我再次寻找能彻底解决的方法。但是其实这个技术缺陷可以通过产品设计的方法来规避:我们进程启动时,清除“僵尸”图标,然后创建一个可用的图标。这样会促使通知区域重新计算区域大小,从而触发一次自动调整。
模拟鼠标方式去除“僵尸”图标
- 如何计算出“僵尸”图标的位置
- 发送哪些消息
wParam
Command identifier of the button.
lParam
Pointer to a RECT structure that will receive the bounding rectangle information.
第二个问题我们可以在计算好滑动区域的情况下,发送WM_MOUSEMOVE,对应的代码是
else {RECT rc;StBufferInfo stLocalBuffer;stLocalBuffer.lpBuffer = &rc;stLocalBuffer.dwBufferSize = sizeof(rc);DWORD dwGetRectRet = 0;if ( FALSE == SendRemoteMessage(TB_GETRECT, dwIndexOrCmdID, &stLocalBuffer, dwGetRectRet ) ) {break;;}if ( 0 == dwGetRectRet ) {break;}MouseMoveOnWindow(m_hWnd, &rc);}} while (0);return bContinue;
}
VOID MouseMoveOnWindow( HWND hWnd, LPRECT lpRect )
{if ( FALSE == ::IsWindow(hWnd) || NULL == lpRect) {return;}RECT rc;memset(&rc, 0, sizeof(rc));if ( FALSE == ::GetWindowRect(hWnd, &rc) ) {return;}// 滑动的点POINT pt;DWORD dwStart = 0;DWORD dwEnd = 0;dwStart = lpRect->left;dwEnd = lpRect->right;pt.y = (lpRect->top + lpRect->bottom)/2;for ( DWORD loffset = dwStart; loffset < dwEnd; loffset = loffset + STOPLENGTH) {pt.x = loffset; ::PostMessage(hWnd, WM_MOUSEMOVE, 0, MAKELONG(pt.x, pt.y));Sleep(10);}
}
使用IAccessible接口枚举并删除“僵尸”图标
#include "Accessible.h"BOOL EnumAccessible( HWND hwnd, XENUMACCESSIBLEPROC lpEnumAccessibleProc )
{BOOL bRet = FALSE;_ASSERTE (::IsWindow(hwnd));_ASSERTE(lpEnumAccessibleProc);if (::IsWindow(hwnd) && lpEnumAccessibleProc){CComPtr<IAccessible> pIAcc;HRESULT hr = AccessibleObjectFromWindow(hwnd, OBJID_WINDOW, IID_IAccessible, (void**)&pIAcc);if (SUCCEEDED(hr) && pIAcc){CComVariant varChild;CComPtr<IAccessible> pIAccChild;FindChild(pIAcc, pIAccChild, varChild, lpEnumAccessibleProc);bRet = TRUE;}}return bRet;
}static BOOL FindChild(CComPtr<IAccessible>& pIAccParent, CComPtr<IAccessible>& pIAccChild,CComVariant& varChild, XENUMACCESSIBLEPROC lpEnumAccessibleProc)
{BOOL bSuc = FALSE;BOOL bContinue = TRUE;do {if ( NULL == pIAccParent || NULL ==lpEnumAccessibleProc) {break;}BOOL bContinue = TRUE;CComPtr<IEnumVARIANT> pEnum;HRESULT hr = pIAccParent->QueryInterface(IID_IEnumVARIANT, (PVOID*) &pEnum);if ( SUCCEEDED(hr) && pEnum) {pEnum->Reset();}// get child countlong nChildren = 0;unsigned long nFetched = 0;pIAccParent->get_accChildCount(&nChildren);for (long index = 1; (index <= nChildren) && bContinue; index++) {varChild.Clear();if ( pEnum ) {hr = pEnum->Next(1, &varChild, &nFetched );if ( FAILED(hr)) {bContinue = FALSE;break;}}else {varChild.vt = VT_I4;varChild.lVal = index;}// get IDispatch interface for the childCComPtr<IDispatch> pDisp;if ( VT_I4 == varChild.vt ) {hr = pIAccParent->get_accChild(varChild, &pDisp);}else if ( VT_DISPATCH == varChild.vt ) {pDisp = varChild.pdispVal;}// get IAccessible interface for the childCComPtr<IAccessible> pCAcc;if ( NULL != pDisp ) {hr = pDisp->QueryInterface(IID_IAccessible, (void**)&pCAcc);if ( FAILED(hr) ) {continue;}}// get information about the childif ( NULL != pCAcc) {varChild.Clear();varChild.vt = VT_I4;varChild.lVal = CHILDID_SELF;pIAccChild = pCAcc;}else {pIAccChild = pIAccParent;}DWORD dwState = 0;if ( FALSE == GetObjectState(pIAccChild, varChild, dwState) ) {continue;}// check if object is availableif (dwState & STATE_SYSTEM_INVISIBLE ) {continue;}HWND hwndChild = 0;WindowFromAccessibleObject(pIAccChild, &hwndChild);// call enum callbackbContinue = lpEnumAccessibleProc(pIAccChild, varChild, hwndChild);if (bContinue && pCAcc) {bContinue = FindChild(pCAcc, pIAccChild, varChild, lpEnumAccessibleProc);}}bSuc = TRUE;} while (0);return bContinue;
}BOOL GetObjectState( CComPtr<IAccessible>& pAcc, CComVariant& varChild, DWORD& dwState )
{BOOL bRet = FALSE;dwState = 0;do {if ( NULL == pAcc ) {break;}CComVariant varState;HRESULT hr = pAcc->get_accState(varChild, &varState);if ( FAILED(hr) || VT_I4 != varState.vt ) {break;}dwState = varState.lVal;bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectRole( CComPtr<IAccessible>& pAcc, CComVariant& varChild, DWORD& dwRole )
{BOOL bRet = FALSE;dwRole = 0;do {if ( NULL == pAcc ) {break;}CComVariant varRole;HRESULT hr = pAcc->get_accRole(varChild, &varRole);if ( FAILED(hr) || VT_I4 != varRole.vt ) {break;}dwRole = varRole.lVal;bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectName( CComPtr<IAccessible>& pAcc, CComVariant& varChild, LPVOID lpName, DWORD dwBufferSize, DWORD& dwWrite )
{BOOL bRet = FALSE;do {if ( NULL == pAcc || NULL == lpName ) {break;}CComBSTR bstrName;HRESULT hr = pAcc->get_accName(varChild, &bstrName);if ( FAILED(hr) ) {break;}if ( dwBufferSize < bstrName.ByteLength() ) {break;}if ( 0 != memcpy_s(lpName, dwBufferSize, (LPWSTR)bstrName, bstrName.ByteLength()) ) {break;}dwWrite = bstrName.ByteLength();bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectRoleString( CComPtr<IAccessible>& pAcc, CComVariant& varChild, LPVOID lpBuffer, DWORD dwBufferLength, DWORD& dwWrite )
{BOOL bRet = FALSE;DWORD dwRole = 0;do {if ( NULL == pAcc || NULL == lpBuffer ) {break;}CComVariant varRole;HRESULT hr = pAcc->get_accRole(varChild, &varRole);if ( FAILED(hr) ) {break;}if ( VT_I4 == varRole.vt ) {dwRole = varRole.lVal;dwWrite = ::GetRoleText(dwRole, (LPWSTR)lpBuffer, dwBufferLength / sizeof(WCHAR));if ( 0 == dwWrite ) {break;}dwWrite *= sizeof(WCHAR);}else if ( VT_BSTR == varRole.vt ) {CComBSTR bstrRoletext(varRole.bstrVal);if ( dwBufferLength < bstrRoletext.ByteLength() ) {break;}if ( 0 != memcpy_s(lpBuffer, dwBufferLength, (LPWSTR)bstrRoletext, bstrRoletext.ByteLength())) {break;}dwWrite = bstrRoletext.ByteLength();}bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectDescription( CComPtr<IAccessible>& pAcc, CComVariant& varChild, LPVOID lpBuffer, DWORD dwBufferLength, DWORD& dwWrite )
{BOOL bRet = FALSE;do {if ( NULL == pAcc || NULL == lpBuffer ) {break;}CComBSTR bstrDescription;HRESULT hr = pAcc->get_accDescription(varChild, &bstrDescription);if ( FAILED(hr) ) {break;}if ( dwBufferLength < bstrDescription.ByteLength() ) {break;}if ( 0 != memcpy_s(lpBuffer, dwBufferLength, (LPWSTR)bstrDescription, bstrDescription.ByteLength()) ) {break;}dwWrite = bstrDescription.ByteLength();bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectValue( CComPtr<IAccessible>& pAcc, CComVariant& varChild, LPVOID lpBuffer, DWORD dwBufferLength, DWORD& dwWrite )
{BOOL bRet = FALSE;do {if ( NULL == pAcc || NULL == lpBuffer ) {break;}CComBSTR bstrValue;HRESULT hr = pAcc->get_accValue(varChild, &bstrValue);if ( FAILED(hr) ) {break;}if ( dwBufferLength < bstrValue.ByteLength() ) {break;}if ( 0 != memcpy_s(lpBuffer, dwBufferLength, (LPWSTR)bstrValue, bstrValue.ByteLength() ) ) {break;}dwWrite = bstrValue.ByteLength();bRet = TRUE;} while (0);return bRet;
}BOOL GetObjectLocation( CComPtr<IAccessible>& pAcc, CComVariant& varChild, RECT& rect )
{BOOL bRet = FALSE;do {if ( NULL == pAcc) {break;}HRESULT hr = pAcc->accLocation(&rect.left, &rect.top, &rect.right, &rect.bottom, varChild);if ( FAILED(hr) ) {break;}// accLocation returns width and heightrect.right += rect.left;rect.bottom += rect.top;bRet = TRUE;} while (0);return bRet;
}
一种清除windows通知区域“僵尸”图标的方案——XP系统解决方案相关推荐
- 一种清除windows通知区域“僵尸”图标的方案——Windows7系统解决方案
Windows7下"僵尸"图标的解决方案 从<一种清除windows通知区域"僵尸"图标的方案--问题分析>(以后简称<问题分析>)一文 ...
- 一种清除windows通知区域“僵尸”图标的方案——问题分析
通知区域名称有趣的历史 假如说到windows通知区域,可能很多人还是不清楚它是什么.如果改称Tray区域,可能有人就懂了.如果再白话点,叫它"托盘"或者"系统托盘&qu ...
- 28、Windows API Shell任务栏通知区域(Tray)图标
使用了通知区域图标的程序应该是图形用户界面的程序,具有窗口,和一般的窗口程序一样,应该创建一个主窗口,并处理窗口消息. 一.相关API 1.Shell_NotifyIcon Sends a messa ...
- Windows 10任务栏中托盘区(通知区域)图标怎么缩略成^
1.重启电脑后windows的通知区域就不能缩略了,有些图标还没有,用了这个办法才重新显示.但是图标不能缩略看着很不习惯. Windows 10任务栏中托盘区(通知区域)图标消失的解决方法_Ayka的 ...
- 【转】PB实现在通知区域添加图标
"为应用程序在任务栏通知区域加个图标,按最小化控制菜单后隐藏窗口,单击通知区域内的图标恢复并显示窗口,右击通知区域内的图标弹出快捷方式菜单",已成为时下程序设计上的一个时髦主题,有 ...
- Windows 10任务栏中托盘区(通知区域)图标消失的解决方法
该方法仅使用于部分情况.首先参考了win10系统托盘图标不见了_Win10任务栏托盘区域图标异常怎么办?任务栏通知区域重置方法...一文重置了通知区域TrayNotify的注册表,然而没有见效.通过尝 ...
- 我xp电脑桌面没有计算机图标不见了,xp系统我的电脑图标不见了怎么办|如何找回我的电脑图标-系统城...
xp系统我的电脑图标不见了怎么办?我们使用xp电脑过程中,有时候会不小心把我的电脑图标给弄丢了,对于一些新手来说,不知道我的电脑图标不见了怎么解决,下面小编就教大家如何找回雨林木风xp纯净版系统我的电 ...
- Windows 10可三步重回经典XP系统外观 免安装主题
还怀念Windows XP吗?历史车轮滚滚向前,发布近两年的Windows 10已经占据了全球1/4的操作系统市场,XP作为曾经的经典也难逃被弃的命运.不过,如果你是怀旧党,不妨尝试大神TurrboS ...
- Windows 7 自动更新失败导致无法进系统解决方案
故障现象:自动更新后,重启电脑,提示: (配置Windows update 失败 还原更改 请勿关闭计算机), 而计算机一直停留该界面,如果半个小时以上都无反应.此时,就不要再继续等待了.可采取以下办 ...
最新文章
- Linux那些事儿 之 戏说USB(18)设备的生命线(一)
- 我的android绘图学习笔记(二)
- C 语言 *** glibc detected *** free(): invalid next size (fast): 0x0000000000be1010 ***
- Struts2+Android (3) 多种方式向服务器发送信息
- linux终端传文件,如何使用Linux FTP命令传输文件
- Java设计模式之行为型:备忘录模式
- mysql看表关联视图_MySQL数据库 : 自关联,视图,事物,索引
- BeetleX.WebFamily之Markdown编辑器
- python编译成c代码_python如何调用c编译好可执行程序
- 酷我 android 目录,酷我听听:Android平台最强音乐播放器
- 基于京东家电商品知识图谱的自动问答系统(三) -- Java实现问答系统
- 在mysql中productname_ASP如何读出数据库里的英文符号.....%rs(ProductName)% 其是ProductName=10×26wood...
- ubuntu下git使用
- 【iOS-Cocos2d游戏开发】解决滚屏背景/拼接地图有黑边(缝隙)
- 数据结构和算法 第一章 综述(1)
- 公司给你调岗降薪,逼你主动辞职如何应对?
- 如何将PDF压缩突破限制大小
- OpenCV系列四 --- 颜色通道分离与多通道图像混合
- 计算机十进制转为八位二进制,Java将十进制转换为8位二进制(Java convert from decimal to 8-bit binary)...
- 如何理解、分析DNU/DAU?(案例:DNU、DAU面积图)
热门文章
- Python装饰器的神奇功能:自动打印每个方法耗时
- python基础知识整理 第六节:面向对象封装练习
- PCL调错:(3)error C2589“(“:“::“右边的非法标记
- 非线性滤波(opencv)
- 有java基础的人学python_准备自学Python ,会java,有什么建议吗?
- 前端如何查看音频的长度_重学前端基础:如何查看文档对象的所有属性?如何文档查找节点?...
- qdockwidget设置隐藏标题栏,重叠时tab标签位置,自动填充满整个窗口
- Blender从头到尾创建一辆宝马轿车视频教程
- Rocksdb DeleteRange实现原理
- GoAccess安装及分析nginx实时日志