课上完了连老师见都没见一面QAQ….记录一下该小项目

效果如下:

1、实现文件搜索功能,并封装为类

1)首先是文件搜索类Rapidfinder的构造函数和析构函数和文件信息初始化函数和文件路径规格化函数;重新初始化文件函数

CRapidFinder::CRapidFinder(HWND MainHwnd, CString MatchName, CString MatchDir)
{
	m_hThrds = NULL;
	InitializeCriticalSection(&m_gCriticalSection);
	ThreadSet();
	FinderSet(MainHwnd, MatchName, MatchDir);
}
CRapidFinder::~CRapidFinder()
{
	DeleteCriticalSection(&m_gCriticalSection);
	if (m_hExitEvent)CloseHandle(m_hExitEvent);
}


//初始化搜索文件信息
void CRapidFinder::FinderSet(HWND MainHwnd, CString MatchName, CString MatchDir)
{
	FinderReset();
	m_MainhWnd = MainHwnd;

	if (!MatchName.IsEmpty())
	{
		MatchName.MakeUpper(); 
		MatchDir.MakeUpper();//makerupper()将CString字符转化为一个大写的字符串。
		m_strFileName = MatchName;
		m_Option |= OP_FILENAME;//按位或后赋值,OP_FILENAME为0x01
	}
	m_strFileDir = MatchDir;

	m_hExitEvent = CreateEvent(NULL, TRUE, FALSE, L"RAPIDFINDER");//创建事件,手动设置信号,初始化为未激发
	CreatePathList();//规格化文件路径
}

//初始化创建线程的信息
void CRapidFinder::ThreadSet(LONG MaxThreadCount, int priority)
{
	m_Priority = priority;
	m_ActiveCount = m_MaxThreadCount = MaxThreadCount;
	if (m_hThrds)delete[]m_hThrds;//释放线程句柄数组
	m_hThrds = new HANDLE[MaxThreadCount];

}

//重新初始化文件
void CRapidFinder::FinderReset()
{
	m_lpText = NULL;
	m_NextVal = NULL;
	m_Option = 0;
	m_strFileName = "";
	m_strFileDir = "";
	m_DirList.RemoveAll();
	ResetEvent(m_hExitEvent);//重置为未激发态
	m_ExitCode = ERR;
}

//规格化文件路径
void CRapidFinder::CreatePathList()
{
	Trim(m_strFileDir);

	if (m_strFileDir.IsEmpty())
	{
		SetErrNo(0); return;
	}

	int np = 0, op = 0;
	CString str;
	//当有多个路径同时选择时(即用;分隔)
	while ((np = m_strFileDir.Find(';', op)) != -1)
	{
		str = Trim(m_strFileDir.Mid(op, np - op));
		str.TrimRight('\\');//消除“\\”
		if (!str.IsEmpty())m_DirList.AddTail((LPCTSTR)str);
		op = np + 1;
	}
	str = Trim(m_strFileDir.Mid(op, m_strFileDir.GetLength() - op));
	str.TrimRight('\\');
	if (!str.IsEmpty())m_DirList.AddTail((LPCTSTR)str);
}

构造函数用来初始化临界区,初始化线程信息(线程优先级、最大线程数、线程句柄数组),初始化搜索文件信息(目录,文件名);
析构函数退出临界区,关闭事件句柄;

2)文件搜索函数的第二部分就是:判断搜索方式的MatchProc函数(是按文件名还是特定字符串);查询指定文件里是否包含特定字符的FindTextFromFile()函数;预先处理特定字符串的CalNextPos()函数

//判断搜索文件时的方式,是按文件名还是包含字符
BOOL __fastcall CRapidFinder::MatchProc(CString& findpath)
{
	CString fname(findpath);
	int pos = fname.ReverseFind('\\');
	fname.MakeUpper();
	if ((m_Option & OP_FILENAME) && (fname.Find(m_strFileName, pos + 1) == -1))return false;
	if ((m_Option & OP_FILETEXT) && !FindTextFromFile(findpath))return false;
	return true;
}


//查询文件里是否包含我们指定的字符
BOOL __fastcall CRapidFinder::FindTextFromFile(CString& findpath)
{
	CFile file;
	if (NULL == file.Open(findpath.GetBuffer(0), CFile::modeRead | CFile::typeBinary))return false;
	BYTE* Buffer = new BYTE[512];
	int nRead;
	if (!(nRead = file.Read(Buffer, 512))) { file.Close(); return false; }//如果读回字节数为0,则关闭Cfile
	int i = 0, j = 0;

	while (j < m_TextSize)
		if (j == -1 || Buffer[i] == m_lpText[j])//判断是否包含我们指定的字符
		{
			if (++i == nRead)//如果读回字节数为1,则只需要一轮循环集合
			{
				/*PeekAndPump();*/
				if (!(nRead = file.Read(Buffer, 512))) { file.Close(); return false; }
				i = 0;
			}
			j++;
		}
		else j = m_NextVal[j];
	file.Close();
	return true;
}

//决定是否从下一位字符再开始匹配。比如在“aab”中搜索“ab”,当在“aab”中有重复的字符时给每一位字符设置一个是否进行到下一位匹配的标志
int* CRapidFinder::CalNextPos()
{
	int j = 0, k = -1;
	m_NextVal[0] = -1;
	while (j < m_TextSize - 1)
		if ((k == -1) || (m_lpText[j] == m_lpText[k]))
		{
			j++; k++;
			if (m_lpText[j] != m_lpText[k])m_NextVal[j] = k;
			else m_NextVal[j] = m_NextVal[k];
		}
		else k = m_NextVal[k];
	return m_NextVal;
}


3)第三部分就是多线程的处理,包括主线程函数MainThreadProc()、子线程函数ThreadProc()、启动子线程函数StartFinder()、暂停函数PauseFinder()、停止函数StopFinder()、暂停后继续函数ResumeFinder();

//主线程函数,为每个线程分配搜索任务
DWORD WINAPI CRapidFinder::MainThreadProc(LPVOID lpParam)
{
	//新建一个finder对象来存储文件信息
	CRapidFinder* finder = (CRapidFinder*)lpParam;

resume:
	//Reset,重新开始
	ResetEvent(finder->m_hExitEvent);//重置m_hExitEvent,使之处于未激发态
	finder->m_ExitCode = ERR;
	finder->m_ActiveCount = finder->m_MaxThreadCount;

	PostMessage(finder->m_MainhWnd, WM_THREADCOUNT, (WPARAM)(finder->m_ActiveCount), NULL);
	//启动子线程
	for (int i = 0; i < finder->m_MaxThreadCount; i++)
		finder->m_hThrds[i] = StartThread(ThreadProc, lpParam);

	WaitForMultipleObjects(finder->m_MaxThreadCount, finder->m_hThrds, TRUE, INFINITE);//等待所有线程返回

	for (int i = 0; i < finder->m_MaxThreadCount; i++)
		CloseHandle(finder->m_hThrds[i]); //关闭所有线程句柄


	//查看线程退出原因
	switch (finder->m_ExitCode)
	{
	case PAUSE:SendMessage(finder->m_MainhWnd, WM_THREADPAUSE, NULL, NULL);
		ResetEvent(finder->m_hExitEvent);//重置为未激发态,为暂停后重新开始做准备
		//等待继续查找
		WaitForSingleObject(finder->m_hExitEvent, INFINITE);
		goto resume;
	//下面两个类似,一个是直接退出,一个是执行完退出
	case EXIT:SendMessage(finder->m_MainhWnd, WM_THREADEXIT, EXIT, NULL);
		finder->FinderReset();
		break;;
	case STOP:SendMessage(finder->m_MainhWnd, WM_THREADEXIT, STOP, NULL);
		finder->FinderReset();
		break;
	default:finder->SetErrNo(1); return 0;
	}
	return 1;
}

//启动子线程的函数
HANDLE CRapidFinder::StartThread(LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParam)
{
	DWORD ThreadID;//线程id
	CRapidFinder* finder = (CRapidFinder*)lpParam;//文件搜索类对象

	HANDLE htmp = CreateThread(NULL, 0, lpStartAddress, lpParam, CREATE_SUSPENDED, &ThreadID);
	BOOL re = SetThreadPriority(htmp, finder->m_Priority);
	ASSERT(re);
	ResumeThread(htmp);
	return htmp;
}

上述两个函数相互配合,创建指定数目的子线程(在StartFinder()函数初始化子线程信息,创建子线程);主线程使用WaitForMultipleObjects函数等待所有子线程返回,在全部返回后关闭所有句柄;
并且主线程还监听和处理子线程的状态,根据m_ExitCode判断子线程是处于暂停、停止还是暂停后继续的状态(即文件搜索中的暂停、停止、暂停后继续的功能),然后通过设置m_hExitEvent事件对象的激发状态来控制子线程的工作;

2、文件搜索中的开始、暂停、暂停后继续、停止函数
//开始查找
BOOL CRapidFinder::StartFinder()
{

	if (m_DirList.IsEmpty()) { SetErrNo(0); return FALSE; }
	PostMessage(m_MainhWnd, WM_THREADCOUNT, (WPARAM)m_ActiveCount, NULL);

	DWORD ThreadID;
	//创建主线程
	HANDLE hMainThread = CreateThread(NULL, 0, MainThreadProc, (LPVOID)this, CREATE_SUSPENDED, &ThreadID); //线程是在挂起状态下创建的,并且在调用ResumeThread函数之前不会运行 。
	ASSERT(hMainThread);
	BOOL re = SetThreadPriority(hMainThread, m_Priority);//调整优先级
	ASSERT(re);//作用是如果它的条件返回错误,则终止程序执行
	ResumeThread(hMainThread);
	CloseHandle(hMainThread);

	return TRUE;
}
//暂停查找
void CRapidFinder::PauseFinder()
{
	if (m_ExitCode == PAUSE)return;
	m_ExitCode = PAUSE;
	SetEvent(m_hExitEvent);//发出m_hExitEvent信号
	Sleep(40);
}
//继续查找
void CRapidFinder::ResumeFinder()
{
	SetEvent(m_hExitEvent);
}
//停止查找
void CRapidFinder::StopFinder()
{
	if (m_ExitCode == STOP)return;

	if (m_ExitCode == PAUSE)
	{
		ResumeFinder();//重新开始
		Sleep(40);//延时
	}
	m_ExitCode = STOP;
	SetEvent(m_hExitEvent);
}
在StartFinder()中创建主线程;暂停、暂停后继续、停止都是通过改变m_hExitEvent的激发状态来实现
因为创建m_hExitEvent事件时:CreateEvent(NULL, TRUE, FALSE, L"RAPIDFINDER");是手动重置激发状态,所以在主函数的个状态的处理逻辑里每次都需要先用resetEvent()重置激发态

2、MFC界面

主要用到了mfc的按钮控件、Edit控件和list控件

主要界面布局如上

相关成员的定义:

事件处理程序(函数)的实现

1)MFC类的初始化,对相关成员变量赋初值;以及对话框界面的初始化(设置菜单,图标),另外还使用到了CListCtrl控件来显示搜索到的文件信息

CFinderDemoDlg::CFinderDemoDlg(CWnd* pParent /=NULL/)
  : CDialog(CFinderDemoDlg::IDD, pParent)
{

  //{{AFX_DATA_INIT(CFinderDemoDlg)
  m_ActiveCount = _T("0");
  m_folder = _T("");//C:\;D:\;E:\;F:");
  m_count = _T("0");
  m_findfolder = _T("");
  m_text = _T("");
  m_filename = _T("");
  m_threadcount = 10;
  m_priority = 0;
  //}}AFX_DATA_INIT

  // Note that LoadIcon does not require a subsequent DestroyIcon in Win32

  m_folder=GetAllDriverList();
  m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);//图标

}

 

//初始化对话框
BOOL CFinderDemoDlg::OnInitDialog()
{
  CDialog::OnInitDialog();
  // IDM_ABOUTBOX must be in the system command range.
  ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
  ASSERT(IDM_ABOUTBOX < 0xF000);

m_ListCtrl.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_TRACKSELECT|LVS_EX_FLATSB|LVS_EX_UNDERLINEHOT|LVS_EX_GRIDLINES);//具体参考https://docs.microsoft.com/zh-cn/windows/win32/Controls/extended-list-view-styles

  m_ListCtrl.InsertColumn(0,T("文件名"),LVCFMT_IMAGE|LVCFMT_LEFT,80,80);//插入一列
  m_ListCtrl.InsertColumn(1,T("路径"),LVCFMT_LEFT,180);
  m_ListCtrl.InsertColumn(2,T("大小"),LVCFMT_LEFT,80);
  m_ListCtrl.InsertColumn(3,T("类型"),LVCFMT_LEFT,80);
  m_ListCtrl.SetHoverTime(500);//设置列表视图控件的当前逗留时间

  finder.ThreadSet(30);
  m_ListCtrl.SetRedraw();//数据更新时闪烁
  UIControl(false);

  CMenu* pSysMenu = GetSystemMenu(FALSE);// 返回当前使用窗口菜单的拷贝的句柄。该拷贝初始时与窗口菜单相同,但可以被修改。

  if (pSysMenu != NULL)
  {
    CString strAboutMenu;
    strAboutMenu.LoadString(IDS_ABOUTBOX);
    if (!strAboutMenu.IsEmpty())
    {
      pSysMenu->AppendMenu(MF_SEPARATOR);//画一条水平区分线
      pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);//将IDM_ABOUTBOX添加到菜单中
    }
  }

 

  //设置图标
  SetIcon(m_hIcon, TRUE);     // 设置大图标
  SetIcon(m_hIcon, FALSE);  // 设置小图标

  // TODO: Add extra initialization here

  return TRUE; // return TRUE unless you set the focus to a control
}

2)数据交换函数和消息响应函数
将控件的值传给类中的将变量的值显示到控件上;以及对相关消息的响应函数(如开始、暂停、显示“正在查找的文件”的函数…)

void CFinderDemoDlg::DoDataExchange(CDataExchange* pDX)
{
  CDialog::DoDataExchange(pDX);
  //{{AFX_DATA_MAP(CFinderDemoDlg)
  //将控件的值传递给指定变量假设为类型1,将变量的值传递给控件假设为类型2
  DDX_Control(pDX, IDC_LIST, m_ListCtrl);//类型2
  DDX_Control(pDX, IDC_STOP, m_stop);//将IDC_STOP窗口的值传递给m_stop
  DDX_Control(pDX, IDC_PAUSE, m_pause);//类型1
  DDX_Control(pDX, IDC_START, m_start);//类型1
  DDX_Text(pDX, IDC_ACTIVE_THREAD, m_ActiveCount);//将m_ActiveCount的值显示到IDC_ACTIVE_THREAD窗口
  DDX_Text(pDX, IDC_FOLDER, m_folder);//类型1
  DDX_Text(pDX, IDC_COUNT, m_count);//类型2
  DDX_Text(pDX, IDC_FINDING_FOLDER, m_findfolder);//将m_findfolder的值显示到IDC_FINDING_FOLDER窗口
  DDX_Text(pDX, IDC_INCLUDE_TEXT, m_text);//类型1
  DDX_Text(pDX, IDC_FILENAME, m_filename);//类型1
  DDX_Text(pDX, IDC_THREAD_NO, m_threadcount);//类型1
  //DDX_Text(pDX, IDC_PRIORITY, m_priority);
  //}}AFX_DATA_MAP
}

 

BEGIN_MESSAGE_MAP(CFinderDemoDlg, CDialog)//添加消息响应函数,为每个消息处理函数加入一个入口。
  ON_WM_SYSCOMMAND()//系统消息
  ON_WM_PAINT()//绘图消息
  ON_WM_QUERYDRAGICON()//查询icon消息
  ON_BN_CLICKED(IDC_STOP, OnStop)//停止
  ON_BN_CLICKED(IDC_START, OnStart)//开始
  ON_BN_CLICKED(IDC_PAUSE, OnPause)//暂停
  ON_BN_CLICKED(IDC_BROWSE, OnBrowse)//选择文件
  ON_EN_CHANGE(IDC_THREAD_NO, OnChangeThreadNo)//设置线程数
  ON_MESSAGE(WM_THREADEXIT, OnFindExit)//查找时停止
  ON_MESSAGE(WM_THREADCOUNT, OnFindThreadCount)
  ON_MESSAGE(WM_FINDERITEM , OnFindItem)
  ON_MESSAGE(WM_THREADPAUSE, OnFindPause)//查找时暂停
  ON_MESSAGE(WM_FINDERFOLDER, OnFindingFolder)//正在查找
  ON_EN_CHANGE(IDC_FOLDER, &CFinderDemoDlg::OnEnChangeFolder)
END_MESSAGE_MAP()

3)文件相关函数,OnBrowse()选择在哪个路径进行查找文件;修改查找文件的线程数的OnChangeThreadNo()函数;

点击选择文件按钮,触发OnBrowse事件处理函数,打开选择文件路径的面板;在mfc界面上设置线程数目,通过值交换函数,将控件上的值赋给相关类的成员变量,再通过OnChangeThreadNo()函数修改创建线程是默认设置的线程数目为新的值

//选择查找文件路径

void CFinderDemoDlg::OnBrowse()
{
  // TODO: Add your control notification handler code here
  BROWSEINFO bi;
  char dispname[MAX_PATH], path[MAX_PATH];
  ITEMIDLIST* pidl;
  //
  bi.hwndOwner = m_hWnd;
  bi.pidlRoot = 0;
  bi.pszDisplayName = dispname;
  bi.lpszTitle = "请选择查找目录:";
  bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_DONTGOBELOWDOMAIN;
  bi.lpfn = 0;
  bi.lParam = 0;
  bi.iImage = 0;
  if (pidl = SHBrowseForFolder(&bi))
  {
    SHGetPathFromIDList(pidl, path);
    m_folder = CString(path);
    if (m_folder.IsEmpty())m_folder = GetAllDriverList();
    UpdateData(false);
  }
}

 

//当用户未指定查找路径时的默认路径
CString CFinderDemoDlg::GetAllDriverList()
{
  CString tmp = _T("D:"), Dir;//D:
  for (int i = 1; i <= 25; i++)
  {
    Dir = CString("D:" + i) + _T(":");
    if (GetDriveType(Dir.GetBuffer(0)) == DRIVE_NO_ROOT_DIR)continue;//判断该路径是否是有效的
    tmp += ";" + Dir;
  }
  return tmp;
}



//修改默认的线程数为我们MFC界面选择的线程数

void CFinderDemoDlg::OnChangeThreadNo() 
{
  int count=GetDlgItemInt(IDC_THREAD_NO);
 if(count<1||count>99){count=10;SetDlgItemInt(IDC_THREAD_NO,count);}
  //finder.ThreadSet(count,finder.GetThreadPrioriy()); 
  finder.ThreadSet(count, m_priority);
}

4)开始、暂停、停止查找文件的函数

//开始
void CFinderDemoDlg::OnStart()

{
  // TODO: Add your control notification handler code here
  // ::SendMessage(GetSafeHwnd(),WM_THREADCOUNT,(WPARAM)100,NULL);
  UpdateData(true);//刷新控件的值到变量
  m_count = _T("0");
  m_ActiveCount = _T("0");
  count = 0;
  m_imglist.DeleteImageList();
  m_imglist.Create(16, 16, ILC_MASK | ILC_COLORDDB, 1, 100);//新建一个图像列表,然后用add添加图标(这里在OnFindItem函数使用到了)
  m_ListCtrl.SetImageList(&m_imglist, LVSIL_SMALL);//再将该列表中的图像绑定到m_ListCtrl列表控件上
  m_ListCtrl.DeleteAllItems();
  UpdateData(false);//将变量刷新到控件进行显示
  finder.FinderSet(GetSafeHwnd(), m_filename, m_folder);
  finder.FindWithText(m_text, m_text.GetLength());
  finder.StartFinder();
  UIControl(true);//开始后就调用UIControl进制相关控件接收鼠标或键盘消息
}

 

//暂停
void CFinderDemoDlg::OnPause()
{
  // TODO: Add your control notification handler code here
  static BOOL ispause = true;
  if (ispause)
  {
    m_pause.SetWindowText("继续");//如果是暂停状态就将按钮修改为“继续”
    finder.PauseFinder();
    ispause = false;
  }
  else
  {
    m_pause.SetWindowText("暂停");
    finder.ResumeFinder();
    ispause = true;
  }
}

 

//停止
void CFinderDemoDlg::OnStop()
{
  // TODO: Add your control notification handler code here
  finder.StopFinder();
}



//弹出框
LRESULT CFinderDemoDlg::OnFindExit(WPARAM wparam, LPARAM lparam)

{
  m_pause.SetWindowText("暂停");
  UIControl(false);
  if (wparam == 1)AfxMessageBox("停止查找!");//:AfxMessageBox比MessageBox简单一些,因为它是一个全局函数所以不需要对应的一个窗口类,但是不能控制消息框标题
  else AfxMessageBox("查找结束!");
  m_ListCtrl.RedrawItems(0, count - 1);//暂停时只显示现在查到的
  return 0;
}
LRESULT CFinderDemoDlg::OnFindPause(WPARAM wparam, LPARAM lparam)
{
  AfxMessageBox("用户暂停!");
  return 0;
}

5)界面更新函数,如更新当前活动线程数,查找到符合的文件数目,开始、暂停、停止的按钮状态,显示正在查找的文件,显示已经找到的符合的文件

LRESULT CFinderDemoDlg::OnFindExit(WPARAM wparam,LPARAM lparam)
{
  m_pause.SetWindowText("暂停");
  UIControl(false);
  if(wparam==1)AfxMessageBox("停止查找!");//:AfxMessageBox比MessageBox简单一些,因为它是一个全局函数所以不需要对应的一个窗口类,但是不能控制消息框标题
  else AfxMessageBox("查找结束!"); 
  m_ListCtrl.RedrawItems(0,count-1);//暂停时只显示现在查到的
  return 0;

}

LRESULT CFinderDemoDlg::OnFindPause(WPARAM wparam,LPARAM lparam)
{
  AfxMessageBox("用户暂停!");
  return 0;
}

//查找到的文件的文件信息
LRESULT CFinderDemoDlg::OnFindItem(WPARAM wparam,LPARAM lparam)

{
  m_count.Format("%d",++count);
  UpdateData(false);
  CString pathname=*((CString *)wparam);
  CFileStatus Status;
  CFile::GetStatus(pathname,Status);
  CString Unit="Byte";
  float flen=(float)Status.m_size;
  if(flen>1024)
  {  
    flen/=1024;
    if(flen<1024){Unit="KB";}
    else{flen/=1024;Unit="MB";}
  }

  CString Size;
  Size.Format("%1.2f",flen);
  int pos=pathname.ReverseFind('');
  SHFILEINFO sfi;
 if (::SHGetFileInfo (pathname, FILE_ATTRIBUTE_NORMAL, &sfi, sizeof(SHFILEINFO),SHGFI_USEFILEATTRIBUTES | SHGFI_DISPLAYNAME | SHGFI_TYPENAME |SHGFI_ICON|SHGFI_SMALLICON ))
  {  
    //更新mfc界面的list框显示
    m_imglist.Add(sfi.hIcon);
    m_ListCtrl.InsertItem(count-1,sfi.szDisplayName,count-1);
    m_ListCtrl.SetItemText(count-1,1,pathname.Mid(0,pos));
    m_ListCtrl.SetItemText(count-1,2,(Size+Unit));
    m_ListCtrl.SetItemText(count-1,3,sfi.szTypeName);
  }
  m_ListCtrl.Update(count-1);
  PeekAndPump();
  return 0;

}

//在MFC界面显示当前活动的线程数
LRESULT CFinderDemoDlg::OnFindThreadCount(WPARAM wparam,LPARAM lparam)
{
  m_ActiveCount.Format("%ld",LONG(wparam));
  UpdateData(FALSE);
  return 0;
}

//在mfc界面显示当前正在查询的文件路径
LRESULT CFinderDemoDlg::OnFindingFolder(WPARAM wparam,LPARAM lparam)
{
  m_findfolder=*((CString *)wparam);
  UpdateData(false);
  return 0;
}

/控制相关控件的状态
void CFinderDemoDlg::UIControl(BOOL bOp)//start with true;
{
  m_start.EnableWindow(!bOp); 
  m_stop.EnableWindow(bOp);  
  m_pause.EnableWindow(bOp);
  GetDlgItem(IDC_THREAD_NO)->EnableWindow(!bOp);//EnableWindow函数允许/禁止指定的窗口或控件接受鼠标和键盘的输入
  GetDlgItem(IDC_SPIN_THREAD_NO)->EnableWindow(!bOp);  
}

版权声明:本文为zw1sh原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/zw1sh/p/13024330.html