一、功能目标:
1.阅读txt文件
2.阅读时可以调整字体及字的大小
3.打开曾经阅读过的文件时,能够自动从上次阅读结束的位置开始显示,也就是能够保存和再次使用阅读信息。
4.对于利用剪贴板粘贴来的文字能够存储成txt文件保存。
5.显示界面可以调整大小。
二、设计步骤
- 建立一个基于对话框的MFC项目,项目名:simpleReader
1.1 对话框左上角图标Icon改为自己的图标
步骤:右键资源视图Icon->选择添加资源->导入自己的图标->回到资源视图->删除ID为IDR_MAINFRAME的Icon->右键新导入Icon ID->选择属性,ID号改为IDR_MAINFRAME;
2.从工具箱中向对话框内拖入一个文本编辑框(Edit Control)。
2.1 让编辑框充满对话框,这个编辑框是显示阅读文字的界面。
2.2 右键编辑框,点击属性,将编辑框的ID改为IDC_TEXTEDIT。同时,对编辑框的其它属性进行设置:Auto VScroll置为True,垂直滚动置为True,多行置为True,想要返回置为True。
2.3 右键文本编辑框,选择添加变量,变量名:m_textEdit,注意是控件类型。
3.在CsimpleReaderDlg类中以public方式声明需要的变量
具体位置可在simpleReaderDlg.h文件最后(在自动生成的变量m_textEdit的后边)。
3.1 声明 CString filePathName; //阅读文件的路径及文件名
3.2 声明自定义类readFileInfo,用于记录阅读文件的相关信息
typedef class
{
public:
CString fileName; //已阅读文件名(含路径)
int startPos; //阅读位置
int endPos; //阅读位置
} readFileInfo;
//说明:阅读位置使用阅读结束时光标所在位置(字符数)表示。这个位置实质是光标选黑的区域,因此表示其位置需有两个数表示。通过编辑框对象的成员函数GetSel()函数获得,该函数的返回值是DWORD类型,通过LOWORD、HIWORD两个有参宏将返回值解析得到两个阅读位置。当没有选择区域只是光标停在某位置时,这两个值相等。
3.3 #define READNUM 4 //定义记录阅读文件个数的常量(可更改),打开文件个数超过READNUM时,最早打开的文件会被顶掉。
3.4 声明readFileInfo数组:
readFileInfo readfileinfo[READNUM]; //用于记录最多READNUM个文件阅读信息的自定义类数组
3.5 #define MAXTCHAR 10000 //文本编辑框接收数组最大长度
- 阅读信息存储文件
4.1 前面声明了自定义数组readfileinfo[READNUM]用来处理阅读信息,这些信息如不保存进文件,程序关闭时信息会丢失,因此有必要建立一个文件进行保存,文件名:readInfo.txt,保存路径就在项目目录中。
4.2 在simpleReaderDlg.h文件中,类内以public方式声明阅读信息保存函数:readInfoSave(); 在文件simpleReaderDlg.cpp中实现这个函数。代码如下:
cpp
void CsimpleReaderDlg::readInfoSave()
{
CFile file;
file.Open(L"readInfo.txt", CFile::modeCreate | CFile::modeWrite);
for (int i = 0; i < READNUM; i++)
{
TCHAR a[256] = { 0 };
wcscpy_s(a, readfileinfo[i].fileName);
TCHAR b[64] = { 0 };
TCHAR c[64] = { 0 };
wsprintf(b, L"%i\0", readfileinfo[i].startPos);
wsprintf(c, L"%i\0", readfileinfo[i].endPos);
file.Write(a, 256);
file.Write(b, 64);
file.Write(c, 64);
}
file.Close();
}
4.3 在simpleReaderDlg.h文件中,类内以public方式声明阅读信息读取函数:readInfoRead(); 并在simpleReaderDlg.cpp中实现这个函数。代码如下:
cpp
void CsimpleReaderDlg::readInfoRead()
{
CFile file;
if (file.Open(L"readInfo.txt", CFile::modeRead))//打开成功
{
TCHAR a[258] = { 0 };
TCHAR b[64] = { 0 };
TCHAR c[64] = { 0 };
for (int i = 0; i < READNUM; i++)
{
file.Read(a, 256);
file.Read(b, 64);
file.Read(c, 64);
readfileinfo[i].fileName = a;
readfileinfo[i].startPos = _wtoi(b);
readfileinfo[i].endPos = _wtoi(c);
}
file.Close();
} else
{
MessageBox(L"调试");
return;
}
}
4.4 在simpleReaderDlg.cpp中,找到OnInitDialog()函数,在里面添加一些代码。目的是,当程序第一次运行,保存阅读文件信息的文件readInfo.txt不存在时,添加一个记录内容为空的文件保存成readInfo.txt。具体在TODO:后面添加下列代码:
cpp
CFile fileCheck;
if (!fileCheck.Open(L"readInfo.txt", CFile::modeRead))
{
fileCheck.Open(L"readInfo.txt", CFile::modeCreate |
CFile::modeWrite); //如不存在,新建一个
for (int i = 0; i < READNUM; i++) //填入空数据
{
readfileinfo[i].fileName = L"";
readfileinfo[i].startPos = 0;
readfileinfo[i].endPos = 0;
}
fileCheck.Close();
readInfoSave(); //保存文件
}
else fileCheck.Close(); //如存在,结束检查
readInfoRead(); //读入数据存进数组readfileinfo[READNUM]
5.退出函数
5.1 对话框右上角"X"单击响应退出函数
右键对话框->属性->单击消息列->选中消息 WM_CLOSE->点击<Add>OnClose。这样,在simpleReaderDlg.cpp中添加了void CsimpleReaderDlg::OnClose()函数,这个函数是点击对话框右上角"X"的响应函数,这个系统自动生成的函数在函数尾部自动调用了父类关闭函数CDialogEx::OnClose()。我们在此只需处理保存阅读文件信息即可,其余交给CDialogEx::OnClose()处理,代码如下:
cpp
void CsimpleReaderDlg::OnClose()
{
int m_startPos = LOWORD(m_textEdit.GetSel());
int m_endPos = HIWORD(m_textEdit.GetSel());
//m_textEdit.GetSel()的返回值是DWORD类型,其低位、高位有不同含义。
//低位代表选择区域的开始位置、高位代表选择区域的结束位置。将光标
//置于某处不做选择,高低位值相等,用此位置表示阅读位置。
readFileInfo temp;
temp.fileName = filePathName;
temp.startPos = m_startPos;
temp.endPos = m_endPos;
int choice = -1;
//choice用来表示当前阅读文件是否读过以及相关信息在readfileinfo数组
//中的位置,-1表示没有读过,数组中没有它的信息
for (int i = 0; i < READNUM; i++) //确定choice的值
{
if (readfileinfo[i].fileName == filePathName) choice = i;
}
if (choice == -1) //当前阅读文件信息不在readfileinfo数组中
{
for (int i = READNUM - 1; i > 0; i--) //将数组第0位置腾出
{
readfileinfo[i] = readfileinfo[i - 1];
}
}
if (choice > 0 && choice < READNUM)
/*当前阅读文件信息在readfileinfo数组1~READNUM-1之间,需将其移动
到数组第0位置,实际是空出0位,后边重新写入*/
{
for (int i = choice; i > 0; i--)
{
readfileinfo[i] = readfileinfo[i - 1];
}
}
//choice==0的情况无需调整,故不考虑
readfileinfo[0] = temp; //将当前阅读文件信息存入数组第0位置
readInfoSave(); //将readfileinfo数组存入文件readInfo.txt
5.1 选择菜单退出
见后。
- 在对话框上添加菜单栏
步骤:解决方案资源管理器->右键项目名simpleReader->添加->资源->选择Meni->新建->进入菜单编辑界面->顶行写上"文件(F)"。这时,回到资源视图,点击Menu,可看到菜单(指整个菜单)的ID是IDR_MENU1。右键对话框->属性->菜单->选中这个ID。
6.1 打开文件函数
再回到菜单编辑界面,在"文件(F)"的下方添加菜单"打开(O)",接着,右键"打开(O)"选择属性,将其ID改为ID_OPEN;再次右键"打开(O)",选择"添加事件处理程序"进入事件处理程序设置界面:类列表选择CsimpleReaderDlg,其余按缺省,确定后在simpleReaderDlg.cpp可看到空的函数void CsimpleReaderDlg::OnOpen(),相关代码如下:
cpp
void CsimpleReaderDlg::OnOpen()
{
CFileDialog dlg ( //打开文件对话框
TRUE, NULL, NULL,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
L"All Files(*.TXT)|*.TXT||", AfxGetMainWnd());
CString tempText = _T("");
if (dlg.DoModal() == IDOK)
{
filePathName = dlg.GetPathName();
CFile file(filePathName, CFile::modeRead);
TCHAR* read = new TCHAR[MAXTCHAR];
memset(read, '\0', sizeof(TCHAR) * MAXTCHAR);
file.Read(read, MAXTCHAR);
for (int i = 0;; i++)
{
tempText += read[i];
if (read[i] == '\0')break;
}
file.Close();
m_textEdit.SetWindowText(tempText);
}
int choice = -1; //检查当前打开文件是否已阅读过
for (int i = 0; i < READNUM; i++) //
{
if (readfileinfo[i].fileName == filePathName) choice = i;
}
int a, b;
if (choice == -1)
{
a = 0; b = 0; //如果是未阅读读过的阅读位置设在开始
}
else
{
a = readfileinfo[choice].startPos;
b = readfileinfo[choice].endPos;
}
m_textEdit.SetSel(a,b,0);
}
6.2 从菜单退出函数
回到菜单编辑界面,在"打开(O)"下面填写"退出(Q)",右键"退出"->属性->ID改为ID_QUIT;再右键"退出",选择"添加事件处理程序"进入事件处理程序设置界面:类列表选择CsimpleReaderDlg,其余按缺省,确定后在simpleReaderDlg.cpp可看到空的函数void CsimpleReaderDlg::OnQuit(),有关处理文件退出代码如下,就一句:
cpp
void CsimpleReaderDlg::OnQuit()
{
// TODO: 在此添加命令处理程序代码
OnClose();//这里只需调用点击右上角"X"的响应函数即可
}
6.3 字体调整函数
回到菜单编辑界面,在"文件(F)"右边再在添加一列下拉函数"格式(O)",在"格式"下面填写"字体(F)",右键"字体"->属性->ID改为ID_FONT;再右键"字体",选择"添加事件处理程序"进入事件处理程序设置界面:类列表选择CsimpleReaderDlg,其余按缺省,确定后在simpleReaderDlg.cpp可看到空的函数void CsimpleReaderDlg::OnFont()。在写代码前,在CsimpleReaderDlg.h类内先声明一个CFont对象m_Font,用于保存新选定的字体。具体代码如下:
cpp
void CsimpleReaderDlg::OnFont()
{
// TODO: 在此添加命令处理程序代码
//记录阅读位置
int m_startPos = LOWORD(m_textEdit.GetSel());
int m_endPos = HIWORD(m_textEdit.GetSel());
//设置字体
CFont* tempFont = m_textEdit.GetFont(); //获取编辑框当前字体指针
LOGFONT LogFont; //声明逻辑字体对象
tempFont->GetLogFont(&LogFont); //加载逻辑字体
CFontDialog dlg(&LogFont); //逻辑字体对话框,在此选择字体
if (dlg.DoModal() == IDOK)
{
m_Font.Detach(); //将m_Font与原对象剥离
LOGFONT temp;
dlg.GetCurrentFont(&temp); //获取当前字体信息
m_Font.CreateFontIndirect(&temp); //创建字体
m_textEdit.SetFont(&m_Font); //设置字体
}
//重置阅读位置
m_textEdit.SetSel(m_startPos, m_endPos, 0);
}
7.处理文本框内容变动函数
单击文本编辑框->右键选择属性->点击控件事件->点击EN_CHANGE->选择
<add>OnEnChangeTextedit,在cpp文件中可以看到增加了void CsimpleReaderDlg::OnEnChangeTextedit()这个文件。添加这个文件的目的是当采用Crt_V方式粘贴进阅读内容时,对粘贴的内容予以保存。相关代码如下:
cpp
void CsimpleReaderDlg::OnEnChangeTextedit()
{
// TODO: 如果该控件是 RICHEDIT 控件,它将不
// 发送此通知,除非重写 CDialogEx::OnInitDialog()
// 函数并调用 CRichEditCtrl().SetEventMask(),
// 同时将 ENM_CHANGE 标志"或"运算到掩码中。
// TODO: 在此添加控件通知处理程序代码
CString writeText;
m_textEdit.GetWindowText(writeText);
TCHAR* write = new TCHAR[MAXTCHAR]; //在堆区声明一个一百万字节的字符串数组
memset(write, '\0', sizeof(TCHAR) * MAXTCHAR);
write = writeText.GetBuffer();
CFileDialog dlg( //保存文件对话框
FALSE, NULL, NULL, OFN_HIDEREADONLY |
OFN_OVERWRITEPROMPT,
_T("All Files(*.TXT)|*.TXT||"), AfxGetMainWnd());
if (dlg.DoModal() == IDOK) //判断另存为确认键是否按下
{
filePathName = dlg.GetPathName();
//获取路径及文件名并赋给全局变量,除此处使用外退出时也需使用
if ((filePathName.Right(4) != _T(".TXT")) && (filePathName.Right(4) != _T(".txt")))
{
filePathName += _T(".TXT"); //如无后缀则添加
}
CFile file(filePathName, CFile::modeCreate | CFile::modeWrite);
file.Write(write, writeText.GetLength() * sizeof(write[0]));
file.Close();
}
}
9.处理对话框大小变动函数
选中对话框,右键属性->单击消息->选择WM_SIZE消息->点击<add>OnSize,这样在.cpp文件中,多了一个函数void CsimpleReaderDlg::OnSize(UINT nType, int cx, int cy),在这个函数中TODO:行后边添加:
cpp
CWnd* pEdit = GetDlgItem(IDC_TEXTEDIT);
if (pEdit) pEdit->MoveWindow(0, 0, cx, cy);
10.帮助文件
选择菜单编辑界面,在"格式"右边在添加一列下拉:"帮助(H)"。在"帮助"下边填上"使用说明(N)",右键"使用说明"选择属性,将ID改为ID_NOTE;接着,再右键使用说明,选择添加事件处理程序。这样,在.cpp中生成函数void CsimpleReaderDlg::OnNote()。先不写代码,在资源视图中:右键项目名->添加->资源->Dialog->新建;在对话框界面,将"取消"按钮删除。再拖入两个静态文本框,将其ID改为IDC_NOTE、IDC_NOTE1。
右键对话框,添加MFC类,类名FileHelp,基类CDialogEx,对话框ID使用默认IDD_DIALOG1。然后在 simpleReaderDlg.cpp首部添加:#include "Filehelp.h"。再在解决方案资源管理器中找到HelpFile.cpp,点开,在void HelpFile::DoDataExchange(CDataExchange* pDX)函数的CDialogEx::DoDataExchange(pDX)行下面添加下列代码:
cpp
CFont font,font1;
font.CreateFont(20, 20, 0, 0, FW_NORMAL, FALSE, FALSE,
FALSE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
FIXED_PITCH | FF_MODERN, _T("楷体") );
GetDlgItem(IDC_NOTE)->SetFont(&font);
CString strText;
strText = L"使用方法:";
GetDlgItem(IDC_NOTE)->SetWindowText(strText);
font1.CreateFont(18, 0, 0, 0, FW_NORMAL, FALSE, TRUE,
TRUE, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
FIXED_PITCH | FF_MODERN, _T("楷体"));
strText = L"1.一次最好只打开一个文件,只有关闭程序时阅读位置信息才被保存。";
GetDlgItem(IDC_NOTE1)->SetWindowText(strText);
strText = "2.若调整界面窗口大小可以在打开文件前进行,以免文字跑动。";
GetDlgItem(IDC_NOTE2)->SetWindowText(strText);
strText = "3.阅读期间改变字体、字号等操作对原文件不起作用。";
GetDlgItem(IDC_NOTE3)->SetWindowText(strText);
strText = "4.本简易阅读器的字符集是UTF-16,如打开某些文件显示乱码,可用其它软件打开文件然后Ctr_C、Ctr_V到本阅读器,保存一下再打开。";
GetDlgItem(IDC_NOTE4)->SetWindowText(strText);
前面生成的OnNote()函数的代码如下:
cpp
void CsimpleReaderDlg::OnNote()
{
// TODO: 在此添加命令处理程序代码
HelpFile dlg;
dlg.DoModal();//创建模态对话框
}
程序完成后的界面截图如下: