//------------------------------------------------------------------------ //! Update the subtitle in the group header //! //! @param nGroupId ID of the group //! @param strSubtitle The subtitle description text //! @return Succeeded in updating the group subtitle //------------------------------------------------------------------------ BOOL CGridListCtrlGroups::SetGroupSubtitle(int nGroupId, const CString& strSubtitle) { if (!IsGroupStateEnabled()) return FALSE; #if _WIN32_WINNT >= 0x0600 LVGROUP lg = {0}; lg.cbSize = sizeof(lg); lg.mask = LVGF_SUBTITLE; #ifdef UNICODE lg.pszSubtitle = (LPWSTR)(LPCTSTR)strSubtitle; lg.cchSubtitle = strSubtitle.GetLength(); #else CComBSTR bstrSubtitle = strSubtitle; lg.pszSubtitle = bstrSubtitle; lg.cchSubtitle = bstrSubtitle.Length(); #endif if (SetGroupInfo(nGroupId, (PLVGROUP)&lg)==-1) return FALSE; return TRUE; #else return FALSE; #endif }
//------------------------------------------------------------------------ //! Update the description text of the group footer //! //! @param nGroupId ID of the group //! @param strFooter The footer description text //! @param dwAlign Indicates the alignment of the footer text for the group //! @return Succeeded in updating the group footer //------------------------------------------------------------------------ BOOL CGridListCtrlGroups::SetGroupFooter(int nGroupId, const CString& strFooter, DWORD dwAlign) { if (!IsGroupStateEnabled()) return FALSE; #if _WIN32_WINNT >= 0x0600 LVGROUP lg = {0}; lg.cbSize = sizeof(lg); lg.mask = LVGF_FOOTER | LVGF_ALIGN; lg.uAlign = dwAlign; #ifdef UNICODE lg.pszFooter = (LPWSTR)(LPCTSTR)strFooter; lg.cchFooter = strFooter.GetLength(); #else CComBSTR bstrFooter = strFooter; lg.pszFooter = bstrFooter; lg.cchFooter = bstrFooter.Length(); #endif if (SetGroupInfo(nGroupId, (PLVGROUP)&lg)==-1) return FALSE; return TRUE; #else return FALSE; #endif }
//------------------------------------------------------------------------ //! Create a group for each unique values within a column //! //! @param nCol The index of the column //! @return Succeeded in creating the group //------------------------------------------------------------------------ BOOL CGridListCtrlGroups::GroupByColumn(int nCol) { CWaitCursor waitCursor; SetSortArrow(-1, false); SetRedraw(FALSE); RemoveAllGroups(); EnableGroupView( GetItemCount() > 0 ); if (IsGroupViewEnabled()) { CSimpleMap<CString,CSimpleArray<int> > groups; // Loop through all rows and find possible groups for(int nRow=0; nRow<GetItemCount(); ++nRow) { CString cellText = GetItemText(nRow, nCol); int nGroupId = groups.FindKey(cellText); if (nGroupId==-1) { CSimpleArray<int> rows; groups.Add(cellText, rows); nGroupId = groups.FindKey(cellText); } groups.GetValueAt(nGroupId).Add(nRow); } // Look through all groups and assign rows to group for(int nGroupId = 0; nGroupId < groups.GetSize(); ++nGroupId) { const CSimpleArray<int>& groupRows = groups.GetValueAt(nGroupId); DWORD dwState = LVGS_NORMAL; #ifdef LVGS_COLLAPSIBLE if (IsGroupStateEnabled()) dwState = LVGS_COLLAPSIBLE; #endif VERIFY( InsertGroupHeader(nGroupId, nGroupId, groups.GetKeyAt(nGroupId), dwState) != -1); for(int groupRow = 0; groupRow < groupRows.GetSize(); ++groupRow) { VERIFY( SetRowGroupId(groupRows[groupRow], nGroupId) ); } } SetRedraw(TRUE); Invalidate(FALSE); return TRUE; } SetRedraw(TRUE); Invalidate(FALSE); return FALSE; }
//------------------------------------------------------------------------ //! Update the image icon in the group header together with top and bottom //! description. Microsoft encourage people not to use this functionality. //! //! @param nGroupId ID of the group //! @param nImage Index of the title image in the control imagelist. //! @param strTopDesc Description text placed oppposite of the image //! @param strBottomDesc Description text placed below the top description //! @return Succeeded in updating the group image //------------------------------------------------------------------------ BOOL CGridListCtrlGroups::SetGroupTitleImage(int nGroupId, int nImage, const CString& strTopDesc, const CString& strBottomDesc) { if (!IsGroupStateEnabled()) return FALSE; #if _WIN32_WINNT >= 0x0600 LVGROUP lg = {0}; lg.cbSize = sizeof(lg); lg.mask = LVGF_TITLEIMAGE; lg.iTitleImage = nImage; // Index of the title image in the control imagelist. #ifdef UNICODE if (!strTopDesc.IsEmpty()) { // Top description is drawn opposite the title image when there is // a title image, no extended image, and uAlign==LVGA_HEADER_CENTER. lg.mask |= LVGF_DESCRIPTIONTOP; lg.pszDescriptionTop = (LPWSTR)(LPCTSTR)strTopDesc; lg.cchDescriptionTop = strTopDesc.GetLength(); } if (!strBottomDesc.IsEmpty()) { // Bottom description is drawn under the top description text when there is // a title image, no extended image, and uAlign==LVGA_HEADER_CENTER. lg.mask |= LVGF_DESCRIPTIONBOTTOM; lg.pszDescriptionBottom = (LPWSTR)(LPCTSTR)strBottomDesc; lg.cchDescriptionBottom = strBottomDesc.GetLength(); } #else CComBSTR bstrTopDesc = strTopDesc; CComBSTR bstrBottomDesc = strBottomDesc; if (!strTopDesc.IsEmpty()) { lg.mask |= LVGF_DESCRIPTIONTOP; lg.pszDescriptionTop = bstrTopDesc; lg.cchDescriptionTop = bstrTopDesc.Length(); } if (!strBottomDesc.IsEmpty()) { lg.mask |= LVGF_DESCRIPTIONBOTTOM; lg.pszDescriptionBottom = bstrBottomDesc; lg.cchDescriptionBottom = bstrBottomDesc.Length(); } #endif if (SetGroupInfo(nGroupId, (PLVGROUP)&lg)==-1) return FALSE; return TRUE; #else return FALSE; #endif }
//------------------------------------------------------------------------ //! The framework calls this member function when the user double-clicks //! the left mouse button. Used to expand and collapse groups when group //! header is clicked. //! //! @param nFlags Indicates whether various virtual keys are down (MK_CONTROL, MK_SHIFT, etc.) //! @param point Specifies the x- and y-coordinate of the cursor relative to the upper-left corner of the window. //------------------------------------------------------------------------ void CGridListCtrlGroups::OnLButtonDblClk(UINT nFlags, CPoint point) { CGridListCtrlEx::OnLButtonDblClk(nFlags, point); if (!IsGroupStateEnabled()) return; int nGroupId = GroupHitTest(point); if (nGroupId!=-1) { if (HasGroupState(nGroupId, LVGS_COLLAPSED)) SetGroupState(nGroupId, LVGS_NORMAL); else SetGroupState(nGroupId, LVGS_COLLAPSED); } }
//------------------------------------------------------------------------ //! Expand all groups //------------------------------------------------------------------------ void CGridListCtrlGroups::ExpandAllGroups() { if (!IsGroupStateEnabled()) return; // Loop through all rows and find possible groups for(int nRow=0; nRow<GetItemCount(); ++nRow) { int nGroupId = GetRowGroupId(nRow); if (nGroupId!=-1) { if (HasGroupState(nGroupId,LVGS_COLLAPSED)) { SetGroupState(nGroupId,LVGS_NORMAL); } } } }
//------------------------------------------------------------------------ //! Override this method to change the context menu when activating context //! menu for the group headers //! //! @param pWnd Handle to the window in which the user right clicked the mouse //! @param point Position of the cursor, in screen coordinates, at the time of the mouse click. //! @param nGroupId ID of the group //------------------------------------------------------------------------ void CGridListCtrlGroups::OnContextMenuGroup(CWnd* pWnd, CPoint point, int nGroupId) { CMenu menu; UINT uFlags = MF_BYPOSITION | MF_STRING; VERIFY( menu.CreatePopupMenu() ); const CString& groupHeader = GetGroupHeader(nGroupId); // Provide menu-options for collapsing groups, if the collapsible state is not available #ifndef LVGS_COLLAPSIBLE if (IsGroupStateEnabled()) { if (HasGroupState(nGroupId,LVGS_COLLAPSED)) { CString menuText = CString(_T("Expand group: ")) + groupHeader; menu.InsertMenu(0, uFlags, 1, menuText); } else { CString menuText = CString(_T("Collapse group: ")) + groupHeader; menu.InsertMenu(0, uFlags, 2, menuText); } } #endif if (GetExtendedStyle() & LVS_EX_CHECKBOXES) { CString menuText = CString(_T("Check group: ")) + groupHeader; menu.InsertMenu(1, uFlags, 3, menuText); menuText = CString(_T("Uncheck group: ")) + groupHeader; menu.InsertMenu(2, uFlags, 4, menuText); } int nResult = menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, 0); switch(nResult) { case 1: SetGroupState(nGroupId, LVGS_NORMAL); break; case 2: SetGroupState(nGroupId, LVGS_COLLAPSED); break; case 3: CheckEntireGroup(nGroupId, true); break; case 4: CheckEntireGroup(nGroupId, false); break; } }
//------------------------------------------------------------------------ //! Override this method to change the context menu when activating context //! menu in client area with no rows //! //! @param pWnd Handle to the window in which the user right clicked the mouse //! @param point Position of the cursor, in screen coordinates, at the time of the mouse click. //------------------------------------------------------------------------ void CGridListCtrlGroups::OnContextMenuGrid(CWnd* pWnd, CPoint point) { if (IsGroupStateEnabled()) { CMenu menu; UINT uFlags = MF_BYPOSITION | MF_STRING; VERIFY( menu.CreatePopupMenu() ); menu.InsertMenu(0, uFlags, 1, _T("Expand all groups")); menu.InsertMenu(1, uFlags, 2, _T("Collapse all groups")); menu.InsertMenu(2, uFlags, 3, _T("Disable grouping")); int nResult = menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RETURNCMD, point.x, point.y, this, 0); switch(nResult) { case 1: ExpandAllGroups(); break; case 2: CollapseAllGroups(); break; case 3: RemoveAllGroups(); EnableGroupView(FALSE); break; } } }
//------------------------------------------------------------------------ //! Updates the state of a group //! //! @param nGroupId ID of the group //! @param dwState Specifies the new state of the group //! @return The group state was updated (true / false) //------------------------------------------------------------------------ BOOL CGridListCtrlGroups::SetGroupState(int nGroupId, DWORD dwState) { // Vista SDK - ListView_SetGroupState / LVM_SETGROUPINFO if (!IsGroupStateEnabled()) return FALSE; LVGROUP lg = {0}; lg.cbSize = sizeof(lg); lg.mask = LVGF_STATE; lg.state = dwState; lg.stateMask = dwState; #ifdef LVGS_COLLAPSIBLE // Maintain LVGS_COLLAPSIBLE state if (HasGroupState(nGroupId, LVGS_COLLAPSIBLE)) lg.state |= LVGS_COLLAPSIBLE; #endif if (SetGroupInfo(nGroupId, (PLVGROUP)&lg)==-1) return FALSE; return TRUE; }
//------------------------------------------------------------------------ //! Find the group-id below the given point //! //! @param point Mouse position //! @return ID of the group //------------------------------------------------------------------------ int CGridListCtrlGroups::GroupHitTest(const CPoint& point) { if (!IsGroupViewEnabled()) return -1; if (HitTest(point)!=-1) return -1; if (IsGroupStateEnabled()) { #if _WIN32_WINNT >= 0x0600 #ifdef ListView_HitTestEx #ifdef LVHT_EX_GROUP #ifdef ListView_GetGroupInfoByIndex LVHITTESTINFO lvhitinfo = {0}; lvhitinfo.pt = point; ListView_HitTestEx(m_hWnd, &lvhitinfo); if ((lvhitinfo.flags & LVHT_EX_GROUP)==0) return -1; LVGROUP lg = {0}; lg.cbSize = sizeof(lg); lg.mask = LVGF_GROUPID; VERIFY( ListView_GetGroupInfoByIndex(m_hWnd, lvhitinfo.iGroup, &lg) ); return lg.iGroupId; #endif #endif #endif #endif } if (IsGroupStateEnabled()) { // Running on Vista or newer, but compiled without _WIN32_WINNT >= 0x0600 #ifndef LVM_GETGROUPINFOBYINDEX #define LVM_GETGROUPINFOBYINDEX (LVM_FIRST + 153) #endif #ifndef LVM_GETGROUPCOUNT #define LVM_GETGROUPCOUNT (LVM_FIRST + 152) #endif #ifndef LVM_GETGROUPRECT #define LVM_GETGROUPRECT (LVM_FIRST + 98) #endif #ifndef LVGGR_HEADER #define LVGGR_HEADER (1) #endif LRESULT groupCount = SNDMSG((m_hWnd), LVM_GETGROUPCOUNT, (WPARAM)0, (LPARAM)0); if (groupCount <= 0) return -1; for(int i = 0 ; i < groupCount; ++i) { LVGROUP lg = {0}; lg.cbSize = sizeof(lg); lg.mask = LVGF_GROUPID; VERIFY( SNDMSG((m_hWnd), LVM_GETGROUPINFOBYINDEX, (WPARAM)(i), (LPARAM)(&lg)) ); CRect rect(0,LVGGR_HEADER,0,0); VERIFY( SNDMSG((m_hWnd), LVM_GETGROUPRECT, (WPARAM)(lg.iGroupId), (LPARAM)(RECT*)(&rect)) ); if (rect.PtInRect(point)) return lg.iGroupId; } // Don't try other ways to find the group return -1; } // We require that each group contains atleast one item if (GetItemCount()==0) return -1; // This logic doesn't support collapsible groups int nFirstRow = -1; CRect gridRect; GetWindowRect(&gridRect); for(CPoint pt = point ; pt.y < gridRect.bottom ; pt.y += 2) { nFirstRow = HitTest(pt); if (nFirstRow!=-1) break; } if (nFirstRow==-1) return -1; int nGroupId = GetRowGroupId(nFirstRow); // Extra validation that the above row belongs to a different group int nAboveRow = GetNextItem(nFirstRow,LVNI_ABOVE); if (nAboveRow!=-1 && nGroupId==GetRowGroupId(nAboveRow)) return -1; return nGroupId; }
BOOL CFeedIcoItemListCtrl::GroupAllFeedsByType(int nType) { if (!IsCommonControlsEnabled()) return FALSE; SetRedraw(FALSE); RemoveAllGroups(); RemoveAllGroupData(); EnableGroupView( GetItemCount() > 0 ); if (IsGroupViewEnabled()) { // Loop through all rows and find possible groups for(int nItem = 0; nItem < GetItemCount(); nItem++ ) { CString strCatalogName = GetResString( IDS_UNKNOWN ); CRssFeed* pFeed = (CRssFeed*)GetItemData(nItem); if (pFeed == NULL) continue; if ( pFeed->m_uCatalogId != FeedCatalog::INVALID_CATALOG_ID ) { FeedCatalog & catalog = CFeedCatalogs::GetInstance()[pFeed->m_uCatalogId]; ASSERT(catalog.m_uId == pFeed->m_uCatalogId); ASSERT(catalog.IsLeaf()); FeedCatalog * catalogParent; if (catalog.m_pParent != NULL) { catalogParent = catalog.m_pParent; strCatalogName = catalogParent->m_strName; } } int nGroupId = m_mapGroups.FindKey(strCatalogName); if (nGroupId == -1) { CSimpleArray<int> nItems; m_mapGroups.Add(strCatalogName, nItems); nGroupId = m_mapGroups.FindKey(strCatalogName); } //必须添加不重复数据 if (m_mapGroups.GetValueAt(nGroupId).Find(nItem) == -1) { m_mapGroups.GetValueAt(nGroupId).Add(nItem); } } // Look through all groups and assign rows to group for(int nGroupId = 0; nGroupId < m_mapGroups.GetSize(); nGroupId++) { const CSimpleArray<int>& groupRows = m_mapGroups.GetValueAt(nGroupId); DWORD dwState = LVGS_NORMAL; #ifdef LVGS_COLLAPSIBLE if (IsGroupStateEnabled()) dwState = LVGS_COLLAPSIBLE; #endif VERIFY( InsertTypeGroup(nGroupId, nGroupId, m_mapGroups.GetKeyAt(nGroupId), dwState) != -1); for(int groupRow = 0; groupRow < groupRows.GetSize(); ++groupRow) { VERIFY( SetItemGroupId(groupRows[groupRow], nGroupId) ); } } } SetRedraw(TRUE); Invalidate(TRUE); return FALSE; }
BOOL CFeedIcoItemListCtrl::GroupFeedByType(int nIndex, int nType) { if (!IsCommonControlsEnabled()) return FALSE; SetRedraw(FALSE); EnableGroupView(TRUE); if (IsGroupViewEnabled()) { CString cellText = GetResString( IDS_UNKNOWN ); bool bNew = false;//Is new Group? CRssFeed* pFeed = (CRssFeed*)GetItemData(nIndex); if (pFeed == NULL) return FALSE; if ( pFeed->m_uCatalogId != FeedCatalog::INVALID_CATALOG_ID ) { FeedCatalog & catalog = CFeedCatalogs::GetInstance()[pFeed->m_uCatalogId]; ASSERT(catalog.m_uId == pFeed->m_uCatalogId); ASSERT(catalog.IsLeaf()); FeedCatalog * catalogParent; if (catalog.m_pParent != NULL) { catalogParent = catalog.m_pParent; cellText = catalogParent->m_strName; } } int nGroupId = m_mapGroups.FindKey(cellText); if (nGroupId == -1) { CSimpleArray<int> nItems; m_mapGroups.Add(cellText, nItems); nGroupId = m_mapGroups.FindKey(cellText); bNew = true; } //必须添加不重复数据 if (m_mapGroups.GetValueAt(nGroupId).Find(nIndex) == -1) { m_mapGroups.GetValueAt(nGroupId).Add(nIndex); } if (bNew)//new Group { DWORD dwState = LVGS_NORMAL; #ifdef LVGS_COLLAPSIBLE if (IsGroupStateEnabled()) dwState = LVGS_COLLAPSIBLE; #endif //InsertTypeGroup(nGroupId, nGroupId, cellText, dwState); VERIFY(InsertTypeGroup(nGroupId, nGroupId, cellText, dwState) != -1); } VERIFY( SetItemGroupId(nIndex, nGroupId) ); } SetRedraw(TRUE); Invalidate(TRUE); return FALSE; }