从图书管理系统看mfc基础知识

通过这个简单图书管理系统我温习了不少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的内容:

  1. #include <fstream>

  2. ofstream //文件写操作 内存写入存储设备

  3. ifstream //文件读操作,存储设备读取到内存中

  4. fstream //读写操作,对打开的文件可进行读写操作

1.打开文件

在fstream类中,成员函数open()实现打开文件的操作,从而将数据流和文件进行关联,通过ofstream,ifstream,fstream对象进行对文件的读写操作

函数:open()

  1. void open ( const char * filename,

  2. ios_base::openmode mode = ios_base::in | ios_base::out );

  3.  
  4. void open(const wchar_t *_Filename,

  5. ios_base::openmode mode= ios_base::in | ios_base::out,

  6. int prot = ios_base::_Openprot);

参数: filename   操作文件名

           mode        打开文件的方式

           prot         打开文件的属性                            //基本很少用到,在查看资料时,发现有两种方式

打开文件的方式在ios类(所以流式I/O的基类)中定义,有如下几种方式:

ios::in为输入(读)而打开文件
ios::out为输出(写)而打开文件
ios::ate初始位置:文件尾
ios::app所有输出附加在文件末尾
ios::trunc如果文件已存在则先删除该文件
ios::binary二进制方式

这些方式是能够进行组合使用的,以“或”运算(“|”)的方式:例如

  1. ofstream out;

  2. 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);

 

至此,这个软件就完成的差不多了,具体代码见下方链接:

https://download.csdn.net/download/blake321/10739840

  • 3
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值