MultiColumnSortListView.cpp
上传用户:ayqaqa
上传日期:2007-01-02
资源大小:37k
文件大小:16k
源码类别:

ListView/ListBox

开发平台:

Visual C++

  1. /*
  2. Usage:
  3. You generally should not use this class directly, though it
  4. is possible. You need to do two things to use it directly.
  5. Set m_strUniqueName to someting, and set m_strColumnWidthSection
  6. to where you want the column widths to be saved.
  7. The purpose of m_strUniqueName is to allow for saving of
  8. multiple instances of listview objects. So obviously you would
  9. need to set this differently for each instance. SetUniqueName must be called
  10. before calling InsertColumn() or LoadColumnWidths().
  11. If you are deriving from this class, you need to do the following:
  12. Add a class to your project derived from CListView, then go into the
  13. header file and include MultiColumnSortListView.h and change all
  14. references to CListView to CMultiColumnSortListView. Then in the .cpp
  15. file of your class, change all the message maps to CMultiColumnSortListView
  16. instead of CListView. That should do it.
  17.   Compiling:
  18. One problem you will have is finding IDB_ARROWUP and IDB_ARROWDOWN.
  19. Those bitmaps will be included with this set of classes, You should
  20. add them to your project or add your own bitmaps named correctly.
  21. These are the bitmaps that show the sort order on the header control.
  22.         I hope this is simple enough, kind of a pain to get started but after
  23. that it should be cake and hopefully it will be useful.
  24.   
  25.   Things to be aware of:
  26. Multithreading:
  27.      If you delete all the items from another thread
  28.  in the middle of a sort, it will crash. This is the only
  29.  bug i have found.
  30. Column Widths:
  31.  
  32. MINCOLWIDTH - Minimum width a column can be.
  33. MAXCOLWIDTH - Maximum width a column can be.
  34. These are hard coded in the header file. Be aware.
  35. MAXCOLUMNS  - The most columns you can have right
  36. now is 64, that is only because im use __int64 to
  37. hold the column sort states. Who needs more than
  38. 64 columns anyway? If you do, you can change it to
  39. CUIntArray, i just didnt see the need for a whole int
  40. when all i needed was a bit. 
  41.         
  42.   Credits:
  43. Iuri Apollonio -- Sorting Class ( great class )
  44. Zafir Anjum    -- CMultiColumnSortListView::GetColumnCount
  45. Roger Onslow   -- CMultiColumnSortListView::AutoSizeColumns
  46. Zafir Anjum    -- CSortableHeaderCtrl::SetSortImage
  47. Me             -- The Rest, I think.
  48. */
  49. // MultiColumnSortListView.cpp : implementation file
  50. //
  51. #include "stdafx.h"
  52. #include "MultiColumnSortListView.h"
  53. #include "SortClass.h"
  54. #ifdef _DEBUG
  55. #define new DEBUG_NEW
  56. #undef THIS_FILE
  57. static char THIS_FILE[] = __FILE__;
  58. #endif
  59. /////////////////////////////////////////////////////////////////////////////
  60. // CMultiColumnSortListView
  61. IMPLEMENT_DYNCREATE(CMultiColumnSortListView, CListView)
  62. /*
  63. When deriving from this class you must set m_strUniqueName to something
  64. this name is used to save each instances column widths to the registry
  65. */
  66. CMultiColumnSortListView::CMultiColumnSortListView()
  67. {
  68. m_strUniqueName.Empty();
  69. m_strColumnWidthSection = "ColumnWidths";
  70. m_bSorting = false;
  71. m_lColumnSortStates = 0;
  72. }
  73. CMultiColumnSortListView::~CMultiColumnSortListView()
  74. {
  75. }
  76. BEGIN_MESSAGE_MAP(CMultiColumnSortListView, CListView)
  77. //{{AFX_MSG_MAP(CMultiColumnSortListView)
  78. ON_WM_CREATE()
  79. ON_WM_DESTROY()
  80. //}}AFX_MSG_MAP
  81. ON_NOTIFY(HDN_ITEMCLICKA, 0, OnHeaderClicked) 
  82. ON_NOTIFY(HDN_ITEMCLICKW, 0, OnHeaderClicked)
  83. END_MESSAGE_MAP()
  84. /////////////////////////////////////////////////////////////////////////////
  85. // CMultiColumnSortListView drawing
  86. void CMultiColumnSortListView::OnDraw(CDC* pDC)
  87. {
  88. CDocument* pDoc = GetDocument();
  89. }
  90. /////////////////////////////////////////////////////////////////////////////
  91. // CMultiColumnSortListView diagnostics
  92. #ifdef _DEBUG
  93. void CMultiColumnSortListView::AssertValid() const
  94. {
  95. CListView::AssertValid();
  96. }
  97. void CMultiColumnSortListView::Dump(CDumpContext& dc) const
  98. {
  99. CListView::Dump(dc);
  100. }
  101. #endif //_DEBUG
  102. /////////////////////////////////////////////////////////////////////////////
  103. // CMultiColumnSortListView message handlers
  104. /*
  105. This function saves the columns widths of the listctrl to the registry.
  106. This is called in two places, OnDestroy, and OnEndTrack in the headerCtrl class.
  107. */
  108. void CMultiColumnSortListView::SaveColumnWidths()
  109. { //You must set a unique name for every listctrl
  110. ASSERT( m_strUniqueName.GetLength() );
  111. CString strEntry( m_strUniqueName );
  112. CString strValue;
  113. CListCtrl &rListCtrl = GetListCtrl();
  114. int iNumColumns = GetColumnCount();
  115. for( int i = 0; i < iNumColumns; i++ )
  116. {
  117. CString strTemp;
  118. strTemp.Format("%d,", rListCtrl.GetColumnWidth( i ) );
  119. strValue += strTemp;
  120. }
  121. AfxGetApp()->WriteProfileString( m_strColumnWidthSection, strEntry, strValue );
  122. }
  123. /*
  124. This function loads all the column widths for each column that was saved and applies the width
  125. to the column. This function should be called when you are done inserting data. Or you 
  126. can call SetColumnWidth to set the column width right after you InsertColumn(), If you call 
  127. my InsertColumn it will do this for you.
  128. */
  129. int CMultiColumnSortListView::LoadColumnWidths()
  130. { //This function will load all the column widths and apply the widths to each respective column
  131. int iNumColumns = GetColumnCount();
  132. CListCtrl &rListCtrl = GetListCtrl();
  133. for( int i = 0; i < iNumColumns; i++ )
  134. {
  135. SetColumnWidth( i );
  136. }
  137. return 1;
  138. }
  139. /*
  140. This function is the heart of the class. This will get called automatically
  141. when you click a header, and if you press control while clicking the header,
  142. a multi column sort will take place (ie: sorting the current columns within all the
  143. previously control+clicked columns). The multi column sort saves all the previosuly
  144. control+clicked columns in an array and sorts them in reverse order. So if you click 
  145. column 0, 2, 3, 5 while holding control, it will sort 5, 3, 2, 0. ( this acheives a
  146. muli-column sort).
  147. */
  148. void CMultiColumnSortListView::SortColumn( int iSubItem, bool bSortingMultipleColumns )
  149. {
  150. CListCtrl &rListCtrl = GetListCtrl();
  151. int iNumCombinedSortedCols = GetNumCombinedSortedColumns();
  152. m_bSorting = true;
  153. if( bSortingMultipleColumns )
  154. {
  155. if( NotInCombinedSortedColumnList( iSubItem ) )
  156. m_aCombinedSortedColumns[ iNumCombinedSortedCols++ ] = iSubItem;
  157. else
  158. MoveItemInCombinedSortedListToEnd( iSubItem );
  159. for( int i = iNumCombinedSortedCols - 1; i >= 0 ; i-- )
  160. {
  161. SORT_STATE ssEachItem = GetItemSortState( m_aCombinedSortedColumns[i] );
  162. if( iNumCombinedSortedCols - 1 != i )
  163. ssEachItem = (SORT_STATE)!ssEachItem;
  164. CSortClass csc(&rListCtrl, m_aCombinedSortedColumns[i], IsColumnNumeric( m_aCombinedSortedColumns[i] ) );
  165. csc.Sort( ssEachItem );
  166. if( i == iNumCombinedSortedCols - 1 )
  167. { //Only swap the last column's sort order.
  168. m_ctlHeaderCtrl.SetSortImage( m_aCombinedSortedColumns[i], ssEachItem );
  169. SetItemSortState( m_aCombinedSortedColumns[i] , (SORT_STATE)!ssEachItem );
  170. }
  171. }
  172. }
  173. else
  174. {
  175. m_ctlHeaderCtrl.RemoveAllSortImages();
  176. EmptyArray(m_aCombinedSortedColumns);
  177. m_aCombinedSortedColumns[ 0 ] = iSubItem;
  178. SORT_STATE ssEachItem = GetItemSortState( iSubItem );
  179. CSortClass csc(&rListCtrl, iSubItem, IsColumnNumeric( iSubItem ) );
  180. csc.Sort( ssEachItem );
  181. m_ctlHeaderCtrl.SetSortImage( iSubItem, ssEachItem );
  182. SetItemSortState( iSubItem , (SORT_STATE)!ssEachItem );
  183. }
  184. m_bSorting = false;
  185. }
  186. /*
  187. My version of InsertColumn that will automatically load the last column
  188. width from the registry. 
  189. */
  190. int CMultiColumnSortListView::InsertColumn(int nCol, LPCTSTR lpszColumnHeading, int nFormat, int nWidth, int nSubItem)
  191. {
  192. CListCtrl &rListCtrl = GetListCtrl();
  193. GetListCtrl().InsertColumn( nCol, lpszColumnHeading, nFormat, nWidth, nSubItem );
  194. SetColumnWidth( nCol );
  195. return 1;
  196. }
  197. /*
  198. Utility function to size columns based on its data.
  199. written by Roger Onslow
  200. */
  201. void CMultiColumnSortListView::AutoSizeColumn( int iColumn )
  202. {
  203. CListCtrl &rListCtrl = GetListCtrl();
  204. rListCtrl.SetRedraw(false);
  205. rListCtrl.SetColumnWidth(iColumn,LVSCW_AUTOSIZE);
  206. int wc1 = rListCtrl.GetColumnWidth( iColumn );
  207. rListCtrl.SetColumnWidth(iColumn,LVSCW_AUTOSIZE_USEHEADER);
  208. int wc2 = rListCtrl.GetColumnWidth( iColumn );
  209. int wc = max(MINCOLWIDTH,max( wc1,wc2 ));
  210. if( wc > MAXCOLWIDTH )
  211. wc = MAXCOLWIDTH;
  212. rListCtrl.SetColumnWidth( iColumn,wc );  
  213. rListCtrl.SetRedraw(true);
  214. }
  215. /*
  216. Utility function to get rid of all the columns
  217. */
  218. void CMultiColumnSortListView::DeleteAllColumns()
  219. {
  220. CListCtrl &rListCtrl = GetListCtrl();
  221. int iNumCols = GetColumnCount();
  222. for ( int i = 0; i < iNumCols; i++ )
  223. rListCtrl.DeleteColumn(0);
  224. }
  225. /*
  226. Utility function to get rid of all items.
  227. */
  228. void CMultiColumnSortListView::DeleteAllItems()
  229. {
  230. CListCtrl &rListCtrl = GetListCtrl();
  231. if( rListCtrl.GetItemCount() > 0 )
  232. rListCtrl.DeleteAllItems();
  233. }
  234. /*
  235. Utility function to get the number of columns
  236. written by Zafir Anjum
  237. */
  238. UINT CMultiColumnSortListView::GetColumnCount()
  239. {
  240. CHeaderCtrl *pHeaderCtrl = (CHeaderCtrl*)GetListCtrl().GetDlgItem(0);
  241. return pHeaderCtrl->GetItemCount();
  242. }
  243. /*
  244. Just add some extended styles from the new IE4 stuff.
  245. Of course you can either change the code or change your
  246. derived class's OnCreate to call CListView::OnCreate
  247. */
  248. int CMultiColumnSortListView::OnCreate(LPCREATESTRUCT lpCreateStruct) 
  249. {
  250. if (CListView::OnCreate(lpCreateStruct) == -1)
  251. return -1;
  252. // set list control's style to hilight the entire row
  253. DWORD dwStyle = SendMessage(LVM_GETEXTENDEDLISTVIEWSTYLE);
  254. dwStyle |= LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP;
  255. SendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, 0, (LPARAM)dwStyle);
  256. m_ctlHeaderCtrl.SubclassWindow( GetDlgItem(0)->m_hWnd );
  257. return 0;
  258. }
  259. /*
  260. We are only sorting in report view so far.
  261. */
  262. BOOL CMultiColumnSortListView::PreCreateWindow(CREATESTRUCT& cs) 
  263. {
  264. cs.style |= LVS_REPORT;
  265. return CListView::PreCreateWindow(cs);
  266. }
  267. /*
  268. Utility function to tell you if a sort is taking place
  269. */
  270. const bool CMultiColumnSortListView::IsSorting() const
  271. {
  272. return CMultiColumnSortListView::m_bSorting;
  273. }
  274. /*
  275. Utility function to tell you if the control key is being pressed
  276. */
  277. const int CMultiColumnSortListView::IsControlPressed() const
  278. {
  279. return (::GetKeyState( VK_CONTROL ) < 0 );
  280. }
  281. /*
  282. Message handler for when a header is clicked.
  283. */
  284. void CMultiColumnSortListView::OnHeaderClicked(NMHDR* pNMHDR, LRESULT* pResult) 
  285. {
  286. HD_NOTIFY *pHDN = (HD_NOTIFY *) pNMHDR;
  287. if( pHDN->iButton == 0 )
  288. {
  289. if( IsControlPressed() )
  290. SortColumn( pHDN->iItem, MULTI_COLUMN_SORT );
  291. else
  292. SortColumn( pHDN->iItem, SINGLE_COLUMN_SORT );
  293. }
  294. *pResult = 0;
  295. }
  296. /*
  297. Message handler for when control is about to be destroyed.
  298. This is where the column widths are saved.
  299. */
  300. void CMultiColumnSortListView::OnDestroy() 
  301. {
  302. SaveColumnWidths();
  303. CListView::OnDestroy();
  304. }
  305. /*
  306. Utility function to tell you if a column is in the combined sorted list.
  307. */
  308. bool CMultiColumnSortListView::NotInCombinedSortedColumnList(int iItem) const
  309. {
  310. int iNumCombinedSortedColumns = GetNumCombinedSortedColumns();
  311. for( int i = 0; i < iNumCombinedSortedColumns; i++ )
  312. {
  313. if( m_aCombinedSortedColumns[i] == iItem )
  314. return false;
  315. }
  316. return true;
  317. }
  318. /*
  319. Utility function to get you the sort state of a column
  320. */
  321. const SORT_STATE CMultiColumnSortListView::GetItemSortState( int iItem ) const
  322. {
  323. return (SORT_STATE)((m_lColumnSortStates) & ( 1 << iItem ));
  324. }
  325. /*
  326. Utility function to set the sort state of a column
  327. */
  328. void CMultiColumnSortListView::SetItemSortState(int iItem, SORT_STATE bSortState)
  329. {
  330. if( bSortState != GetItemSortState( iItem ) )
  331. m_lColumnSortStates ^= (1 << iItem);
  332. }
  333. /*
  334. Utility function to get you the number of combined sorted columns
  335. */
  336. const int CMultiColumnSortListView::GetNumCombinedSortedColumns() const
  337. {
  338. for( int i = 0; i < MAX_COLUMNS; i++ )
  339. if( m_aCombinedSortedColumns[i] == -1 )
  340. return i;
  341. return MAX_COLUMNS;
  342. }
  343. /*
  344. Utility function clear some internal arrays
  345. */
  346. void CMultiColumnSortListView::EmptyArray( int *pArray )
  347. {
  348. memset( pArray, -1, MAX_COLUMNS );
  349. }
  350. /*
  351. This function will move a clicked column to the end of the combined
  352. column list. This is useful when you move backwards through column clicks.
  353. Like click columns: 0, 1, 2, 1. The array will hold [0,1,2] after the first 3
  354. clicks, this function will change it to [0,2,1] after the 4th click.
  355. */
  356. void CMultiColumnSortListView::MoveItemInCombinedSortedListToEnd(int iItem)
  357. {
  358. int iNumCombinedSortedColumns = GetNumCombinedSortedColumns();
  359. int aCombinedSortedColumns[MAX_COLUMNS];
  360. memset( aCombinedSortedColumns, -1, MAX_COLUMNS );
  361. int iItemIndex = FindItemInCombedSortedList( iItem );
  362. if( iItemIndex != -1 )
  363. {
  364. if( iItemIndex > 0 )
  365. {
  366. memcpy( aCombinedSortedColumns, m_aCombinedSortedColumns, iItemIndex * sizeof( int ) );
  367. memcpy( &aCombinedSortedColumns[iItemIndex], &m_aCombinedSortedColumns[iItemIndex + 1], (iNumCombinedSortedColumns - iItemIndex - 1) * sizeof(int) );
  368. }
  369. }
  370. aCombinedSortedColumns[ iNumCombinedSortedColumns - 1 ] = iItem;
  371. memcpy( m_aCombinedSortedColumns, aCombinedSortedColumns, MAX_COLUMNS * sizeof(int) );
  372. for( int i = 0; i < MAX_COLUMNS ; i++ )
  373. {
  374. if( aCombinedSortedColumns[i] == -1 )
  375. break;
  376. }
  377. }
  378. /*
  379. Utility function to find an item in the combined sorted list.
  380. */
  381. int CMultiColumnSortListView::FindItemInCombedSortedList( int iItem )
  382. {
  383. int iNumCombinedSortedColumns = GetNumCombinedSortedColumns();
  384. for( int i = 0; i < iNumCombinedSortedColumns; i++ )
  385. {
  386. if(m_aCombinedSortedColumns[i] == iItem )
  387. return i;
  388. }
  389. return -1;
  390. }
  391. /*
  392. Utility function to look up a columns width in the registry.
  393. */
  394. const int CMultiColumnSortListView::GetRegColumnWidth( int iColumn ) const
  395. { //You must set a unique name for each 
  396. ASSERT( m_strUniqueName.GetLength() );
  397. CString strEntry( m_strUniqueName );
  398. CString strValue, strSubString;
  399. CString strSection( m_strColumnWidthSection );
  400. strValue = AfxGetApp()->GetProfileString( strSection, strEntry, "" );
  401. AfxExtractSubString(strSubString, strValue, iColumn, ',');
  402. return atoi( (LPCTSTR )strSubString );
  403. }
  404. /*
  405. Utility function to Autosize all columns in the case there is no registry entry.
  406. */
  407. void CMultiColumnSortListView::AutoSizeAllColumns()
  408. {
  409. int iNumCols = GetColumnCount();
  410. for( int i = 0; i < iNumCols; i++ )
  411. {
  412. AutoSizeColumn( i );
  413. }
  414. }
  415. /*
  416. Utility function to set the width on the column based on the registry
  417. value and a set minimum column width.
  418. */
  419. void CMultiColumnSortListView::SetColumnWidth( int nCol )
  420. {
  421. CListCtrl &rListCtrl = GetListCtrl();
  422. int iWidth = GetRegColumnWidth( nCol );
  423. if( iWidth < MINCOLWIDTH )
  424. AutoSizeColumn( nCol );
  425. else
  426. rListCtrl.SetColumnWidth( nCol, iWidth );
  427. }
  428. /*
  429. Utility function to set a column that will contain only numeric values.
  430. Speeds up the sorting if this is set on the right columns.
  431. */
  432. void CMultiColumnSortListView::SetColumnNumeric( int iCol )
  433. {
  434. m_aNumericColumns.Add( iCol );
  435. }
  436. /*
  437. Utility function to tell you if the given column is set as numeric.
  438. */
  439. const bool CMultiColumnSortListView::IsColumnNumeric( int iCol ) const
  440. {
  441. for( int i = 0; i < m_aNumericColumns.GetSize(); i++ )
  442. {
  443. if( m_aNumericColumns.GetAt( i ) == (UINT)iCol )
  444. return true;
  445. }
  446. return false;
  447. }
  448. /*
  449. Utility function to remove the numeric status of a column
  450. */
  451. void CMultiColumnSortListView::UnsetColumnNumeric(int iCol)
  452. {
  453. int iIndex = FindNumericColumnIndex( iCol );
  454. if( iIndex >= 0 )
  455. m_aNumericColumns.RemoveAt( iIndex );
  456. }
  457. /*
  458. Utility function to find a numeric column in the array.
  459. */
  460. int CMultiColumnSortListView::FindNumericColumnIndex( int iCol )
  461. {
  462. for( int i = 0; i < m_aNumericColumns.GetSize(); i++ )
  463. {
  464. if( m_aNumericColumns.GetAt( i ) == (UINT)iCol )
  465. return i;
  466. }
  467. return -1;
  468. }
  469. void CMultiColumnSortListView::SetUniqueName(LPCTSTR lpszUniqueName)
  470. {
  471. m_strUniqueName = lpszUniqueName;
  472. }