課上完了連老師見都沒見一面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);
}