• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            平凡的天才

            目的是為人類造福
            posts - 20, comments - 41, trackbacks - 0, articles - 6
              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

            2007年11月20日

            http://blog.csdn.net/lixiaosan/archive/2006/04/07/653563.aspx

            以下未經說明,listctrl默認view 風格為report

            相關類及處理函數

            MFC:CListCtrl類

            SDK:以 “ListView_”開頭的一些宏。如 ListView_InsertColumn


            1. CListCtrl 風格

                  LVS_ICON: 為每個item顯示大圖標
                  LVS_SMALLICON: 為每個item顯示小圖標
                  LVS_LIST: 顯示一列帶有小圖標的item
                  LVS_REPORT: 顯示item詳細資料

                  直觀的理解:windows資源管理器,“查看”標簽下的“大圖標,小圖標,列表,詳細資料”



            2. 設置listctrl 風格及擴展風格

                  LONG lStyle;
                  lStyle = GetWindowLong(m_list.m_hWnd, GWL_STYLE);//獲取當前窗口style
                  lStyle &= ~LVS_TYPEMASK; //清除顯示方式位
                  lStyle |= LVS_REPORT; //設置style
                  SetWindowLong(m_list.m_hWnd, GWL_STYLE, lStyle);//設置style
             
                  DWORD dwStyle = m_list.GetExtendedStyle();
                  dwStyle |= LVS_EX_FULLROWSELECT;//選中某行使整行高亮(只適用與report風格的listctrl)
                  dwStyle |= LVS_EX_GRIDLINES;//網格線(只適用與report風格的listctrl)
                  dwStyle |= LVS_EX_CHECKBOXES;//item前生成checkbox控件
                  m_list.SetExtendedStyle(dwStyle); //設置擴展風格
             
                  注:listview的style請查閱msdn
                  http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wceshellui5/html/wce50lrflistviewstyles.asp

             


            3. 插入數據

                  m_list.InsertColumn( 0, "ID", LVCFMT_LEFT, 40 );//插入列
                  m_list.InsertColumn( 1, "NAME", LVCFMT_LEFT, 50 );
                  int nRow = m_list.InsertItem(0, “11”);//插入行
                  m_list.SetItemText(nRow, 1, “jacky”);//設置數據

             


            4. 一直選中item

                選中style中的Show selection always,或者在上面第2點中設置LVS_SHOWSELALWAYS



            5. 選中和取消選中一行

                int nIndex = 0;
                //選中
                m_list.SetItemState(nIndex, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED);
                //取消選中
                m_list.SetItemState(nIndex, 0, LVIS_SELECTED|LVIS_FOCUSED);
             


            6. 得到listctrl中所有行的checkbox的狀態

                  m_list.SetExtendedStyle(LVS_EX_CHECKBOXES);
                  CString str;
                  for(int i=0; i<m_list.GetItemCount(); i++)
                  {
                       if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED || m_list.GetCheck(i))
                       {
                            str.Format(_T("第%d行的checkbox為選中狀態"), i);
                            AfxMessageBox(str);
                       }
                  }



            7. 得到listctrl中所有選中行的序號


                  方法一:
                  CString str;
                  for(int i=0; i<m_list.GetItemCount(); i++)
                  {
                       if( m_list.GetItemState(i, LVIS_SELECTED) == LVIS_SELECTED )
                       {
                            str.Format(_T("選中了第%d行"), i);
                            AfxMessageBox(str);
                       }
                  }

                  方法二:
                  POSITION pos = m_list.GetFirstSelectedItemPosition();
                  if (pos == NULL)
                       TRACE0("No items were selected!\n");
                  else
                  {
                       while (pos)
                       {
                            int nItem = m_list.GetNextSelectedItem(pos);
                            TRACE1("Item %d was selected!\n", nItem);
                            // you could do your own processing on nItem here
                       }
                  }



            8. 得到item的信息

                  TCHAR szBuf[1024];
                  LVITEM lvi;
                  lvi.iItem = nItemIndex;
                  lvi.iSubItem = 0;
                  lvi.mask = LVIF_TEXT;
                  lvi.pszText = szBuf;
                  lvi.cchTextMax = 1024;
                  m_list.GetItem(&lvi);

                  關于得到設置item的狀態,還可以參考msdn文章
                  Q173242: Use Masks to Set/Get Item States in CListCtrl
                           http://support.microsoft.com/kb/173242/en-us



            9. 得到listctrl的所有列的header字符串內容

                  LVCOLUMN lvcol;
                  char  str[256];
                  int   nColNum;
                  CString  strColumnName[4];//假如有4列

                  nColNum = 0;
                  lvcol.mask = LVCF_TEXT;
                  lvcol.pszText = str;
                  lvcol.cchTextMax = 256;
                  while(m_list.GetColumn(nColNum, &lvcol))
                  {
                       strColumnName[nColNum] = lvcol.pszText;
                       nColNum++;
                  }



            10. 使listctrl中一項可見,即滾動滾動條

                m_list.EnsureVisible(i, FALSE);


            11. 得到listctrl列數

                int nHeadNum = m_list.GetHeaderCtrl()->GetItemCount();


            12. 刪除所有列

                  方法一:
                     while ( m_list.DeleteColumn (0))
                   因為你刪除了第一列后,后面的列會依次向上移動。

                  方法二:
                  int nColumns = 4;
                  for (int i=nColumns-1; i>=0; i--)
                      m_list.DeleteColumn (i);



            13. 得到單擊的listctrl的行列號

                  添加listctrl控件的NM_CLICK消息相應函數
                  void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
                  {
                       // 方法一:
                       /*
                       DWORD dwPos = GetMessagePos();
                       CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
              
                       m_list.ScreenToClient(&point);
              
                       LVHITTESTINFO lvinfo;
                       lvinfo.pt = point;
                       lvinfo.flags = LVHT_ABOVE;
                
                       int nItem = m_list.SubItemHitTest(&lvinfo);
                       if(nItem != -1)
                       {
                            CString strtemp;
                            strtemp.Format("單擊的是第%d行第%d列", lvinfo.iItem, lvinfo.iSubItem);
                            AfxMessageBox(strtemp);
                       }
                      */
              
                      // 方法二:
                      /*
                       NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
                       if(pNMListView->iItem != -1)
                       {
                            CString strtemp;
                            strtemp.Format("單擊的是第%d行第%d列",
                                            pNMListView->iItem, pNMListView->iSubItem);
                            AfxMessageBox(strtemp);
                       }
                      */
                       *pResult = 0;
                  }

             


            14. 判斷是否點擊在listctrl的checkbox上

                  添加listctrl控件的NM_CLICK消息相應函數
                  void CTest6Dlg::OnClickList1(NMHDR* pNMHDR, LRESULT* pResult)
                  {
                       DWORD dwPos = GetMessagePos();
                       CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
              
                       m_list.ScreenToClient(&point);
              
                       LVHITTESTINFO lvinfo;
                       lvinfo.pt = point;
                       lvinfo.flags = LVHT_ABOVE;
                
                       UINT nFlag;
                       int nItem = m_list.HitTest(point, &nFlag);
                       //判斷是否點在checkbox上
                       if(nFlag == LVHT_ONITEMSTATEICON)
                       {
                            AfxMessageBox("點在listctrl的checkbox上");
                       }
                       *pResult = 0;
                  }



            15. 右鍵點擊listctrl的item彈出菜單

                  添加listctrl控件的NM_RCLICK消息相應函數
                  void CTest6Dlg::OnRclickList1(NMHDR* pNMHDR, LRESULT* pResult)
                  {
                       NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
                       if(pNMListView->iItem != -1)
                       {
                            DWORD dwPos = GetMessagePos();
                            CPoint point( LOWORD(dwPos), HIWORD(dwPos) );
               
                            CMenu menu;
                            VERIFY( menu.LoadMenu( IDR_MENU1 ) );
                            CMenu* popup = menu.GetSubMenu(0);
                            ASSERT( popup != NULL );
                            popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this );
                       }
                       *pResult = 0;
              }


             


            16. item切換焦點時(包括用鍵盤和鼠標切換item時),狀態的一些變化順序

                  添加listctrl控件的LVN_ITEMCHANGED消息相應函數
                  void CTest6Dlg::OnItemchangedList1(NMHDR* pNMHDR, LRESULT* pResult)
                  {
                       NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
                       // TODO: Add your control notification handler code here
               
                       CString sTemp;
             
                       if((pNMListView->uOldState & LVIS_FOCUSED) == LVIS_FOCUSED &&
                        (pNMListView->uNewState & LVIS_FOCUSED) == 0)
                       {
                            sTemp.Format("%d losted focus",pNMListView->iItem);
                       }
                       else if((pNMListView->uOldState & LVIS_FOCUSED) == 0 &&
                           (pNMListView->uNewState & LVIS_FOCUSED) == LVIS_FOCUSED)
                       {
                            sTemp.Format("%d got focus",pNMListView->iItem);
                       }
             
                       if((pNMListView->uOldState & LVIS_SELECTED) == LVIS_SELECTED &&
                        (pNMListView->uNewState & LVIS_SELECTED) == 0)
                       {
                            sTemp.Format("%d losted selected",pNMListView->iItem);
                       }
                       else if((pNMListView->uOldState & LVIS_SELECTED) == 0 &&
                        (pNMListView->uNewState & LVIS_SELECTED) == LVIS_SELECTED)
                       {
                            sTemp.Format("%d got selected",pNMListView->iItem);
                       }
               
                       *pResult = 0;
                  }




            17. 得到另一個進程里的listctrl控件的item內容

            http://www.codeproject.com/threads/int64_memsteal.asp



            18. 選中listview中的item

            Q131284: How To Select a Listview Item Programmatically
            http://support.microsoft.com/kb/131284/en-us



            19. 如何在CListView中使用CListCtrl的派生類

            http://www.codeguru.com/cpp/controls/listview/introduction/article.php/c919/



            20. listctrl的subitem添加圖標

                  m_list.SetExtendedStyle(LVS_EX_SUBITEMIMAGES);
                  m_list.SetItem(..); //具體參數請參考msdn

             


            21. 在CListCtrl顯示文件,并根據文件類型來顯示圖標

                  網上找到的代碼,share
                  BOOL CTest6Dlg::OnInitDialog()
                  {
                       CDialog::OnInitDialog();
              
                       HIMAGELIST himlSmall;
                       HIMAGELIST himlLarge;
                       SHFILEINFO sfi;
                       char  cSysDir[MAX_PATH];
                       CString  strBuf;
             
                       memset(cSysDir, 0, MAX_PATH);
              
                       GetWindowsDirectory(cSysDir, MAX_PATH);
                       strBuf = cSysDir;
                       sprintf(cSysDir, "%s", strBuf.Left(strBuf.Find("\\")+1));
             
                       himlSmall = (HIMAGELIST)SHGetFileInfo ((LPCSTR)cSysDir, 
                                  0, 
                                  &sfi,
                                  sizeof(SHFILEINFO), 
                                  SHGFI_SYSICONINDEX | SHGFI_SMALLICON );
              
                       himlLarge = (HIMAGELIST)SHGetFileInfo((LPCSTR)cSysDir, 
                                  0, 
                                  &sfi, 
                                  sizeof(SHFILEINFO), 
                                  SHGFI_SYSICONINDEX | SHGFI_LARGEICON);
              
                       if (himlSmall && himlLarge)
                       {
                            ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST,
                                         (WPARAM)LVSIL_SMALL, (LPARAM)himlSmall);
                            ::SendMessage(m_list.m_hWnd, LVM_SETIMAGELIST,
                                         (WPARAM)LVSIL_NORMAL, (LPARAM)himlLarge);
                       }
                       return TRUE;  // return TRUE  unless you set the focus to a control
                  }
             
                  void CTest6Dlg::AddFiles(LPCTSTR lpszFileName, BOOL bAddToDocument)
                  {
                       int nIcon = GetIconIndex(lpszFileName, FALSE, FALSE);
                       CString strSize;
                       CFileFind filefind;
             
                       //  get file size
                       if (filefind.FindFile(lpszFileName))
                       {
                            filefind.FindNextFile();
                            strSize.Format("%d", filefind.GetLength());
                       }
                       else
                            strSize = "0";
              
                       // split path and filename
                       CString strFileName = lpszFileName;
                       CString strPath;
             
                       int nPos = strFileName.ReverseFind('\\');
                       if (nPos != -1)
                       {
                            strPath = strFileName.Left(nPos);
                            strFileName = strFileName.Mid(nPos + 1);
                       }
              
                       // insert to list
                       int nItem = m_list.GetItemCount();
                       m_list.InsertItem(nItem, strFileName, nIcon);
                       m_list.SetItemText(nItem, 1, strSize);
                       m_list.SetItemText(nItem, 2, strFileName.Right(3));
                       m_list.SetItemText(nItem, 3, strPath);
                  }
             
                  int CTest6Dlg::GetIconIndex(LPCTSTR lpszPath, BOOL bIsDir, BOOL bSelected)
                  {
                       SHFILEINFO sfi;
                       memset(&sfi, 0, sizeof(sfi));
              
                       if (bIsDir)
                       {
                        SHGetFileInfo(lpszPath, 
                                     FILE_ATTRIBUTE_DIRECTORY, 
                                     &sfi, 
                                     sizeof(sfi), 
                                     SHGFI_SMALLICON | SHGFI_SYSICONINDEX |
                                     SHGFI_USEFILEATTRIBUTES |(bSelected ? SHGFI_OPENICON : 0)); 
                        return  sfi.iIcon;
                       }
                       else
                       {
                        SHGetFileInfo (lpszPath, 
                                     FILE_ATTRIBUTE_NORMAL, 
                                     &sfi, 
                                     sizeof(sfi), 
                                     SHGFI_SMALLICON | SHGFI_SYSICONINDEX | 
                                     SHGFI_USEFILEATTRIBUTES | (bSelected ? SHGFI_OPENICON : 0));
                        return   sfi.iIcon;
                       }
                       return  -1;
                  }



            22. listctrl內容進行大數據量更新時,避免閃爍

                  m_list.SetRedraw(FALSE);
                  //更新內容
                  m_list.SetRedraw(TRUE);
                  m_list.Invalidate();
                  m_list.UpdateWindow();
             
            或者參考

            http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclib/html/_mfc_cwnd.3a3a.setredraw.asp



            23. listctrl排序

            Q250614:How To Sort Items in a CListCtrl in Report View
            http://support.microsoft.com/kb/250614/en-us



            24. 在listctrl中選中某個item時動態改變其icon或bitmap

            Q141834: How to change the icon or the bitmap of a CListCtrl item in Visual C++
            http://support.microsoft.com/kb/141834/en-us

            How to change the icon or the bitmap of a

            CListCtrl item in Visual C++

            Article ID : 141834
            Last Review : June 2, 2005
            Revision : 3.0
            This article was previously published under Q141834
            NOTE: Microsoft Visual C++ NET (2002) supported both the managed code model that is provided by the .NET Framework and the unmanaged native Windows code model. The information in this article applies to unmanaged Visual C++ code only.
            T>

            SUMMARY

            This article shows how to change the icon or bitmap of a CListCtrl item when it is selected.

            MORE INFORMATION

            When you initialize the CListCtrl by calling CListCtrl::InsertItem(), you can pass in a value of I_IMAGECALLBACK for the index of the image. This means that the system expects you to fill in the image index when you get an LVN_GETDISPINFO notification. Inside of the handler for LVN_GETDISPINFO, you can check if the item is selected and set the appropriate image index.

            Sample Code

               BEGIN_MESSAGE_MAP(CTestView, CView)
            //{{AFX_MSG_MAP(CTestView)
            ON_WM_CREATE()
            //}}AFX_MSG_MAP
            ON_NOTIFY (LVN_GETDISPINFO, IDI_LIST, OnGetDispInfo)
            END_MESSAGE_MAP()

            int CTestView::OnCreate(LPCREATESTRUCT lpCreateStruct)
            {
            if (CView::OnCreate(lpCreateStruct) == -1)
            return -1;

            // m_pImage is a CTestView's member variable of type CImageList*
            // create the CImageList with 16x15 images
            m_pImage = new CImageList();
            VERIFY (m_pImage->Create (16, 15, TRUE, 0, 1));
            CBitmap bm;
            // IDR_MAINFRAME is the toolbar bitmap in a default AppWizard
            // project.
            bm.LoadBitmap (IDR_MAINFRAME);
            // This will automatically parse the bitmap into nine images.
            m_pImage->Add (&bm, RGB (192, 192, 192));

            // m_pList is CTestView's member variable of type CListCtrl*
            // create the CListCtrl.
            m_pList = new CListCtrl();
            VERIFY (m_pList->Create (WS_VISIBLE | WS_CHILD | LVS_REPORT |
            LVS_EDITLABELS, CRect (0, 0, 400, 400), this, IDI_LIST));
            // Create column.
            m_pList->InsertColumn (0, "Button Number", LVCFMT_LEFT, 100);
            // Associate CImageList with CListCtrl.
            m_pList->SetImageList (m_pImage, LVSIL_SMALL);

            char szTemp[10];
            for (int iCntr = 0; iCntr < 9; iCntr++)
            {
            wsprintf (szTemp, "%d", iCntr);
            m_pList->InsertItem (LVIF_IMAGE | LVIF_TEXT,
            iCntr, szTemp, 0, 0, I_IMAGECALLBACK, 0L);
            }
            return 0;
            }

            void CTestView::OnGetDispInfo (NMHDR* pnmhdr, LRESULT* pResult)
            {
            LV_DISPINFO* pdi = (LV_DISPINFO *) pnmhdr;

            // Fill in the LV_ITEM structure with the image info.
            // When an item is selected, the image is set to the first
            // image (the new bitmap on the toolbar).
            // When it is not selected, the image index is equal to the
            // item number (that is, 0=new, 1=open, 2=save, and so on.)
            if (LVIS_SELECTED == m_pList->GetItemState (pdi->item.iItem,
            LVIS_SELECTED))
            pdi->item.iImage = 0;
            else
            pdi->item.iImage = pdi->item.iItem;
            }

            CTestView::~CTestView()
            {
            // Clean up.
            delete m_pImage;
            delete m_pList;
            }


            25. 在添加item后,再InsertColumn()后導致整列數據移動的問題

            Q151897: CListCtrl::InsertColumn() Causes Column Data to Shift
            http://support.microsoft.com/kb/151897/en-us



            26. 關于listctrl第一列始終居左的問題

            解決辦法:把第一列當一個虛列,從第二列開始插入列及數據,最后刪除第一列。
                 
            具體解釋參閱   http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/listview/structures/lvcolumn.asp

             


            27. 鎖定column header的拖動

            http://msdn.microsoft.com/msdnmag/issues/03/06/CQA/



            28. 如何隱藏clistctrl的列

                把需隱藏的列的寬度設為0,然后檢測當該列為隱藏列時,用上面第27點的鎖定column 的拖動來實現


            29. listctrl進行大數據量操作時,使用virtual list   

            http://www.microsoft.com/msj/archive/S2061.aspx
            http://www.codeguru.com/cpp/controls/listview/advanced/article.php/c4151/
            http://www.codeproject.com/listctrl/virtuallist.asp



            30. 關于item只能顯示259個字符的問題

            解決辦法:需要在item上放一個edit。



            31. 響應在listctrl的column header上的鼠標右鍵單擊

            Q125694: How To Find Out Which Listview Column Was Right-Clicked
            http://support.microsoft.com/kb/125694/en-us



            32. 類似于windows資源管理器的listview

            Q234310: How to implement a ListView control that is similar to Windows Explorer by using DirLV.exe
            http://support.microsoft.com/kb/234310/en-us

             


            33. 在ListCtrl中OnTimer只響應兩次的問題

            Q200054:
            PRB: OnTimer() Is Not Called Repeatedly for a List Control
            http://support.microsoft.com/kb/200054/en-us


            34. 以下為一些為實現各種自定義功能的listctrl派生類

                      (1)    拖放       
                               http://www.codeproject.com/listctrl/dragtest.asp

                               在CListCtrl和CTreeCtrl間拖放
                               http://support.microsoft.com/kb/148738/en-us
             
                      (2)    多功能listctrl
                               支持subitem可編輯,圖標,radiobutton,checkbox,字符串改變顏色的類
                               http://www.codeproject.com/listctrl/quicklist.asp
             
                               支持排序,subitem可編輯,subitem圖標,subitem改變顏色的類
                               http://www.codeproject.com/listctrl/ReportControl.asp

                      (3)    subitem中顯示超鏈接
                               http://www.codeproject.com/listctrl/CListCtrlLink.asp

                      (4)    subitem的tooltip提示
                               http://www.codeproject.com/listctrl/ctooltiplistctrl.asp

                      (5)    subitem中顯示進度條   
                               http://www.codeproject.com/listctrl/ProgressListControl.asp
                               http://www.codeproject.com/listctrl/napster.asp
                               http://www.codeguru.com/Cpp/controls/listview/article.php/c4187/

                      (6)    動態改變subitem的顏色和背景色
                                http://www.codeproject.com/listctrl/highlightlistctrl.asp
                                http://www.codeguru.com/Cpp/controls/listbox/colorlistboxes/article.php/c4757/
             
                      (7)    類vb屬性對話框
                                http://www.codeproject.com/listctrl/propertylistctrl.asp
                                http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c995/
                                http://www.codeguru.com/Cpp/controls/listview/propertylists/article.php/c1041/
             
                      (8)    選中subitem(只高亮選中的item)
                                http://www.codeproject.com/listctrl/SubItemSel.asp
                                http://www.codeproject.com/listctrl/ListSubItSel.asp
             
                      (9)    改變行高
                                http://www.codeproject.com/listctrl/changerowheight.asp
             
                      (10)   改變行顏色
                                http://www.codeproject.com/listctrl/coloredlistctrl.asp
             
                      (11)   可編輯subitem的listctrl
                                http://www.codeproject.com/listctrl/nirs2000.asp
                                http://www.codeproject.com/listctrl/editing_subitems_in_listcontrol.asp
             
                      (12)   subitem可編輯,插入combobox,改變行顏色,subitem的tooltip提示
                                http://www.codeproject.com/listctrl/reusablelistcontrol.asp
             
                      (13)   header 中允許多行字符串
                                http://www.codeproject.com/listctrl/headerctrlex.asp
             
                      (14)   插入combobox
                                http://www.codeguru.com/Cpp/controls/listview/editingitemsandsubitem/article.php/c979/
             
                      (15)   添加背景圖片
                                http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c4173/
                                http://www.codeguru.com/Cpp/controls/listview/backgroundcolorandimage/article.php/c983/
                                http://www.vchelp.net/vchelp/archive.asp?type_id=9&class_id=1&cata_id=1&article_id=1088&search_term=
               
                      (16)  自適應寬度的listctrl
                                http://www.codeproject.com/useritems/AutosizeListCtrl.asp

                      (17)  改變ListCtrl高亮時的顏色(默認為藍色)
                               處理 NM_CUSTOMDRAW
                       http://www.codeproject.com/listctrl/lvcustomdraw.asp

                 (18)  改變header顏色
                      http://www.pocketpcdn.com/articles/hdr_color.html


            原文地址 http://blog.csdn.net/lixiaosan/archive/2006/04/07/653563.aspx

            posted @ 2007-11-20 14:08 平凡的天才 閱讀(9076) | 評論 (3)編輯 收藏

            2007年9月13日

               當前流行的Windows操作系統能同時運行幾個程序(獨立運行的程序又稱之為進程),對于同一個程序,它又可以分成若干個獨立的執行流,我們稱之 為線程,線程提供了多任務處理的能力。用進程和線程的觀點來研究軟件是當今普遍采用的方法,進程和線程的概念的出現,對提高軟件的并行性有著重要的意義。 現在的大型應用軟件無一不是多線程多任務處理,單線程的軟件是不可想象的。因此掌握多線程多任務設計方法對每個程序員都是必需要掌握的。本實例針對多線程 技術在應用中經常遇到的問題,如線程間的通信、同步等,分別進行探討,并利用多線程技術進行線程之間的通信,實現了數字的簡單排序。  

            一、 實現方法

            1、理解線程

            要講解線程,不得不說一下進程,進程是應用程序的執行實例,每個進程是由私有的虛擬地址空間、代碼、數據和其它系統資源組成。進程在運行時創建的資源 隨著進程的終止而死亡。線程的基本思想很簡單,它是一個獨立的執行流,是進程內部的一個獨立的執行單元,相當于一個子程序,它對應于Visual C++中的CwinThread類對象。單獨一個執行程序運行時,缺省地包含的一個主線程,主線程以函數地址的形式出現,提供程序的啟動點,如main ()或WinMain()函數等。當主線程終止時,進程也隨之終止。根據實際需要,應用程序可以分解成許多獨立執行的線程,每個線程并行的運行在同一進程 中。

            一個進程中的所有線程都在該進程的虛擬地址空間中,使用該進程的全局變量和系統資源。操作系統給每個線程分配不同的CPU時間片,在某一個時刻, CPU只執行一個時間片內的線程,多個時間片中的相應線程在CPU內輪流執行,由于每個時間片時間很短,所以對用戶來說,仿佛各個線程在計算機中是并行處 理的。操作系統是根據線程的優先級來安排CPU的時間,優先級高的線程優先運行,優先級低的線程則繼續等待。

            線程被分為兩種:用戶界面線程和工作線程(又稱為后臺線程)。用戶界面線程通常用來處理用戶的輸入并響應各種事件和消息,其實,應用程序的主執行線程 CWinAPP對象就是一個用戶界面線程,當應用程序啟動時自動創建和啟動,同樣它的終止也意味著該程序的結束,進程終止。工作線程用來執行程序的后臺處 理任務,比如計算、調度、對串口的讀寫操作等,它和用戶界面線程的區別是它不用從CWinThread類派生來創建,對它來說最重要的是如何實現工作線程 任務的運行控制函數。工作線程和用戶界面線程啟動時要調用同一個函數的不同版本;最后需要讀者明白的是,一個進程中的所有線程共享它們父進程的變量,但同 時每個線程可以擁有自己的變量。

            2、線程的管理和操作

            (一)線程的啟動

            創建一個用戶界面線程,首先要從類CwinThread產生一個派生類,同時必須使用DECLARE_DYNCREATE和 IMPLEMENT_DYNCREATE來聲明和實現這個CwinThread派生類。第二步是根據需要重載該派生類的一些成員函數如: ExitInstance()、InitInstance()、OnIdle()、PreTranslateMessage()等函數。最后調用 AfxBeginThread()函數的一個版本:CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ) 啟動該用戶界面線程,其中第一個參數為指向定義的用戶界面線程類指針變量,第二個參數為線程的優先級,第三個參數為線程所對應的堆棧大小,第四個參數為線 程創建時的附加標志,缺省為正常狀態,如為CREATE_SUSPENDED則線程啟動后為掛起狀態。

            對于工作線程來說,啟動一個線程,首先需要編寫一個希望與應用程序的其余部分并行運行的函數如Fun1(),接著定義一個指向CwinThread對 象的指針變量*pThread,調用AfxBeginThread(Fun1,param,priority)函數,返回值賦給pThread變量的同時 一并啟動該線程來執行上面的Fun1()函數,其中Fun1是線程要運行的函數的名字,也既是上面所說的控制函數的名字,param是準備傳送給線程函數 Fun1的任意32位值,priority則是定義該線程的優先級別,它是預定義的常數,讀者可參考MSDN。

            (二)線程的優先級

            以下的CwinThread類的成員函數用于線程優先級的操作:

            int GetThreadPriority();
            BOOL SetThradPriority()(int nPriority);

              上述的二個函數分別用來獲取和設置線程的優先級,這里的優先級,是相對于該線程所處的優先權層次而言的,處于同一優先權層次的線程,優 先級高的線程先運行;處于不同優先權層次上的線程,誰的優先權層次高,誰先運行。至于優先級設置所需的常數,自己參考MSDN就可以了,要注意的是要想設 置線程的優先級,這個線程在創建時必須具有THREAD_SET_INFORMATION訪問權限。對于線程的優先權層次的設置,CwinThread類 沒有提供相應的函數,但是可以通過Win32 SDK函數GetPriorityClass()和SetPriorityClass()來實現。

            (三)線程的懸掛和恢復

            CWinThread類中包含了應用程序懸掛和恢復它所創建的線程的函數,其中SuspendThread()用來懸掛線程,暫停線程的執行; ResumeThread()用來恢復線程的執行。如果你對一個線程連續若干次執行SuspendThread(),則需要連續執行相應次的 ResumeThread()來恢復線程的運行。

            (四)結束線程

            終止線程有三種途徑,線程可以在自身內部調用AfxEndThread()來終止自身的運行;可以在線程的外部調用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )來強行終止一個線程的運行,然后調用CloseHandle()函數釋放線程所占用的堆棧;第三種方法是改變全局變量,使線程的執行函數返回,則該線程 終止。下面以第三種方法為例,給出部分代碼:

            ////////////////////////////////////////////////////////////////
            //////CtestView message handlers
            /////Set to True to end thread
            Bool bend=FALSE;//定義的全局變量,用于控制線程的運行;
            //The Thread Function;
            UINT ThreadFunction(LPVOID pParam)//線程函數
            {
            while(!bend)
            {
            Beep(100,100);
            Sleep(1000);
            }
            return 0;
            }
            /////////////////////////////////////////////////////////////
            CwinThread *pThread;
            HWND hWnd;
            Void CtestView::OninitialUpdate()
            {
            hWnd=GetSafeHwnd();
            pThread=AfxBeginThread(ThradFunction,hWnd);//啟動線程
            pThread->m_bAutoDelete=FALSE;//線程為手動刪除
            Cview::OnInitialUpdate();
            }
            ////////////////////////////////////////////////////////////////
            Void CtestView::OnDestroy()
            {
            bend=TRUE;//改變變量,線程結束
            WaitForSingleObject(pThread->m_hThread,INFINITE);//等待線程結束
            delete pThread;//刪除線程
            Cview::OnDestroy();
            }

              3、線程之間的通信

            通常情況下,一個次級線程要為主線程完成某種特定類型的任務,這就隱含著表示在主線程和次級線程之間需要建立一個通信的通道。一般情況下,有下面的幾 種方法實現這種通信任務:使用全局變量(上一節的例子其實使用的就是這種方法)、使用事件對象、使用消息。這里我們主要介紹后兩種方法。

            (一) 利用用戶定義的消息通信

            在Windows程序設計中,應用程序的每一個線程都擁有自己的消息隊列,甚至工作線程也不例外,這樣一來,就使得線程之間利用消息來傳遞信息就變的 非常簡單。首先用戶要定義一個用戶消息,如下所示:#define WM_USERMSG WMUSER+100;在需要的時候,在一個線程中調用::PostMessage((HWND)param,WM_USERMSG,0,0)或 CwinThread::PostThradMessage()來向另外一個線程發送這個消息,上述函數的四個參數分別是消息將要發送到的目的窗口的句 柄、要發送的消息標志符、消息的參數WPARAM和LPARAM。下面的代碼是對上節代碼的修改,修改后的結果是在線程結束時顯示一個對話框,提示線程結 束:

            UINT ThreadFunction(LPVOID pParam)
            {
            while(!bend)
            {
            Beep(100,100);
            Sleep(1000);
            }
            ::PostMessage(hWnd,WM_USERMSG,0,0);
            return 0;
            }
            ////////WM_USERMSG消息的響應函數為OnThreadended(WPARAM wParam,
            LPARAM lParam)
            LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam)
            {
            AfxMessageBox("Thread ended.");
            Retrun 0;
            }

              上面的例子是工作者線程向用戶界面線程發送消息,對于工作者線程,如果它的設計模式也是消息驅動的,那么調用者可以向它發送初始化、退 出、執行某種特定的處理等消息,讓它在后臺完成。在控制函數中可以直接使用::GetMessage()這個SDK函數進行消息分檢和處理,自己實現一個 消息循環。GetMessage()函數在判斷該線程的消息隊列為空時,線程將系統分配給它的時間片讓給其它線程,不無效的占用CPU的時間,如果消息隊 列不為空,就獲取這個消息,判斷這個消息的內容并進行相應的處理。

            (二)用事件對象實現通信

            在線程之間傳遞信號進行通信比較復雜的方法是使用事件對象,用MFC的Cevent類的對象來表示。事件對象處于兩種狀態之一:有信號和無信號,線程可以監視處于有信號狀態的事件,以便在適當的時候執行對事件的操作。上述例子代碼修改如下:

            ////////////////////////////////////////////////////////////////////
            Cevent threadStart ,threadEnd;
            UINT ThreadFunction(LPVOID pParam)
            {
            ::WaitForSingleObject(threadStart.m_hObject,INFINITE);
            AfxMessageBox("Thread start.");
            while(!bend)
            {
            Beep(100,100);
            Sleep(1000);
            Int result=::WaitforSingleObject(threadEnd.m_hObject,0);
            //等待threadEnd事件有信號,無信號時線程在這里懸停
            If(result==Wait_OBJECT_0)
            Bend=TRUE;
            }
            ::PostMessage(hWnd,WM_USERMSG,0,0);
            return 0;
            }
            /////////////////////////////////////////////////////////////
            Void CtestView::OninitialUpdate()
            {
            hWnd=GetSafeHwnd();
            threadStart.SetEvent();//threadStart事件有信號
            pThread=AfxBeginThread(ThreadFunction,hWnd);//啟動線程
            pThread->m_bAutoDelete=FALSE;
            Cview::OnInitialUpdate();
            }
            ////////////////////////////////////////////////////////////////
            Void CtestView::OnDestroy()
            {
            threadEnd.SetEvent();
            WaitForSingleObject(pThread->m_hThread,INFINITE);
            delete pThread;
            Cview::OnDestroy();
            }

              運行這個程序,當關閉程序時,才顯示提示框,顯示"Thread ended"。

              4、線程之間的同步

            前面我們講過,各個線程可以訪問進程中的公共變量,所以使用多線程的過程中需要注意的問題是如何防止兩個或兩個以上的線程同時訪問同一個數據,以免破 壞數據的完整性。保證各個線程可以在一起適當的協調工作稱為線程之間的同步。前面一節介紹的事件對象實際上就是一種同步形式。Visual C++中使用同步類來解決操作系統的并行性而引起的數據不安全的問題,MFC支持的七個多線程的同步類可以分成兩大類:同步對象 (CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步訪問對象 (CmultiLock和CsingleLock)。本節主要介紹臨界區(critical section)、互斥(mutexe)、信號量(semaphore),這些同步對象使各個線程協調工作,程序運行起來更安全。

            (一) 臨界區

            臨界區是保證在某一個時間只有一個線程可以訪問數據的方法。使用它的過程中,需要給各個線程提供一個共享的臨界區對象,無論哪個線程占有臨界區對象, 都可以訪問受到保護的數據,這時候其它的線程需要等待,直到該線程釋放臨界區對象為止,臨界區被釋放后,另外的線程可以強占這個臨界區,以便訪問共享的數 據。臨界區對應著一個CcriticalSection對象,當線程需要訪問保護數據時,調用臨界區對象的Lock()成員函數;當對保護數據的操作完成 之后,調用臨界區對象的Unlock()成員函數釋放對臨界區對象的擁有權,以使另一個線程可以奪取臨界區對象并訪問受保護的數據。同時啟動兩個線程,它 們對應的函數分別為WriteThread()和ReadThread(),用以對公共數組組array[]操作,下面的代碼說明了如何使用臨界區對象:

            #include "afxmt.h"
            int array[10],destarray[10];
            CCriticalSection Section;
            UINT WriteThread(LPVOID param)
            {
            Section.Lock();
            for(int x=0;x<10;x++)
            array[x]=x;
            Section.Unlock();
            }
            UINT ReadThread(LPVOID param)
            {
            Section.Lock();
            For(int x=0;x<10;x++)
            Destarray[x]=array[x];
            Section.Unlock();
            }

              上述代碼運行的結果應該是Destarray數組中的元素分別為1-9,而不是雜亂無章的數,如果不使用同步,則不是這個結果,有興趣的讀者可以實驗一下。

            (二)互斥

            互斥與臨界區很相似,但是使用時相對復雜一些,它不僅可以在同一應用程序的線程間實現同步,還可以在不同的進程間實現同步,從而實現資源的安全共享。 互斥與Cmutex類的對象相對應,使用互斥對象時,必須創建一個CSingleLock或CMultiLock對象,用于實際的訪問控制,因為這里的例 子只處理單個互斥,所以我們可以使用CSingleLock對象,該對象的Lock()函數用于占有互斥,Unlock()用于釋放互斥。實現代碼如下:

            #include "afxmt.h"
            int array[10],destarray[10];
            CMutex Section;

            UINT WriteThread(LPVOID param)
            {
            CsingleLock singlelock;
            singlelock (&Section);
            singlelock.Lock();
            for(int x=0;x<10;x++)
            array[x]=x;
            singlelock.Unlock();
            }

            UINT ReadThread(LPVOID param)
            {
            CsingleLock singlelock;
            singlelock (&Section);
            singlelock.Lock();
            For(int x=0;x<10;x++)
            Destarray[x]=array[x];
            singlelock.Unlock();
            }

              (三)信號量

            信號量的用法和互斥的用法很相似,不同的是它可以同一時刻允許多個線程訪問同一個資源,創建一個信號量需要用Csemaphore類聲明一個對象,一 旦創建了一個信號量對象,就可以用它來對資源的訪問技術。要實現計數處理,先創建一個CsingleLock或CmltiLock對象,然后用該對象的 Lock()函數減少這個信號量的計數值,Unlock()反之。下面的代碼分別啟動三個線程,執行時同時顯示二個消息框,然后10秒后第三個消息框才得 以顯示。

            /////////////////////////////////////////////////////////////////////////
            Csemaphore *semaphore;
            Semaphore=new Csemaphore(2,2);
            HWND hWnd=GetSafeHwnd();
            AfxBeginThread(threadProc1,hWnd);
            AfxBeginThread(threadProc2,hWnd);
            AfxBeginThread(threadProc3,hWnd);
            UINT ThreadProc1(LPVOID param)
            {
            CsingleLock singelLock(semaphore);
            singleLock.Lock();
            Sleep(10000);
            ::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK);
            return 0;
            }
            UINT ThreadProc2(LPVOID param)
            {
            CSingleLock singelLock(semaphore);
            singleLock.Lock();
            Sleep(10000);
            ::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK);
            return 0;
            }

            UINT ThreadProc3(LPVOID param)
            {
            CsingleLock singelLock(semaphore);
            singleLock.Lock();
            Sleep(10000);
            ::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK);
            return 0;
            }

              二、 編程步驟

            1、 啟動Visual C++6.0,生成一個32位的控制臺程序,將該程序命名為"sequence"

            2、 輸入要排續的數字,聲明四個子線程;

            3、 輸入代碼,編譯運行程序。

            三、 程序代碼

            //////////////////////////////////////////////////////////////////////////////////////
            // sequence.cpp : Defines the entry point for the console application.
            /*
            主要用到的WINAPI線程控制函數,有關詳細說明請查看MSDN;
            線程建立函數:
            HANDLE CreateThread(
            LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全屬性結構指針,可為NULL;
            DWORD dwStackSize, // 線程棧大小,若為0表示使用默認值;
            LPTHREAD_START_ROUTINE lpStartAddress, // 指向線程函數的指針;
            LPVOID lpParameter, // 傳遞給線程函數的參數,可以保存一個指針值;
            DWORD dwCreationFlags, // 線程建立是的初始標記,運行或掛起;
            LPDWORD lpThreadId // 指向接收線程號的DWORD變量;
            );

            對臨界資源控制的多線程控制的信號函數:

            HANDLE CreateEvent(
            LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全屬性結構指針,可為NULL;
            BOOL bManualReset, // 手動清除信號標記,TRUE在WaitForSingleObject后必須手動//調用RetEvent清除信號。若為 FALSE則在WaitForSingleObject
            //后,系統自動清除事件信號;
            BOOL bInitialState, // 初始狀態,TRUE有信號,FALSE無信號;
            LPCTSTR lpName // 信號量的名稱,字符數不可多于MAX_PATH;
            //如果遇到同名的其他信號量函數就會失敗,如果遇
            //到同類信號同名也要注意變化;
            );

            HANDLE CreateMutex(
            LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全屬性結構指針,可為NULL
            BOOL bInitialOwner, // 當前建立互斥量是否占有該互斥量TRUE表示占有,
            //這樣其他線程就不能獲得此互斥量也就無法進入由
            //該互斥量控制的臨界區。FALSE表示不占有該互斥量
            LPCTSTR lpName // 信號量的名稱,字符數不可多于MAX_PATH如果
            //遇到同名的其他信號量函數就會失敗,
            //如果遇到同類信號同名也要注意變化;
            );

            //初始化臨界區信號,使用前必須先初始化
            VOID InitializeCriticalSection(
            LPCRITICAL_SECTION lpCriticalSection // 臨界區變量指針
            );

            //阻塞函數
            //如果等待的信號量不可用,那么線程就會掛起,直到信號可用
            //線程才會被喚醒,該函數會自動修改信號,如Event,線程被喚醒之后
            //Event信號會變得無信號,Mutex、Semaphore等也會變。
            DWORD WaitForSingleObject(
            HANDLE hHandle, // 等待對象的句柄
            DWORD dwMilliseconds // 等待毫秒數,INFINITE表示無限等待
            );
            //如果要等待多個信號可以使用WaitForMutipleObject函數
            */

            #include "stdafx.h"
            #include "stdlib.h"
            #include "memory.h"
            HANDLE evtTerminate; //事件信號,標記是否所有子線程都執行完
            /*
            下面使用了三種控制方法,你可以注釋其中兩種,使用其中一種。
            注意修改時要連帶修改臨界區PrintResult里的相應控制語句
            */
            HANDLE evtPrint; //事件信號,標記事件是否已發生
            //CRITICAL_SECTION csPrint; //臨界區
            //HANDLE mtxPrint; //互斥信號,如有信號表明已經有線程進入臨界區并擁有此信號
            static long ThreadCompleted = 0;
            /*用來標記四個子線程中已完成線程的個數,當一個子線程完成時就對ThreadCompleted進行加一操作, 要使用InterlockedIncrement(long* lpAddend)和InterlockedDecrement(long* lpAddend)進行加減操作*/

            //下面的結構是用于傳送排序的數據給各個排序子線程
            struct MySafeArray
            {
            long* data;
            int iLength;
            };

            //打印每一個線程的排序結果
            void PrintResult(long* Array, int iLength, const char* HeadStr = "sort");

            //排序函數
            unsigned long __stdcall BubbleSort(void* theArray); //冒泡排序
            unsigned long __stdcall SelectSort(void* theArray); //選擇排序
            unsigned long __stdcall HeapSort(void* theArray); //堆排序
            unsigned long __stdcall InsertSort(void* theArray); //插入排序
            /*以上四個函數的聲明必須適合作為一個線程函數的必要條件才可以使用CreateThread
            建立一個線程。
            (1)調用方法必須是__stdcall,即函數參數壓棧順序由右到左,而且由函數本身負責
            棧的恢復, C和C++默認是__cdecl, 所以要顯式聲明是__stdcall
            (2)返回值必須是unsigned long
            (3)參數必須是一個32位值,如一個指針值或long類型
            (4) 如果函數是類成員函數,必須聲明為static函數,在CreateThread時函數指針有特殊的寫法。如下(函數是類CThreadTest的成員函數中):
            static unsigned long _stdcall MyThreadFun(void* pParam);
            handleRet = CreateThread(NULL, 0, &CThreadTestDlg::MyThreadFun, NULL, 0, &ThreadID);
            之所以要聲明為static是由于,該函數必須要獨立于對象實例來使用,即使沒有聲明實例也可以使用。*/

            int QuickSort(long* Array, int iLow, int iHigh); //快速排序

            int main(int argc, char* argv[])
            {
            long data[] = {123,34,546,754,34,74,3,56};
            int iDataLen = 8;
            //為了對各個子線程分別對原始數據進行排序和保存排序結果
            //分別分配內存對data數組的數據進行復制
            long *data1, *data2, *data3, *data4, *data5;
            MySafeArray StructData1, StructData2, StructData3, StructData4;
            data1 = new long[iDataLen];
            memcpy(data1, data, iDataLen << 2); //把data中的數據復制到data1中
            //內存復制 memcpy(目標內存指針, 源內存指針, 復制字節數), 因為long的長度
            //為4字節,所以復制的字節數為iDataLen << 2, 即等于iDataLen*4
            StructData1.data = data1;
            StructData1.iLength = iDataLen;
            data2 = new long[iDataLen];
            memcpy(data2, data, iDataLen << 2);
            StructData2.data = data2;
            StructData2.iLength = iDataLen;
            data3 = new long[iDataLen];
            memcpy(data3, data, iDataLen << 2);
            StructData3.data = data3;
            StructData3.iLength = iDataLen;
            data4 = new long[iDataLen];
            memcpy(data4, data, iDataLen << 2);
            StructData4.data = data4;
            StructData4.iLength = iDataLen;
            data5 = new long[iDataLen];
            memcpy(data5, data, iDataLen << 2);
            unsigned long TID1, TID2, TID3, TID4;
            //對信號量進行初始化
            evtTerminate = CreateEvent(NULL, FALSE, FALSE, "Terminate");
            evtPrint = CreateEvent(NULL, FALSE, TRUE, "PrintResult");
            //分別建立各個子線程
            CreateThread(NULL, 0, &BubbleSort, &StructData1, NULL, &TID1);
            CreateThread(NULL, 0, &SelectSort, &StructData2, NULL, &TID2);
            CreateThread(NULL, 0, &HeapSort, &StructData3, NULL, &TID3);
            CreateThread(NULL, 0, &InsertSort, &StructData4, NULL, &TID4);
            //在主線程中執行行快速排序,其他排序在子線程中執行
            QuickSort(data5, 0, iDataLen - 1);
            PrintResult(data5, iDataLen, "Quick Sort");
            WaitForSingleObject(evtTerminate, INFINITE); //等待所有的子線程結束
            //所有的子線程結束后,主線程才可以結束
            delete[] data1;
            delete[] data2;
            delete[] data3;
            delete[] data4;
            CloseHandle(evtPrint);
            return 0;
            }

            /*
            冒泡排序思想(升序,降序同理,后面的算法一樣都是升序):從頭到尾對數據進行兩兩比較進行交換,小的放前大的放后。這樣一次下來,最大的元素就會被交換的最后,然后下一次
            循環就不用對最后一個元素進行比較交換了,所以呢每一次比較交換的次數都比上一次循環的次數少一,這樣N次之后數據就變得升序排列了*/
            unsigned long __stdcall BubbleSort(void* theArray)
            {
            long* Array = ((MySafeArray*)theArray)->data;
            int iLength = ((MySafeArray*)theArray)->iLength;
            int i, j=0;
            long swap;
            for (i = iLength-1; i >0; i--)
            {
            for(j = 0; j < i; j++)
            {
            if(Array[j] >Array[j+1]) //前比后大,交換
            {
            swap = Array[j];
            Array[j] = Array[j+1];
            Array[j+1] = swap;
            }
            }
            }
            PrintResult(Array, iLength, "Bubble Sort"); //向控制臺打印排序結果
            InterlockedIncrement(&ThreadCompleted); //返回前使線程完成數標記加1
            if(ThreadCompleted == 4) SetEvent(evtTerminate); //檢查是否其他線程都已執行完
            //若都執行完則設置程序結束信號量
            return 0;
            }

            /*選擇排序思想:每一次都從無序的數據中找出最小的元素,然后和前面已經有序的元素序列的后一個元素進行交換,這樣整個源序列就會分成兩部分,前面一部 分是已經排好序的有序序列,后面一部分是無序的,用于選出最小的元素。循環N次之后,前面的有序序列加長到跟源序列一樣長,后面的無序部分長度變為0,排 序就完成了。*/
            unsigned long __stdcall SelectSort(void* theArray)
            {
            long* Array = ((MySafeArray*)theArray)->data;
            int iLength = ((MySafeArray*)theArray)->iLength;
            long lMin, lSwap;
            int i, j, iMinPos;
            for(i=0; i < iLength-1; i++)
            {
            lMin = Array[i];
            iMinPos = i;
            for(j=i + 1; j <= iLength-1; j++) //從無序的元素中找出最小的元素
            {
            if(Array[j] < lMin)
            {
            iMinPos = j;
            lMin = Array[j];
            }
            }
            //把選出的元素交換拼接到有序序列的最后
            lSwap = Array[i];
            Array[i] = Array[iMinPos];
            Array[iMinPos] = lSwap;
            }
            PrintResult(Array, iLength, "Select Sort"); //向控制臺打印排序結果
            InterlockedIncrement(&ThreadCompleted); //返回前使線程完成數標記加1
            if(ThreadCompleted == 4) SetEvent(evtTerminate);//檢查是否其他線程都已執行完
            //若都執行完則設置程序結束信號量
            return 0;
            }

            /*堆排序思想:堆:數據元素從1到N排列成一棵二叉樹,而且這棵樹的每一個子樹的根都是該樹中的元素的最小或最大的元素這樣如果一個無序數據集合是一個 堆那么,根元素就是最小或最大的元素堆排序就是不斷對剩下的數據建堆,把最小或最大的元素析透出來。下面的算法,就是從最后一個元素開始,依據一個節點比 父節點數值大的原則對所有元素進行調整,這樣調整一次就形成一個堆,第一個元素就是最小的元素。然后再對剩下的無序數據再進行建堆,注意這時后面的無序數 據元素的序數都要改變,如第一次建堆后,第二個元素就會變成堆的第一個元素。*/
            unsigned long __stdcall HeapSort(void* theArray)
            {
            long* Array = ((MySafeArray*)theArray)->data;
            int iLength = ((MySafeArray*)theArray)->iLength;
            int i, j, p;
            long swap;
            for(i=0; i {
            for(j = iLength - 1; j>i; j--) //從最后倒數上去比較字節點和父節點
            {
            p = (j - i - 1)/2 + i; //計算父節點數組下標
            //注意到樹節點序數跟數組下標不是等同的,因為建堆的元素個數逐個遞減
            if(Array[j] < Array[p]) //如果父節點數值大則交換父節點和字節點
            {
            swap = Array[j];
            Array[j] = Array[p];
            Array[p] = swap;
            }
            }
            }
            PrintResult(Array, iLength, "Heap Sort"); //向控制臺打印排序結果
            InterlockedIncrement(&ThreadCompleted); //返回前使線程完成數標記加1
            if(ThreadCompleted == 4) SetEvent(evtTerminate); //檢查是否其他線程都已執行完
            //若都執行完則設置程序結束信號量
            return 0;
            }

            /*插入排序思想:把源數據序列看成兩半,前面一半是有序的,后面一半是無序的,把無序的數據從頭到尾逐個逐個的插入到前面的有序數據中,使得有序的數據的個數不斷增大,同時無序的數據個數就越來越少,最后所有元素都會變得有序。*/
            unsigned long __stdcall InsertSort(void* theArray)
            {
            long* Array = ((MySafeArray*)theArray)->data;
            int iLength = ((MySafeArray*)theArray)->iLength;
            int i=1, j=0;
            long temp;
            for(i=1; i {
            temp = Array[i]; //取出序列后面無序數據的第一個元素值
            for(j=i; j>0; j--) //和前面的有序數據逐個進行比較找出合適的插入位置
            {
            if(Array[j - 1] >temp) //如果該元素比插入值大則后移
            Array[j] = Array[j - 1];
            else //如果該元素比插入值小,那么該位置的后一位就是插入元素的位置
            break;
            }
            Array[j] = temp;
            }
            PrintResult(Array, iLength, "Insert Sort"); //向控制臺打印排序結果
            InterlockedIncrement(&ThreadCompleted); //返回前使線程完成數標記加1
            if(ThreadCompleted == 4) SetEvent(evtTerminate); //檢查是否其他線程都已執行完
            //若都執行完則設置程序結束信號量
            return 0;
            }

            /*快速排序思想:快速排序是分治思想的一種應用,它先選取一個支點,然后把小于支點的元素交換到支點的前邊,把大于支點的元素交換到支點的右邊。然后再對支點左邊部分和右
            邊部分進行同樣的處理,這樣若干次之后,數據就會變得有序。下面的實現使用了遞歸
            建立兩個游標:iLow,iHigh;iLow指向序列的第一個元素,iHigh指向最后一個先選第一個元素作為支點,并把它的值存貯在一個輔助變量里。 那么第一個位置就變為空并可以放置其他的元素。 這樣從iHigh指向的元素開始向前移動游標,iHigh查找比支點小的元素,如果找到,則把它放置到空置了的位置(現在是第一個位置),然后iHigh 游標停止移動,這時iHigh指向的位置被空置,然后移動iLow游標尋找比支點大的元素放置到iHigh指向的空置的位置,如此往復直到iLow與 iHigh相等。最后使用遞歸對左右兩部分進行同樣處理*/

            int QuickSort(long* Array, int iLow, int iHigh)
            {
            if(iLow >= iHigh) return 1; //遞歸結束條件
            long pivot = Array[iLow];
            int iLowSaved = iLow, iHighSaved = iHigh; //保未改變的iLow,iHigh值保存起來
            while (iLow < iHigh)
            {
            while (Array[iHigh] >= pivot && iHigh >iLow) //尋找比支點大的元素
            iHigh -- ;
            Array[iLow] = Array[iHigh]; //把找到的元素放置到空置的位置
            while (Array[iLow] < pivot && iLow < iHigh) //尋找比支點小的元素
            iLow ++ ;
            Array[iHigh] = Array[iLow]; //把找到的元素放置到空置的位置
            }
            Array[iLow] = pivot; //把支點值放置到支點位置,這時支點位置是空置的
            //對左右部分分別進行遞歸處理
            QuickSort(Array, iLowSaved, iHigh-1);
            QuickSort(Array, iLow+1, iHighSaved);
            return 0;
            }

            //每一個線程都要使用這個函數進行輸出,而且只有一個顯示器,產生多個線程
            //競爭對控制臺的使用權。
            void PrintResult(long* Array, int iLength, const char* HeadStr)
            {
            WaitForSingleObject(evtPrint, INFINITE); //等待事件有信號
            //EnterCriticalSection(&csPrint); //標記有線程進入臨界區
            //WaitForSingleObject(mtxPrint, INFINITE); //等待互斥量空置(沒有線程擁有它)
            int i;
            printf("%s: ", HeadStr);
            for (i=0; i {
            printf("%d,", Array[i]);
            Sleep(100); //延時(可以去掉)
            /*只是使得多線程對臨界區訪問的問題比較容易看得到
            如果你把臨界控制的語句注釋掉,輸出就會變得很凌亂,各個排序的結果會
            分插間隔著輸出,如果不延時就不容易看到這種不對臨界區控制的結果
            */
            }
            printf("%d\n", Array[i]);
            SetEvent(evtPrint); //把事件信號量恢復,變為有信號
            }

              四、 小結

            對復雜的應用程序來說,線程的應用給應用程序提供了高效、快速、安全的數據處理能力。本實例講述了線程處理中經常遇到的問題,希望對讀者朋友有一定的幫助,起到拋磚引玉的作用。

            轉自(http://hi.baidu.com/laodun/blog/item/8ab8f3241af3f7318644f916.html)

            posted @ 2007-09-13 20:13 平凡的天才 閱讀(1239) | 評論 (0)編輯 收藏

            2007年4月17日

          1. 需要構造器嗎?
          2. 數據成員是private的嗎?它可以是const的嗎?
          3. 需要默認構造器嗎?
          4. 是不是每個構造器初始化了所有成員?
          5. 需要析構器嗎?它需要虛化嗎?
          6. 需要拷貝構造器嗎?
          7. 需要assigment operator嗎?它能正確自賦值嗎?
          8. 需要關系操作符嗎?
          9. 在函數形參上使用了const嗎?在成員函數之后呢?
          10. 刪除數組成員時用delete []嗎?
          11. posted @ 2007-04-17 16:44 平凡的天才 閱讀(870) | 評論 (0)編輯 收藏

            2007年4月13日

            1.賦值(=),下標([]),調用( () )和成員訪問箭頭(->)等操作符必須定義為成員,將這些定義為非成員函數將在編譯時標記為錯誤

            2.像賦值一樣,復合賦值操作符通常應定義為類的成員,與賦值不同的是,不一定非得這樣做,如果定義非成員復合賦值操作符,不會出現編譯錯誤.
            3.改變對象狀態或與給頂類型緊密聯系的其他一些操作符,如自增,自減和自解引用,通常應定義為類成員
            4.對稱的操作符號,如算術操作符,相等操作符,關系操作符和位操作符,最好定義為普通非成員函數.

            posted @ 2007-04-13 11:40 平凡的天才 閱讀(955) | 評論 (0)編輯 收藏

            既可以在函數聲明也可以在函數定義中指定默認實參.但是在一個文件中,只能為一個形參指定默認實參一次下面的例子是錯誤的,
            //ff.h
            int ff(int=0);
            //ff.cc
            #include "ff.h"
            int ff(int i=0){}//error
            如果在函數定義的形參表中提供默認實參,那么只有在包含該函數定義的源文件中調用該函數時,默認實參才是有效的.

            posted @ 2007-04-13 10:43 平凡的天才 閱讀(964) | 評論 (0)編輯 收藏

            2007年4月10日

            有些成員必須在構造函數初始化列表中進行初始化.對于這樣的成員,在構造函數函數體中對它們賦值不起作用.必須使用初始化列表的情況有以下幾種:
            1.沒有默認構造函數的類類型的成員(這是因為構造函數分為兩個階段:1.初始化階段,2.普通計算階段.初始化階段發生在計算階段開始之前)
            2.成員為const或引用類型.
             

            posted @ 2007-04-10 12:01 平凡的天才 閱讀(1746) | 評論 (0)編輯 收藏

            2007年4月9日

               可以聲明一個類而不定義它
               class Screen;//declaration of the Screen class
               這個聲明,有時候被稱為前向聲明(forward declaration),在程序中引入了類類型的Screen.在聲明之后,定義之前,類Screen是一個不完全類型(incompete type),即已知Screen是一個類型,但不知道包含哪些成員.
               不完全類型只能以有限方式使用,不能定義該類型的對象,不完全類型只能用于定義指向該類型的指針及引用,或者用于聲明(而不是定義)使用該類型作為形參類型或返回類型的函數.

            posted @ 2007-04-09 23:24 平凡的天才 閱讀(5307) | 評論 (3)編輯 收藏

            2007年3月9日

            PostMessage 只是把消息放入隊列,不管其他程序是否處理都返回,然后繼續執行 ;
            SendMessage 必須等待其他程序處理消息后才返回,繼續執行。
            PostMessage
            的返回值表示 PostMessage 函數執行是否正確 ;
            SendMessage 的返回值表示其他程序處理消息后的返回值。
            使用這兩個發送消息函數的最重要的是要看你的程序是否要對消息的滯后性關注否 ,PostMessage 會造成消息的滯后性 , SendMessage 則不會 , 但如果 SendMessage 消息處理失敗 , 則會造成程序停止 !

            posted @ 2007-03-09 11:31 平凡的天才 閱讀(7362) | 評論 (4)編輯 收藏

            轉載自http://blog.csdn.net/Image_Graphics/archive/2006/11/22/1405436.aspx


            1. 怎樣使用MFC發送一個消息用MFC發送一個消息的方法是,
            ????首先,應獲取接收消息的CWnd類對象的指針;
            ????然后,調用CWnd的成員函數SendMessage( )。
            ????????LRESULT Res=pWnd->SendMessage(UINT Msg, WPARAM wParam, LPARAM lParam);
            ????????pWnd指針指向目標CWnd類對象。變量Msg是消息,wParam和lParam變量包含消息的參數,如鼠標單擊哪里或選擇了什么菜單項。目標窗口返回的消息結果放在變量Res中。
            ????????發送消息到一個沒有CWnd類對象的窗口,可以用下列目標窗口的句柄直接調用Windows API:
            ????????LRESULT Res=::SendMessage(HWND hWnd, UINT Msg,? WPARAM wParam, LPARAM lParam);
            ????????這里的hWnd是目標窗口的句柄。
            2. 怎樣用MFC寄送一個消息
            ????用MFC寄送一個消息與發送一個消息幾乎相同,但寄送時用PostMessage( ) ,而不是用SendMessage( );返回值Res也不一樣,Res不是一個由目標窗口返回的值,而是一個布爾值,用來表示消息是否成功地放到消息隊列中。
            3. 檢索一個寄送消息
            ????正常情況下,一旦消息被寄送后,應用程序在后臺發送它。但是在特殊情況下,需要你自己去刪除一個消息,例如想在應用程序接收到某種消息之前停止應用程序。有兩種方法可以從應用程序消息隊列中刪除一個消息,但這兩種方法都沒有涉及MFC。
            ■ 第一種方法:在不干擾任何事情之下窺視消息隊列,看看一個消息是否在那里。
            ????BOOL res=::PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg ) ;
            ■ 第二種方法:實際上是等待,一直等到一個新的消息到達隊列為止,然后刪除并返回該消息。
            ????BOOL res=::GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);
            ????在這兩種方法中,變量hWnd指定要截獲消息的窗口,如果該變量設為NULL,所有窗口消息將被截獲。wMsgFilterMin和wMsgFilterMax變量與SendMessage( )中的變量Msg相對應,指定查看消息的范圍。如果用"0,0",則所有的消息都將被截獲。如果用WM_KEYFIRST,WM_KEYLAST或WM_MOUSEFIRST,WM_MOUSELAST,則所有鍵盤或鼠標的消息將被截獲。wRemoveMsg變量指定PeekMessage( )是否應該真正地從隊列中刪除該消息。(GetMessage( )總是刪除消息)。該變量可以取兩個值:
            ????■ PM_REMOVE,PeekMessage( )將刪除消息。
            ????■ PM_NOREMOVE,PeekMessage( )將把消息留在隊列里,并返回它的一個拷貝。
            ????當然,如果把消息留在消息隊列中,然后再次調用PeekMessage( )查看相同類型的消息,則將返回完全相同的消息。
            ????lpMsg變量是一個指向MSG結構的指針,MSG包含檢索到的消息。
            ????typedef struct tagMSG {
            ????????????????????????HWND hwnd; // window handle message is intended for
            ????????????????????????UINT message;
            ????????????????????????WPARAM wParam;
            ????????????????????????LPARAM lParam;
            ????????????????????????DWORD time; // the time the message was put in the queue
            ????????????????????????POINT pt; // the location of the mouse cursor when the
            ?????????????????????????????????????? // message was put in the queue
            ????????????????????????} MSG;
            4. MFC怎樣接收一個寄送的消息
            ??? MFC處理一個寄送和發送消息的唯一明顯不同是寄送的消息要在應用程序的消息隊列中花費一些時間。在消息泵(message pump)彈出它之前,它要一直在隊列中。
            ??? 消息泵
            ??? MFC應用程序中的消息泵在CWinApp的成員函數Run()中。應用程序開始運行時,Run()就被調用,Run()把時間分割成兩部分。一部分用來執行后臺處理,如取消臨時CWnd對象;另一部分用來檢查消息隊列。當一個新的消息進來時,Run()抽取它—即用GetMessage( )從隊列中取出該消息,運行兩個消息翻譯函數,然后用DispatchMessage( )函數調用該消息預期的目標窗口進程。
            ??? 消息泵調用的兩個翻譯函數是PreTranslateMessage( )和::TranslateMessage( )。目標窗口的MFC類可調用reTranslateMessage在發送消息給它之前進行消息翻譯,例如,CFrameWnd用PreTranslateMessage( )將加速鍵(如,Ctrl+S存儲文件)轉換為命令消息。翻譯前的消息通常被處理掉,而翻譯后的消息(如果有的話)將被重新寄送到隊列里。::TranslateMessage是一個窗口函數,將原始鍵碼轉換為鍵字符。消息一旦被DispatchMessage()發送,MFC處理它就像處理SendMessage()發送的消息一樣。
            5. MFC怎樣處理一個接收到的消息
            ??? 處理接收到的消息的目的非常簡單:將消息指向一個函數,該函數通過消息中的消息標識符處理它。非MFC窗口用簡單的case語句來實現該目標,每個case語句執行一些函數,或調用其他一些函數。
            ??? MainWndProc(HWND hWnd, UINT message, W PARAM wParam,LPARAM lParam)
            ??? {
            ??????? switch(message)
            ??????? {
            ??????? case WM_CREATE:
            ??????????? : : :
            ??????? break;
            ??????? case WM_PAINT:
            ??????????? : : :
            ??????? break;
            ??????? default:
            ??????? return(DefWindowProc(hWnd,message,wParam,lParam));
            ??????? }
            ??????? return(NULL);
            ??? }
            ??? 任何遺漏的消息將被傳輸到一個默認的消息處理函數,但是,case語句不能很好地適應C++和封裝技術。在C++環境中,要求消息被一個專門處理該類型消息的類的成員函數處理。因此,MFC不采用case語句,而采用更加復雜和回旋的方法。但它允許用私有類處理消息,而只需做下面三件事情:
            ??? ■ 從將要接收消息的CWnd類對象派生類(對于命令消息是CCmdTarget)。
            ??? ■ 在派生類中寫一個處理消息的成員函數。
            ??? ■ 在類中定義一個查找表(叫做消息映像),該表具有成員函數的條目和它要處理的消息的標識符。
            ??? 然后,MFC依次調用下面的函數,指引輸入消息到處理函數。
            ??? 1) AfxWndProc( )接收消息,尋找消息所屬的CWnd對象,然后調用AfxCallWndProc( )。
            ??? 2) AfxCallWndProc( )存儲消息(消息標識符和參數)供未來參考,然后調用WindowProc( )。
            ??? 3) WindowProc( ) 發送消息給OnWndMsg( ) ,然后,如果消息未被處理,則發送給DefWindowproc( )。
            ??? 4) OnWndMsg( )要么為WM_COMMAND消息調用OnCommand( ),要么為WM_NOTIFY消息調用OnNotify( )。任何被遺漏的消息都將是一個窗口消息。OnWndMsg( )搜索類的消息映像,以找到一個能處理任何窗口消息的處理函數。如果OnWndMsg( )不能找到這樣的處理函數,則把消息返回到WindowProc( ),由它將消息發送給DefWindowProc( )。
            ??? 5) OnCommand()查看這是不是一個控件通知(lParam不是NULL);如果它是,OnCommand( )就試圖將消息映射到制造通知的控件;如果它不是一個控件通知,或者控件拒絕映射的消息,OnCommand( )就調用OnCmdMsg( )。
            ??? 6) OnNotify( )也試圖將消息映射到制造通知的控件;如果映射不成功, OnNotify( )就調用相同的OnCmdMsg( )函數。
            ??? 7) 根據接收消息的類,OnCmdMsg( )將在一個稱為命令傳遞(Command Routing)的過程中潛在地傳遞命令消息和控件通知。例如,如果擁有該窗口的類是一個框架類,則命令和通知消息也被傳遞到視圖和文檔類,并為該類尋找一個消息處理函數。
            為什么要消息映像?
            ??? 這畢竟是C++語言;為什么OnWndMsg( )不為每個窗口消息調用一個預定義的虛擬函數?因為它太占CPU。若是那樣,當掃描一個消息映像以加速該過程時,OnWndMsg( )可能會做出意想不到的事情,并陷入匯編器。注意通過重載WindowProc( )、OnWndMsg( )、OnCommand( )、OnNotify( ) 或OnCmdMsg( )可以修改這一過程。重載OnWndMsg( )可以在窗口消息被排序之前插入該過程。重載OnCommand( )或OnNotify( )可以在消息被反射之前插入該過程。

            posted @ 2007-03-09 11:22 平凡的天才 閱讀(7505) | 評論 (0)編輯 收藏

            2007年3月6日

            ?????????在file類中,對靜態方法的每一次調用都會進行安全檢查,而由于fileinfo類是一個實例類,因此只需要進行一次安全檢查,如何判斷應該使用哪個類呢?有個經驗法則可以參考:如果對文件進行單次操作,file類是比較好的選擇,而如果要對文件進行多個操作,那么使用fileinfo類會有更高的效率。

            posted @ 2007-03-06 19:29 平凡的天才 閱讀(3162) | 評論 (0)編輯 收藏

            亚洲AV无码成人网站久久精品大| 国产AⅤ精品一区二区三区久久| 人妻无码久久一区二区三区免费| 久久福利青草精品资源站| 国产精品免费福利久久| 热综合一本伊人久久精品| 亚洲欧洲精品成人久久曰影片| 久久中文骚妇内射| 国产成人精品久久亚洲| 久久亚洲AV成人无码| 色诱久久久久综合网ywww| 亚洲国产成人久久综合碰碰动漫3d| 久久综合狠狠色综合伊人| 亚洲精品国产美女久久久| 久久91精品国产91| 久久精品国产欧美日韩| 国产精品女同一区二区久久| 久久久久AV综合网成人 | 久久国语露脸国产精品电影 | 精品久久久久久无码免费| 久久国产乱子伦精品免费强| 久久不见久久见免费视频7| 亚洲日本va中文字幕久久| 国内精品久久久久影院老司| 久久久久国产一区二区| 欧美伊人久久大香线蕉综合69 | 伊人热热久久原色播放www | 久久久女人与动物群交毛片| 97精品久久天干天天天按摩| 精品国产91久久久久久久a| 久久婷婷色综合一区二区| 亚洲AV日韩精品久久久久久久| 99久久人妻无码精品系列| 日本精品久久久久影院日本| 性色欲网站人妻丰满中文久久不卡| 麻豆精品久久精品色综合| 久久精品极品盛宴观看| 97久久久精品综合88久久| 国产精品久久久久久久久久影院| 久久久av波多野一区二区| 久久精品极品盛宴观看|