本文亦是在《【mfc】用对话框的切换实现重新登录》(点击打开链接)的进一步工作,也是对其的进一步改进,上次的登录只是在判断用户输入的用户名与密码是否为admin与123,这次则利用文件的读写实现用户帐号的创建、删除与修改,不再拘泥与admin与123这个帐号,用户可以创建很多帐号,并且赋予其是否有修改帐号的权限,同时,利用theApp全局变量,用户一旦成功,其登录信息则会一直被记录,类似与网页中的Session。虽然现在VC6中的mfc已经过时了,但是仍然具有研究意义,毕竟XP曾经是一个无法超越的时代!
一、基本目标
1、在权限管理中,用户可以对登录帐号进行添加、删除、修改管理,并赋予不同的权限,如果用户的“用户名”、“密码”为空,则认为用户不进行此项的修改。同时,必须要求用户至少保留一个“高级”权限的帐号
2、重新登录之后可以发现,刚才添加的帐号已经生效了,只有“高级”权限的帐号才能进入到“帐号管理”中修改用户信息,而“普通”权限的帐号不允许进入
二、制作过程
“登录”与“重新登录”功能,“控制面板”已经在《【mfc】用对话框的切换实现重新登录》(点击打开链接)中重点说明怎么完成,本章主要介绍一下怎么完成“帐号管理”的功能
1、首先,在资源列表中插入一个对话框
2、删除自带的“确定”与“删除”按钮,把对话框的字体改成宋体9号,因为这个对话框要容纳很多东西、标题栏改成“权限管理”,添加三个静态文本与两个编辑框,怎么添加在【mfc】用对话框分页实现用户登录》(点击打开链接)已经说过,主要是一个关于权限的下拉控件Combobox,如图所示,改好样式
3、值得注意的是,这个下拉列表调好位置与样式之后,注意点其三角形来调好其每一个下拉选项的大小,否则下拉选项太小,根本就看不见。只能通过键盘的上与下来进行选择,这非常猥琐!
4、之后添加三个按钮,按钮可以在其Caption样式中以“&快捷键”的方式来添加快捷键,增加用户体验
5、最后拉出如图的所示的列表控件,并设置好相应的样式,这个控件在《【mfc】学生信息管理,实现List控件节点的增删改查》(点击打开链接)已经重点说过,这里不再赘述,为了使你的程序更加美观,请使用下方的控件排版工具调好控件的位置。
6、如《【mfc】用对话框的切换实现重新登录》(点击打开链接)一样为这个Prior对话框添加类向导,并且命名为CPriorDlg之后,要再为这个对话框添加窗体消息函数
7、从左边的列表中找到窗体初始化事件也就是窗体打开时候做什么与窗体销毁事件,也就是窗体关闭的时候做什么。这个本质上就是点击按钮时候的消息映射函数,只不过一个是点击按钮时候,告诉系统需要做什么,现在是捕获系统在一定时候,做什么
8、点击确定之后,你就可以在ClassView类列表,在CPriorDlg旗下,找到OnInitDialog()这个类,在BOOL CPriorDlg::OnInitDialog()函数中,系统初始化的使用要完成两件事情,(1)初始化控件,(2)从文件中读取用户列表ReadUser(),因此函数如下:
BOOL CPriorDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//设置此窗体在任务栏显示
SetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE, WS_EX_APPWINDOW);
//初始化LIST控件
CListCtrl* plist=(CListCtrl*)GetDlgItem(IDC_LIST1);
plist->InsertColumn(0,"帐号",0,100);
plist->InsertColumn(1,"密码",0,100);
plist->InsertColumn(2,"权限",0,100);
//初始化的时候,必须先列添加完,才调用这个函数,不然List控件连列都没有
ReadUser(plist);
//初始化下拉列表COMBO控件
CComboBox* pcombo=(CComboBox*)GetDlgItem(IDC_COMBO1);
pcombo->AddString("普通");
pcombo->AddString("高级");
//设置下拉列表COMBO控件默认选项为第一个
pcombo->SetCurSel(0);
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
9、由于从文件中读取用户列表是一个单独的功能,也因为其语句比较复杂,没必要杂糅在这个BOOL CPriorDlg::OnInitDialog()函数中,因此,我们再开一个函数叫ReadUser(),可以如下添加属于自己的函数,同样是右击这个CPriorDlg,然后选择添加成员函数:
10、在弹出的对话框中输入函数的名词与返回值,当然,你自己手写也可以,
这个ReadUser函数是把操纵列表控件的指针扔进去,然后则对其进行操作,函数如下:
void CPriorDlg::ReadUser(CListCtrl *plist)
{
//先把原有地清空,刷新一下
plist->DeleteAllItems();
CFile file;
//CFile::shareDenyNone是此文件允许被多个人打开,默认只允许一个线程操作
if(!file.Open("user.dat",CFile::modeRead|CFile::shareDenyNone)){
return;
}
SUSER u;
int i=0;
//读出来的东西必须与结构体的大小相匹配,不匹配,要么读完,要么读取出错
while(file.Read(&u,sizeof(u))==sizeof(u))
{
//整个过程与插入到列的时候非常相似,基本是相同的
plist->InsertItem(i,u.sName);
plist->SetItemText(i,1,u.sPwd);
switch(u.Prior){
case 0:
plist->SetItemText(i,2,"普通");
break;
case 1:
plist->SetItemText(i,2,"高级");
break;
}
}
file.Close();
}
使用CFile类能够轻松地文件进行操作,如果文件读取失败则返回,其中用到一个结构体SUSER,规范化地每次从这个user.dat文件中读取这个结构体大小的东西,然后再把结构体中的内容对程序进行分配。由于这个结构体以后其他函数中、其他类、其他对话框,很多地方都要用到,不妨把这个结构地设成一个关于整个程序的全局变量。
11、找到CxxApp类,其中xx是你正在编辑的工程名,比如我正在编辑的工程叫做Isys,那么就找到CIsysApp,直接双击这个类,也就进入到这个类的头文件CIsysApp.h当中,在里面定义一个如下图所示的结构体SUSER,里面包含两个长度为20的字符串记录用户名与密码,与一个记录权限的整形。并且顺便在下面的public中设置一个m_info,专门用来存登录信息,其后在任何地方中要使用,在其非头文件的地方,也就是可以点击其属下的类来进入其.cpp而不是.h,中加入一句extern CxxApp theApp;就可以,图片中拼错单词,见谅!
12、这样设置之后,回到CPriorDlg中再编译发现没有问题了,下面继续完成CPriorDlg中的OnDestroy函数void CPriorDlg::OnDestroy() ,整个函数主要完成的工作是把此时列表控件的内容写入到user.dat中
//一旦对话框关闭,无论返回任何值,都要执行这个函数
void CPriorDlg::OnDestroy()
{
CDialog::OnDestroy();
CFile file;
SUSER u;
// TODO: Add your message handler code here
//打开当前目录的一个user.dat文件,没有则创建
if(!file.Open("user.dat",CFile::modeCreate|CFile::modeWrite)){
AfxMessageBox("创建/打开文件失败");
return;
}
//与上面一样都是一个遍历的List控件列表的操作
CListCtrl* plist=(CListCtrl*)GetDlgItem(IDC_LIST1);
//如果用户把所有用户都删干净了,我们要重新把admin,123这个默认帐号激活
int nCount=plist->GetItemCount();
if(nCount==0){
//字符数组的操作必须用以下的方法进行,u.sName="admin";是不行的
strcpy(u.sName, "admin");
strcpy(u.sPwd, "123");
u.Prior=1;
file.Write(&u,sizeof(u));
}
else{
for(int i=0;i<nCount;i++){
//写成plist->GetItemText(i,0)则不保存到任何地方,只返回一个获取到的值
plist->GetItemText(i,0,u.sName,sizeof(u.sName));
plist->GetItemText(i,1,u.sPwd,sizeof(u.sPwd));
if(plist->GetItemText(i,2)=="高级")
u.Prior=1;
else
u.Prior=0;
//把结构体的内容存到文件当中
file.Write(&u,sizeof(u));
}
}
//操作完文件则关闭文件,也可以不写,因为函数结束了之后,会析构这个对象
file.Close();
}
这个user.dat直接用记事本打开会发生乱码,但是利用结构体的读写能够毫无障碍地读写:
13、之后回到资源列表中的对话框,双击“添加”、“删除”、“修改”按钮完成如下的消息映射函数一样,由于窗体打开与关闭时候已经涉及文件的操作,这里就没有必要用户边修改边保存了,大体的功能与《【mfc】学生信息管理,实现List控件节点的增删改查》(点击打开链接)基本是一样的:
(1)对于添加按钮void CPriorDlg::OnButton3()
void CPriorDlg::OnButton3()
{
// TODO: Add your control notification handler code here
//获取输入框的值
SUSER u;
GetDlgItemText(IDC_EDIT1,u.sName,sizeof(u.sName));
GetDlgItemText(IDC_EDIT2,u.sPwd,sizeof(u.sPwd));
//如果输入框没有东西就没必要执行下去了,执行提示错误
if(u.sName[0]=='\0'||u.sPwd[0]=='\0'){
AfxMessageBox("输入的用户名或密码为空!");
return;
}
CListCtrl* plist=(CListCtrl*)GetDlgItem(IDC_LIST1);
//plist->GetItemCount()能够求出当前LIST控件具有的项有多少个
int nCount=plist->GetItemCount();
for(int i=0;i<nCount;i++){
if(plist->GetItemText(i,0)==u.sName){
AfxMessageBox("要插入的用户名已经存在!");
//查询到存在就直接打断这个循环,没有必要要进行下去了;
return;
}
}
//如果没有相同,则插入到LIST列表的最后一行
plist->InsertItem(nCount,u.sName);
//要把获取到的密码,插入到刚新开行的第2列里面
plist->SetItemText(nCount,1,u.sPwd);
//下面是获取下拉列表的值
CComboBox* pcombo=(CComboBox*)GetDlgItem(IDC_COMBO1);
//pcombo->GetCurSel();能取到当前下拉列表被选择的是第几项
int nSel=pcombo->GetCurSel();
switch(nSel){
case 0:
plist->SetItemText(nCount,2,"普通");
break;
case 1:
plist->SetItemText(nCount,2,"高级");
break;
}
//添加完清空两个输入框
SetDlgItemText(IDC_EDIT1,"");
SetDlgItemText(IDC_EDIT2,"");
}
(2)对于删除按钮void CPriorDlg::OnButton4() ,这里用户一旦删除一个对象之后,必须遍历一下整个列表,看是否都是普通权限,如果是,那么我们弹窗,并强行把控件列表的最后一项设置回“高级”:
void CPriorDlg::OnButton4()
{
// TODO: Add your control notification handler code here
CListCtrl *pList=(CListCtrl *)GetDlgItem(IDC_LIST1);
POSITION pos=pList->GetFirstSelectedItemPosition();
int nSel=pList->GetNextSelectedItem(pos);
if(nSel<0)
AfxMessageBox("请选中要删除的项!",MB_OK);
else{
if(AfxMessageBox("确认删除:"+pList->GetItemText(nSel,0)+"吗?",MB_YESNO)==IDYES){
pList->DeleteItem(nSel);
int nCount=pList->GetItemCount();
BOOL is_all_putong = TRUE;
for(int i=0;i<nCount;i++){
if(pList->GetItemText(i,2)=="高级")
is_all_putong = FALSE;
}
if(is_all_putong){
AfxMessageBox("必须有一个高级权限账户!");
pList->SetItemText(nCount-1,2,"高级");
}
}
}
}
(3)对于修改按钮void CPriorDlg::OnButton5() ,同样要在用户每修改完一次之后遍历一下,是否都是普通权限,如果是,我们自动把用户修改的那项从“普通”强行变回“高级”
void CPriorDlg::OnButton5()
{
// TODO: Add your control notification handler code here
CListCtrl *pList=(CListCtrl *)GetDlgItem(IDC_LIST1);
POSITION pos=pList->GetFirstSelectedItemPosition();
int nSel=pList->GetNextSelectedItem(pos);
if(nSel<0)
AfxMessageBox("请选中要修改的项!",MB_OK);
else{
if(AfxMessageBox("确认修改?",MB_YESNO)==IDYES){
CString str;
GetDlgItemText(IDC_EDIT1,str);
if(!str.IsEmpty())
pList->SetItemText(nSel,0,str);
GetDlgItemText(IDC_EDIT2,str);
if(!str.IsEmpty())
pList->SetItemText(nSel,1,str);
CComboBox* pcombo=(CComboBox*)GetDlgItem(IDC_COMBO1);
int nSelect=pcombo->GetCurSel();
switch(nSelect){
case 0:
pList->SetItemText(nSel,2,"普通");
break;
case 1:
pList->SetItemText(nSel,2,"高级");
break;
}
int nCount=pList->GetItemCount();
BOOL is_all_putong = TRUE;
for(int i=0;i<nCount;i++){
if(pList->GetItemText(i,2)=="高级")
is_all_putong = FALSE;
}
if(is_all_putong){
AfxMessageBox("必须有一个高级权限账户!");
pList->SetItemText(nSel,2,"高级");
}
}
}
}
14、至此整个权限管理的窗体完成,我们还要修改之前的程序,完成与之前《【mfc】用对话框的切换实现重新登录》( 点击打开链接)那个程序的串联。
(1)首先要改写控件面板初始化BOOL CIsysDlg::OnInitDialog()函数,由于控件面板这个对话框在新建工程就有,所有这个函数不用像CPriorDlg那样添加窗体消息函数,直接改写就行,看用户的权限是什么来判断是否开放帐号管理的按钮,由于这里使用到全局变量,必须extern CIsysApp theApp;:
extern CIsysApp theApp;
BOOL CIsysDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//把全局登录信息设置到窗口标题
CString WinTitle="控制面板 - 欢迎";
SetWindowLong(this->GetSafeHwnd(),GWL_EXSTYLE, WS_EX_APPWINDOW);
WinTitle+=theApp.m_info.sName;
WinTitle+="登录";
SetWindowText(WinTitle);
//调用控件中的EnableWindow方法,EnableWindow(0)则禁用这个按钮,EnableWindow(1)则启用这个按钮
//ShowWindow(0)则隐藏这个按钮,看都看不见
GetDlgItem(IDC_BUTTON2)->EnableWindow(theApp.m_info.Prior);
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
// TODO: Add extra initialization here
return TRUE; // return TRUE unless you set the focus to a control
}
(2)之后是为其里面的Button2也就是”帐号管理“这个按钮增加消息映射函数,由于要弹出”权限管理“这个模态框,因此要引入头文件PriorDlg.h:
#include "PriorDlg.h"
void CIsysDlg::OnButton2()
{
//这段代码的逻辑与上面“重新登录”的基本相同,先隐藏这个对话框,再显示出来
//这里不需要任何逻辑判断了,因为“权限管理”界面就只有一种结果,关闭!
ShowWindow(SW_HIDE);
CPriorDlg dlg;
dlg.DoModal();
ShowWindow(SW_SHOW);
}
(3)对于之前写好的重新登录按钮要加上设置窗体标题的那一句,主要是重新登录之后,要刷新一下窗体标题
#include "LoginDlg.h"
void CIsysDlg::OnButton1()
{
//先把本对话框隐藏
ShowWindow(SW_HIDE);
//再弹出登录对话框
CLoginDlg dlg;
//最后重新显示本对话框,如果没有登录成功,用户点了“取消”,那么由于登录对话框返回IDCANCEL,EndDialog(-1);能够退出整个程序
if(IDCANCEL==dlg.DoModal())
EndDialog(-1);
else{
CString WinTitle="控制面板 - 欢迎";
WinTitle+=theApp.m_info.sName;
WinTitle+="登录";
SetWindowText(WinTitle);
GetDlgItem(IDC_BUTTON2)->EnableWindow(theApp.m_info.Prior);
ShowWindow(SW_SHOW);
}
}
15、theApp中的m_info这个全局变量当然也要刷新,同时,我们的登录对话框再也不是对admin与123这个帐号进行简单的判断,因此要对之前在《【mfc】用对话框的切换实现重新登录》( 点击打开链接)写好CLoginDlg中的函数进行改写,首先是里面的OK按钮:
extern CIsysApp theApp;
void CLoginDlg::OnOK()
{
// TODO: Add extra validation here
CString sid,spwd;
GetDlgItemText(IDC_EDIT1,sid);
GetDlgItemText(IDC_EDIT2,spwd);
CFile file;
if(!file.Open("user.dat",CFile::modeRead|CFile::shareDenyNone)){
if(sid=="admin"&&spwd=="123"){
strcpy(theApp.m_info.sName, "admin");
strcpy(theApp.m_info.sPwd, "123");
theApp.m_info.Prior=1;
//关闭这个对话框,并且向DoModal()返回IDOK
CDialog::OnOK();
}
else
AfxMessageBox("用户名或者密码错误!");
}
else{
if(CheckUser())
//关闭这个对话框,并且向DoModal()返回IDOK
CDialog::OnOK();
else
AfxMessageBox("用户名或者密码错误!");
}
}
里面关于对登录帐号的判断,我们也像之前那样为其开一个新的函数:
然后在里面写入取得你输入的用户名与密码,如果能够在user.dat找到你的用户名与密码,那么就返回成功,如果到最后还没有找到,那么就失败了:
BOOL CLoginDlg::CheckUser()
{
CString sid,spwd;
GetDlgItemText(IDC_EDIT1,sid);
GetDlgItemText(IDC_EDIT2,spwd);
CFile file;
if(!file.Open("user.dat",CFile::modeRead|CFile::shareDenyNone)){
return false;
}
SUSER u;
while(file.Read(&u,sizeof(u))==sizeof(u))
{
if(sid==u.sName&&spwd==u.sPwd){
theApp.m_info=u;
return true;
}
}
file.Close();
return false;
}
至此,整个程序做完!