首页
归档
友情链接
关于
Search
1
在wsl2中安装archlinux
80 阅读
2
nvim番外之将配置的插件管理器更新为lazy
58 阅读
3
2018总结与2019规划
54 阅读
4
PDF标准详解(五)——图形状态
33 阅读
5
为 MariaDB 配置远程访问权限
30 阅读
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
登录
Search
标签搜索
c++
c
学习笔记
windows
文本操作术
编辑器
NeoVim
Vim
win32
VimScript
Java
emacs
linux
文本编辑器
elisp
反汇编
OLEDB
数据库编程
数据结构
内核编程
Masimaro
累计撰写
308
篇文章
累计收到
27
条评论
首页
栏目
心灵鸡汤
软件与环境配置
博客搭建
从0开始配置vim
Vim 从嫌弃到依赖
archlinux
Emacs
MySQL
Git与Github
AndroidStudio
cmake
读书笔记
菜谱
编程
PDF 标准
从0自制解释器
qt
C/C++语言
Windows 编程
Python
Java
算法与数据结构
PE结构
页面
归档
友情链接
关于
搜索到
147
篇与
的结果
2015-11-04
Win32 API 三态按钮的实现
Windows平台提供了丰富的控件,但是在使用中我们不会使用它提供的默认风格,有时候需要对控件进行改写,让它展现出更友好的一面,这次主要是说明三态按钮的实现。三态按钮指的是按钮在鼠标移到按钮上时显示一种状态,鼠标在按下时展现一种状态,在鼠标移开时又展现出另外一种状态,总共三种。当然鼠标按下和移出按钮展示的状态系统自己提供的有,这个时候在处理这两种状态只需要贴相应的图片就行了,三态按钮的实现关键在于如何判断鼠标已经移动到按钮上以及鼠标移出按钮,然后根据鼠标的位置将按钮做相应的调整。判断鼠标在按钮的相应位置,系统提供了一个函数_TrackMouseEvent用户处理鼠标移出、移入按钮。函数原型如下:BOOL _TrackMouseEvent( LPTRACKMOUSEEVENT lpEventTrack );函数需要传入一个TRACKMOUSEEVENT类型的指针,该结构的原型如下:typedef struct tagTRACKMOUSEEVENT { 2 DWORD cbSize;//该结构体所占空间大小 3 DWORD dwFlags;//指定服务的请求(指定它需要侦听的事件),这次主要用到的是TME_HOVER和TME_LEAVE(侦听鼠标移开和移入事件) 4 HWND hwndTrack;//指定我们需要侦听的控件的句柄 5 DWORD dwHoverTime;//HOVER消耗的时间,可以用系统提供的一个常量HOVER_DEFAULT由系统默认给出,也可以自己填写,单位是毫秒 6 } TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT;在使用该函数时需要包含头文件commctrl.h和lib文件comctl32.lib解决了鼠标行为的检测之后,就是针对不同的鼠标行为重绘相应的按钮。重绘按钮需要在消息WM_DRAWITEM中,这个消息的处理是在相应控件的父窗口中实现的,而在一般情况下父窗口不会收到该消息,需要我们手工指定控件资源的属性为的OWNERDRAW为真,或者在创建相应的按钮窗口时将样式设置为BS_OWNERDRAW 。设置完成后就可以在对应的父窗口处理函数中接收并处理WM_DRAWITEM,在该消息中重绘按钮该消息中主要使用的参数是lpParam它里面包含的是一个指向DRAWITEMSTRUCT的结构体:typedef struct tagDRAWITEMSTRUCT { UINT CtlType; //控件类型 UINT CtlID; //控件ID UINT itemID; //子菜单项的ID主要用于菜单 UINT itemAction; //控件发出的动作,如ODA_SELECT表示控件被选中 UINT itemState; //控件状态,这次需要用到的状态为ODS_SELECTED表示按钮被按下 HWND hwndItem; //控件句柄 HDC hDC; RECT rcItem;//控件的矩形区域 ULONG_PTR itemData; } DRAWITEMSTRUCT;该结构体中的一些成员需要根据控件类型赋值,同时结构体中的itemAction、itemState是可以由多个值通过位或组成在判断是否具有某种状态时需要使用位与运算而绘制控件时我们可以使用函数DrawFrameControl,该函数可以根据指定的控件类型、控件所处的状态来绘制控件的样式,绘制出来的任然是系统的之前的标准样式,处理WM_DRAWITEN消息的具体代码如下:LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam; char szBuf[50]; GetWindowText(lpdis->hwndItem,szBuf,50); if (ODT_BUTTON ==lpdis->CtlType)<BR>{ UINTuState = DFCS_BUTTONPUSH; if(lpdis->itemState & ODS_SELECTED) { uState |= DFCS_PUSHED; } DrawFrameControl(lpdis->hDC,&(lpdis->rcItem),DFC_BUTTON,uState); SetTextColor(lpdis->hDC,RGB(255,0,0)); DrawText(lpdis->hDC,szBuf,strlen(szBuf) + 1,&(lpdis->rcItem),DT_CENTER | DT_VCENTER | DT_SINGLELINE); }函数_TrackMouseEvent根据其检测的鼠标状态不同可以返回不同的消息,这次主要用的是WM_MOUSEHOVER(表示鼠标移动到按钮上)、WM_MOUSELEAVE(鼠标移出按钮),还需要注意的是这个函数每次检测完成返回后不会再次检测,需要我们自己主动调用函数检测鼠标状态,由于要多次调用,而每次调用都需要初始化所需要的结构体指针,所以我们封装一个函数专门用于调用_TrackMouseEvent:void Track(HWND hWnd) { TRACKMOUSEEVENT tme; tme.cbSize =sizeof(TRACKMOUSEEVENT); tme.dwFlags = TME_HOVER | TME_LEAVE; tme.dwHoverTime = 10; tme.hwndTrack = hWnd; _TrackMouseEvent(&tme); }消息WM_MOUSEHOVER和消息WM_MOUSELEAVE的处理是在对应的窗口过程中处理的,而按钮的窗口过程由系统提供我们并不知道,所以只有使用子类化的方法在我们的窗口过程中处理这两个消息。在按钮创建后立马要检测鼠标所以可以按钮对应的父窗口完成创建后子类化,对于窗口可以在它的WM_CREATE消息中处理,对于对话框可以在WM_INITDIALOG消息中处理,子类化调用函数SetWindowLong:g_OldProc = (LRESULT*)SetWindowLong(GetDlgItem(hDlg,IDC_BUTTON1),GWL_WNDPROC,(LONG)BtnProc); return0; 在新的窗口过程中处理消息,完成三态按钮:switch(uMsg) { caseWM_MOUSEMOVE: Track(hBtn);//当鼠标移动时检测 break; caseWM_MOUSEHOVER: { charszBuf[50]; RECT rtBtn;<BR> GetClientRect(hBtn,&rtBtn); HDChDc = GetDC(hBtn); DrawFrameControl(hDc,&(rtBtn),DFC_BUTTON,DFCS_BUTTONPUSH); HBRUSHhBr = CreateSolidBrush(RGB(255,255,255)); FillRect(hDc,&rtBtn,hBr); GetWindowText(hBtn,szBuf,50); SetBkMode(hDc,TRANSPARENT); DrawText(hDc,szBuf,strlen(szBuf),&rtBtn,DT_CENTER | DT_VCENTER | DT_SINGLELINE); ReleaseDC(hBtn,hDc); } break; caseWM_MOUSELEAVE: { charszBuf[50]; RECT rtBtn; GetClientRect(hBtn,&rtBtn); HDChDc = GetDC(hBtn); DrawFrameControl(hDc,&(rtBtn),DFC_BUTTON,DFCS_BUTTONPUSH); GetWindowText(hBtn,szBuf,50); SetBkMode(hDc,TRANSPARENT);//设置字体背景为透明 DrawText(hDc,szBuf,strlen(szBuf),&rtBtn,DT_CENTER | DT_VCENTER | DT_SINGLELINE); ReleaseDC(hBtn,hDc); } break; default: returnCallWindowProc((WNDPROC)g_OldProc,hBtn,uMsg,wParam, lParam);//在处理完我们感兴趣的消息后一定要记得将按钮的窗口过程还原 } return0;到这个地方为止,已经实现了三态按钮的基本样式,通过检测鼠标的位置设置按钮样式,上述代码只是改变了按钮的背景颜色和文字颜色,可能效果不好看。
2015年11月04日
4 阅读
0 评论
0 点赞
2015-10-05
程序隐藏到任务栏的实现
我们在使用软件的时候,有的软件允许最小化到任务栏,然后双击任务栏的图标时又会显示出来,这篇文章主要说明如何实现这种功能;实现这种功能主要分为两步,一是将程序窗口隐藏,二是将程序图标添加到任务栏,再次显示也是分为两步:第一步是将任务栏上的图标删除,第二步是将窗口显示出来。窗口的隐藏与显示我们用API函数ShowWindow,而添加和删除任务栏中的程序图标用的是Shell_NotifyIcon函数,ShowWindow函数平时用的比较多,而且也比较简单,这里就不在阐述,下面主要说明Shell_NotifyIcon的用法:BOOL Shell_NotifyIcon( DWORD dwMessage, PNOTIFYICONDATA lpdata ); 该函数有两个参数,第一个表示你希望对图标做何种操作主要有这几个值:NIM_ADD、NIM_DELETE、NIM_MODIFY、NIM_SETFOCUS、NIM_SETVERSION;常用的是前面3个主要是向任务栏添加图标、删除图标、修改图标;第二个参数是一个结构体该结构体的定义如下:typedef struct _NOTIFYICONDATA { DWORD cbSize;//该结构的大小 HWND hWnd; //表明当对任务栏图标进行操作是将消息发送给那个窗口 UINT uID; //应用程序的ID UINT uFlags; //一个标志 UINT uCallbackMessage;//对任务栏图标操作时向窗口发送的一个消息 HICON hIcon; //放到任务栏中的图标句柄 WCHAR szTip[64]; //当鼠标停在图标上时显示的提示信息 } NOTIFYICONDATA, *PNOTIFYICONDATA;UINT uID 参数是应用程序的ID,这个ID并不是必须的可以任意给值UINT uFlags 参数是一个标志,主要用于控制图标的行为:NIF_ICON:有这个标志hIcon才是有效值NIF_MESSAGE:有这个标志uCallbackMessage才有效,也就是说有这个标志,当我们进行操作时才有消息产生NIF_TIP:当有这个标志时szTip,才有效,才会出现提示信息;UINT uCallbackMessage:当我们对任务栏图标进行操作时会发送一条消息这个消息由用户自己定义,并且在窗口过程中处理。函数介绍完了,接下来就是实现的代码://这里是将移出图标与添加图标放到一个函数中,根据第二个参数判断是需要移出或是添加 BOOL TrackIcon(HWND hWnd, BOOL bTrak) { NOTIFYICONDATA nid = {0}; nid.cbSize = sizeof(NOTIFYICONDATA); nid.hWnd = hWnd; nid.uID = 0; if (bTrak) { nid.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; nid.uCallbackMessage = WM_TRAKICON_MSG; nid.hIcon = LoadIcon(NULL, IDI_APPLICATION); _tcscpy_s(nid.szTip, sizeof(nid.szTip), _T("提示信息")); ShowWindow(hWnd, SW_MINIMIZE); ShowWindow(hWnd, SW_HIDE); return Shell_NotifyIcon(NIM_ADD, &nid); }else { //当需要移出图标时,窗口也应该完全显示,因此不需要对图标操作,后面的几个值就是无效值,这里可以不用管它们 ShowWindow(hWnd, SW_SHOWNA); return Shell_NotifyIcon(NIM_DELETE, &nid); } } //这是对我们自定义的消息进行处理,这个消息中的lParam保存了相应的消息ID case WM_TRAKICON_MSG: { switch (lParam) { case WM_LBUTTONDBLCLK: TrackIcon(hwndDlg,FALSE); ShowWindow(hwndDlg,SW_SHOWNORMAL); break; } } break;最后程序的运行结果如下:
2015年10月05日
5 阅读
0 评论
0 点赞
2015-09-23
MFC中属性表单和向导对话框的使用
每次在使用MFC创建一个框架时,需要一步步选择自己的程序的外观,基本功能等选项,最后MFC会生成一个基本的程序框架,这个就是向导对话框;而属性表单则是另外一种对话框,表单上有多个属性页,每点击某一页,会显示该页的内容,最好的例子是Visual C++6.0中的Option对话框;如图:属性表单的创建:属性表单上由许多属性页组成,每个属性页都可以在可视化的编辑环境中编辑,需要添加的资源名称是对话框下面的IDD_PROPPAGE_LARGE、IDD_PROPPAGE_MEDIUM, IDD_PROPPAGE_SMALL,如下图:创建资源时也可以直接创建对话框,在属性中将Style属性选为Child、Border选为Thin、勾上Disable选项;创建了资源,下面就是关联MFC的类,属性页的类是CPropertyPage,该类是继承于CDialog类,在使用时需要从CPropertyPage中派生。创建了多个属性页就需要派生多个新类;创建了属性页,下面就需要创建属性表单,属性表单不需要编辑资源,可以从类CproppertyPage中派生一个新类,用来表示新表单类;为了将属性页加到属性表单上需要在对应的构造函数中调用AddPage函数,最后需要调用该类的DoModal或者Create函数创建一个模态或者非模态的属性表单;在一下代码中有三个对应的属性页的类(CProp1、CProp2、CProp3)和一个属性表单的类(CPropSheet);//在CPropSheet中创建三个属性页的对象 public: CProp1 m_Prop1; Cprop2 m_Prop2; CProp3 m_Prop3;//在构造函数中添加属性页 AddPage(&m_Prop1); AddPage(&m_Prop2); AddPage(&m_Prop3);至于它的使用则是于普通的对话框类似向导的创建与使用:向导所使用的类与属性表单相同,这里就不在说明,为了创建向导,需要在调用DoModal或者Create之前调用SetWizardMode()函数,这样之前的属性表单就变为了向导程序,向导程序上通过下一步来转到下一个属性页,每个页面上都有“下一步”、“上一步”、“取消”按钮,这个特性不便于用户的操作,我们一般习惯于将第一个向导页的“上一步”隐藏,最后一页的“下一步”变为“完成”,为了实现这个需要使用函数SetWizardButtons(),这个函数只有一个参数表示的是页面上按钮的特性,它的取值可以是PSWIZB_BACK、PSWIZB_NEXT、PSWIZB_FINISH、PSWIZB_DISABLEDFINISH中的一个或者几个,分别用来设置该页上的一个“上一步”按钮、“下一步”按钮、“完成”按钮、和一个禁用的“完成”按钮,一般来说在属性页中的OnSetActive函数中调用,当属性页被选中,从而被激活时程序会响应WM_ACTIVATE,而该消息在函数OnSetActive中处理,由于在基类中有了这个函数,所以我们需要重写这个函数,下面是一个例子:BOOL CProp1::OnSetActive() { ((CPropertySheet*)GetParent())>SetWizardButtons(PSWIZB_NEXT); return CPropertyPage::OnSetActive(); }有时候需要实现这样的功能:属性页上有一些信息需要用户填写或选择,当用户没有选择或填写完整时不允许进入下一个页面。每次需要进入下个页面时用户会单击“下一步”按钮,而这个时候程序会调用OnWizardNext函数进入下一个页面(根据页面按钮的不同,点击不同的按钮程序会调用OnWizardNext、OnWizardBack、OnWizardFinish),当该函数返回-1时会禁止属性页发生变更,返回0时会正常进入下一页,下面是一个例子代码:LRESULT CProp1::OnWizardNext() { // TODO: 在此添加专用代码和/或调用基类 UpdateData(TRUE); if (-1 == m_occupation) { MessageBox(TEXT("请选择你的职业")); return -1; } if (TEXT("") == m_workAddr) { MessageBox(TEXT("请选择你的工作地点")); return -1; } return CPropertyPage::OnWizardNext(); }注意:将变量与控件相关联时为了获取控件返回的值需要调用UpdateData()函数,当该函数参数为TRUE时会调用DoDataExchange,该函数会根据控件返回的值,动态更新变量的值;一般情况下只有当用户点击完成时才保存用户输入的信息当用户点击取消时应该取消信息的保存;一般情况下CPropertySheet类的DoModal函数返回值是IDOK或者IDCANCLE。但是当属性表单被创建为向导时会返回ID_WIZFINISH和IDCANCLE这个时候我们可以根据返回值来判断是否保存;
2015年09月23日
7 阅读
0 评论
0 点赞
2015-09-21
对话框伸缩功能的实现
对话框的伸缩功能是指当触发某一操作时只显示部分对话框的内容,再次触发时显示全部的对话框范围,就比如画图软件上的选择颜色对话框,我们可以使用系统预定义的颜色,也可以选择自定义颜色,当点击自定义的按钮时,对话框出现原先隐藏的另一边,让用户填写颜色的RGB值。为了实现这个功能,我们需要记录两个矩形范围:全部对话框的大小和我们希望显示的部分对话框的大小,利用函数SetWindowPos来设置显示的对话框的大小,该函数的原型如下:BOOL SetWindowPos( HWND hWnd, // 需要设置的窗口的句柄 HWND hWndInsertAfter, // Z序中下一个窗口的句柄 int X, // int Y, // 窗口所在矩形的顶点坐标(x, y) int cx, // 矩形宽 int cy, // 矩形高 UINT uFlags // 显示属性 ); 下面是对该函数的补充:hWndInsertAfter:除了给出具体的窗口句柄外还可以是这几个值:HWND_BOTTOM、HWND_NOTOPMOST、HWND_TOP、HWND_TOPMOST;uFlags主要的一些标志: SWP_NOMOVE:调用该函数不改变窗口之前的顶点位置,当设置这个这个值的时候,x、y参数将被忽略; SWP_NOZORDER:忽略Z序,这个标志被设置时将忽略hWndInsertAfter参数;具体的信息可以在MSDN中查找;以下是具体的实现代码://按钮的WM_COMMAND消息处理 case WM_COMMAND: { if (LOWORD(wParam) == IDC_BUTTON) { if (HIWORD(wParam) == BN_CLICKED) { TCHAR szBuf[255] = TEXT(""); GetWindowText(GetDlgItem(hDlg, IDC_BUTTON), szBuf, 255); if (0 == _tcscmp(szBuf, TEXT("收缩>>"))) { SetWindowText(GetDlgItem(hDlg, IDC_BUTTON), TEXT("扩张<<")); }else { SetWindowText(GetDlgItem(hDlg, IDC_BUTTON), TEXT("收缩>>")); } Extern(hDlg, szBuf); } } }//改变对话框大小的函数 void Extern(HWND hWnd, const TCHAR *pszStr) { //保存对话框在扩张和收缩状态下的矩形大小 static RECT rtSmall; static RECT rtLarge; //当两个量不是有效值时,获取这两种状态下的矩形大小 if (IsRectEmpty(&rtLarge)) { RECT rtSpecrator; GetWindowRect(GetDlgItem(hWnd, IDC_SPERATOR), &rtSpecrator); rtSmall.left = rtLarge.left; rtSmall.top = rtLarge.top; rtSmall.right = rtSpecrator.right; rtSmall.bottom = rtSpecrator.bottom; } if (0 == _tcscmp(pszStr, TEXT("收缩>>"))) { SetWindowPos(hWnd, NULL, 0, 0, rtSmall.right - rtSmall.left, rtSmall.bottom - rtSmall.top, SWP_NOZORDER | SWP_NOMOVE); } else { SetWindowPos(hWnd, NULL, 0, 0, rtLarge.right - rtLarge.left, rtLarge.bottom - rtLarge.top, SWP_NOZORDER | SWP_NOMOVE); } }IDC_SPERATOR是一个分割线的ID,分割线我们采用的是一个图片控件,将这个控件的高度尽量缩小,这个控件本身也是一个矩形,可以用GetWindowRect函数获取它的矩形大小,缩小时只保存控件之上的部分;
2015年09月21日
5 阅读
0 评论
0 点赞
2015-09-15
select模型
在Windows中所有的socket函数都是阻塞类型的,也就是说只有网络中有特定的事件发生时才会返回,在没有发生事件时会一直等待,虽说我们将它们设置为非阻塞状态,但是在对于服务器段而言,肯定会一直等待客户端的消息,也就是说即使设置为非阻塞状态,时间到了函数返回,但是程序不能结束,需要一个循环不断的侦听,特别是对于有多个客户端需要管理的时候,每一个与客户端通信的socket都需要一个侦听,这样管理起来非常麻烦,我们希望系统帮助我们管理,告诉我们有哪些socket现在可以操作。为了实现这个,我们可以使用select模型select模型中需要一个结构体fd_set,该结构体是一个socket的集合,我们可以看到该结构体的定义:typedef struct fd_set { u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set;从这个定义中可以看到,结构体中主要保存了一个socket的数组和一个保存数组的大小变量;使用select模型主要使用函数select,该函数原型如下:int select ( int nfds, //系统保留,无意义 fd_set FAR * readfds,//可读的socket集合 fd_set FAR * writefds,//可写的socket集合 fd_set FAR * exceptfds,//外带socket集合 const struct timeval FAR * timeout//该函数的超时值 );在程序中使用该函数前需要在特定的集合中放入需要检测的socket值,当发生某一时间导致该函数返回时,函数会将特定集合中未待决的socket全部剔除出去,保留待决套接字,比如在readfds集合中放入几个套接字并执行完成函数,那么留下的套接字都是可以从系统的相应缓冲区读数据的。通过遍历相应的集合我们知道如何对套接字做相应的操作;select模型最多支持64个套接字,这个值由FD_SETSIZE宏定义的,我们可以修改这个宏的值,以便支持更多的套接字,修改时尽量不要在系统文件中修改,在我们的工程文件中修改,可用使用如下方式:#ifdef FD_SETSIZE #undef FD_SETSIZE #endif #define FD_SETSIZE 200这段代码使得select模型支持200个套接字;虽然可以修改,但是这个数组太大,会消耗过多的系统资源,每次在遍历数组时总会从头到尾遍历,数组太大效率必然底下,所以最好不要修改这个值,处理大于64个套接字的情况下可以使用多线程的方式,多定义几个集合处理;为了操作这个集合,Windows专门定义了一组宏,他们分别是:FD_SET(fd, &set) //将fd套接字压入集合set中 FD_ISSET(fd, &set)//判断fd是否在set中 FD_ZERO(&set)//将集合set清零 FD_CLR(fd, &set)//将fd从集合set中删除下面说一下服务端一个简单的select模型的编写1)创建套接字,绑定、侦听;2)等待客户端链接3)将连接返回的套接字压入一个数组中保存4)将数组的套接字填入集合中5)调用select函数6)检测特定集合中的套接字7)进行读写操作8)返回到第四步,等待客户端下一步请求在编写时需要注意以下几点:1)为了与多个客户端保持连接,需要一个数组保存与客户端连接的所有的socket,由于select函数只会执行一次,每次返回后需要再次将徐监控的套接字压入集合,调用select,以便进行下一次检测;所以一般将这一步写在一个死循环中2)注意select是一个阻塞函数,所以为了可以支持多个客户端可以采用一些方法:第一种就是采用多线程的方式,每有一个客户端连接都需要将新开一个线程处理并调用select监控;另一种就是调用select对侦听套接字以及与客户端通信的套接字;为什么可以这样呢,这就要说到TCP/IP中的三次握手,首先一般由客户端发起链接,发送一条数据包到服务器,服务器接收到数据,发送一条确认信息给客户端,然后客户端再发送一条数据,这样就正式建立连接,所以在客户端与服务器建立连接时必然会发送数据,而服务器一定会收到数据,所以将侦听套接字放入到read集合中,当有客户端需要连接时自然会收到一条数据,这个时候select会返回,我们需要校验集合中的套接字是否是侦听套接字,如果是则表明有客户端需要连接;这样当客户端有请求select会返回,可以进行下一次的侦听,没有请求,会死锁在select函数上,但是对于所有客户端并没有太大的影响;3)我们用数组存储所有的套接字时,每当有客户端链接,我们需要添加,而有客户端断开链接我们需要在数组中删除,并将下一个套接字添加进该位置,为了管理套接字数组,我们另外需要一个队列用来记录退出客户端的socket在数组中的位置,下一次有新的链接进来就将相应的套接字放到这个位置。下面是一个简单的例子:SOCKET g_sockArray[FD_SETSIZE] = { 0 }; int g_nCount = 0; int _tmain(int argc, _TCHAR* argv[]) { WSAData wd; WSAStartup(MAKEWORD(2, 2), &wd); SOCKET sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (INVALID_SOCKET == sockListen) { cout << "创建套接字失败,错误码为:" << WSAGetLastError() << endl; closesocket(sockListen); WSACleanup(); return 0; } SOCKADDR_IN SrvAddr = { AF_INET }; SrvAddr.sin_port = htons(6666); SrvAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (SOCKET_ERROR == bind(sockListen, (SOCKADDR*)&SrvAddr, sizeof(SOCKADDR))) { cout << "绑定失败,错误码:" << WSAGetLastError() << endl; closesocket(sockListen); WSACleanup(); return 0; } if (SOCKET_ERROR == listen(sockListen, 5)) { cout << "侦听失败,错误码:" << WSAGetLastError() << endl; closesocket(sockListen); WSACleanup(); } g_sockArray[g_nCount] = sockListen; g_nCount++; fd_set fdRead = { 0 }; while (true) { FD_ZERO(&fdRead); for (int i = 0; i < g_nCount; i++) { FD_SET(g_sockArray[i], &fdRead); } select(0, &fdRead, NULL, NULL, NULL); for (int i = 0; i < g_nCount; i++) { if (FD_ISSET(g_sockArray[i], &fdRead)) { if (sockListen == g_sockArray[i]) { SOCKET sockClient = accept(sockListen, NULL, NULL); if (INVALID_SOCKET == sockClient) { cout << "连接失败, 错误码为:" << WSAGetLastError() << endl; break; } cout << "有客户端链接进来" << endl; if (g_nCount == FD_SETSIZE) { cout << "以达到服务器管理上限" << endl; break; } if (NULL == g_pFirst) { g_sockArray[g_nCount] = sockClient; } else { int n = Pop(); g_sockArray[n] = sockClient; } g_nCount++; } else { char szBuf[255] = ""; recv(g_sockArray[i], szBuf, 255, 0); cout << "客户端发送消息:"<< szBuf << endl; if (0 == strcmp(szBuf, "exit")) { Push(i); closesocket(g_sockArray[i]); cout << "与客户端链接断开" << endl; g_nCount--; } break; } } } } WSACleanup(); return 0; }上述代码中,每当检测到有待决套接字就处理,处理完一个后就不在继续检测了,我们知道在理论上select执行完成后,保留的是所有待决套接字,那么待决套接字可不可能有多个呢,我觉得这个基本上不可能,因为服务器端判定在某一时刻该套接字是否处于待决状态是在毫秒级别的,就算有几个客户端在某时刻毫秒不差的向服务器发送数据,那么我们还要考虑双方之间的距离(虽说光速很快可以忽略不计但是当单位是毫秒是应该还是有影响)以及网络状况,虽说在大规模的情况可能出现在毫秒级别上同时响应,但是我们的select只支持64个(超过64时需要另外开线程再创建一个相应的集合),在64个客户端中找到这样的两个客户端是不可能的,所以我们就假定每次只有一个待决套接字,使用break为了让其跳出循环,避免做无用功;
2015年09月15日
5 阅读
0 评论
0 点赞
2015-09-12
socket模型处理多个客户端
最近学完了简单的socket编程,发现其实socket的网络编程其实并没有什么难度,只是简单的函数调用,记住客户端与服务端的步骤,写起来基本没有什么问题。在服务器程序的设计中,一个服务器不可能只相应一个客户端的链接,为了响应多个客户端的链接,需要使用多线程的方式,每当有一个客户端连接进来,我们就开辟一个线程,用来处理双方的交互(主要是利用recv或者recvfrom用于收发信息),由于但是在网络中可能出现这样一种情况:由于处理比较复杂,下一条信息到来之后,上一条信息的处理还没有完成,这样信息太多了之后系统的缓冲占满之后可能会发生丢包的现象,所以为了解决这个问题,需要另外再开一个线程,专门用来处理接收到的数据,这样总共至少有3个线程,主线程,收发信息的线程,处理线程;这样可能也不完整,处理的操作种类多了的话可能需要根据不同的请求来开辟不同的线程用来处理这一类请求,下面是实现这一思路的部分代码:全局变量:DWORD WINAPI AcceptThread(LPVOID lpParameter); DWORD WINAPI RecvThread(LPVOID lpParameter); DWORD g_nAcceptID = 123; DWORD g_nRecvID = 234; HANDLE g_hAccpetThread; HANDLE g_hRecvThread;主线程函数:int _tmain(int argc, _TCHAR* argv[]) { WSADATA wd; WSAStartup(MAKEWORD(2, 2), &wd); SOCKET sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (INVALID_SOCKET == sockListen) { cout << "创建侦听套接字失败,错误码为:" << WSAGetLastError() << endl; return -1; } SOCKADDR_IN srvAddr = { 0 }; srvAddr.sin_family = AF_INET; srvAddr.sin_port = htons(6666); srvAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (SOCKET_ERROR == bind(sockListen, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR))) { cout << "绑定失败,错误码为:" << WSAGetLastError() << endl; WSACleanup(); return -1; } if (SOCKET_ERROR == listen(sockListen, 5)) { cout << "侦听失败,错误码为:" << WSAGetLastError() << endl; WSACleanup(); return -1; } while (true) { SOCKET sockConn = accept(sockListen, NULL, 0); if (INVALID_SOCKET == sockConn) { cout << "本次连接失败,即将进入下一次连接,错误码为:" << WSAGetLastError() << endl; closesocket(sockConn); closesocket(sockListen); WSACleanup(); continue; } g_hAccpetThread = CreateThread(NULL, 0, AcceptThread, &sockConn, 0, &g_nAcceptID); } WaitForSingleObject(g_hAccpetThread, INFINITE); WSACleanup(); return 0; }收发数据函数:DWORD WINAPI AcceptThread(LPVOID lpParameter) { cout << "有客户端连接进来" << endl; SOCKET sockConn = *(SOCKET*)lpParameter; while (true) { char *pszBuf = (char*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY | HEAP_NO_SERIALIZE, 255); if (SOCKET_ERROR == recv(sockConn, pszBuf, 255, 0)) { cout << "接受数据失败,错误码为:" << WSAGetLastError() << endl; cout << "准备进行下一次接受数据....." << endl; continue; } g_hRecvThread = CreateThread(NULL, 0, RecvThread, pszBuf, 0, &g_nRecvID); WaitForSingleObject(g_hRecvThread, INFINITE); if (0 == strcmp("exit", pszBuf)) { cout << "正在断开与该客户端的连接" << endl; HeapFree(GetProcessHeap(), 255, pszBuf); return 0; } } return 0; }信息处理子线程:DWORD WINAPI RecvThread(LPVOID lpParameter) { cout << "接受到客户端的数据:" << (char*)lpParameter << endl; return 0; }虽说这个解决了多个客户端与服务器通信的问题,但是这样写确定也很明显:所有的与客户端通信的socket都有程序员自己管理,无疑加重了程序员的负担;每有一个连接都需要创建一个线程,当有大量的客户端连接进来开辟的线程数是非常多的,线程是非常耗资源的,所以为了解决这些问题就提出了异步的I/O模型,它们解决了这些问题,由系统管理套接字,不要要人为的一个个管理,同时不需要开辟多个线程来处理与客户端的连接,我们可以将线程主要用于处理客户端的请求上;
2015年09月12日
5 阅读
0 评论
0 点赞
2015-09-11
VC++平台上的内存对齐操作
我们知道当内存的边界正好对齐在相应机器字长边界上时,CPU的执行效率最高,为了保证效率,在VC++平台上内存对齐都是默认打开的,在32位机器上内存对齐的边界为4字节;比如看如下的代码:struct MyStruct { int i; char c; }; int _tmain(int argc, _TCHAR* argv[]) { cout<<sizeof(MyStruct)<<endl; return 0; } 此时输出的结果并不是sizeof(int) + sizeof(char) = 5而是8,因为内存对齐的原因,将char分配为4个字节效率更高;在VC平台上我们可以通过预处理指令:#pragma pack(show)来查看当前内存对齐的方式,我们在代码前加上一句#pragma pack(show),再次编译,在编译器的“生成”窗口中看到一个警告:“warning C4810: 杂注 pack(show) 的值 == 8”说明这时编译器采用的是8字节的对齐方式,另外可以通过这个预处理指令更改对齐方式,比如将代码改写一下:#pragma pack(show) #pragma pack(1) struct MyStruct { int i; char c; }; int _tmain(int argc, _TCHAR* argv[]) { cout<<sizeof(MyStruct)<<endl; system("PAUSE"); return 0; }这个时候得到结果为5,也就是说我们已经将对齐方式改为了1;除了这个预处理指令我们也可以通过VC++扩展关键字align来改变内存的对齐方式:#pragma pack(show) #pragma pack(1) struct MyStruct { int i; char c; }; struct __declspec(align(1)) MyStruct1 { int i; char c; }; int _tmain(int argc, _TCHAR* argv[]) { cout<<sizeof(MyStruct)<<endl; cout<<sizeof(MyStruct1)<<endl; system("PAUSE"); return 0; }这个时候输出的结果为两个5;
2015年09月11日
3 阅读
0 评论
0 点赞
2015-09-03
ADO对SQL Server 2008数据库的基础操作
最近在学习ADO与数据库的相关知识,现在我将自己学到的东西整理写出来,也算是对学习的一种复习。这篇文章主要说明如何遍历某台机器上所有的数据库服务,遍历某个服务中所有的数据库,遍历数据库中的所有表以及表中所有字段字段,最后再说明如何通过ADO操作数据库中的表。遍历所有数据库服务:遍历数据库服务我们通过函数NetServerEnum来实现,该函数可以 遍历局域网中所有的服务可以通过指定服务类型来有筛选的进行枚举,以达到遍历所有数据库服务的目的,该函数的原型如下:NET_API_STATUS NetServerEnum( _In_opt_ LPCWSTR servername, _In_ DWORD level, _Out_ LPBYTE *bufptr, _In_ DWORD prefmaxlen, _Out_ LPDWORD entriesread, _Out_ LPDWORD totalentries, _In_ DWORD servertype, _In_opt_ LPCWSTR domain, _Inout_opt_ LPDWORD resume_handle );各个参数的说明如下:servername:这个参数是系统保留的必须为NULLlevel:参数用于指明返回参数的结构体的版本,主要有100和101两个值,分别对应SERVER_INFO_100和SERVER_INFO_100;bufptr:是一个返回参数,系统在这个指针所指向的区域中填充一个SERVER_INFO_100或SERVER_INFO_100的结构体,具体使用哪一种由上一个参数指定;prefmaxlen:返回值的最大长度,以字节为单位,一般我们设置为MAX_PREFREED_LENGHT,这个参数表明,具体需要多大的缓冲由函数指定entriesread:由函数返回,表示当前获取的枚举的数量totalentries:由函数返回,表示当前机器上所有的服务的总数servertype:获取的服务的类型;(具体类型请参阅MSDN,我们这里主要用的是SV_TYPE_SQLSERVER获取数据库服务)domain:一个常量字符串,用于指定要返回服务器列表域的名称,如果这个值为NULL则域名是隐含的resume_handle:保留参数,该参数必须为NULL;以下是实现的代码,大部分是从MSDN上Copy下来的,只是修改了小部分代码:#include "stdafx.h" #include <iostream> #include <windows.h> #include <lm.h> using namespace std; #pragma comment(lib, "netapi32.lib") int _tmain(int argc, _TCHAR* argv[]) { LPSERVER_INFO_101 pBuf = NULL; LPSERVER_INFO_101 pTmpBuf; DWORD dwLevel = 101; DWORD dwPrefMaxLen = MAX_PREFERRED_LENGTH; DWORD dwEntriesRead = 0; DWORD dwTotalEntries = 0; DWORD dwTotalCount = 0; DWORD dwServerType = SV_TYPE_SQLSERVER; // SQL SERVER DWORD dwResumeHandle = 0; NET_API_STATUS nStatus; LPWSTR pszServerName = NULL; LPWSTR pszDomainName = NULL; DWORD i; nStatus = NetServerEnum(pszServerName, dwLevel, (LPBYTE *)&pBuf, dwPrefMaxLen, &dwEntriesRead, &dwTotalEntries, dwServerType, pszDomainName, &dwResumeHandle); if ((nStatus == NERR_Success) || (nStatus == ERROR_MORE_DATA)) { if ((pTmpBuf = pBuf) != NULL) { for (i = 0; i < dwEntriesRead; i++) //成功则依次输出结果 { assert(pTmpBuf != NULL); printf("\tPlatform: %d\n", pTmpBuf->sv101_platform_id); wprintf(L"\tName: %s\n", pTmpBuf->sv101_name); printf("\tVersion: %d.%d\n",pTmpBuf->sv101_version_major, pTmpBuf->sv101_version_minor); printf("\tType: sql server"); printf("\n"); wprintf(L"\tComment: %s\n\n", pTmpBuf->sv101_comment); pTmpBuf++; dwTotalCount++; } } else { printf("No servers were found\n"); printf("The buffer (bufptr) returned was NULL\n"); printf(" entriesread: %d\n", dwEntriesRead); printf(" totalentries: %d\n", dwEntriesRead); } } else//函数执行错误 { fprintf(stderr, "NetServerEnum failed with error: %d\n", nStatus); } if (pBuf != NULL) { NetApiBufferFree(pBuf);//释放内存空间 } }遍历数据库中服务器中所有数据库:通过上一步我们可以遍历局域网中所有数据库服务,这个时候我们更进一步来遍历数据库服务中所有的数据库,实现这个功能没有什么特殊的函数,主要是对系统表的应用,我们用的是系统表master.sys.database,该表中记录了服务中所有的数据库,通过ADO的方式来进行操作://初始化COM组件库 CoInitialize(NULL); _ConnectionPtr pConnect; HRESULT hErr; try { //创建Connection对象 hErr = pConnect.CreateInstance("ADODB.Connection"); if (SUCCEEDED(hErr)) { pConnect->Open("Driver={SQL Server};Server=LIUHAO-PC;Uid = sa;Pw = 1234", "", "", adModeUnknown); } } catch (_com_error &e) { cout << e.Description() << endl; } try { //创建RECORD, COMMAND对象 _RecordsetPtr pRecord; pRecord.CreateInstance("ADODB.Recordset"); _CommandPtr pCommand; pCommand.CreateInstance("ADODB.Command"); //遍历服务器中所有数据库 cout << "服务器中所有数据库:" << endl; pRecord->Open("select * from master.sys.databases", pConnect.GetInterfacePtr(), adOpenStatic, adLockOptimistic, adCmdText); _variant_t vName; while (!pRecord->adoEOF) { vName = pRecord->GetCollect(_variant_t("name")); wcout << (TCHAR*)_bstr_t(vName) << endl; pRecord->MoveNext(); } pRecord->Close(); } catch { cout << e.Description() << endl; }遍历某个数据库中所有的表:该操作也是使用系统表的内容,代码如下://遍历test数据库中所有表 cout << "数据库中所有表:" << endl; pRecord->Open("use test SELECT name FROM sys.sysobjects WHERE type='U' ORDER BY name", pConnect.GetInterfacePtr(), adOpenStatic, adLockOptimistic, adCmdText); while (!pRecord->adoEOF) { vName = pRecord->GetCollect(_variant_t("name")); wcout << (TCHAR*)_bstr_t(vName) << endl; pRecord->MoveNext(); } pRecord->Close();sql语句中前面的"use test"表示在test数据库中查找表后面的“type = 'U' ”表示我们遍历的是用户表此外type 还可以是一下值:U = 用户表V = 视图,TF = 表函数,P = 存储过程,L = 日志等遍历某个表中的所有字段://显示表中所有的字段名 cout << "test表中所有字段:" << endl; FieldsPtr fields;//用于保存字段信息 pRecord->Open("SELECT * FROM users", pConnect.GetInterfacePtr(), adOpenStatic, adLockOptimistic, adCmdText); fields = pRecord->GetFields();//获取字段信息 long nCount = fields->GetCount();//获取遍历到的字段总数 for (int i = 0; i < nCount; i++) {//获取到的字段信息是放到field对象的item结构体数组中,利用GetItem可以获取该结构体数组中的某一项,利用GetName函数可以获取结构体中存储的字段名 bstr_t bstrName = (fields->GetItem(_variant_t((long)i)))->GetName(); TCHAR *pText = static_cast<TCHAR*>(bstrName);//将字段名转化为字符串输出 wcout << pText << endl; }利用以上所有代码,用户可以根据获取到局域网中所有SQL SERVER数据库服务器,遍历其中的所有数据库,根据获取到的数据库获取数据库中所有表,进到某一个表中遍历所有字段,有了字段用户就可以通过SQL语句操作某个表或者其中的某个字段了
2015年09月03日
5 阅读
0 评论
0 点赞
2015-08-21
C++继承
在封装的过程中,我们发现有很多地方有问题,比如我们在封装Windows API 的过程中,每个窗口都有各自的消息处理,而我们封装时不同的窗口要针对不同的消息而编写不同的消息处理函数,不可能所有窗口对于某些消息都进行相同的处理,所以在面向对象的程序设计中,提供了一种新的方式——继承与派生;在c++中将继承的一方称作派生类或者子类,将被继承的一方叫做基类或者父类继承的基本格式如下(CB 继承CA):class CA { public: CA(); ~CA(); } class CB : public CA { public: CB(); ~CB(); }派生类中前面相应大小空间的内存保存的是基类的内容,而后面的部分保存的是派生类的内容,这样派生类就可以拥有基类的所有成员,而不必重写代码达到了代码重用的目的。在设计中一般将类的共性提取出来作为基类,而将不同的部分从基类派生,作为每个类的特性,对于共性的内容我们只需要在基类中编写,而在派生类中直接使用。下面我们来探讨一下,基类与派生类中构造与析构的调用关系,通过写下面一个简单的小例子:class CA { public: CA(){ cout <<"CA()"<<endl; } ~CA(){ cout <<"~CA()" << endl; } }; class CB : public CA { public: CB(){ cout <<"CB()" << endl; } ~CB(){ cout <<"~CB()" << endl; } }; int _tmain(int argc, _TCHAR* argv[]) { CB objB; return 0; }最终的结果是先调用基类的构造函数在调用派生类的构造函数,而对于析构的调用顺序正好相反,先调用派生类在调用基类:对于继承来说有三种:共有继承、私有继承以及保护继承,继承的方式不同,派生类对于基类的各种不同属性之间成员的访问权限不同,下面再给出一个表格用以说明这个问题:通过这个表我们可以总结出一下几点:1)私有成员在任何情况下都不能被派生类访问;2)公有继承下其他基类成员在派生类中的访问属性不变;3)私有继承下其他基类成员在派生类中全部变为私有;4)保护继承下其他类成员在派生类中全部变为保护属性;从这个表中我们可以看出,私有继承与保护继承对于基类的访问属性完全相同,那么它们有何区别呢?保护成员的访问情况与私有相同,即类的保护成员在类内可以访问在类外不能访问,它们二者的区别在这个表中确实没有体现出来,主要的区别可以在下一层的继承中体现比如有三个类继承关系为CC->CB->CA,继承类型分别为,我们知道基类的非私有成员在保护继承下公有的变为保护,保护的仍然为保护,而私有继承则是将所有都变为私有,他们之间如果都是保护继承的方式,那么CA中的其他成员在CB中都变为保护类型那么在CC中仍然能够访问到CA的成员;当他们之间都是以私有继承的方式,那么CA中的成员在CB中都为私有,在CC中就不能访问CA中的成员,所以在一般情况下,我们将基类的数据成员声明为保护类型,这样既起到了封装的作用,又方便派生类的访问;
2015年08月21日
5 阅读
0 评论
0 点赞
2015-08-05
菜单的使用
# Windows菜单的基本知识:1)顶级菜单:紧贴在标题栏下面的菜单称为顶级菜单,也可以叫做程序的主菜单;2)弹出式菜单:一般在顶级菜单上都有很多菜单项,单击这些菜单项时会弹出一个下拉式的菜单项,我们点击的这个菜单称为弹出式菜单3)菜单项:每一个可选菜单项被赋予一个唯一的ID,当用户单击某个菜单项时Windows会将该菜单项的ID发送给父窗口,父窗口通过WM_COMMAND消息处理菜单的单击消息,但是弹出式菜单没有ID,WM_COMMAND消息也不处理弹出式菜单的点击信息4)菜单加速键:主要是多个键的组合,当同时按下这些键的时候相当于点击了菜单的某个菜单项5)菜单项一般具有“可用”(Enabled)、“不可用”(disabled)、“变灰”(gray)几种选项,其中变灰选项将菜单项变成不可用的同时也会将菜单项变成灰色,所以当我们需要禁用某个菜单项的时候最好将它变灰,以便提示用户;6)菜单句柄:每一种菜单都有一个菜单句柄,包括弹出式菜单的菜单项,顶级菜单,弹出式菜单;菜单的创建:Windows中菜单有两种方式,一种是通过资源的方式通过可视化或者编写rc文件来创建一个菜单资源,并在代码中显示的加载,另一种是通过调用CreateMenu、AppendMenu、InsertMenu等函数创建菜单并插入相应的菜单项,下面对这两种方式一一进行说明:1)采用rc文件的方式:可以在visual studio中利用可视化的方式编辑菜单,在这里就不在说明,而需要手工编写rc文件请参考我的另外一篇博文当我们编辑好了rc文件之后有三种方法添加菜单:通过在创建窗口类的时候在lpszMenuName项的后面添加一个用于标示菜单的字符串,若菜单使用的是ID号作为标示那么可以使用宏MAKEINTRESOURCE;在函数CreateWindow或者CreateWindowEx中的相应参数中填入菜单句柄,为了获取这个句柄需要提前使用LoadMenu函数加载菜单,这个函数的功能是将资源文件中的菜单加载到内存,并返回一个菜单句柄,函数的原型如下:HMENU LoadMenu( HINSTANCE hInstance, // 当前应用程序的实例句柄 LPCTSTR lpMenuName // 菜单唯一标示,可以是字符串或者用MAKEINTRESOURCE转化而来的字符串 );第三种方式是先通过LoadMenu函数获取菜单句柄后在窗口创建后通过SetMenu函数设置菜单,该函数用于为指定窗口加载一个顶级菜单、该函数原型如下:BOOL SetMenu( HWND hWnd, // 需加载菜单的窗口句柄 HMENU hMenu // 菜单句柄 );各个方式的源代码如下:WNDCLASS wd = {0}; wd.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wd.hCursor = LoadCursor(NULL, IDC_ARROW); wd.hIcon = LoadIcon(NULL, IDI_APPLICATION); wd.hInstance = hInstance; wd.lpfnWndProc = WindowProc; wd.lpszClassName = "MenuClass"; //第一种方式 //wd.lpszMenuName = MAKEINTRESOURCE(IDM_MENU); wd.style = CS_HREDRAW | CS_VREDRAW; HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDM_MENU)); //加载加速键 HACCEL hAccelerator = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDA_MAIN)); if (!RegisterClass(&wd)) { int nErr = GetLastError(); return nErr; } //第二种方式 //HWND hWnd = CreateWindow("MenuClass", "Menu", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, hMenu, hInstance, NULL); //第三种方式 HWND hWnd = CreateWindow("MenuClass", "Menu", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); SetMenu(hWnd, hMenu);如果采用函数动态创建的方式,需要如下几个步骤:1)通过函数CreateMenu()创建一个顶级菜单;2)通过CreateMenu()创建一个弹出式菜单;3)利用AppendMenu()或者InsertMenu()向弹出式菜单中插入菜单项;4)利用AppendMenu()将弹出式菜单插入到顶级菜单中;5)用SetMenu函数将创建好的菜单加到程序下面分别说明这些函数的功能和用法:CreateMenu()用于创建一个菜单(函数会将菜单初始化为空菜单),并返回一个菜单句柄,函数原型如下:HMENU CreateMenu(VOID) AppendMenu()用于在顶级菜单、弹出式菜单的最后面的菜单项后查入新菜单项,函数原型如下: BOOL AppendMenu( HMENU hMenu, // 菜单项的句柄 UINT uFlags, // 新菜单项的类型,主要使用的是MF_STRING、MF_POUP(弹出式菜单) UINT uIDNewItem, // 新菜单项的ID,如果是弹出式菜单、则使用菜单的句柄 LPCTSTR lpNewItem //该值取决于第二个参数,若为MF_STRING则应该是一个以0结尾的字符串 ); InsterMenu()函数作用与AppendMenu相同,函数原型如下: BOOL InsertMenu( HMENU hMenu, // 菜单项的句柄 UINT uPosition, // 新菜单项的识别方式,主要有两种MF_BYCOMMAND和MF_BYPOSITION,在以后我们取菜单项的句柄或者对菜单项做其他操作,需要辨认时会有一定的作用,主要表明是靠ID号辨别还是靠在菜单中的相对位置(以0为第一个菜单项) UINT uFlags, // 新菜单项的类型,主要使用的是MF_STRING、MF_POUP(弹出式菜单) UINT uIDNewItem, // 新菜单项的ID,如果是弹出式菜单、则使用菜单的句柄 LPCTSTR lpNewItem //该值取决于第三个个参数,若为MF_STRING则应该是一个以0结尾的字符串 );下面是一个使用这种方式的例子#include <Windows.h> LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); #define IDM_FILE 100 #define IDM_ABOUT 200 #define IDM_CLOSE 300 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { WNDCLASS wd = {0}; wd.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wd.hCursor = LoadCursor(NULL, IDC_ARROW); wd.hIcon = LoadIcon(NULL, IDI_APPLICATION); wd.hInstance = hInstance; wd.lpfnWndProc = WindowProc; wd.lpszClassName = "MenuClass"; wd.style = CS_HREDRAW | CS_VREDRAW; if (!RegisterClass(&wd)) { int nErr = GetLastError(); return nErr; } HWND hWnd = CreateWindow("MenuClass", "Menu", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); //创建主菜单 HMENU hMenu = CreateMenu(); //创建弹出式菜单 HMENU hPopup = CreateMenu(); //向弹出式菜单中插入菜单项 AppendMenu(hPopup, MF_STRING, IDM_FILE, TEXT("文件")); AppendMenu(hPopup, MF_STRING, IDM_ABOUT, TEXT("关于")); InsertMenu(hPopup, MF_BYCOMMAND, MF_STRING, IDM_CLOSE, TEXT("关闭")); //将弹出式菜单插入到主菜单中 AppendMenu(hMenu, MF_POPUP,(UINT_PTR)hPopup, TEXT("系统")); SetMenu(hWnd,hMenu); if (NULL == hWnd) { int nErr = GetLastError(); return nErr; } ShowWindow(hWnd, nShowCmd); MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_COMMAND: { if (IDM_ABOUT == LOWORD(wParam)) { MessageBox(hWnd, TEXT("About"), TEXT("TEST"), MB_OK); } } break; case WM_CLOSE: DestroyWindow(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); } return 0; }键菜单的使用:创建一个右键菜单有如下步骤(在WM_RBUTTONDOWN消息下处理):1)创建一个可用的菜单(一般是主菜单);2)根据主菜单获取弹出式菜单的句柄,使用函数GetSubMenu()2)加载菜单项3)获取鼠标点击的位置4)将客户区坐标转化为屏幕坐标(这一步千万别忘了)5)调用TrackPopupMenu函数,该函数用来显示一个快捷菜单,这个函数中需要填入菜单显示的位置,这个位置值为屏幕坐标,这也就是我们为什么需要转化坐标的原因;该函数的原型为:BOOL TrackPopupMenu( HMENU hMenu, // 快捷菜单的句柄 UINT uFlags, // 快捷菜单显示的类型 int x, // int y, //菜单显示点的坐标,根据第二个参数确定如何显示,一般有左对齐(最左边顶点为该坐标)、右对齐(右上角坐标为该坐标)、中间对齐(上边线的中点坐标为该坐标); int nReserved, // 该参数必须给0 HWND hWnd, // 显示快捷菜单的窗口句柄 CONST RECT *prcRect // 该参数被忽略,一般给NNULL );下面是一段例子代码:HMENU hMenu = LoadMenu(GetModuleHandle(NULL), MAKEINTRESOURCE(IDM_MENU)); hMenu = GetSubMenu(hMenu, 0); POINT ptChick = {LOWORD(lParam), HIWORD(lParam)}; ClientToScreen(hWnd, &ptChick); TrackPopupMenu(hMenu, TPM_LEFTALIGN, ptChick.x, ptChick.y, 0, hWnd, NULL);其他菜单操作的函数主要有:GetSystemMenu()获取系统菜单句柄;Deletemenu()从菜单中删除某一菜单项并销毁它RemoveMenu()从菜单中移出某一菜单项但不销毁它InsertMenu()在菜单中插入一个菜单项NodifyMenu()修改一个已存在的菜单项
2015年08月05日
6 阅读
0 评论
0 点赞
1
...
13
14
15