MFC案例:基于对话框的简易阅读器

一、功能目标:

1.阅读txt文件

2.阅读时可以调整字体及字的大小

3.打开曾经阅读过的文件时,能够自动从上次阅读结束的位置开始显示,也就是能够保存和再次使用阅读信息。

4.对于利用剪贴板粘贴来的文字能够存储成txt文件保存。

5.显示界面可以调整大小。

二、设计步骤

  1. 建立一个基于对话框的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 //文本编辑框接收数组最大长度

  1. 阅读信息存储文件

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 选择菜单退出

见后。

  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();//创建模态对话框
}

程序完成后的界面截图如下:

相关推荐
獨枭4 小时前
MFC 自定义编辑框:打造灵活的数据输入控件
c++·mfc
金士顿10 小时前
在 MFC 中如何验证 C# 强命名库
c++·c#·mfc
世_生2 天前
【MFC】vs2019中使用sqlite3完成学生管理系统
c++·sqlite·mfc
XZHOUMIN2 天前
MFC中如何创建一个非模态对话框
c++·mfc
獨枭2 天前
MFC 自定义静态文本控件:增强型标签控件
c++·microsoft·mfc
越甲八千3 天前
非MFC工程实现消息映射
c++·mfc
神仙别闹3 天前
基于MFC绘制门电路
c++·mfc
界面开发小八哥5 天前
MFC扩展库BCGControlBar Pro v36.0新版亮点:黑色主题中的自动反转图标
c++·ui·mfc·bcg·界面控件·ui开发
DOT小文哥6 天前
百问FB显示开发图像处理 - PNG图像处理
图像处理·人工智能·mfc·lcd·png