通过这个简单图书管理系统我温习了不少mfc即c++的基础知识,收获还是很多的。现在把其中涉及到的一些基础知识都记录下来。
1.book类,这里用的是固定长度的char数组来存储图书信息,这里有些浪费,不过好处是容易读取和修改。
class CBook
{
public:
CBook(){};
CBook(char* cName,char* cIsbn,char* cPrice,char* cAuthor);
//~CBook();
char* GetName();
void SetName(char* cName);
char* GetIsbn();
void SetIsbn(char* cIsbn);
char* GetPrice();
void SetPrice(char* cPrice);
char* GetAuthor();
void SetAuthor(char* cAuthor);
void WriteData();
void DeleteData(int iCount);
void GetBookFromFile(int iCount);
protected:
char m_cName[NUM1];
char m_cIsbn[NUM1];
char m_cPrice[NUM2];
char m_cAuthor[NUM2];
};
先是定义了book类,里面包含了,书名,isbn号,作者和价钱几个成员变量,另外还定义了构造函数和析构函数,以及成员函数。具体的实现方法在book.cpp里实现。
2.接着是登录界面的设计,以前用java也写过登录界面,但是和mfc还是不太一样的,在这里遇到的问题是,输入登录名后直接回车就进入界面了,有些尴尬,另外在输入完密码后如何按回车进入登录操作,在这里费了一些时间,后面找到解决方法,就是重载PreTranslateMessage函数,这个函数是在进入消息循环之前的拦截消息的函数,如其名,在转换消息之前拦截,
BOOL BookManagerLogin::PreTranslateMessage(MSG* pMsg)
{
if (pMsg->message == WM_KEYDOWN)
{
switch (pMsg->wParam)
{
case VK_RETURN:
//按下回车,执行登陆.....
OnBnClickedButtonLogin();
break;
default:
break;
}
}
//return CDialog::PreTranslateMessage(pMsg);//rreturn 1,则不响应这个消息
return false;
}
在这里拦截回车按键VK-wParam,就可以解决上面的两个问题
3.另外还有一个是在两个界面之间的切换,一开始的时候,在登录成功之后,登录界面并不会消失,
MSDN是这样说明其返回值的
If successful, the value of the nRetCode parameter specified in the call toEndDialog; otherwise, -1.
如果操作成功,其返回值为由EndDialog指定的nRetCode的值,而此参数nRetCode的含义为关闭对话框所采用的方式也就是说,在关闭此模态对话框时,其返回值为关闭对话框时所采用的方式 因此它只在对话框关闭时才返回相关参数值
默认对话框关闭方式有2种:OnOK(); OnCancel()
当使用OnOK()函数关闭对话框时,返回值为IDOK
当使用OnCancel()函数关闭对话框时,返回值为IDCANCEL返回值与ID无关
所以我在验证登录账号和密码的处理函数中加入了CDialogEx::OnOK();或者是this->OnOk()都可以,关闭之前的对话框,
4.接着就是主界面的设计
整体来说界面做的很丑,很简单,不过主要是为了验证功能,也就无所谓了,这里面还是遇到了不少的难题的。这里说明一下,这里没有用到数据库,做的都是本地存储。
(1)图书信息存储,这里用的是fstream,ofstream,ifstream而没有用到mfc专门封装的CFile和CStdioFile,
这里主要是讨论fstream的内容:
-
#include <fstream>
-
ofstream //文件写操作 内存写入存储设备
-
ifstream //文件读操作,存储设备读取到内存中
-
fstream //读写操作,对打开的文件可进行读写操作
1.打开文件
在fstream类中,成员函数open()实现打开文件的操作,从而将数据流和文件进行关联,通过ofstream,ifstream,fstream对象进行对文件的读写操作
函数:open()
-
void open ( const char * filename,
-
ios_base::openmode mode = ios_base::in | ios_base::out );
-
void open(const wchar_t *_Filename,
-
ios_base::openmode mode= ios_base::in | ios_base::out,
-
int prot = ios_base::_Openprot);
参数: filename 操作文件名
mode 打开文件的方式
prot 打开文件的属性 //基本很少用到,在查看资料时,发现有两种方式
打开文件的方式在ios类(所以流式I/O的基类)中定义,有如下几种方式:
ios::in | 为输入(读)而打开文件 |
ios::out | 为输出(写)而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 所有输出附加在文件末尾 |
ios::trunc | 如果文件已存在则先删除该文件 |
ios::binary | 二进制方式 |
这些方式是能够进行组合使用的,以“或”运算(“|”)的方式:例如
-
ofstream out;
-
out.open("Hello.txt", ios::in|ios::out|ios::binary) //根据自己需要进行适当的选取
打开文件的属性同样在ios类中也有定义:
0 | 普通文件,打开操作 |
1 | 只读文件 |
2 | 隐含文件 |
4 | 系统文件 |
对于文件的属性也可以使用“或”运算和“+”进行组合使用,这里就不做说明了。
ofstream流,以ios::app打开(或者“ios::app|ios::out”),如果没有文件,那么生成空文件;如果有文件,那么在文件尾追加。
以ios::app|ios::in打开,不管有没有文件,都是失败。
以ios::ate打开(或者”ios::ate|ios::out”),如果没有文件,那么生成空文件;如果有文件,那么清空该文件
以ios::ate|ios::out打开,如果没有文件,那么打开失败;如果有文件,那么定位到文件尾,并可以写文件,但是不能读文件
ifstream流,以ios::app打开(“ios::app|ios::out”),不管有没有文件,打开都是失败。
以ios::ate打开(“ios::ate|ios::out”),如果没有文件,打开失败
如果有文件,打开成功,并定位到文件尾,但是不能写文件
fstream流,默认是ios::in,所以如果没有文件,ios::app和ios::ate都是失败,
以ios::app|ios::out,如果没有文件则创建文件,如果有文件,则在文件尾追加
以ios::ate|ios::out打开,如果没有文件则创建文件,如果有,则清空文件。
以ios::ate|ios::out|ios::in打开,如果没有文件,则打开失败,有文件则定位到文件尾
可见:ios::app不能用来打开输入流,即不能和ios::in相配合
而ios::ate可以和ios::in配合,此时定位到文件尾;如果没有ios::in相配合而只是同ios::out配合,那么将清空原文件
可以在《C++ 输入输出流及本地化》1.4.2中找到更详细的描述:(大意)以ios::app方式打开文件,即使修改文件指针,也只能输出到文件尾。实际上以ios::app打开的文件的写入,和文件指针五关。
奇怪的是:《C++ 输入输出流及本地化》和《C++编程思想》都说以ios::ate打开的文件,文件指针都会定位到文件尾且不清空文件,但是我发现ios::ate如果不和ios::in配合的话,将清空原文件。
总之一句,如果要创建文件,用ios::app|ios::out,没有文件的时候回创建文件,有文件的时候在后面追加,但是不可以用seek函数,无论怎样都是在文件后面添加。
要修改文件的话,用ios::out,可用使用seek定位到你想要的位置然后修改。
ofstream:打开文件不存在,默认会创建这个文件。(除非指定ios::nocreate)
ifstream:打开文件存在与否,默认不会创建在个文件.
fstream:打开文件不存在,默认会创建这个文件。(除非只是指定ios::in 或者指定ios::nocreate)
二进制模式和Text模式的区别。
二进制模式:对于一行的结尾我们必须输入'\r\n',才能表示回车换行的效果。
Text模式:'\r'回车的工作是自动完成的,我们只需要写入'\n'即可。在使用Text模式时从外部读入文件时,'\r\n'会被翻译成'\n',写入文件时,我们对于回车换行只需提供'\n',即可,'\r\n'会被写入到文件中
6.图书显示
这里用了分页显示,每页显示20本图书,而且点击图书时右上角会出现图书的图片,如果没有图片的话会显示暂无此书,这里面涉及到一些细节的处理,如图片显示,和右键菜单的功能。首先是图片显示的问题,导入图片时一定要注意图片的大小,最好是先在其他软件上处理之后再导入,
添加一个Picture Control控件,在图片控件的属性页中有一个Type属性,Type属性下拉列表中有8中类型,下面分别介绍下:
Frame:显示一个无填充的矩形框,边框颜色可以通过Color属性的下拉列表设定
Etched Horz:显示一条横分割线
Etched Vert:显示一条竖分割线
Rectangle:显示一个填充的矩形框,矩形颜色可通过Color属性的下拉列表设定
Icon:显示一个图标(Icon),图标通过Image下拉列表来设置图标资源ID
Bitmap:显示一个位图(Bitmap),位图通过Iamge下拉列表来设置位图资源ID
Enhanced Metafile:显示一个加强的元数据文件(Metafile)
Owner Draw:自绘
这里我们将类型设置为frame,然后自己重新绘制显示,具体如下
CBitmap bmp;
if (bmp.LoadBitmap(IDB_BITMAP4))
{//将bitmap读到bmp中
BITMAP bmpInfo;
bmp.GetBitmap(&bmpInfo);
CDC dcMemory;
CDC* pDC = GetDlgItem(IDC_PICTURE)->GetDC();//获得画布
dcMemory.CreateCompatibleDC(pDC);
CBitmap* pOldBitmap = dcMemory.SelectObject(&bmp);
CRect rect;
GetDlgItem(IDC_PICTURE)->GetClientRect(&rect);
//int nX = rect.left + (rect.Width() - bmpInfo.bmWidth) / 2;
//int nY = rect.top + (rect.Height() - bmpInfo.bmHeight) / 2;
//pDC->BitBlt(0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, &dcMemory, 0, 0, SRCCOPY);
pDC->SetStretchBltMode(COLORONCOLOR);
pDC->StretchBlt(0, 0, rect.Width(), rect.Height(), &dcMemory, 0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, SRCCOPY);
dcMemory.SelectObject(pOldBitmap);
ReleaseDC(pDC);
另外一个是菜单的显示,先要新建一个menu,注意,如果是作为主菜单那样子的话,直接在oninitdialog里面加入
CMenu m_Menu;
m_Menu.LoadMenu(IDR_MENU2);//IDR_MENU2是你菜单的ID
SetMenu(&m_Menu);
就可以像其他软件那样在左上角显示菜单了,
另外一种是右键弹出菜单,这里用到了TrackPopupMenu,注意这里弹出的菜单一定要是二级菜单,要不然显示不了,
关于这个函数 msdn是这样描述的:
Displays a shortcut menu at the specified location and tracks the selection of items on the menu. The shortcut menu can appear anywhere on the screen.
看到了吧,是一个shortcut菜单,更重要的是可以展示在屏幕的任何位置,只要你愿意。这时候,你应该迫不及待看看函数语法了吧:
BOOL WINAPI TrackPopupMenu(
_In_ HMENU hMenu,
_In_ UINT uFlags,
_In_ int x,
_In_ int y,
_In_ int nReserved,
_In_ HWND hWnd,
_In_opt_ const RECT *prcRect
);
1
2
3
4
5
6
7
8
9
各个参数又是什么意思:
hMenu:被显示的快捷菜单的句柄。此句柄可为调用CreatePopupMenu创建的新快捷菜单的句柄,也可以为调用GetSubMenu取得的与一个已存在菜单项相联系的子菜单的句柄。
uFlags:一种指定功能选项的位标志。用下列标志位之一来确定函数如何水平放置快捷菜单:
TPM_CENTERALIGN:若设置此标志,函数将按参数x指定的坐标水平居中放置快捷菜单。
TPM_LEFTALIGN:若设置此标志,函数使快捷菜单的左边界与由参数X指定的坐标对齐。
TPM_RIGHTALIGN:若设置此标志,函数使快捷菜单的右边界与由参数X指定的坐标对齐。
用下列标志位之一来确定函数如何垂直放置快捷菜单:
TPM_BOTTOMALIGN:若设置此标志,函数使快捷菜单的下边界与由参数y指定的坐标对齐。
TPM_TOPALIGN:若设置此标志,函数使快捷菜单的上边界与由参数y指定的坐标对齐。
TPM_VCENTERALIGN;若设置此标志,函数将按参数y指定的坐标垂直居中放置快捷菜单
用下列标志位之一来确定在菜单没有父窗口的情况下用户的选择:
TPM_NONOTIFY:若设置此标志,当用户单击菜单项时函数不发送通知消息。
TPM_RETURNCMD;若设置此标志;函数将用户所选菜单项的标识符返回到返回值里。
(注意:当TrackPopupMenu的返回值大于0,就说明用户从弹出菜单中选择了一个菜单。当不设置TPM_NONOTIFY和TPM_RETURNCMD时,程序给自己发送了一个WM_COMMAND消息,以返回的ID号为参数wParam的值)
用下列标志位之一来确定在快捷菜单跟踪哪一个鼠标键:
TPM_LEFTBUTTON:若设置此标志,用户只能用鼠标左键选择菜单项。
TPM_RIGHTBUTTON:若设置此标志,用户能用鼠标右键选择菜单项。
X:在屏幕坐标下,快捷菜单的水平位置。
Y:在屏幕坐标下,快捷菜单的垂直位置。
NReserved:保留值,必须为零。
HWnd:拥有快捷菜单的窗口的句柄。此窗口接收来自菜单的所有消息。函数返回前,此窗口不接受来自菜单的WM_COMMAND消息。
如果在参数uFlags里指定了TPM_NONOTIFY值,此函数不向hWnd标识的窗口发消息。 但必须给hWnd里传一个窗口句柄,可以是应用程序里的任一个窗口句柄。
PrcRect:未用。
返回值:如果在参数uFlags里指定了TPM_RETURNCMD值,则返回值是用户选择的菜单项的标识符。如果用户未作选择就取消了菜单或发生了错误,则退回值是零。如果没在参数uFlags里指定TPM_RETURNCMD值,若函数调用成功,返回非零值,若函数调用失败,返回零。
还是同样的原则,无需记住每个参数如何设置,用的时候会查阅就可以了。
这里要特别注意的是参数X,和参数Y,为了让你信服,看下msdn如何描述这两个参数的吧:
x :The horizontal location of the shortcut menu, in screen coordinates.
y :The vertical location of the shortcut menu, in screen coordinates.
看到了吗,是相对于屏幕坐标系的,或可以说是相对screen的。
那么问题就来了,在我们开发的程序中,几乎使用的都是相对于client的坐标系。
所以,特别需要注意的是,如果你的整个程序都使用的是相对于client,那么再使用TrackPopupMenu函数时,需要进行坐标转换。需要使用的函数就是ClientToScreen()。
具体如下:
CMenu menu;
menu.LoadMenu(IDR_MENU1);
CPoint point;
GetCursorPos(&point);
CMenu* pSubMenu = menu.GetSubMenu(0);
pSubMenu->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, this);
至此,这个软件就完成的差不多了,具体代码见下方链接: