// GridCtrl.cpp : implementation file // // MFC Grid Control v2.23 // // Written by Chris Maunder // Copyright (c) 1998-2001. All Rights Reserved. // // The code contained in this file is based on the original // WorldCom Grid control written by Joe Willcoxson, // mailto:chinajoe@aol.com // http://users.aol.com/chinajoe // (These addresses may be out of date) The code has gone through // so many modifications that I'm not sure if there is even a single // original line of code. In any case Joe's code was a great // framework on which to build. // // This code may be used in compiled form in any way you desire. This // file may be redistributed unmodified by any means PROVIDING it is // not sold for profit without the authors written consent, and // providing that this notice and the authors name and all copyright // notices remains intact. // // An email letting me know how you are using it would be nice as well. // // This file is provided "as is" with no expressed or implied warranty. // The author accepts no liability for any damage/loss of business that // this product may cause. // // Expect bugs! // // Please use and enjoy, and let me know of any bugs/mods/improvements // that you have found/implemented and I will fix/incorporate them into // this file. // // History: // -------- // This control is constantly evolving, sometimes due to new features that I // feel are necessary, and sometimes due to existing bugs. Where possible I // have credited the changes to those who contributed code corrections or // enhancements (names in brackets) or code suggestions (suggested by...) // // 1.0 - 1.13 20 Feb 1998 - 6 May 1999 // First release version. Progressed from being a basic // grid based on the original WorldCom Grid control // written by Joe Willcoxson (mailto:chinajoe@aol.com, // http://users.aol.com/chinajoe) to something a little // more feature rich. Rewritten so many times I doubt // there is a single line of Joe's code left. Many, many, // MANY people sent in bug reports and fixes. Thank you // all. // // 2.0 1 Feb 2000 // Rewritten to make the grid more object oriented, in // that the CGridCell class now takes care of cell-specific // tasks. This makes the code more robust, but more // importantly it allows the simple insertion of other // types of cells. // // 2.01 20 Feb 2000 - Eric Woodruff // Added better support for printing grids and // also fixed some other minor problems. // // 2.02 29 Feb 2000 - Brian V. Shifrin, Scot Reed, // Fixes to reduce flicker, fix font selection bug, // Fixed SetFixed[Row/Col]Count bug // // 2.03 28 Mar 2000 - Aqiruse (marked with //FNA) // Titletips now use cell color // // 2.10 11 Mar 2000 - Ken Bertelson and Chris Maunder // - Additions for virtual CGridCell support of embedded tree // & cell buttons implementation // - Optional WYSIWYG printing // - Awareness of hidden (0 width/height) rows and columns for // key movements, cut, copy, paste, and autosizing // - CGridCell can make title tips display any text rather than // cell text only // - Minor vis bug fixes // - CGridCtrl now works with CGridCellBase instead of CGridCell // This is a taste of things to come. // // 2.11 19 May 2000 - Chris Maunder // - Increasing fixed cells clashed with selected cells (Ivan Ilinov) // - AutoSizeRows obvous bug fixed // - OnLButtonDown fix (Ken Bertelson) // - ExpandToFit bug fixed (scrollbar space) (Igor Proskuriakov) // - List mode selection/deselection fixed // - Keyboard cell movement improved. You can now see the cells! // - m_nBarState MS madness fixed (Phil K) // // 2.12 26 May 2000 - Martin Richter // - If using TRY/CATCH (winCE) instead of try/catch (win32), // e->Delete is not called // - EnsureVisible "fix" was fixed properly. // // 2.20 30 Jul 2000 - Chris Maunder // - Font storage optimised (suggested by Martin Richter) // - AutoSizeColumn works on either column header, data or both // - EnsureVisible. The saga continues... (Ken) // - Rewrote exception handling // - Added TrackFocusCell and FrameFocusCell properties, as well as // ExpandLastColumn (suggested by Bruce E. Stemplewski). // - InsertColumn now allows you to insert columns at the end of the // column range (David Weibel) // - Shift-cell-selection more intuitive // - API change: Set/GetGridColor now Set/GetGridLineColor // - API change: Set/GetBkColor now Set/GetGridBkColor // - API change: Set/GetTextColor, Set/GetTextBkColor depricated // - API change: Set/GetFixedTextColor, Set/GetFixedBkColor depricated // - Stupid DDX_GridControl workaround removed. // - Added "virtual mode" via Set/GetVirtualMode // - Added SetCallbackFunc to allow callback functions in virtual mode // - Added Set/GetAutoSizeStyle // - AutoSize() bug fixed // - added GVIS_FIXEDROW, GVIS_FIXEDCOL states // - added Get/SetFixed[Row|Column]Selection // - cell "Get" methods now const'd. Sorry folks... // - GetMouseScrollLines now uses win98/W2K friendly code // - WS_EX_CLIENTEDGE style now implicit // // 2.21 29 Aug 2000 - Chris Maunder // - re-jigged OLE initialisation and drag/drop registration // - Cut and Paste bug fixed (empty cells not being treated correctly) // - Added "bExpandFixed" parameter to Expand*ToFit functions (Hans-Peter Werner) // - SetRedraw bug fixed // - Resizing 0-width last row and column now works // - Fixed CreateCell SetFormat bug (Loic Baudry) // - Fixed GetFixedTextColor typo (raybie@Exabyte.COM) // - Modified EnableScrollBars calls in Expand*ToFit functions (Simon Hughes) // - GetCellFromPt now public // - GV_CACHEHINT sent more appropriately, and also sent with -1 cellrange // at the end of operations (so App can discard data - Martin Richter) // - CGridCellBase::operator= checks for NULL font before setting (Martin Richter) // - After drag/drop, mouse button was still marked as "down". Fixed. // // 2.22 1 Jan 2001 - Chris Maunder // - SetModified bug in SetItem (Keith Worden) // - ifdef around SetItemTextFmt corrected for win32 // - Save() bug fixed (row 0 saved twice) // - GetTopleftNonFixedCell, GetUnobstructedNonFixedCellRange and // GetVisibleNonFixedCellRange have optional parameter to force recalc. // - added virtual ValidateEdit. // - if the return value from the SendMessage operation for // GVN_ENDLABELEDIT or GVN_BEGINLABELEDIT is < 0 then the // edit, or the attempt to edit (respectively) is rejected. // - Added GetEditWnd to CGridCellBase // - Fixed the paste bug for cells being edited (Gary Lyben). // - GVN_SELCHANGING/GVN_SELCHANGED messages now sent correctly // - OnRButtonUp was not handling clicks on fixed cells correctly // - SB_ENDSCROLL message now sent on keypress initiated scroll // - GetCellTextExtent bug fixed (Elco) // - SetSelectedRange inefficiency fixed by huangchaoyi // - DT_NOCLIP removed from CGridCellBase::Paint // - SetRowCount returns correct value on OOM condition (asigal@hotmail.com) // - Bug when editing using Japanese characters fixed (Michael Dunn) // - Fixed columns and rows printed correctly (fletch - untested) // - Added the "ClearCells" function // // TODO: 1) Implement sparse grids (super easy now) // 2) Fix it so that as you drag select, the speed of selection increases // with time. // 3) Scrolling is still a little dodgy (too much grey area). I know there // is a simple fix but it's been a low priority5 // 4) Get some sleep // // ISSUES: 1) Crashes in the HPC/Pro emulation - but works fine on a device. ??? // 2) WindowFromPoint seems to do weird things in W2K. Causing problems for // the rigt-click-on-titletip code. // ///////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "MemDC.h" #include "GridCtrl.h" // OLE stuff for clipboard operations #include // For CSharedFile #include // For LPTSTR -> LPSTR macros #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif // Spit out some messages as a sanity check for programmers #ifdef GRIDCONTROL_NO_TITLETIPS #pragma message(" -- CGridCtrl: No titletips for cells with large data") #endif #ifdef GRIDCONTROL_NO_DRAGDROP #pragma message(" -- CGridCtrl: No OLE drag and drop") #endif #ifdef GRIDCONTROL_NO_CLIPBOARD #pragma message(" -- CGridCtrl: No clipboard support") #endif #ifdef GRIDCONTROL_NO_PRINTING #pragma message(" -- CGridCtrl: No printing support") #endif IMPLEMENT_DYNCREATE(CGridCtrl, CWnd) // Get the number of lines to scroll with each mouse wheel notch // Why doesn't windows give us this function??? UINT GetMouseScrollLines() { int nScrollLines = 3; // reasonable default #ifndef _WIN32_WCE // Do things the hard way in win95 OSVERSIONINFO VersionInfo; VersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (!GetVersionEx(&VersionInfo) || (VersionInfo.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS && VersionInfo.dwMinorVersion == 0)) { HKEY hKey; if (RegOpenKeyEx(HKEY_CURRENT_USER, _T("Control Panel\\Desktop"), 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { TCHAR szData[128]; DWORD dwKeyDataType; DWORD dwDataBufSize = sizeof(szData); if (RegQueryValueEx(hKey, _T("WheelScrollLines"), NULL, &dwKeyDataType, (LPBYTE)&szData, &dwDataBufSize) == ERROR_SUCCESS) { nScrollLines = _tcstoul(szData, NULL, 10); } RegCloseKey(hKey); } } // win98 or greater else SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &nScrollLines, 0); #endif return nScrollLines; } ///////////////////////////////////////////////////////////////////////////// // CGridCtrl CGridCtrl::CGridCtrl(int nRows, int nCols, int nFixedRows, int nFixedCols) { RegisterWindowClass(); #if !defined(GRIDCONTROL_NO_DRAGDROP) || !defined(GRIDCONTROL_NO_CLIPBOARD) _AFX_THREAD_STATE* pState = AfxGetThreadState(); if (!pState->m_bNeedTerm && !AfxOleInit()) AfxMessageBox(_T("OLE initialization failed. Make sure that the OLE libraries are the correct version")); #endif // Store the system colours in case they change. The gridctrl uses // these colours, and in OnSysColorChange we can check to see if // the gridctrl colours have been changed from the system colours. // If they have, then leave them, otherwise change them to reflect // the new system colours. m_crWindowText = ::GetSysColor(COLOR_WINDOWTEXT); m_crWindowColour = ::GetSysColor(COLOR_WINDOW); m_cr3DFace = ::GetSysColor(COLOR_3DFACE); m_crShadow = ::GetSysColor(COLOR_3DSHADOW); m_crGridLineColour = RGB(192, 192, 192); m_nRows = 0; m_nCols = 0; m_nFixedRows = 0; m_nFixedCols = 0; m_bVirtualMode = FALSE; m_pfnCallback = NULL; m_nVScrollMax = 0; // Scroll position m_nHScrollMax = 0; m_nRowsPerWheelNotch = GetMouseScrollLines(); // Get the number of lines // per mouse wheel notch to scroll m_nBarState = GVL_NONE; m_MouseMode = MOUSE_NOTHING; m_nGridLines = GVL_BOTH; m_bEditable = TRUE; m_bListMode = FALSE; m_bSingleRowSelection = FALSE; m_bSingleColSelection = FALSE; m_bLMouseButtonDown = FALSE; m_bRMouseButtonDown = FALSE; m_bAllowDraw = TRUE; // allow draw updates m_bEnableSelection = TRUE; m_bFixedColumnSelection = TRUE; m_bFixedRowSelection = TRUE; m_bAllowRowResize = TRUE; m_bAllowColumnResize = TRUE; m_bSortOnClick = FALSE; // Sort on header row click m_bHandleTabKey = TRUE; #ifdef _WIN32_WCE m_bDoubleBuffer = FALSE; // Use double buffering to avoid flicker? #else m_bDoubleBuffer = TRUE; // Use double buffering to avoid flicker? #endif m_bTitleTips = TRUE; // show cell title tips m_bWysiwygPrinting = FALSE; // use size-to-width printing m_bHiddenColUnhide = TRUE; // 0-width columns can be expanded via mouse m_bHiddenRowUnhide = TRUE; // 0-Height rows can be expanded via mouse m_bAllowColHide = TRUE; // Columns can be contracted to 0-width via mouse m_bAllowRowHide = TRUE; // Rows can be contracted to 0-height via mouse m_bAscending = TRUE; // sorting stuff m_nSortColumn = -1; m_pfnCompare = NULL; m_nAutoSizeColumnStyle = GVS_BOTH; // Autosize grid using header and data info m_nTimerID = 0; // For drag-selection m_nTimerInterval = 25; // (in milliseconds) m_nResizeCaptureRange = 3; // When resizing columns/row, the cursor has to be // within +/-3 pixels of the dividing line for // resizing to be possible m_pImageList = NULL; // Images in the grid m_bAllowDragAndDrop = FALSE; // for drag and drop - EFW - off by default m_bTrackFocusCell = TRUE; // Track Focus cell? m_bFrameFocus = TRUE; // Frame the selected cell? m_pRtcDefault = RUNTIME_CLASS(CGridCell); SetupDefaultCells(); SetGridBkColor(m_crShadow); // Set up the initial grid size SetRowCount(nRows); SetColumnCount(nCols); SetFixedRowCount(nFixedRows); SetFixedColumnCount(nFixedCols); SetTitleTipTextClr(CLR_DEFAULT); //FNA SetTitleTipBackClr(CLR_DEFAULT); // set initial selection range (ie. none) m_SelectedCellMap.RemoveAll(); m_PrevSelectedCellMap.RemoveAll(); #if !defined(_WIN32_WCE_NO_PRINTING) && !defined(GRIDCONTROL_NO_PRINTING) // EFW - Added to support shaded/unshaded printout and // user-definable margins. m_bShadedPrintOut = TRUE; SetPrintMarginInfo(2, 2, 4, 4, 1, 1, 1); #endif } CGridCtrl::~CGridCtrl() { DeleteAllItems(); #ifndef GRIDCONTROL_NO_TITLETIPS if (m_bTitleTips && ::IsWindow(m_TitleTip.GetSafeHwnd())) m_TitleTip.DestroyWindow(); #endif DestroyWindow(); #if !defined(GRIDCONTROL_NO_DRAGDROP) || !defined(GRIDCONTROL_NO_CLIPBOARD) // BUG FIX - EFW COleDataSource *pSource = COleDataSource::GetClipboardOwner(); if (pSource) COleDataSource::FlushClipboard(); #endif } // Register the window class if it has not already been registered. BOOL CGridCtrl::RegisterWindowClass(HINSTANCE hInstance) { WNDCLASS wndcls; //HINSTANCE hInst = AfxGetInstanceHandle(); //HINSTANCE hInst = AfxGetResourceHandle(); HINSTANCE hInst = hInstance ? hInstance : AfxGetInstanceHandle(); if (!(::GetClassInfo(hInst, GRIDCTRL_CLASSNAME, &wndcls))) { // otherwise we need to register a new class wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW; wndcls.lpfnWndProc = ::DefWindowProc; wndcls.cbClsExtra = wndcls.cbWndExtra = 0; wndcls.hInstance = hInst; wndcls.hIcon = NULL; #ifndef _WIN32_WCE_NO_CURSOR wndcls.hCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW); #else wndcls.hCursor = 0; #endif wndcls.hbrBackground = (HBRUSH)(COLOR_3DFACE + 1); wndcls.lpszMenuName = NULL; wndcls.lpszClassName = GRIDCTRL_CLASSNAME; if (!AfxRegisterClass(&wndcls)) { AfxThrowResourceException(); return FALSE; } } return TRUE; } BOOL CGridCtrl::Initialise() { // Stop re-entry problems static BOOL bInProcedure = FALSE; if (bInProcedure) return FALSE; bInProcedure = TRUE; #ifndef GRIDCONTROL_NO_TITLETIPS m_TitleTip.SetParentWnd(this); #endif if (::IsWindow(m_hWnd)) ModifyStyleEx(0, WS_EX_CLIENTEDGE, 0); bInProcedure = FALSE; return TRUE; } // creates the control - use like any other window create control BOOL CGridCtrl::Create(const RECT& rect, CWnd* pParentWnd, UINT nID, DWORD dwStyle) { ASSERT(pParentWnd->GetSafeHwnd()); if (!CWnd::Create(GRIDCTRL_CLASSNAME, NULL, dwStyle, rect, pParentWnd, nID)) return FALSE; //Initialise(); // The number of rows and columns will only be non-zero if the constructor // was called with non-zero initialising parameters. If this window was created // using a dialog template then the number of rows and columns will be 0 (which // means that the code below will not be needed - which is lucky 'cause it ain't // gonna get called in a dialog-template-type-situation. TRY { m_arRowHeights.SetSize(m_nRows); // initialize row heights m_arColWidths.SetSize(m_nCols); // initialize column widths } CATCH(CMemoryException, e) { e->ReportError(); return FALSE; } END_CATCH int i; for (i = 0; i < m_nRows; i++) m_arRowHeights[i] = m_cellDefault.GetHeight(); for (i = 0; i < m_nCols; i++) m_arColWidths[i] = m_cellDefault.GetWidth(); ResetScrollBars(); //- called in OnSize anyway return TRUE; } void CGridCtrl::SetupDefaultCells() { m_cellDefault.SetGrid(this); // Normal editable cell m_cellFixedColDef.SetGrid(this); // Cell for fixed columns m_cellFixedRowDef.SetGrid(this); // Cell for fixed rows m_cellFixedRowColDef.SetGrid(this); // Cell for area overlapped by fixed columns/rows m_cellDefault.SetTextClr(m_crWindowText); m_cellDefault.SetBackClr(m_crWindowColour); m_cellFixedColDef.SetTextClr(m_crWindowText); m_cellFixedColDef.SetBackClr(m_cr3DFace); m_cellFixedRowDef.SetTextClr(m_crWindowText); m_cellFixedRowDef.SetBackClr(m_cr3DFace); m_cellFixedRowColDef.SetTextClr(m_crWindowText); m_cellFixedRowColDef.SetBackClr(m_cr3DFace); } void CGridCtrl::PreSubclassWindow() { CWnd::PreSubclassWindow(); //HFONT hFont = ::CreateFontIndirect(m_cellDefault.GetFont()); //OnSetFont((LPARAM)hFont, 0); //DeleteObject(hFont); Initialise(); // ResetScrollBars(); - called in OnSize anyway } // Sends a message to the parent in the form of a WM_NOTIFY message with // a NM_GRIDVIEW structure attached LRESULT CGridCtrl::SendMessageToParent(int nRow, int nCol, int nMessage) const { if (!IsWindow(m_hWnd)) return 0; NM_GRIDVIEW nmgv; nmgv.iRow = nRow; nmgv.iColumn = nCol; nmgv.hdr.hwndFrom = m_hWnd; nmgv.hdr.idFrom = GetDlgCtrlID(); nmgv.hdr.code = nMessage; CWnd *pOwner = GetOwner(); if (pOwner && IsWindow(pOwner->m_hWnd)) return pOwner->SendMessage(WM_NOTIFY, nmgv.hdr.idFrom, (LPARAM)&nmgv); else return 0; } // Send a request to the parent to return information on a given cell LRESULT CGridCtrl::SendDisplayRequestToParent(GV_DISPINFO* pDisplayInfo) const { if (!IsWindow(m_hWnd)) return 0; // Fix up the message headers pDisplayInfo->hdr.hwndFrom = m_hWnd; pDisplayInfo->hdr.idFrom = GetDlgCtrlID(); pDisplayInfo->hdr.code = GVN_GETDISPINFO; // Send the message CWnd *pOwner = GetOwner(); if (pOwner && IsWindow(pOwner->m_hWnd)) return pOwner->SendMessage(WM_NOTIFY, pDisplayInfo->hdr.idFrom, (LPARAM)pDisplayInfo); else return 0; } // Send a hint to the parent about caching information LRESULT CGridCtrl::SendCacheHintToParent(const CCellRange& range) const { if (!IsWindow(m_hWnd)) return 0; GV_CACHEHINT CacheHint; // Fix up the message headers CacheHint.hdr.hwndFrom = m_hWnd; CacheHint.hdr.idFrom = GetDlgCtrlID(); CacheHint.hdr.code = GVN_ODCACHEHINT; CacheHint.range = range; // Send the message CWnd *pOwner = GetOwner(); if (pOwner && IsWindow(pOwner->m_hWnd)) return pOwner->SendMessage(WM_NOTIFY, CacheHint.hdr.idFrom, (LPARAM)&CacheHint); else return 0; } BEGIN_MESSAGE_MAP(CGridCtrl, CWnd) //EFW - Added ON_WM_RBUTTONUP //{{AFX_MSG_MAP(CGridCtrl) ON_WM_PAINT() ON_WM_HSCROLL() ON_WM_VSCROLL() ON_WM_SIZE() ON_WM_LBUTTONUP() ON_WM_LBUTTONDOWN() ON_WM_MOUSEMOVE() ON_WM_TIMER() ON_WM_GETDLGCODE() ON_WM_KEYDOWN() ON_WM_CHAR() ON_WM_LBUTTONDBLCLK() ON_WM_ERASEBKGND() ON_UPDATE_COMMAND_UI(ID_EDIT_SELECT_ALL, OnUpdateEditSelectAll) ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll) ON_WM_SYSKEYDOWN() //}}AFX_MSG_MAP #ifndef _WIN32_WCE_NO_CURSOR ON_WM_SETCURSOR() #endif #ifndef _WIN32_WCE ON_WM_RBUTTONUP() ON_WM_SYSCOLORCHANGE() ON_WM_CAPTURECHANGED() #endif #ifndef GRIDCONTROL_NO_CLIPBOARD ON_COMMAND(ID_EDIT_COPY, OnEditCopy) ON_UPDATE_COMMAND_UI(ID_EDIT_COPY, OnUpdateEditCopy) ON_COMMAND(ID_EDIT_CUT, OnEditCut) ON_UPDATE_COMMAND_UI(ID_EDIT_CUT, OnUpdateEditCut) ON_COMMAND(ID_EDIT_PASTE, OnEditPaste) ON_UPDATE_COMMAND_UI(ID_EDIT_PASTE, OnUpdateEditPaste) #endif #if (_WIN32_WCE >= 210) ON_WM_SETTINGCHANGE() #endif #if !defined(_WIN32_WCE) && (_MFC_VER >= 0x0421) ON_WM_MOUSEWHEEL() #endif ON_MESSAGE(WM_SETFONT, OnSetFont) ON_MESSAGE(WM_GETFONT, OnGetFont) ON_MESSAGE(WM_IME_CHAR, OnImeChar) ON_NOTIFY(GVN_ENDLABELEDIT, IDC_INPLACE_CONTROL, OnEndInPlaceEdit) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CGridCtrl message handlers void CGridCtrl::OnPaint() { CPaintDC dc(this); // device context for painting if (m_bDoubleBuffer) // Use a memory DC to remove flicker { CMemDC MemDC(&dc); OnDraw(&MemDC); } else // Draw raw - this helps in debugging vis problems. OnDraw(&dc); } BOOL CGridCtrl::OnEraseBkgnd(CDC* /*pDC*/) { return TRUE; // Don't erase the background. } // Custom background erasure. This gets called from within the OnDraw function, // since we will (most likely) be using a memory DC to stop flicker. If we just // erase the background normally through OnEraseBkgnd, and didn't fill the memDC's // selected bitmap with colour, then all sorts of vis problems would occur void CGridCtrl::EraseBkgnd(CDC* pDC) { CRect VisRect, ClipRect, rect; CBrush FixedRowColBack(GetDefaultCell(TRUE, TRUE)->GetBackClr()), FixedRowBack(GetDefaultCell(TRUE, FALSE)->GetBackClr()), FixedColBack(GetDefaultCell(FALSE, TRUE)->GetBackClr()), TextBack(GetDefaultCell(FALSE, FALSE)->GetBackClr()); CBrush Back(GetGridBkColor()); //CBrush Back(GetTextBkColor()); if (pDC->GetClipBox(ClipRect) == ERROR) return; GetVisibleNonFixedCellRange(VisRect); int nFixedColumnWidth = GetFixedColumnWidth(); int nFixedRowHeight = GetFixedRowHeight(); // Draw Fixed row/column background if (ClipRect.left < nFixedColumnWidth && ClipRect.top < nFixedRowHeight) pDC->FillRect(CRect(ClipRect.left, ClipRect.top, nFixedColumnWidth, nFixedRowHeight), &FixedRowColBack); // Draw Fixed columns background if (ClipRect.left < nFixedColumnWidth && ClipRect.top < VisRect.bottom) pDC->FillRect(CRect(ClipRect.left, ClipRect.top, nFixedColumnWidth, VisRect.bottom), &FixedColBack); // Draw Fixed rows background if (ClipRect.top < nFixedRowHeight && ClipRect.right > nFixedColumnWidth && ClipRect.left < VisRect.right) pDC->FillRect(CRect(nFixedColumnWidth - 1, ClipRect.top, VisRect.right, nFixedRowHeight), &FixedRowBack); // Draw non-fixed cell background if (rect.IntersectRect(VisRect, ClipRect)) { CRect CellRect(max(nFixedColumnWidth, rect.left), max(nFixedRowHeight, rect.top), rect.right, rect.bottom); pDC->FillRect(CellRect, &TextBack); } // Draw right hand side of window outside grid if (VisRect.right < ClipRect.right) pDC->FillRect(CRect(VisRect.right, ClipRect.top, ClipRect.right, ClipRect.bottom), &Back); // Draw bottom of window below grid if (VisRect.bottom < ClipRect.bottom && ClipRect.left < VisRect.right) pDC->FillRect(CRect(ClipRect.left, VisRect.bottom, VisRect.right, ClipRect.bottom), &Back); } void CGridCtrl::OnSize(UINT nType, int cx, int cy) { static BOOL bAlreadyInsideThisProcedure = FALSE; if (bAlreadyInsideThisProcedure) return; if (!::IsWindow(m_hWnd)) return; // This hurts - but I need a place for initialisation for the drop target, // and the window must be created. "Create" is a good place - if one is // creating the window dynamically. If one is using a dialog template then // this is no good. Oh well... # ifndef GRIDCONTROL_NO_DRAGDROP m_DropTarget.Register(this); # endif // Start re-entry blocking bAlreadyInsideThisProcedure = TRUE; EndEditing(); // destroy any InPlaceEdit's CWnd::OnSize(nType, cx, cy); ResetScrollBars(); // End re-entry blocking bAlreadyInsideThisProcedure = FALSE; } UINT CGridCtrl::OnGetDlgCode() { UINT nCode = DLGC_WANTARROWS | DLGC_WANTCHARS; // DLGC_WANTALLKEYS; // if (m_bHandleTabKey && !IsCTRLpressed()) nCode |= DLGC_WANTTAB; return nCode; } #ifndef _WIN32_WCE // If system colours change, then redo colours void CGridCtrl::OnSysColorChange() { CWnd::OnSysColorChange(); if (GetDefaultCell(FALSE, FALSE)->GetTextClr() == m_crWindowText) // Still using system colours GetDefaultCell(FALSE, FALSE)->SetTextClr(::GetSysColor(COLOR_WINDOWTEXT)); // set to new system colour if (GetDefaultCell(FALSE, FALSE)->GetBackClr() == m_crWindowColour) GetDefaultCell(FALSE, FALSE)->SetBackClr(::GetSysColor(COLOR_WINDOW)); if (GetDefaultCell(TRUE, FALSE)->GetTextClr() == m_crWindowText) // Still using system colours GetDefaultCell(TRUE, FALSE)->SetTextClr(::GetSysColor(COLOR_WINDOWTEXT)); // set to new system colour if (GetDefaultCell(TRUE, FALSE)->GetBackClr() == m_crWindowColour) GetDefaultCell(TRUE, FALSE)->SetBackClr(::GetSysColor(COLOR_WINDOW)); if (GetDefaultCell(FALSE, TRUE)->GetTextClr() == m_crWindowText) // Still using system colours GetDefaultCell(FALSE, TRUE)->SetTextClr(::GetSysColor(COLOR_WINDOWTEXT)); // set to new system colour if (GetDefaultCell(FALSE, TRUE)->GetBackClr() == m_crWindowColour) GetDefaultCell(FALSE, TRUE)->SetBackClr(::GetSysColor(COLOR_WINDOW)); if (GetDefaultCell(TRUE, TRUE)->GetTextClr() == m_crWindowText) // Still using system colours GetDefaultCell(TRUE, TRUE)->SetTextClr(::GetSysColor(COLOR_WINDOWTEXT)); // set to new system colour if (GetDefaultCell(TRUE, TRUE)->GetBackClr() == m_crWindowColour) GetDefaultCell(TRUE, TRUE)->SetBackClr(::GetSysColor(COLOR_WINDOW)); if (GetGridBkColor() == m_crShadow) SetGridBkColor(::GetSysColor(COLOR_3DSHADOW)); m_crWindowText = ::GetSysColor(COLOR_WINDOWTEXT); m_crWindowColour = ::GetSysColor(COLOR_WINDOW); m_cr3DFace = ::GetSysColor(COLOR_3DFACE); m_crShadow = ::GetSysColor(COLOR_3DSHADOW); } #endif #ifndef _WIN32_WCE_NO_CURSOR // If we are drag-selecting cells, or drag and dropping, stop now void CGridCtrl::OnCaptureChanged(CWnd *pWnd) { if (pWnd->GetSafeHwnd() == GetSafeHwnd()) return; // kill timer if active if (m_nTimerID != 0) { KillTimer(m_nTimerID); m_nTimerID = 0; } #ifndef GRIDCONTROL_NO_DRAGDROP // Kill drag and drop if active if (m_MouseMode == MOUSE_DRAGGING) m_MouseMode = MOUSE_NOTHING; #endif } #endif #if (_MFC_VER >= 0x0421) || (_WIN32_WCE >= 210) // If system settings change, then redo colours void CGridCtrl::OnSettingChange(UINT uFlags, LPCTSTR lpszSection) { CWnd::OnSettingChange(uFlags, lpszSection); if (GetDefaultCell(FALSE, FALSE)->GetTextClr() == m_crWindowText) // Still using system colours GetDefaultCell(FALSE, FALSE)->SetTextClr(::GetSysColor(COLOR_WINDOWTEXT)); // set to new system colour if (GetDefaultCell(FALSE, FALSE)->GetBackClr() == m_crWindowColour) GetDefaultCell(FALSE, FALSE)->SetBackClr(::GetSysColor(COLOR_WINDOW)); if (GetDefaultCell(TRUE, FALSE)->GetTextClr() == m_crWindowText) // Still using system colours GetDefaultCell(TRUE, FALSE)->SetTextClr(::GetSysColor(COLOR_WINDOWTEXT)); // set to new system colour if (GetDefaultCell(TRUE, FALSE)->GetBackClr() == m_crWindowColour) GetDefaultCell(TRUE, FALSE)->SetBackClr(::GetSysColor(COLOR_WINDOW)); if (GetDefaultCell(FALSE, TRUE)->GetTextClr() == m_crWindowText) // Still using system colours GetDefaultCell(FALSE, TRUE)->SetTextClr(::GetSysColor(COLOR_WINDOWTEXT)); // set to new system colour if (GetDefaultCell(FALSE, TRUE)->GetBackClr() == m_crWindowColour) GetDefaultCell(FALSE, TRUE)->SetBackClr(::GetSysColor(COLOR_WINDOW)); if (GetDefaultCell(TRUE, TRUE)->GetTextClr() == m_crWindowText) // Still using system colours GetDefaultCell(TRUE, TRUE)->SetTextClr(::GetSysColor(COLOR_WINDOWTEXT)); // set to new system colour if (GetDefaultCell(TRUE, TRUE)->GetBackClr() == m_crWindowColour) GetDefaultCell(TRUE, TRUE)->SetBackClr(::GetSysColor(COLOR_WINDOW)); if (GetGridBkColor() == m_crShadow) SetGridBkColor(::GetSysColor(COLOR_3DSHADOW)); m_crWindowText = ::GetSysColor(COLOR_WINDOWTEXT); m_crWindowColour = ::GetSysColor(COLOR_WINDOW); m_cr3DFace = ::GetSysColor(COLOR_3DFACE); m_crShadow = ::GetSysColor(COLOR_3DSHADOW); m_nRowsPerWheelNotch = GetMouseScrollLines(); // Get the number of lines } #endif // For drag-selection. Scrolls hidden cells into view // TODO: decrease timer interval over time to speed up selection over time void CGridCtrl::OnTimer(UINT nIDEvent) { ASSERT(nIDEvent == WM_LBUTTONDOWN); if (nIDEvent != WM_LBUTTONDOWN) return; CPoint pt, origPt; #ifdef _WIN32_WCE if (m_MouseMode == MOUSE_NOTHING) return; origPt = GetMessagePos(); #else if (!GetCursorPos(&origPt)) return; #endif ScreenToClient(&origPt); CRect rect; GetClientRect(rect); int nFixedRowHeight = GetFixedRowHeight(); int nFixedColWidth = GetFixedColumnWidth(); pt = origPt; if (pt.y > rect.bottom) { //SendMessage(WM_VSCROLL, SB_LINEDOWN, 0); SendMessage(WM_KEYDOWN, VK_DOWN, 0); if (pt.x < rect.left) pt.x = rect.left; if (pt.x > rect.right) pt.x = rect.right; pt.y = rect.bottom; OnSelecting(GetCellFromPt(pt)); } else if (pt.y < nFixedRowHeight) { //SendMessage(WM_VSCROLL, SB_LINEUP, 0); SendMessage(WM_KEYDOWN, VK_UP, 0); if (pt.x < rect.left) pt.x = rect.left; if (pt.x > rect.right) pt.x = rect.right; pt.y = nFixedRowHeight + 1; OnSelecting(GetCellFromPt(pt)); } pt = origPt; if (pt.x > rect.right) { // SendMessage(WM_HSCROLL, SB_LINERIGHT, 0); SendMessage(WM_KEYDOWN, VK_RIGHT, 0); if (pt.y < rect.top) pt.y = rect.top; if (pt.y > rect.bottom) pt.y = rect.bottom; pt.x = rect.right; OnSelecting(GetCellFromPt(pt)); } else if (pt.x < nFixedColWidth) { //SendMessage(WM_HSCROLL, SB_LINELEFT, 0); SendMessage(WM_KEYDOWN, VK_LEFT, 0); if (pt.y < rect.top) pt.y = rect.top; if (pt.y > rect.bottom) pt.y = rect.bottom; pt.x = nFixedColWidth + 1; OnSelecting(GetCellFromPt(pt)); } } // move about with keyboard void CGridCtrl::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { if (!IsValid(m_idCurrentCell)) { CWnd::OnKeyDown(nChar, nRepCnt, nFlags); return; } CCellID next = m_idCurrentCell; BOOL bChangeLine = FALSE; BOOL bHorzScrollAction = FALSE; BOOL bVertScrollAction = FALSE; if (IsCTRLpressed()) { switch (nChar) { case 'A': OnEditSelectAll(); break; #ifndef GRIDCONTROL_NO_CLIPBOARD case 'X': OnEditCut(); break; case VK_INSERT: case 'C': OnEditCopy(); break; case 'V': OnEditPaste(); break; #endif } } #ifndef GRIDCONTROL_NO_CLIPBOARD if (IsSHIFTpressed() && (nChar == VK_INSERT)) OnEditPaste(); #endif BOOL bFoundVisible; int iOrig; if (nChar == VK_DELETE) { ValidateAndModifyCellContents(m_idCurrentCell.row, m_idCurrentCell.col, _T("")); } else if (nChar == VK_DOWN) { // don't let user go to a hidden row bFoundVisible = FALSE; iOrig = next.row; next.row++; while (next.row < GetRowCount()) { if (GetRowHeight(next.row) > 0) { bFoundVisible = TRUE; break; } next.row++; } if (!bFoundVisible) next.row = iOrig; } else if (nChar == VK_UP) { // don't let user go to a hidden row bFoundVisible = FALSE; iOrig = next.row; next.row--; while (next.row >= m_nFixedRows) { if (GetRowHeight(next.row) > 0) { bFoundVisible = TRUE; break; } next.row--; } if (!bFoundVisible) next.row = iOrig; } else if (nChar == VK_RIGHT || (nChar == VK_TAB && !IsSHIFTpressed())) { // don't let user go to a hidden column bFoundVisible = FALSE; iOrig = next.col; next.col++; if (nChar == VK_TAB) { if (next.col == (GetColumnCount()) && next.row < (GetRowCount() - 1)) { next.row++; next.col = m_nFixedCols; bChangeLine = TRUE; } else CWnd::OnKeyDown(nChar, nRepCnt, nFlags); } while (next.col < GetColumnCount()) { if (GetColumnWidth(next.col) > 0) { bFoundVisible = TRUE; break; } next.col++; } if (!bFoundVisible) next.col = iOrig; } else if (nChar == VK_LEFT || (nChar == VK_TAB && IsSHIFTpressed())) { // don't let user go to a hidden column bFoundVisible = FALSE; iOrig = next.col; next.col--; if (nChar == VK_TAB) { if (next.col == (GetFixedColumnCount() - 1) && next.row > GetFixedRowCount()) { next.row--; next.col = GetColumnCount() - 1; bChangeLine = TRUE; } else CWnd::OnKeyDown(nChar, nRepCnt, nFlags); } while (next.col >= m_nFixedCols) { if (GetColumnWidth(next.col) > 0) { bFoundVisible = TRUE; break; } next.col--; } if (!bFoundVisible) next.col = iOrig; } else if (nChar == VK_NEXT) { CCellID idOldTopLeft = GetTopleftNonFixedCell(); SendMessage(WM_VSCROLL, SB_PAGEDOWN, 0); bVertScrollAction = TRUE; CCellID idNewTopLeft = GetTopleftNonFixedCell(); int increment = idNewTopLeft.row - idOldTopLeft.row; if (increment) { next.row += increment; if (next.row > (GetRowCount() - 1)) next.row = GetRowCount() - 1; } else next.row = GetRowCount() - 1; } else if (nChar == VK_PRIOR) { CCellID idOldTopLeft = GetTopleftNonFixedCell(); SendMessage(WM_VSCROLL, SB_PAGEUP, 0); bVertScrollAction = TRUE; CCellID idNewTopLeft = GetTopleftNonFixedCell(); int increment = idNewTopLeft.row - idOldTopLeft.row; if (increment) { next.row += increment; if (next.row < m_nFixedRows) next.row = m_nFixedRows; } else next.row = m_nFixedRows; } else if (nChar == VK_HOME) { // Home and Ctrl-Home work more like Excel // and don't let user go to a hidden cell if (IsCTRLpressed()) { SendMessage(WM_VSCROLL, SB_TOP, 0); SendMessage(WM_HSCROLL, SB_LEFT, 0); bVertScrollAction = TRUE; bHorzScrollAction = TRUE; next.row = m_nFixedRows; next.col = m_nFixedCols; } else { SendMessage(WM_HSCROLL, SB_LEFT, 0); bHorzScrollAction = TRUE; next.col = m_nFixedCols; } // adjust column to avoid hidden columns and rows while (next.col < GetColumnCount() - 1) { if (GetColumnWidth(next.col) > 0) break; next.col++; } while (next.row < GetRowCount() - 1) { if (GetRowHeight(next.row) > 0) break; next.row++; } } else if (nChar == VK_END) { // End and Ctrl-End work more like Excel // and don't let user go to a hidden cell if (IsCTRLpressed()) { SendMessage(WM_VSCROLL, SB_BOTTOM, 0); SendMessage(WM_HSCROLL, SB_RIGHT, 0); bHorzScrollAction = TRUE; bVertScrollAction = TRUE; next.row = GetRowCount() - 1; next.col = GetColumnCount() - 1; } else { SendMessage(WM_HSCROLL, SB_RIGHT, 0); bHorzScrollAction = TRUE; next.col = GetColumnCount() - 1; } // adjust column to avoid hidden columns and rows while (next.col > m_nFixedCols + 1) { if (GetColumnWidth(next.col) > 0) break; next.col--; } while (next.row > m_nFixedRows + 1) { if (GetRowHeight(next.row) > 0) break; next.row--; } } else if (nChar == VK_F2) { OnEditCell(m_idCurrentCell.row, m_idCurrentCell.col, CPoint(-1, -1), VK_LBUTTON); } else { CWnd::OnKeyDown(nChar, nRepCnt, nFlags); return; } if (next != m_idCurrentCell) { // While moving with the Cursorkeys the current ROW/CELL will get selected // OR Selection will get expanded when SHIFT is pressed // Cut n paste from OnLButtonDown - Franco Bez // Added check for NULL mouse mode - Chris Maunder. if (m_MouseMode == MOUSE_NOTHING) { m_PrevSelectedCellMap.RemoveAll(); m_MouseMode = m_bListMode ? MOUSE_SELECT_ROW : MOUSE_SELECT_CELLS; if (!IsSHIFTpressed() || nChar == VK_TAB) m_SelectionStartCell = next; OnSelecting(next); m_MouseMode = MOUSE_NOTHING; } SetFocusCell(next); if (!IsCellVisible(next)) { switch (nChar) { case VK_RIGHT: SendMessage(WM_HSCROLL, SB_LINERIGHT, 0); bHorzScrollAction = TRUE; break; case VK_LEFT: SendMessage(WM_HSCROLL, SB_LINELEFT, 0); bHorzScrollAction = TRUE; break; case VK_DOWN: SendMessage(WM_VSCROLL, SB_LINEDOWN, 0); bVertScrollAction = TRUE; break; case VK_UP: SendMessage(WM_VSCROLL, SB_LINEUP, 0); bVertScrollAction = TRUE; break; case VK_TAB: if (IsSHIFTpressed()) { if (bChangeLine) { SendMessage(WM_VSCROLL, SB_LINEUP, 0); bVertScrollAction = TRUE; SetScrollPos32(SB_HORZ, m_nHScrollMax); break; } else { SendMessage(WM_HSCROLL, SB_LINELEFT, 0); bHorzScrollAction = TRUE; } } else { if (bChangeLine) { SendMessage(WM_VSCROLL, SB_LINEDOWN, 0); bVertScrollAction = TRUE; SetScrollPos32(SB_HORZ, 0); break; } else { SendMessage(WM_HSCROLL, SB_LINERIGHT, 0); bHorzScrollAction = TRUE; } } break; } EnsureVisible(next); // Make sure cell is visible Invalidate(); } EnsureVisible(next); // Make sure cell is visible if (bHorzScrollAction) SendMessage(WM_HSCROLL, SB_ENDSCROLL, 0); if (bVertScrollAction) SendMessage(WM_VSCROLL, SB_ENDSCROLL, 0); } } void CGridCtrl::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) { CWnd::OnKeyUp(nChar, nRepCnt, nFlags); } void CGridCtrl::OnSysKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { #ifdef GRIDCONTROL_USE_TITLETIPS m_TitleTip.Hide(); // hide any titletips #endif CWnd::OnSysKeyDown(nChar, nRepCnt, nFlags); } // Instant editing of cells when keys are pressed void CGridCtrl::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { // EFW - BUG FIX if (!IsCTRLpressed() && m_MouseMode == MOUSE_NOTHING && nChar != VK_ESCAPE) { if (!m_bHandleTabKey || (m_bHandleTabKey && nChar != VK_TAB)) OnEditCell(m_idCurrentCell.row, m_idCurrentCell.col, CPoint(-1, -1), nChar); } CWnd::OnChar(nChar, nRepCnt, nFlags); } // Added by KiteFly LRESULT CGridCtrl::OnImeChar(WPARAM wCharCode, LPARAM) { // EFW - BUG FIX if (!IsCTRLpressed() && m_MouseMode == MOUSE_NOTHING && wCharCode != VK_ESCAPE) OnEditCell(m_idCurrentCell.row, m_idCurrentCell.col, CPoint(-1, -1), wCharCode); return 0; } // Callback from any CInPlaceEdits that ended. This just calls OnEndEditCell, // refreshes the edited cell and moves onto next cell if the return character // from the edit says we should. void CGridCtrl::OnEndInPlaceEdit(NMHDR* pNMHDR, LRESULT* pResult) { GV_DISPINFO *pgvDispInfo = (GV_DISPINFO *)pNMHDR; GV_ITEM *pgvItem = &pgvDispInfo->item; // In case OnEndInPlaceEdit called as window is being destroyed if (!IsWindow(GetSafeHwnd())) return; OnEndEditCell(pgvItem->row, pgvItem->col, pgvItem->strText); //InvalidateCellRect(CCellID(pgvItem->row, pgvItem->col)); switch (pgvItem->lParam) { case VK_TAB: case VK_DOWN: case VK_UP: case VK_RIGHT: case VK_LEFT: case VK_NEXT: case VK_PRIOR: case VK_HOME: case VK_END: OnKeyDown(pgvItem->lParam, 0, 0); OnEditCell(m_idCurrentCell.row, m_idCurrentCell.col, CPoint(-1, -1), pgvItem->lParam); } *pResult = 0; } // Handle horz scrollbar notifications void CGridCtrl::OnHScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/) { EndEditing(); #ifndef GRIDCONTROL_NO_TITLETIPS m_TitleTip.Hide(); // hide any titletips #endif int scrollPos = GetScrollPos32(SB_HORZ); CCellID idTopLeft = GetTopleftNonFixedCell(); CRect rect; GetClientRect(rect); switch (nSBCode) { case SB_LINERIGHT: if (scrollPos < m_nHScrollMax) { // may have contiguous hidden columns. Blow by them while (idTopLeft.col < (GetColumnCount() - 1) && GetColumnWidth(idTopLeft.col) < 1) { idTopLeft.col++; } int xScroll = GetColumnWidth(idTopLeft.col); SetScrollPos32(SB_HORZ, scrollPos + xScroll); if (GetScrollPos32(SB_HORZ) == scrollPos) break; // didn't work rect.left = GetFixedColumnWidth(); //rect.left = GetFixedColumnWidth() + xScroll; //ScrollWindow(-xScroll, 0, rect); //rect.left = rect.right - xScroll; InvalidateRect(rect); } break; case SB_LINELEFT: if (scrollPos > 0 && idTopLeft.col > GetFixedColumnCount()) { int iColToUse = idTopLeft.col - 1; // may have contiguous hidden columns. Blow by them while (iColToUse > GetFixedColumnCount() && GetColumnWidth(iColToUse) < 1) { iColToUse--; } int xScroll = GetColumnWidth(iColToUse); SetScrollPos32(SB_HORZ, max(0, scrollPos - xScroll)); rect.left = GetFixedColumnWidth(); //ScrollWindow(xScroll, 0, rect); //rect.right = rect.left + xScroll; InvalidateRect(rect); } break; case SB_PAGERIGHT: if (scrollPos < m_nHScrollMax) { rect.left = GetFixedColumnWidth(); int offset = rect.Width(); int pos = min(m_nHScrollMax, scrollPos + offset); SetScrollPos32(SB_HORZ, pos); rect.left = GetFixedColumnWidth(); InvalidateRect(rect); } break; case SB_PAGELEFT: if (scrollPos > 0) { rect.left = GetFixedColumnWidth(); int offset = -rect.Width(); int pos = max(0, scrollPos + offset); SetScrollPos32(SB_HORZ, pos); rect.left = GetFixedColumnWidth(); InvalidateRect(rect); } break; case SB_THUMBPOSITION: case SB_THUMBTRACK: { SetScrollPos32(SB_HORZ, GetScrollPos32(SB_HORZ, TRUE)); m_idTopLeftCell.row = -1; CCellID idNewTopLeft = GetTopleftNonFixedCell(); if (idNewTopLeft != idTopLeft) { rect.left = GetFixedColumnWidth(); InvalidateRect(rect); } } break; case SB_LEFT: if (scrollPos > 0) { SetScrollPos32(SB_HORZ, 0); Invalidate(); } break; case SB_RIGHT: if (scrollPos < m_nHScrollMax) { SetScrollPos32(SB_HORZ, m_nHScrollMax); Invalidate(); } break; default: break; } } // Handle vert scrollbar notifications void CGridCtrl::OnVScroll(UINT nSBCode, UINT /*nPos*/, CScrollBar* /*pScrollBar*/) { EndEditing(); #ifndef GRIDCONTROL_NO_TITLETIPS m_TitleTip.Hide(); // hide any titletips #endif // Get the scroll position ourselves to ensure we get a 32 bit value int scrollPos = GetScrollPos32(SB_VERT); CCellID idTopLeft = GetTopleftNonFixedCell(); CRect rect; GetClientRect(rect); switch (nSBCode) { case SB_LINEDOWN: if (scrollPos < m_nVScrollMax) { // may have contiguous hidden rows. Blow by them while (idTopLeft.row < (GetRowCount() - 1) && GetRowHeight(idTopLeft.row) < 1) { idTopLeft.row++; } int yScroll = GetRowHeight(idTopLeft.row); SetScrollPos32(SB_VERT, scrollPos + yScroll); if (GetScrollPos32(SB_VERT) == scrollPos) break; // didn't work rect.top = GetFixedRowHeight(); //rect.top = GetFixedRowHeight() + yScroll; //ScrollWindow(0, -yScroll, rect); //rect.top = rect.bottom - yScroll; InvalidateRect(rect); } break; case SB_LINEUP: if (scrollPos > 0 && idTopLeft.row > GetFixedRowCount()) { int iRowToUse = idTopLeft.row - 1; // may have contiguous hidden rows. Blow by them while (iRowToUse > GetFixedRowCount() && GetRowHeight(iRowToUse) < 1) { iRowToUse--; } int yScroll = GetRowHeight(iRowToUse); SetScrollPos32(SB_VERT, max(0, scrollPos - yScroll)); rect.top = GetFixedRowHeight(); //ScrollWindow(0, yScroll, rect); //rect.bottom = rect.top + yScroll; InvalidateRect(rect); } break; case SB_PAGEDOWN: if (scrollPos < m_nVScrollMax) { rect.top = GetFixedRowHeight(); scrollPos = min(m_nVScrollMax, scrollPos + rect.Height()); SetScrollPos32(SB_VERT, scrollPos); rect.top = GetFixedRowHeight(); InvalidateRect(rect); } break; case SB_PAGEUP: if (scrollPos > 0) { rect.top = GetFixedRowHeight(); int offset = -rect.Height(); int pos = max(0, scrollPos + offset); SetScrollPos32(SB_VERT, pos); rect.top = GetFixedRowHeight(); InvalidateRect(rect); } break; case SB_THUMBPOSITION: case SB_THUMBTRACK: { SetScrollPos32(SB_VERT, GetScrollPos32(SB_VERT, TRUE)); m_idTopLeftCell.row = -1; CCellID idNewTopLeft = GetTopleftNonFixedCell(); if (idNewTopLeft != idTopLeft) { rect.top = GetFixedRowHeight(); InvalidateRect(rect); } } break; case SB_TOP: if (scrollPos > 0) { SetScrollPos32(SB_VERT, 0); Invalidate(); } break; case SB_BOTTOM: if (scrollPos < m_nVScrollMax) { SetScrollPos32(SB_VERT, m_nVScrollMax); Invalidate(); } default: break; } } ///////////////////////////////////////////////////////////////////////////// // CGridCtrl implementation functions void CGridCtrl::OnDraw(CDC* pDC) { if (!m_bAllowDraw) return; CRect clipRect; if (pDC->GetClipBox(&clipRect) == ERROR) return; EraseBkgnd(pDC); // OnEraseBkgnd does nothing, so erase bkgnd here. // This necessary since we may be using a Memory DC. CRect rect; int row, col; CGridCellBase* pCell; int nFixedRowHeight = GetFixedRowHeight(); int nFixedColWidth = GetFixedColumnWidth(); CCellID idTopLeft = GetTopleftNonFixedCell(); int minVisibleRow = idTopLeft.row, minVisibleCol = idTopLeft.col; CRect VisRect; CCellRange VisCellRange = GetVisibleNonFixedCellRange(VisRect); int maxVisibleRow = VisCellRange.GetMaxRow(), maxVisibleCol = VisCellRange.GetMaxCol(); if (GetVirtualMode()) SendCacheHintToParent(VisCellRange); // draw top-left cells 0..m_nFixedRows-1, 0..m_nFixedCols-1 rect.bottom = -1; for (row = 0; row < m_nFixedRows; row++) { rect.top = rect.bottom + 1; rect.bottom = rect.top + GetRowHeight(row) - 1; rect.right = -1; for (col = 0; col < m_nFixedCols; col++) { rect.left = rect.right + 1; rect.right = rect.left + GetColumnWidth(col) - 1; pCell = GetCell(row, col); if (pCell) pCell->Draw(pDC, row, col, rect, FALSE); } } // draw fixed column cells: m_nFixedRows..n, 0..m_nFixedCols-1 rect.bottom = nFixedRowHeight - 1; for (row = minVisibleRow; row <= maxVisibleRow; row++) { rect.top = rect.bottom + 1; rect.bottom = rect.top + GetRowHeight(row) - 1; // rect.bottom = bottom pixel of previous row if (rect.top > clipRect.bottom) break; // Gone past cliprect if (rect.bottom < clipRect.top) continue; // Reached cliprect yet? rect.right = -1; for (col = 0; col < m_nFixedCols; col++) { rect.left = rect.right + 1; rect.right = rect.left + GetColumnWidth(col) - 1; if (rect.left > clipRect.right) break; // gone past cliprect if (rect.right < clipRect.left) continue; // Reached cliprect yet? pCell = GetCell(row, col); if (pCell) pCell->Draw(pDC, row, col, rect, FALSE); } } // draw fixed row cells 0..m_nFixedRows, m_nFixedCols..n rect.bottom = -1; for (row = 0; row < m_nFixedRows; row++) { rect.top = rect.bottom + 1; rect.bottom = rect.top + GetRowHeight(row) - 1; // rect.bottom = bottom pixel of previous row if (rect.top > clipRect.bottom) break; // Gone past cliprect if (rect.bottom < clipRect.top) continue; // Reached cliprect yet? rect.right = nFixedColWidth - 1; for (col = minVisibleCol; col <= maxVisibleCol; col++) { rect.left = rect.right + 1; rect.right = rect.left + GetColumnWidth(col) - 1; if (rect.left > clipRect.right) break; // gone past cliprect if (rect.right < clipRect.left) continue; // Reached cliprect yet? pCell = GetCell(row, col); if (pCell) pCell->Draw(pDC, row, col, rect, FALSE); } } // draw rest of non-fixed cells rect.bottom = nFixedRowHeight - 1; for (row = minVisibleRow; row <= maxVisibleRow; row++) { rect.top = rect.bottom + 1; rect.bottom = rect.top + GetRowHeight(row) - 1; // rect.bottom = bottom pixel of previous row if (rect.top > clipRect.bottom) break; // Gone past cliprect if (rect.bottom < clipRect.top) continue; // Reached cliprect yet? rect.right = nFixedColWidth - 1; for (col = minVisibleCol; col <= maxVisibleCol; col++) { rect.left = rect.right + 1; rect.right = rect.left + GetColumnWidth(col) - 1; if (rect.left > clipRect.right) break; // gone past cliprect if (rect.right < clipRect.left) continue; // Reached cliprect yet? pCell = GetCell(row, col); // TRACE(_T("Cell %d,%d type: %s\n"), row, col, pCell->GetRuntimeClass()->m_lpszClassName); if (pCell) pCell->Draw(pDC, row, col, rect, FALSE); } } CPen pen; pen.CreatePen(PS_SOLID, 0, m_crGridLineColour); pDC->SelectObject(&pen); // draw vertical lines (drawn at ends of cells) if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_VERT) { int x = nFixedColWidth; for (col = minVisibleCol; col <= maxVisibleCol; col++) { x += GetColumnWidth(col); pDC->MoveTo(x - 1, nFixedRowHeight); pDC->LineTo(x - 1, VisRect.bottom); } } // draw horizontal lines (drawn at bottom of each cell) if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_HORZ) { int y = nFixedRowHeight; for (row = minVisibleRow; row <= maxVisibleRow; row++) { y += GetRowHeight(row); pDC->MoveTo(nFixedColWidth, y - 1); pDC->LineTo(VisRect.right, y - 1); } } pDC->SelectStockObject(NULL_PEN); // Let parent know it can discard it's data if it needs to. if (GetVirtualMode()) SendCacheHintToParent(CCellRange(-1, -1, -1, -1)); } //////////////////////////////////////////////////////////////////////////////////////// // CGridCtrl Cell selection stuff // Is a given cell designation valid (ie within the bounds of our number // of columns/rows)? BOOL CGridCtrl::IsValid(int nRow, int nCol) const { return (nRow >= 0 && nRow < m_nRows && nCol >= 0 && nCol < m_nCols); } BOOL CGridCtrl::IsValid(const CCellID& cell) const { return IsValid(cell.row, cell.col); } // Is a given cell range valid (ie within the bounds of our number // of columns/rows)? BOOL CGridCtrl::IsValid(const CCellRange& range) const { return (range.GetMinRow() >= 0 && range.GetMinCol() >= 0 && range.GetMaxRow() >= 0 && range.GetMaxCol() >= 0 && range.GetMaxRow() < m_nRows && range.GetMaxCol() < m_nCols && range.GetMinRow() <= range.GetMaxRow() && range.GetMinCol() <= range.GetMaxCol()); } // Enables/Disables redraw for certain operations like columns auto-sizing etc, // but not for user caused things such as selection changes. void CGridCtrl::SetRedraw(BOOL bAllowDraw, BOOL bResetScrollBars /* = FALSE */) { // TRACE(_T("%s: Setting redraw to %s\n"), // GetRuntimeClass()->m_lpszClassName, bAllowDraw? _T("TRUE") : _T("FALSE")); if (bAllowDraw && !m_bAllowDraw) { m_bAllowDraw = TRUE; Refresh(); } m_bAllowDraw = bAllowDraw; if (bResetScrollBars) ResetScrollBars(); } // Forces a redraw of a cell immediately (using a direct DC construction, // or the supplied dc) BOOL CGridCtrl::RedrawCell(const CCellID& cell, CDC* pDC /* = NULL */) { return RedrawCell(cell.row, cell.col, pDC); } BOOL CGridCtrl::RedrawCell(int nRow, int nCol, CDC* pDC /* = NULL */) { BOOL bResult = TRUE; BOOL bMustReleaseDC = FALSE; if (!m_bAllowDraw || !IsCellVisible(nRow, nCol)) return FALSE; CRect rect; if (!GetCellRect(nRow, nCol, rect)) return FALSE; if (!pDC) { pDC = GetDC(); if (pDC) bMustReleaseDC = TRUE; } if (pDC) { // Redraw cells directly if (nRow < m_nFixedRows || nCol < m_nFixedCols) { CGridCellBase* pCell = GetCell(nRow, nCol); if (pCell) bResult = pCell->Draw(pDC, nRow, nCol, rect, TRUE); } else { CGridCellBase* pCell = GetCell(nRow, nCol); if (pCell) bResult = pCell->Draw(pDC, nRow, nCol, rect, TRUE); // Since we have erased the background, we will need to redraw the gridlines CPen pen; pen.CreatePen(PS_SOLID, 0, m_crGridLineColour); CPen* pOldPen = (CPen*)pDC->SelectObject(&pen); if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_HORZ) { pDC->MoveTo(rect.left, rect.bottom); pDC->LineTo(rect.right + 1, rect.bottom); } if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_VERT) { pDC->MoveTo(rect.right, rect.top); pDC->LineTo(rect.right, rect.bottom + 1); } pDC->SelectObject(pOldPen); } } else InvalidateRect(rect, TRUE); // Could not get a DC - invalidate it anyway // and hope that OnPaint manages to get one if (bMustReleaseDC) ReleaseDC(pDC); return bResult; } // redraw a complete row BOOL CGridCtrl::RedrawRow(int row) { BOOL bResult = TRUE; CDC* pDC = GetDC(); for (int col = 0; col < GetColumnCount(); col++) bResult = RedrawCell(row, col, pDC) && bResult; if (pDC) ReleaseDC(pDC); return bResult; } // redraw a complete column BOOL CGridCtrl::RedrawColumn(int col) { BOOL bResult = TRUE; CDC* pDC = GetDC(); for (int row = 0; row < GetRowCount(); row++) bResult = RedrawCell(row, col, pDC) && bResult; if (pDC) ReleaseDC(pDC); return bResult; } // Sets the currently selected cell, returning the previous current cell CCellID CGridCtrl::SetFocusCell(int nRow, int nCol) { return SetFocusCell(CCellID(nRow, nCol)); } CCellID CGridCtrl::SetFocusCell(CCellID cell) { if (cell == m_idCurrentCell) return m_idCurrentCell; CCellID idPrev = m_idCurrentCell; // EFW - Bug Fix - Force focus to be in a non-fixed cell if (cell.row != -1 && cell.row < GetFixedRowCount()) cell.row = GetFixedRowCount(); if (cell.col != -1 && cell.col < GetFixedColumnCount()) cell.col = GetFixedColumnCount(); m_idCurrentCell = cell; if (IsValid(idPrev)) { SetItemState(idPrev.row, idPrev.col, GetItemState(idPrev.row, idPrev.col) & ~GVIS_FOCUSED); RedrawCell(idPrev); // comment to reduce flicker if (GetTrackFocusCell() && idPrev.col != m_idCurrentCell.col) for (int row = 0; row < m_nFixedRows; row++) RedrawCell(row, idPrev.col); if (GetTrackFocusCell() && idPrev.row != m_idCurrentCell.row) for (int col = 0; col < m_nFixedCols; col++) RedrawCell(idPrev.row, col); } if (IsValid(m_idCurrentCell)) { SetItemState(m_idCurrentCell.row, m_idCurrentCell.col, GetItemState(m_idCurrentCell.row, m_idCurrentCell.col) | GVIS_FOCUSED); RedrawCell(m_idCurrentCell); // comment to reduce flicker if (GetTrackFocusCell() && idPrev.col != m_idCurrentCell.col) for (int row = 0; row < m_nFixedRows; row++) RedrawCell(row, m_idCurrentCell.col); if (GetTrackFocusCell() && idPrev.row != m_idCurrentCell.row) for (int col = 0; col < m_nFixedCols; col++) RedrawCell(m_idCurrentCell.row, col); // EFW - New addition. If in list mode, make sure the selected // row highlight follows the cursor. // Removed by C Maunder 27 May //if (m_bListMode) //{ // m_PrevSelectedCellMap.RemoveAll(); // m_MouseMode = MOUSE_SELECT_ROW; // OnSelecting(m_idCurrentCell); // Leave this off so that you can still drag the highlight around // without selecting rows. // m_MouseMode = MOUSE_NOTHING; //} } return idPrev; } // Sets the range of currently selected cells void CGridCtrl::SetSelectedRange(const CCellRange& Range, BOOL bForceRepaint /* = FALSE */, BOOL bSelectCells/*=TRUE*/) { SetSelectedRange(Range.GetMinRow(), Range.GetMinCol(), Range.GetMaxRow(), Range.GetMaxCol(), bForceRepaint, bSelectCells); } void CGridCtrl::SetSelectedRange(int nMinRow, int nMinCol, int nMaxRow, int nMaxCol, BOOL bForceRepaint /* = FALSE */, BOOL bSelectCells/*=TRUE*/) { if (!m_bEnableSelection) return; CDC* pDC = NULL; if (bForceRepaint) pDC = GetDC(); // Only redraw visible cells CCellRange VisCellRange; if (IsWindow(GetSafeHwnd())) VisCellRange = GetVisibleNonFixedCellRange(); // EFW - Bug fix - Don't allow selection of fixed rows if (nMinRow >= 0 && nMinRow < GetFixedRowCount()) nMinRow = GetFixedRowCount(); if (nMaxRow >= 0 && nMaxRow < GetFixedRowCount()) nMaxRow = GetFixedRowCount(); if (nMinCol >= 0 && nMinCol < GetFixedColumnCount()) nMinCol = GetFixedColumnCount(); if (nMaxCol >= 0 && nMaxCol < GetFixedColumnCount()) nMaxCol = GetFixedColumnCount(); // If we are selecting cells, then first clear out the list of currently selected cells, then if (bSelectCells) { POSITION pos; // Unselect all previously selected cells for (pos = m_SelectedCellMap.GetStartPosition(); pos != NULL;) { DWORD key; CCellID cell; m_SelectedCellMap.GetNextAssoc(pos, key, (CCellID&)cell); // Reset the selection flag on the cell if (IsValid(cell)) { // This will remove the cell from the m_SelectedCellMap map SetItemState(cell.row, cell.col, GetItemState(cell.row, cell.col) & ~GVIS_SELECTED); // If this is to be reselected, continue on past the redraw if (nMinRow <= cell.row && cell.row <= nMaxRow && nMinCol <= cell.col && cell.col <= nMaxCol) continue; if (VisCellRange.IsValid() && VisCellRange.InRange(cell)) { if (bForceRepaint && pDC) // Redraw NOW RedrawCell(cell.row, cell.col, pDC); else InvalidateCellRect(cell); // Redraw at leisure } } else { m_SelectedCellMap.RemoveKey(key); // if it's not valid, get rid of it! } } // if we are selecting cells, and there are previous selected cells to be retained // (eg Ctrl is being held down) then copy them to the newly created list, and mark // all these cells as selected // Note that if we are list mode, single row selection, the we won't be adding // the previous cells. Only the current row of cells will be added (see below) if (!GetSingleRowSelection() && nMinRow >= 0 && nMinCol >= 0 && nMaxRow >= 0 && nMaxCol >= 0) { for (pos = m_PrevSelectedCellMap.GetStartPosition(); pos != NULL; /* nothing */) { DWORD key; CCellID cell; m_PrevSelectedCellMap.GetNextAssoc(pos, key, (CCellID&)cell); if (!IsValid(cell)) continue; int nState = GetItemState(cell.row, cell.col); // Set state as Selected. This will add the cell to m_SelectedCellMap SetItemState(cell.row, cell.col, nState | GVIS_SELECTED); if (VisCellRange.IsValid() && VisCellRange.InRange(cell)) { // Redraw (immediately or at leisure) if (bForceRepaint && pDC) RedrawCell(cell.row, cell.col, pDC); else InvalidateCellRect(cell); } } } } // Now select/deselect all cells in the cell range specified. If selecting, and the cell // has already been marked as selected (above) then ignore it. If we are deselecting and // the cell isn't selected, then ignore if (nMinRow >= 0 && nMinCol >= 0 && nMaxRow >= 0 && nMaxCol >= 0 && nMaxRow < m_nRows && nMaxCol < m_nCols && nMinRow <= nMaxRow && nMinCol <= nMaxCol) { for (int row = nMinRow; row <= nMaxRow; row++) for (int col = nMinCol; col <= nMaxCol; col++) { int nState = GetItemState(row, col); // Why is there no XOR operator in C??? We have bitwise XOR ^, but what about ^^?? if ((bSelectCells && (nState & GVIS_SELECTED)) || (!bSelectCells && !(nState & GVIS_SELECTED))) continue; // Already selected or deselected - ignore // Set the selected state. This will add/remove the cell to m_SelectedCellMap if (bSelectCells) SetItemState(row, col, nState | GVIS_SELECTED); else SetItemState(row, col, GetItemState(row, col) & ~GVIS_SELECTED); if (VisCellRange.IsValid() && VisCellRange.InRange(row, col)) { // Redraw (immediately or at leisure) if (bForceRepaint && pDC) RedrawCell(row, col, pDC); else InvalidateCellRect(row, col); } } } // TRACE(_T("%d cells selected.\n"), m_SelectedCellMap.GetCount()); if (pDC != NULL) ReleaseDC(pDC); } // selects all cells void CGridCtrl::SelectAllCells() { if (!m_bEnableSelection) return; SetSelectedRange(m_nFixedRows, m_nFixedCols, GetRowCount() - 1, GetColumnCount() - 1); } // selects columns void CGridCtrl::SelectColumns(CCellID currentCell, BOOL bForceRedraw /*=FALSE*/, BOOL bSelectCells /*=TRUE*/) { if (!m_bEnableSelection) return; //if (currentCell.col == m_idCurrentCell.col) return; if (currentCell.col < m_nFixedCols) return; if (!IsValid(currentCell)) return; if (GetSingleColSelection()) SetSelectedRange(GetFixedRowCount(), currentCell.col, GetRowCount() - 1, currentCell.col, bForceRedraw, bSelectCells); else SetSelectedRange(GetFixedRowCount(), min(m_SelectionStartCell.col, currentCell.col), GetRowCount() - 1, max(m_SelectionStartCell.col, currentCell.col), bForceRedraw, bSelectCells); } // selects rows void CGridCtrl::SelectRows(CCellID currentCell, BOOL bForceRedraw /*=FALSE*/, BOOL bSelectCells /*=TRUE*/) { if (!m_bEnableSelection) return; //if (currentCell.row; == m_idCurrentCell.row) return; if (currentCell.row < m_nFixedRows) return; if (!IsValid(currentCell)) return; if (GetSingleRowSelection()) SetSelectedRange(currentCell.row, GetFixedColumnCount(), currentCell.row, GetColumnCount() - 1, bForceRedraw, bSelectCells); else SetSelectedRange(min(m_SelectionStartCell.row, currentCell.row), GetFixedColumnCount(), max(m_SelectionStartCell.row, currentCell.row), GetColumnCount() - 1, bForceRedraw, bSelectCells); } // selects cells void CGridCtrl::SelectCells(CCellID currentCell, BOOL bForceRedraw /*=FALSE*/, BOOL bSelectCells /*=TRUE*/) { if (!m_bEnableSelection) return; int row = currentCell.row; int col = currentCell.col; if (row < m_nFixedRows || col < m_nFixedCols) return; if (!IsValid(currentCell)) return; // Prevent unnecessary redraws //if (currentCell == m_LeftClickDownCell) return; //else if (currentCell == m_idCurrentCell) return; SetSelectedRange(min(m_SelectionStartCell.row, row), min(m_SelectionStartCell.col, col), max(m_SelectionStartCell.row, row), max(m_SelectionStartCell.col, col), bForceRedraw, bSelectCells); } // Called when mouse/keyboard selection is a-happening. void CGridCtrl::OnSelecting(const CCellID& currentCell) { if (!m_bEnableSelection) return; switch (m_MouseMode) { case MOUSE_SELECT_ALL: SelectAllCells(); break; case MOUSE_SELECT_COL: SelectColumns(currentCell, FALSE); break; case MOUSE_SELECT_ROW: SelectRows(currentCell, FALSE); break; case MOUSE_SELECT_CELLS: SelectCells(currentCell, FALSE); break; } // EFW - Bug fix [REMOVED CJM: this will cause infinite loop in list mode] // SetFocusCell(max(currentCell.row, m_nFixedRows), max(currentCell.col, m_nFixedCols)); } void CGridCtrl::ValidateAndModifyCellContents(int nRow, int nCol, LPCTSTR strText) { if (!IsCellEditable(nRow, nCol)) return; if (SendMessageToParent(nRow, nCol, GVN_BEGINLABELEDIT) >= 0) { CString strCurrentText = GetItemText(nRow, nCol); if (strCurrentText != strText) { SetItemText(nRow, nCol, strText); if (ValidateEdit(nRow, nCol, strText) && SendMessageToParent(nRow, nCol, GVN_ENDLABELEDIT) >= 0) { SetModified(TRUE, nRow, nCol); RedrawCell(nRow, nCol); } else { SetItemText(nRow, nCol, strCurrentText); } } } } void CGridCtrl::ClearCells(CCellRange Selection) { for (int row = Selection.GetMinRow(); row <= Selection.GetMaxRow(); row++) { for (int col = Selection.GetMinCol(); col <= Selection.GetMaxCol(); col++) { // don't clear hidden cells if (m_arRowHeights[row] > 0 && m_arColWidths[col] > 0) { ValidateAndModifyCellContents(row, col, _T("")); } } } Refresh(); } #ifndef GRIDCONTROL_NO_CLIPBOARD //////////////////////////////////////////////////////////////////////////////////////// // Clipboard functions // Deletes the contents from the selected cells void CGridCtrl::CutSelectedText() { if (!IsEditable()) return; CCellRange Selection = GetSelectedCellRange(); if (!IsValid(Selection)) return; ClearCells(Selection); } // Copies text from the selected cells to the clipboard COleDataSource* CGridCtrl::CopyTextFromGrid() { USES_CONVERSION; CCellRange Selection = GetSelectedCellRange(); if (!IsValid(Selection)) return NULL; if (GetVirtualMode()) SendCacheHintToParent(Selection); // Write to shared file (REMEBER: CF_TEXT is ANSI, not UNICODE, so we need to convert) CSharedFile sf(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT); // Get a tab delimited string to copy to cache CString str; CGridCellBase *pCell; for (int row = Selection.GetMinRow(); row <= Selection.GetMaxRow(); row++) { // don't copy hidden cells if (m_arRowHeights[row] <= 0) continue; str.Empty(); for (int col = Selection.GetMinCol(); col <= Selection.GetMaxCol(); col++) { // don't copy hidden cells if (m_arColWidths[col] <= 0) continue; pCell = GetCell(row, col); if (pCell && (pCell->GetState() & GVIS_SELECTED)) { // if (!pCell->GetText()) // str += _T(" "); // else str += pCell->GetText(); } if (col != Selection.GetMaxCol()) str += _T("\t"); } if (row != Selection.GetMaxRow()) str += _T("\n"); sf.Write(T2A(str.GetBuffer(1)), str.GetLength()); str.ReleaseBuffer(); } char c = '\0'; sf.Write(&c, 1); if (GetVirtualMode()) SendCacheHintToParent(CCellRange(-1, -1, -1, -1)); DWORD dwLen = (DWORD)sf.GetLength(); HGLOBAL hMem = sf.Detach(); if (!hMem) return NULL; hMem = ::GlobalReAlloc(hMem, dwLen, GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT); if (!hMem) return NULL; // Cache data COleDataSource* pSource = new COleDataSource(); pSource->CacheGlobalData(CF_TEXT, hMem); return pSource; } // Pastes text from the clipboard to the selected cells BOOL CGridCtrl::PasteTextToGrid(CCellID cell, COleDataObject* pDataObject) { if (!IsValid(cell) || !IsCellEditable(cell) || !pDataObject->IsDataAvailable(CF_TEXT)) return FALSE; // Get the text from the COleDataObject // Ñ¡ÔñºÏÊʵĸñʽ; UINT uiFormat = (sizeof(TCHAR)==sizeof(WCHAR)) ? CF_UNICODETEXT : CF_TEXT; HGLOBAL hmem = pDataObject->GetGlobalData(uiFormat); // CF_TEXT CMemFile sf((BYTE*) ::GlobalLock(hmem), ::GlobalSize(hmem)); // CF_TEXT is ANSI text, so we need to allocate a char* buffer // to hold this. LPTSTR szBuffer = new TCHAR[::GlobalSize(hmem)]; if (!szBuffer) return FALSE; sf.Read(szBuffer, ::GlobalSize(hmem)); ::GlobalUnlock(hmem); // Now store in generic TCHAR form so we no longer have to deal with // ANSI/UNICODE problems CString strText = szBuffer; delete szBuffer; // Parse text data and set in cells... strText.LockBuffer(); CString strLine = strText; int nLine = 0; // Find the end of the first line int nIndex; do { int nColumn = 0; nIndex = strLine.Find(_T("\n")); // Store the remaining chars after the newline CString strNext = (nIndex < 0) ? _T("") : strLine.Mid(nIndex + 1); // Remove all chars after the newline if (nIndex >= 0) strLine = strLine.Left(nIndex); int nLineIndex = strLine.FindOneOf(_T("\t,")); CString strCellText = (nLineIndex >= 0) ? strLine.Left(nLineIndex) : strLine; // skip hidden rows int iRowVis = cell.row + nLine; while (iRowVis < GetRowCount()) { if (GetRowHeight(iRowVis) > 0) break; nLine++; iRowVis++; } while (!strLine.IsEmpty()) { // skip hidden columns int iColVis = cell.col + nColumn; while (iColVis < GetColumnCount()) { if (GetColumnWidth(iColVis) > 0) break; nColumn++; iColVis++; } CCellID TargetCell(iRowVis, iColVis); if (IsValid(TargetCell)) { strCellText.TrimLeft(); strCellText.TrimRight(); ValidateAndModifyCellContents(TargetCell.row, TargetCell.col, strCellText); // Make sure cell is not selected to avoid data loss SetItemState(TargetCell.row, TargetCell.col, GetItemState(TargetCell.row, TargetCell.col) & ~GVIS_SELECTED); } strLine = (nLineIndex >= 0) ? strLine.Mid(nLineIndex + 1) : _T(""); nLineIndex = strLine.FindOneOf(_T("\t,")); strCellText = (nLineIndex >= 0) ? strLine.Left(nLineIndex) : strLine; nColumn++; } strLine = strNext; nLine++; } while (nIndex >= 0); strText.UnlockBuffer(); Refresh(); return TRUE; } #endif #ifndef GRIDCONTROL_NO_DRAGDROP // Start drag n drop void CGridCtrl::OnBeginDrag() { if (!m_bAllowDragAndDrop) return; COleDataSource* pSource = CopyTextFromGrid(); if (pSource) { SendMessageToParent(GetSelectedCellRange().GetTopLeft().row, GetSelectedCellRange().GetTopLeft().col, GVN_BEGINDRAG); m_MouseMode = MOUSE_DRAGGING; m_bLMouseButtonDown = FALSE; DROPEFFECT dropEffect = pSource->DoDragDrop(DROPEFFECT_COPY | DROPEFFECT_MOVE); if (dropEffect & DROPEFFECT_MOVE) CutSelectedText(); if (pSource) delete pSource; // Did not pass source to clipboard, so must delete } } // Handle drag over grid DROPEFFECT CGridCtrl::OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) { // Any text data available for us? if (!m_bAllowDragAndDrop || !IsEditable() || !pDataObject->IsDataAvailable(CF_TEXT)) return DROPEFFECT_NONE; // Find which cell we are over and drop-highlight it CCellID cell = GetCellFromPt(point, FALSE); // If not valid, set the previously drop-highlighted cell as no longer drop-highlighted if (!IsValid(cell)) { OnDragLeave(); m_LastDragOverCell = CCellID(-1, -1); return DROPEFFECT_NONE; } if (!IsCellEditable(cell)) return DROPEFFECT_NONE; // Have we moved over a different cell than last time? if (cell != m_LastDragOverCell) { // Set the previously drop-highlighted cell as no longer drop-highlighted if (IsValid(m_LastDragOverCell)) { UINT nState = GetItemState(m_LastDragOverCell.row, m_LastDragOverCell.col); SetItemState(m_LastDragOverCell.row, m_LastDragOverCell.col, nState & ~GVIS_DROPHILITED); RedrawCell(m_LastDragOverCell); } m_LastDragOverCell = cell; // Set the new cell as drop-highlighted if (IsValid(m_LastDragOverCell)) { UINT nState = GetItemState(m_LastDragOverCell.row, m_LastDragOverCell.col); SetItemState(m_LastDragOverCell.row, m_LastDragOverCell.col, nState | GVIS_DROPHILITED); RedrawCell(m_LastDragOverCell); } } // Return an appropraite value of DROPEFFECT so mouse cursor is set properly if (dwKeyState & MK_CONTROL) return DROPEFFECT_COPY; else return DROPEFFECT_MOVE; } // Something has just been dragged onto the grid DROPEFFECT CGridCtrl::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) { // Any text data available for us? if (!m_bAllowDragAndDrop || !pDataObject->IsDataAvailable(CF_TEXT)) return DROPEFFECT_NONE; // Find which cell we are over and drop-highlight it m_LastDragOverCell = GetCellFromPt(point, FALSE); if (!IsValid(m_LastDragOverCell)) return DROPEFFECT_NONE; if (!IsCellEditable(m_LastDragOverCell)) return DROPEFFECT_NONE; if (IsValid(m_LastDragOverCell)) { UINT nState = GetItemState(m_LastDragOverCell.row, m_LastDragOverCell.col); SetItemState(m_LastDragOverCell.row, m_LastDragOverCell.col, nState | GVIS_DROPHILITED); RedrawCell(m_LastDragOverCell); } // Return an appropraite value of DROPEFFECT so mouse cursor is set properly if (dwKeyState & MK_CONTROL) return DROPEFFECT_COPY; else return DROPEFFECT_MOVE; } // Something has just been dragged away from the grid void CGridCtrl::OnDragLeave() { // Set the previously drop-highlighted cell as no longer drop-highlighted if (IsValid(m_LastDragOverCell)) { UINT nState = GetItemState(m_LastDragOverCell.row, m_LastDragOverCell.col); SetItemState(m_LastDragOverCell.row, m_LastDragOverCell.col, nState & ~GVIS_DROPHILITED); RedrawCell(m_LastDragOverCell); } } // Something has just been dropped onto the grid BOOL CGridCtrl::OnDrop(COleDataObject* pDataObject, DROPEFFECT /*dropEffect*/, CPoint /* point */) { m_MouseMode = MOUSE_NOTHING; if (!m_bAllowDragAndDrop || !IsCellEditable(m_LastDragOverCell)) return FALSE; OnDragLeave(); return PasteTextToGrid(m_LastDragOverCell, pDataObject); } #endif #ifndef GRIDCONTROL_NO_CLIPBOARD void CGridCtrl::OnEditCut() { if (!IsEditable()) return; COleDataSource* pSource = CopyTextFromGrid(); if (!pSource) return; pSource->SetClipboard(); CutSelectedText(); } void CGridCtrl::OnEditCopy() { COleDataSource* pSource = CopyTextFromGrid(); if (!pSource) return; pSource->SetClipboard(); } void CGridCtrl::OnEditPaste() { if (!IsEditable()) return; // Get the Focus cell, or if none, get the topleft (non-fixed) cell CCellID cell = GetFocusCell(); if (!IsValid(cell)) cell = GetTopleftNonFixedCell(); if (!IsValid(cell)) return; // If a cell is being edited, then call it's edit window paste function. if (IsItemEditing(cell.row, cell.col)) { CGridCellBase* pCell = GetCell(cell.row, cell.col); ASSERT(pCell); if (!pCell) return; CWnd* pEditWnd = pCell->GetEditWnd(); if (pEditWnd && pEditWnd->IsKindOf(RUNTIME_CLASS(CEdit))) { ((CEdit*)pEditWnd)->Paste(); return; } } // Attach a COleDataObject to the clipboard and paste the data to the grid COleDataObject obj; if (obj.AttachClipboard()) PasteTextToGrid(cell, &obj); } #endif void CGridCtrl::OnEditSelectAll() { SendMessageToParent(m_LeftClickDownCell.row, m_LeftClickDownCell.col, GVN_SELCHANGING); SelectAllCells(); SendMessageToParent(m_idCurrentCell.row, m_idCurrentCell.col, GVN_SELCHANGED); } #ifndef GRIDCONTROL_NO_CLIPBOARD void CGridCtrl::OnUpdateEditCopy(CCmdUI* pCmdUI) { CCellRange Selection = GetSelectedCellRange(); pCmdUI->Enable(Selection.Count() && IsValid(Selection)); } void CGridCtrl::OnUpdateEditCut(CCmdUI* pCmdUI) { CCellRange Selection = GetSelectedCellRange(); pCmdUI->Enable(IsEditable() && Selection.Count() && IsValid(Selection)); } void CGridCtrl::OnUpdateEditPaste(CCmdUI* pCmdUI) { CCellID cell = GetFocusCell(); BOOL bCanPaste = IsValid(cell) && IsCellEditable(cell) && ::IsClipboardFormatAvailable(CF_TEXT); pCmdUI->Enable(bCanPaste); } #endif void CGridCtrl::OnUpdateEditSelectAll(CCmdUI* pCmdUI) { pCmdUI->Enable(m_bEnableSelection); } //////////////////////////////////////////////////////////////////////////////////////// // hittest-like functions // TRUE if the mouse is over a row resize area BOOL CGridCtrl::MouseOverRowResizeArea(CPoint& point) { if (point.x >= GetFixedColumnWidth()) return FALSE; CCellID idCurrentCell = GetCellFromPt(point); CPoint start; if (!GetCellOrigin(idCurrentCell, &start)) return FALSE; int endy = start.y + GetRowHeight(idCurrentCell.row); if ((point.y - start.y < m_nResizeCaptureRange && idCurrentCell.row != 0) || endy - point.y < m_nResizeCaptureRange) { return TRUE; } else return FALSE; } // TRUE if the mouse is over a column resize area. point is in Client coords BOOL CGridCtrl::MouseOverColumnResizeArea(CPoint& point) { if (point.y >= GetFixedRowHeight()) return FALSE; CCellID idCurrentCell = GetCellFromPt(point); CPoint start; if (!GetCellOrigin(idCurrentCell, &start)) return FALSE; int endx = start.x + GetColumnWidth(idCurrentCell.col); if ((point.x - start.x < m_nResizeCaptureRange && idCurrentCell.col != 0) || endx - point.x < m_nResizeCaptureRange) { return TRUE; } else return FALSE; } // Get cell from point. // point - client coordinates // bAllowFixedCellCheck - if TRUE then fixed cells are checked CCellID CGridCtrl::GetCellFromPt(CPoint point, BOOL bAllowFixedCellCheck /*=TRUE*/) { CCellID cellID; // return value CCellID idTopLeft = GetTopleftNonFixedCell(); if (!bAllowFixedCellCheck && !IsValid(idTopLeft)) return cellID; // calculate column index int fixedColWidth = GetFixedColumnWidth(); if (point.x < 0 || (!bAllowFixedCellCheck && point.x < fixedColWidth)) // not in window cellID.col = -1; else if (point.x < fixedColWidth) // in fixed col { int xpos = 0; int col = 0; while (col < m_nFixedCols) { xpos += GetColumnWidth(col); if (xpos > point.x) break; col++; } cellID.col = col; } else // in non-fixed col { int xpos = fixedColWidth; int col = idTopLeft.col; //m_nFixedCols; while (col < GetColumnCount()) { xpos += GetColumnWidth(col); if (xpos > point.x) break; col++; } if (col >= GetColumnCount()) cellID.col = -1; else cellID.col = col; } // calculate row index int fixedRowHeight = GetFixedRowHeight(); if (point.y < 0 || (!bAllowFixedCellCheck && point.y < fixedRowHeight)) // not in window cellID.row = -1; else if (point.y < fixedRowHeight) // in fixed col { int ypos = 0; int row = 0; while (row < m_nFixedRows) { ypos += GetRowHeight(row); if (ypos > point.y) break; row++; } cellID.row = row; } else { int ypos = fixedRowHeight; int row = idTopLeft.row; //m_nFixedRows; while (row < GetRowCount()) { ypos += GetRowHeight(row); if (ypos > point.y) break; row++; } if (row >= GetRowCount()) cellID.row = -1; else cellID.row = row; } return cellID; } //////////////////////////////////////////////////////////////////////////////// // CGridCtrl cellrange functions // Gets the first non-fixed cell ID CCellID CGridCtrl::GetTopleftNonFixedCell(BOOL bForceRecalculation /*=FALSE*/) { // Used cached value if possible if (m_idTopLeftCell.IsValid() && !bForceRecalculation) return m_idTopLeftCell; int nVertScroll = GetScrollPos(SB_VERT), nHorzScroll = GetScrollPos(SB_HORZ); m_idTopLeftCell.col = m_nFixedCols; int nRight = 0; while (nRight < nHorzScroll && m_idTopLeftCell.col < (GetColumnCount() - 1)) nRight += GetColumnWidth(m_idTopLeftCell.col++); m_idTopLeftCell.row = m_nFixedRows; int nTop = 0; while (nTop < nVertScroll && m_idTopLeftCell.row < (GetRowCount() - 1)) nTop += GetRowHeight(m_idTopLeftCell.row++); //TRACE2("TopLeft cell is row %d, col %d\n",m_idTopLeftCell.row, m_idTopLeftCell.col); return m_idTopLeftCell; } // This gets even partially visible cells CCellRange CGridCtrl::GetVisibleNonFixedCellRange(LPRECT pRect /*=NULL*/, BOOL bForceRecalculation /*=FALSE*/) { CRect rect; GetClientRect(rect); CCellID idTopLeft = GetTopleftNonFixedCell(bForceRecalculation); // calc bottom int i = 0; int bottom = GetFixedRowHeight(); for (i = idTopLeft.row; i < GetRowCount(); i++) { bottom += GetRowHeight(i); if (bottom >= rect.bottom) { bottom = rect.bottom; break; } } int maxVisibleRow = min(i, GetRowCount() - 1); // calc right int right = GetFixedColumnWidth(); for (i = idTopLeft.col; i < GetColumnCount(); i++) { right += GetColumnWidth(i); if (right >= rect.right) { right = rect.right; break; } } int maxVisibleCol = min(i, GetColumnCount() - 1); if (pRect) { pRect->left = pRect->top = 0; pRect->right = right; pRect->bottom = bottom; } return CCellRange(idTopLeft.row, idTopLeft.col, maxVisibleRow, maxVisibleCol); } // used by ResetScrollBars() - This gets only fully visible cells CCellRange CGridCtrl::GetUnobstructedNonFixedCellRange(BOOL bForceRecalculation /*=FALSE*/) { CRect rect; GetClientRect(rect); CCellID idTopLeft = GetTopleftNonFixedCell(bForceRecalculation); // calc bottom int i = 0; int bottom = GetFixedRowHeight(); for (i = idTopLeft.row; i < GetRowCount(); i++) { bottom += GetRowHeight(i); if (bottom >= rect.bottom) break; } int maxVisibleRow = min(i, GetRowCount() - 1); if (maxVisibleRow > 0 && bottom > rect.bottom) maxVisibleRow--; // calc right int right = GetFixedColumnWidth(); for (i = idTopLeft.col; i < GetColumnCount(); i++) { right += GetColumnWidth(i); if (right >= rect.right) break; } int maxVisibleCol = min(i, GetColumnCount() - 1); if (maxVisibleCol > 0 && right > rect.right) maxVisibleCol--; return CCellRange(idTopLeft.row, idTopLeft.col, maxVisibleRow, maxVisibleCol); } // Returns the minimum bounding range of the current selection // If no selection, then the returned CCellRange will be invalid CCellRange CGridCtrl::GetSelectedCellRange() const { CCellRange Selection(GetRowCount(), GetColumnCount(), -1, -1); for (POSITION pos = m_SelectedCellMap.GetStartPosition(); pos != NULL;) { DWORD key; CCellID cell; m_SelectedCellMap.GetNextAssoc(pos, key, (CCellID&)cell); Selection.SetMinRow(min(Selection.GetMinRow(), cell.row)); Selection.SetMinCol(min(Selection.GetMinCol(), cell.col)); Selection.SetMaxRow(max(Selection.GetMaxRow(), cell.row)); Selection.SetMaxCol(max(Selection.GetMaxCol(), cell.col)); } return Selection; } // Returns ALL the cells in the grid CCellRange CGridCtrl::GetCellRange() const { return CCellRange(0, 0, GetRowCount() - 1, GetColumnCount() - 1); } // Resets the selected cell range to the empty set. void CGridCtrl::ResetSelectedRange() { m_PrevSelectedCellMap.RemoveAll(); SetSelectedRange(-1, -1, -1, -1); SetFocusCell(-1, -1); } // Get/Set scroll position using 32 bit functions int CGridCtrl::GetScrollPos32(int nBar, BOOL bGetTrackPos /* = FALSE */) { SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); if (bGetTrackPos) { if (GetScrollInfo(nBar, &si, SIF_TRACKPOS)) return si.nTrackPos; } else { if (GetScrollInfo(nBar, &si, SIF_POS)) return si.nPos; } return 0; } BOOL CGridCtrl::SetScrollPos32(int nBar, int nPos, BOOL bRedraw /* = TRUE */) { m_idTopLeftCell.row = -1; SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_POS; si.nPos = nPos; return SetScrollInfo(nBar, &si, bRedraw); } void CGridCtrl::EnableScrollBars(int nBar, BOOL bEnable /*=TRUE*/) { if (bEnable) { if (!IsVisibleHScroll() && (nBar == SB_HORZ || nBar == SB_BOTH)) { CWnd::EnableScrollBarCtrl(SB_HORZ, bEnable); m_nBarState |= GVL_HORZ; } if (!IsVisibleVScroll() && (nBar == SB_VERT || nBar == SB_BOTH)) { CWnd::EnableScrollBarCtrl(SB_VERT, bEnable); m_nBarState |= GVL_VERT; } } else { if (IsVisibleHScroll() && (nBar == SB_HORZ || nBar == SB_BOTH)) { CWnd::EnableScrollBarCtrl(SB_HORZ, bEnable); m_nBarState &= ~GVL_HORZ; } if (IsVisibleVScroll() && (nBar == SB_VERT || nBar == SB_BOTH)) { CWnd::EnableScrollBarCtrl(SB_VERT, bEnable); m_nBarState &= ~GVL_VERT; } } } // If resizing or cell counts/sizes change, call this - it'll fix up the scroll bars void CGridCtrl::ResetScrollBars() { // Force a refresh. m_idTopLeftCell.row = -1; if (!m_bAllowDraw || !::IsWindow(GetSafeHwnd())) return; CRect rect; // This would have caused OnSize event - Brian //EnableScrollBars(SB_BOTH, FALSE); GetClientRect(rect); if (rect.left == rect.right || rect.top == rect.bottom) return; if (IsVisibleVScroll()) rect.right += GetSystemMetrics(SM_CXVSCROLL) + GetSystemMetrics(SM_CXBORDER); if (IsVisibleHScroll()) rect.bottom += GetSystemMetrics(SM_CYHSCROLL) + GetSystemMetrics(SM_CYBORDER); rect.left += GetFixedColumnWidth(); rect.top += GetFixedRowHeight(); if (rect.left >= rect.right || rect.top >= rect.bottom) { EnableScrollBarCtrl(SB_BOTH, FALSE); return; } CRect VisibleRect(GetFixedColumnWidth(), GetFixedRowHeight(), rect.right, rect.bottom); CRect VirtualRect(GetFixedColumnWidth(), GetFixedRowHeight(), GetVirtualWidth(), GetVirtualHeight()); // Removed to fix single row scrollbar problem (Pontus Goffe) // CCellRange visibleCells = GetUnobstructedNonFixedCellRange(); // if (!IsValid(visibleCells)) return; //TRACE(_T("Visible: %d x %d, Virtual %d x %d. H %d, V %d\n"), // VisibleRect.Width(), VisibleRect.Height(), // VirtualRect.Width(), VirtualRect.Height(), // IsVisibleHScroll(), IsVisibleVScroll()); // If vertical scroll bar, horizontal space is reduced if (VisibleRect.Height() < VirtualRect.Height()) VisibleRect.right -= ::GetSystemMetrics(SM_CXVSCROLL); // If horz scroll bar, vert space is reduced if (VisibleRect.Width() < VirtualRect.Width()) VisibleRect.bottom -= ::GetSystemMetrics(SM_CYHSCROLL); // Recheck vertical scroll bar //if (VisibleRect.Height() < VirtualRect.Height()) // VisibleRect.right -= ::GetSystemMetrics(SM_CXVSCROLL); if (VisibleRect.Height() < VirtualRect.Height()) { EnableScrollBars(SB_VERT, TRUE); m_nVScrollMax = VirtualRect.Height() - 1; } else { EnableScrollBars(SB_VERT, FALSE); m_nVScrollMax = 0; } if (VisibleRect.Width() < VirtualRect.Width()) { EnableScrollBars(SB_HORZ, TRUE); m_nHScrollMax = VirtualRect.Width() - 1; } else { EnableScrollBars(SB_HORZ, FALSE); m_nHScrollMax = 0; } ASSERT(m_nVScrollMax < INT_MAX && m_nHScrollMax < INT_MAX); // This should be fine /* Old code - CJM SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_PAGE; si.nPage = (m_nHScrollMax>0)? VisibleRect.Width() : 0; SetScrollInfo(SB_HORZ, &si, FALSE); si.nPage = (m_nVScrollMax>0)? VisibleRect.Height() : 0; SetScrollInfo(SB_VERT, &si, FALSE); SetScrollRange(SB_VERT, 0, m_nVScrollMax, TRUE); SetScrollRange(SB_HORZ, 0, m_nHScrollMax, TRUE); */ // New code - Paul Runstedler SCROLLINFO si; si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_PAGE | SIF_RANGE; si.nPage = (m_nHScrollMax > 0) ? VisibleRect.Width() : 0; si.nMin = 0; si.nMax = m_nHScrollMax; SetScrollInfo(SB_HORZ, &si, TRUE); si.fMask |= SIF_DISABLENOSCROLL; si.nPage = (m_nVScrollMax > 0) ? VisibleRect.Height() : 0; si.nMin = 0; si.nMax = m_nVScrollMax; SetScrollInfo(SB_VERT, &si, TRUE); } //////////////////////////////////////////////////////////////////////////////////// // Row/Column position functions // returns the top left point of the cell. Returns FALSE if cell not visible. BOOL CGridCtrl::GetCellOrigin(int nRow, int nCol, LPPOINT p) { int i; if (!IsValid(nRow, nCol)) return FALSE; CCellID idTopLeft; if (nCol >= m_nFixedCols || nRow >= m_nFixedRows) idTopLeft = GetTopleftNonFixedCell(); if ((nRow >= m_nFixedRows && nRow < idTopLeft.row) || (nCol >= m_nFixedCols && nCol < idTopLeft.col)) return FALSE; p->x = 0; if (nCol < m_nFixedCols) // is a fixed column for (i = 0; i < nCol; i++) p->x += GetColumnWidth(i); else { // is a scrollable data column for (i = 0; i < m_nFixedCols; i++) p->x += GetColumnWidth(i); for (i = idTopLeft.col; i < nCol; i++) p->x += GetColumnWidth(i); } p->y = 0; if (nRow < m_nFixedRows) // is a fixed row for (i = 0; i < nRow; i++) p->y += GetRowHeight(i); else { // is a scrollable data row for (i = 0; i < m_nFixedRows; i++) p->y += GetRowHeight(i); for (i = idTopLeft.row; i < nRow; i++) p->y += GetRowHeight(i); } return TRUE; } BOOL CGridCtrl::GetCellOrigin(const CCellID& cell, LPPOINT p) { return GetCellOrigin(cell.row, cell.col, p); } // Returns the bounding box of the cell BOOL CGridCtrl::GetCellRect(const CCellID& cell, LPRECT pRect) { return GetCellRect(cell.row, cell.col, pRect); } BOOL CGridCtrl::GetCellRect(int nRow, int nCol, LPRECT pRect) { CPoint CellOrigin; if (!GetCellOrigin(nRow, nCol, &CellOrigin)) return FALSE; pRect->left = CellOrigin.x; pRect->top = CellOrigin.y; pRect->right = CellOrigin.x + GetColumnWidth(nCol) - 1; pRect->bottom = CellOrigin.y + GetRowHeight(nRow) - 1; //TRACE("Row %d, col %d: L %d, T %d, W %d, H %d: %d,%d - %d,%d\n", // nRow,nCol, CellOrigin.x, CellOrigin.y, GetColumnWidth(nCol), GetRowHeight(nRow), // pRect->left, pRect->top, pRect->right, pRect->bottom); return TRUE; } BOOL CGridCtrl::GetTextRect(const CCellID& cell, LPRECT pRect) { return GetTextRect(cell.row, cell.col, pRect); } BOOL CGridCtrl::GetTextRect(int nRow, int nCol, LPRECT pRect) { CGridCellBase* pCell = GetCell(nRow, nCol); if (pCell == NULL) return FALSE; if (!GetCellRect(nRow, nCol, pRect)) return FALSE; return pCell->GetTextRect(pRect); } // Returns the bounding box of a range of cells BOOL CGridCtrl::GetCellRangeRect(const CCellRange& cellRange, LPRECT lpRect) { CPoint MinOrigin, MaxOrigin; if (!GetCellOrigin(cellRange.GetMinRow(), cellRange.GetMinCol(), &MinOrigin)) return FALSE; if (!GetCellOrigin(cellRange.GetMaxRow(), cellRange.GetMaxCol(), &MaxOrigin)) return FALSE; lpRect->left = MinOrigin.x; lpRect->top = MinOrigin.y; lpRect->right = MaxOrigin.x + GetColumnWidth(cellRange.GetMaxCol()) - 1; lpRect->bottom = MaxOrigin.y + GetRowHeight(cellRange.GetMaxRow()) - 1; return TRUE; } //////////////////////////////////////////////////////////////////////////////////// // Grid attribute functions LRESULT CGridCtrl::OnSetFont(WPARAM hFont, LPARAM /*lParam */) { LRESULT result = Default(); // Get the logical font LOGFONT lf; if (!GetObject((HFONT)hFont, sizeof(LOGFONT), &lf)) return result; m_cellDefault.SetFont(&lf); m_cellFixedColDef.SetFont(&lf); m_cellFixedRowDef.SetFont(&lf); m_cellFixedRowColDef.SetFont(&lf); Refresh(); return result; } LRESULT CGridCtrl::OnGetFont(WPARAM /*wParam*/, LPARAM /*lParam*/) { //LOGFONT lf; //m_cellDefault.GetFontObject()->GetLogFont(&lf); return (LRESULT)m_cellDefault.GetFontObject()->GetSafeHandle(); } #ifndef _WIN32_WCE_NO_CURSOR BOOL CGridCtrl::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { if (nHitTest == HTCLIENT) { switch (m_MouseMode) { case MOUSE_OVER_COL_DIVIDE: SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE)); break; case MOUSE_OVER_ROW_DIVIDE: SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENS)); break; #ifndef GRIDCONTROL_NO_DRAGDROP case MOUSE_DRAGGING: break; #endif default: if (!GetVirtualMode()) { CPoint pt(GetMessagePos()); ScreenToClient(&pt); CCellID cell = GetCellFromPt(pt); if (IsValid(cell)) { CGridCellBase* pCell = GetCell(cell.row, cell.col); if (pCell) return pCell->OnSetCursor(); } } SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); } return TRUE; } return CWnd::OnSetCursor(pWnd, nHitTest, message); } #endif //////////////////////////////////////////////////////////////////////////////////// // Row/Column count functions BOOL CGridCtrl::SetFixedRowCount(int nFixedRows) { if (m_nFixedRows == nFixedRows) return TRUE; ASSERT(nFixedRows >= 0); ResetSelectedRange(); // Force recalculation m_idTopLeftCell.col = -1; if (nFixedRows > GetRowCount()) if (!SetRowCount(nFixedRows)) return FALSE; if (m_idCurrentCell.row < nFixedRows) SetFocusCell(-1, -1); if (!GetVirtualMode()) { if (nFixedRows > m_nFixedRows) { for (int i = m_nFixedRows; i < nFixedRows; i++) for (int j = 0; j < GetColumnCount(); j++) { SetItemState(i, j, GetItemState(i, j) | GVIS_FIXED | GVIS_FIXEDROW); SetItemBkColour(i, j, CLR_DEFAULT); SetItemFgColour(i, j, CLR_DEFAULT); } } else { for (int i = nFixedRows; i < m_nFixedRows; i++) { int j; for (j = 0; j < GetFixedColumnCount(); j++) SetItemState(i, j, GetItemState(i, j) & ~GVIS_FIXEDROW); for (j = GetFixedColumnCount(); j < GetColumnCount(); j++) { SetItemState(i, j, GetItemState(i, j) & ~(GVIS_FIXED | GVIS_FIXEDROW)); SetItemBkColour(i, j, CLR_DEFAULT); SetItemFgColour(i, j, CLR_DEFAULT); } } } } m_nFixedRows = nFixedRows; Refresh(); return TRUE; } BOOL CGridCtrl::SetFixedColumnCount(int nFixedCols) { if (m_nFixedCols == nFixedCols) return TRUE; ASSERT(nFixedCols >= 0); if (nFixedCols > GetColumnCount()) if (!SetColumnCount(nFixedCols)) return FALSE; if (m_idCurrentCell.col < nFixedCols) SetFocusCell(-1, -1); ResetSelectedRange(); // Force recalculation m_idTopLeftCell.col = -1; if (!GetVirtualMode()) { if (nFixedCols > m_nFixedCols) { for (int i = 0; i < GetRowCount(); i++) for (int j = m_nFixedCols; j < nFixedCols; j++) { SetItemState(i, j, GetItemState(i, j) | GVIS_FIXED | GVIS_FIXEDCOL); SetItemBkColour(i, j, CLR_DEFAULT); SetItemFgColour(i, j, CLR_DEFAULT); } } else { int i = 0; for (i = 0; i < GetFixedRowCount(); i++) for (int j = nFixedCols; j < m_nFixedCols; j++) SetItemState(i, j, GetItemState(i, j) & ~GVIS_FIXEDCOL); for (i = GetFixedRowCount(); i < GetRowCount(); i++) for (int j = nFixedCols; j < m_nFixedCols; j++) { SetItemState(i, j, GetItemState(i, j) & ~(GVIS_FIXED | GVIS_FIXEDCOL)); SetItemBkColour(i, j, CLR_DEFAULT); SetItemFgColour(i, j, CLR_DEFAULT); } } } m_nFixedCols = nFixedCols; Refresh(); return TRUE; } BOOL CGridCtrl::SetRowCount(int nRows) { BOOL bResult = TRUE; ASSERT(nRows >= 0); if (nRows == GetRowCount()) return bResult; // Force recalculation m_idTopLeftCell.col = -1; if (nRows < m_nFixedRows) m_nFixedRows = nRows; if (m_idCurrentCell.row >= nRows) SetFocusCell(-1, -1); int addedRows = nRows - GetRowCount(); // If we are about to lose rows, then we need to delete the GridCell objects // in each column within each row if (addedRows < 0) { if (!GetVirtualMode()) { for (int row = nRows; row < m_nRows; row++) { // Delete cells for (int col = 0; col < m_nCols; col++) DestroyCell(row, col); // Delete rows GRID_ROW* pRow = m_RowData[row]; if (pRow) delete pRow; } } m_nRows = nRows; } TRY { m_arRowHeights.SetSize(nRows); if (GetVirtualMode()) { m_nRows = nRows; if (addedRows > 0) { int startRow = nRows - addedRows; for (int row = startRow; row < nRows; row++) m_arRowHeights[row] = m_cellDefault.GetHeight(); } } else { // Change the number of rows. m_RowData.SetSize(nRows); // If we have just added rows, we need to construct new elements for each cell // and set the default row height if (addedRows > 0) { // initialize row heights and data int startRow = nRows - addedRows; for (int row = startRow; row < nRows; row++) { m_arRowHeights[row] = m_cellDefault.GetHeight(); m_RowData[row] = new GRID_ROW; m_RowData[row]->SetSize(m_nCols); for (int col = 0; col < m_nCols; col++) { GRID_ROW* pRow = m_RowData[row]; if (pRow && !GetVirtualMode()) pRow->SetAt(col, CreateCell(row, col)); } m_nRows++; } } } } CATCH(CMemoryException, e) { e->ReportError(); bResult = FALSE; } END_CATCH SetModified(); ResetScrollBars(); Refresh(); return bResult; } BOOL CGridCtrl::SetColumnCount(int nCols) { BOOL bResult = TRUE; ASSERT(nCols >= 0); if (nCols == GetColumnCount()) return bResult; // Force recalculation m_idTopLeftCell.col = -1; if (nCols < m_nFixedCols) m_nFixedCols = nCols; if (m_idCurrentCell.col >= nCols) SetFocusCell(-1, -1); int addedCols = nCols - GetColumnCount(); // If we are about to lose columns, then we need to delete the GridCell objects // within each column if (addedCols < 0 && !GetVirtualMode()) { for (int row = 0; row < m_nRows; row++) for (int col = nCols; col < GetColumnCount(); col++) DestroyCell(row, col); } TRY { // Change the number of columns. m_arColWidths.SetSize(nCols); // Change the number of columns in each row. if (!GetVirtualMode()) for (int i = 0; i < m_nRows; i++) if (m_RowData[i]) m_RowData[i]->SetSize(nCols); // If we have just added columns, we need to construct new elements for each cell // and set the default column width if (addedCols > 0) { // initialized column widths int col = 0; int startCol = nCols - addedCols; for (col = startCol; col < nCols; col++) m_arColWidths[col] = m_cellFixedColDef.GetWidth(); // initialise column data if (!GetVirtualMode()) { for (int row = 0; row < m_nRows; row++) for (col = startCol; col < nCols; col++) { GRID_ROW* pRow = m_RowData[row]; if (pRow) pRow->SetAt(col, CreateCell(row, col)); } } } // else // check for selected cell ranges // ResetSelectedRange(); } CATCH(CMemoryException, e) { e->ReportError(); bResult = FALSE; } END_CATCH m_nCols = nCols; SetModified(); ResetScrollBars(); Refresh(); return bResult; } // Insert a column at a given position, or add to end of columns (if nColumn = -1) int CGridCtrl::InsertColumn(LPCTSTR strHeading, UINT nFormat /* = DT_CENTER|DT_VCENTER|DT_SINGLELINE */, int nColumn /* = -1 */) { if (nColumn >= 0 && nColumn < m_nFixedCols) { // TODO: Fix it so column insertion works for in the fixed column area ASSERT(FALSE); return -1; } // If the insertion is for a specific column, check it's within range. if (nColumn >= 0 && nColumn > GetColumnCount()) return -1; // Force recalculation m_idTopLeftCell.col = -1; ResetSelectedRange(); // Gotta be able to at least _see_ some of the column. if (m_nRows < 1) SetRowCount(1); // Allow the user to insert after the last of the columns, but process it as a // "-1" column, meaning it gets flaged as being the last column, and not a regular // "insert" routine. if (nColumn == GetColumnCount()) nColumn = -1; TRY { if (nColumn < 0) { nColumn = m_nCols; m_arColWidths.Add(0); if (!GetVirtualMode()) { for (int row = 0; row < m_nRows; row++) { GRID_ROW* pRow = m_RowData[row]; if (!pRow) return -1; pRow->Add(CreateCell(row, nColumn)); } } } else { m_arColWidths.InsertAt(nColumn, (int)0, 1); if (!GetVirtualMode()) { for (int row = 0; row < m_nRows; row++) { GRID_ROW* pRow = m_RowData[row]; if (!pRow) return -1; pRow->InsertAt(nColumn, CreateCell(row, nColumn)); } } } } CATCH(CMemoryException, e) { e->ReportError(); return FALSE; } END_CATCH m_nCols++; // Initialise column data SetItemText(0, nColumn, strHeading); for (int row = 0; row < m_nRows; row++) SetItemFormat(row, nColumn, nFormat); // initialized column width m_arColWidths[nColumn] = GetTextExtent(0, nColumn, strHeading).cx; if (m_idCurrentCell.col != -1 && nColumn < m_idCurrentCell.col) m_idCurrentCell.col++; ResetScrollBars(); SetModified(); return nColumn; } // Insert a row at a given position, or add to end of rows (if nRow = -1) int CGridCtrl::InsertRow(LPCTSTR strHeading, int nRow /* = -1 */) { if (nRow >= 0 && nRow < m_nFixedRows) { // TODO: Fix it so column insertion works for in the fixed row area ASSERT(FALSE); return -1; } // If the insertion is for a specific row, check it's within range. if (nRow >= 0 && nRow >= GetRowCount()) return -1; // Force recalculation m_idTopLeftCell.col = -1; ResetSelectedRange(); // Gotta be able to at least _see_ some of the row. if (m_nCols < 1) SetColumnCount(1); TRY { // Adding a row to the bottom if (nRow < 0) { nRow = m_nRows; m_arRowHeights.Add(0); if (!GetVirtualMode()) m_RowData.Add(new GRID_ROW); } else { m_arRowHeights.InsertAt(nRow, (int)0, 1); if (!GetVirtualMode()) m_RowData.InsertAt(nRow, new GRID_ROW); } if (!GetVirtualMode()) m_RowData[nRow]->SetSize(m_nCols); } CATCH(CMemoryException, e) { e->ReportError(); return FALSE; } END_CATCH m_nRows++; // Initialise cell data if (!GetVirtualMode()) { for (int col = 0; col < m_nCols; col++) { GRID_ROW* pRow = m_RowData[nRow]; if (!pRow) return -1; pRow->SetAt(col, CreateCell(nRow, col)); } } // Set row title SetItemText(nRow, 0, strHeading); // initialized row height if (strHeading && strHeading[0]) m_arRowHeights[nRow] = GetTextExtent(nRow, 0, strHeading).cy; else m_arRowHeights[nRow] = m_cellFixedRowDef.GetHeight(); if (m_idCurrentCell.row != -1 && nRow < m_idCurrentCell.row) m_idCurrentCell.row++; ResetScrollBars(); SetModified(); return nRow; } /////////////////////////////////////////////////////////////////////////////// // Cell creation stuff BOOL CGridCtrl::SetCellType(int nRow, int nCol, CRuntimeClass* pRuntimeClass) { if (GetVirtualMode()) return FALSE; ASSERT(IsValid(nRow, nCol)); if (!IsValid(nRow, nCol)) return FALSE; if (!pRuntimeClass->IsDerivedFrom(RUNTIME_CLASS(CGridCellBase))) { ASSERT(FALSE); return FALSE; } CGridCellBase* pNewCell = (CGridCellBase*)pRuntimeClass->CreateObject(); CGridCellBase* pCurrCell = GetCell(nRow, nCol); if (pCurrCell) *pNewCell = *pCurrCell; SetCell(nRow, nCol, pNewCell); delete pCurrCell; return TRUE; } BOOL CGridCtrl::SetDefaultCellType(CRuntimeClass* pRuntimeClass) { ASSERT(pRuntimeClass != NULL); if (!pRuntimeClass->IsDerivedFrom(RUNTIME_CLASS(CGridCellBase))) { ASSERT(FALSE); return FALSE; } m_pRtcDefault = pRuntimeClass; return TRUE; } // Creates a new grid cell and performs any necessary initialisation /*virtual*/ CGridCellBase* CGridCtrl::CreateCell(int nRow, int nCol) { ASSERT(!GetVirtualMode()); if (!m_pRtcDefault || !m_pRtcDefault->IsDerivedFrom(RUNTIME_CLASS(CGridCellBase))) { ASSERT(FALSE); return NULL; } CGridCellBase* pCell = (CGridCellBase*)m_pRtcDefault->CreateObject(); if (!pCell) return NULL; pCell->SetGrid(this); pCell->SetCoords(nRow, nCol); if (nCol < m_nFixedCols) pCell->SetState(pCell->GetState() | GVIS_FIXED | GVIS_FIXEDCOL); if (nRow < m_nFixedRows) pCell->SetState(pCell->GetState() | GVIS_FIXED | GVIS_FIXEDROW); pCell->SetFormat(pCell->GetDefaultCell()->GetFormat()); return pCell; } // Performs any cell cleanup necessary to maintain grid integrity /*virtual*/ void CGridCtrl::DestroyCell(int nRow, int nCol) { // Should NEVER get here in virtual mode. ASSERT(!GetVirtualMode()); // Set the cells state to 0. If the cell is selected, this // will remove the cell from the selected list. SetItemState(nRow, nCol, 0); delete GetCell(nRow, nCol); } BOOL CGridCtrl::DeleteColumn(int nColumn) { if (nColumn < 0 || nColumn >= GetColumnCount()) return FALSE; ResetSelectedRange(); if (!GetVirtualMode()) { for (int row = 0; row < GetRowCount(); row++) { GRID_ROW* pRow = m_RowData[row]; if (!pRow) return FALSE; DestroyCell(row, nColumn); pRow->RemoveAt(nColumn); } } m_arColWidths.RemoveAt(nColumn); m_nCols--; if (nColumn < m_nFixedCols) m_nFixedCols--; if (nColumn == m_idCurrentCell.col) m_idCurrentCell.row = m_idCurrentCell.col = -1; else if (nColumn < m_idCurrentCell.col) m_idCurrentCell.col--; ResetScrollBars(); SetModified(); return TRUE; } BOOL CGridCtrl::DeleteRow(int nRow) { if (nRow < 0 || nRow >= GetRowCount()) return FALSE; ResetSelectedRange(); if (!GetVirtualMode()) { GRID_ROW* pRow = m_RowData[nRow]; if (!pRow) return FALSE; for (int col = 0; col < GetColumnCount(); col++) DestroyCell(nRow, col); delete pRow; m_RowData.RemoveAt(nRow); } m_arRowHeights.RemoveAt(nRow); m_nRows--; if (nRow < m_nFixedRows) m_nFixedRows--; if (nRow == m_idCurrentCell.row) m_idCurrentCell.row = m_idCurrentCell.col = -1; else if (nRow < m_idCurrentCell.row) m_idCurrentCell.row--; ResetScrollBars(); SetModified(); return TRUE; } // Handy function that removes all non-fixed rows BOOL CGridCtrl::DeleteNonFixedRows() { ResetSelectedRange(); int nFixed = GetFixedRowCount(); int nCount = GetRowCount(); // Delete all data rows for (int nRow = nCount; nRow >= nFixed; nRow--) DeleteRow(nRow); return TRUE; } // Removes all rows, columns and data from the grid. BOOL CGridCtrl::DeleteAllItems() { ResetSelectedRange(); m_arColWidths.RemoveAll(); m_arRowHeights.RemoveAll(); // Delete all cells in the grid if (!GetVirtualMode()) { for (int row = 0; row < m_nRows; row++) { for (int col = 0; col < m_nCols; col++) DestroyCell(row, col); GRID_ROW* pRow = m_RowData[row]; delete pRow; } // Remove all rows m_RowData.RemoveAll(); } m_idCurrentCell.row = m_idCurrentCell.col = -1; m_nRows = m_nFixedRows = m_nCols = m_nFixedCols = 0; ResetScrollBars(); SetModified(); return TRUE; } void CGridCtrl::AutoFill() { if (!::IsWindow(m_hWnd)) return; CRect rect; GetClientRect(rect); SetColumnCount(rect.Width() / m_cellDefault.GetWidth() + 1); SetRowCount(rect.Height() / m_cellDefault.GetHeight() + 1); SetFixedRowCount(1); SetFixedColumnCount(1); ExpandToFit(); } ///////////////////////////////////////////////////////////////////////////// // CGridCtrl data functions // Set CListCtrl::GetNextItem for details CCellID CGridCtrl::GetNextItem(CCellID& cell, int nFlags) const { if ((nFlags & GVNI_ALL) == GVNI_ALL) { // GVNI_ALL Search whole Grid beginning from cell // First row (cell.row) -- ONLY Columns to the right of cell // following rows -- ALL Columns int row = cell.row, col = cell.col + 1; if (row <= 0) row = GetFixedRowCount(); for (; row < GetRowCount(); row++) { if (col <= 0) col = GetFixedColumnCount(); for (; col < GetColumnCount(); col++) { int nState = GetItemState(row, col); if ((nFlags & GVNI_DROPHILITED && nState & GVIS_DROPHILITED) || (nFlags & GVNI_FOCUSED && nState & GVIS_FOCUSED) || (nFlags & GVNI_SELECTED && nState & GVIS_SELECTED) || (nFlags & GVNI_READONLY && nState & GVIS_READONLY) || (nFlags & GVNI_FIXED && nState & GVIS_FIXED) || (nFlags & GVNI_MODIFIED && nState & GVIS_MODIFIED)) return CCellID(row, col); } // go to First Column col = GetFixedColumnCount(); } } else if ((nFlags & GVNI_BELOW) == GVNI_BELOW && (nFlags & GVNI_TORIGHT) == GVNI_TORIGHT) { // GVNI_AREA Search Grid beginning from cell to Lower-Right of Grid // Only rows starting with cell.row and below // All rows -- ONLY Columns to the right of cell int row = cell.row; if (row <= 0) row = GetFixedRowCount(); for (; row < GetRowCount(); row++) { int col = cell.col + 1; if (col <= 0) col = GetFixedColumnCount(); for (; col < GetColumnCount(); col++) { int nState = GetItemState(row, col); if ((nFlags & GVNI_DROPHILITED && nState & GVIS_DROPHILITED) || (nFlags & GVNI_FOCUSED && nState & GVIS_FOCUSED) || (nFlags & GVNI_SELECTED && nState & GVIS_SELECTED) || (nFlags & GVNI_READONLY && nState & GVIS_READONLY) || (nFlags & GVNI_FIXED && nState & GVIS_FIXED) || (nFlags & GVNI_MODIFIED && nState & GVIS_MODIFIED)) return CCellID(row, col); } } } else if ((nFlags & GVNI_ABOVE) == GVNI_ABOVE) { for (int row = cell.row - 1; row >= GetFixedRowCount(); row--) { int nState = GetItemState(row, cell.col); if ((nFlags & GVNI_DROPHILITED && nState & GVIS_DROPHILITED) || (nFlags & GVNI_FOCUSED && nState & GVIS_FOCUSED) || (nFlags & GVNI_SELECTED && nState & GVIS_SELECTED) || (nFlags & GVNI_READONLY && nState & GVIS_READONLY) || (nFlags & GVNI_FIXED && nState & GVIS_FIXED) || (nFlags & GVNI_MODIFIED && nState & GVIS_MODIFIED)) return CCellID(row, cell.col); } } else if ((nFlags & GVNI_BELOW) == GVNI_BELOW) { for (int row = cell.row + 1; row < GetRowCount(); row++) { int nState = GetItemState(row, cell.col); if ((nFlags & GVNI_DROPHILITED && nState & GVIS_DROPHILITED) || (nFlags & GVNI_FOCUSED && nState & GVIS_FOCUSED) || (nFlags & GVNI_SELECTED && nState & GVIS_SELECTED) || (nFlags & GVNI_READONLY && nState & GVIS_READONLY) || (nFlags & GVNI_FIXED && nState & GVIS_FIXED) || (nFlags & GVNI_MODIFIED && nState & GVIS_MODIFIED)) return CCellID(row, cell.col); } } else if ((nFlags & GVNI_TOLEFT) == GVNI_TOLEFT) { for (int col = cell.col - 1; col >= GetFixedColumnCount(); col--) { int nState = GetItemState(cell.row, col); if ((nFlags & GVNI_DROPHILITED && nState & GVIS_DROPHILITED) || (nFlags & GVNI_FOCUSED && nState & GVIS_FOCUSED) || (nFlags & GVNI_SELECTED && nState & GVIS_SELECTED) || (nFlags & GVNI_READONLY && nState & GVIS_READONLY) || (nFlags & GVNI_FIXED && nState & GVIS_FIXED) || (nFlags & GVNI_MODIFIED && nState & GVIS_MODIFIED)) return CCellID(cell.row, col); } } else if ((nFlags & GVNI_TORIGHT) == GVNI_TORIGHT) { for (int col = cell.col + 1; col < GetColumnCount(); col++) { int nState = GetItemState(cell.row, col); if ((nFlags & GVNI_DROPHILITED && nState & GVIS_DROPHILITED) || (nFlags & GVNI_FOCUSED && nState & GVIS_FOCUSED) || (nFlags & GVNI_SELECTED && nState & GVIS_SELECTED) || (nFlags & GVNI_READONLY && nState & GVIS_READONLY) || (nFlags & GVNI_FIXED && nState & GVIS_FIXED) || (nFlags & GVNI_MODIFIED && nState & GVIS_MODIFIED)) return CCellID(cell.row, col); } } return CCellID(-1, -1); } // Sorts on a given column using the cell text BOOL CGridCtrl::SortTextItems(int nCol, BOOL bAscending, LPARAM data /* = 0 */) { SetSortColumn(nCol); SetSortAscending(bAscending); ResetSelectedRange(); SetFocusCell(-1, -1); // -AfxMessageBox("SortTextItems"); return CGridCtrl::SortItems(pfnCellTextCompare, nCol, bAscending, data); } void CGridCtrl::SetCompareFunction(PFNLVCOMPARE pfnCompare) { m_pfnCompare = pfnCompare; } // Sorts on a given column using the cell text and using the specified comparison // function BOOL CGridCtrl::SortItems(int nCol, BOOL bAscending, LPARAM data /* = 0 */) { SetSortColumn(nCol); SetSortAscending(bAscending); ResetSelectedRange(); SetFocusCell(-1, -1); if (m_pfnCompare == NULL) return CGridCtrl::SortItems(pfnCellTextCompare, nCol, bAscending, data); else return CGridCtrl::SortItems(m_pfnCompare, nCol, bAscending, data); } // Sorts on a given column using the supplied compare function (see CListCtrl::SortItems) BOOL CGridCtrl::SortItems(PFNLVCOMPARE pfnCompare, int nCol, BOOL bAscending, LPARAM data /* = 0 */) { SetSortColumn(nCol); SetSortAscending(bAscending); ResetSelectedRange(); SetFocusCell(-1, -1); return SortItems(pfnCompare, nCol, bAscending, data, GetFixedRowCount(), -1); } int CALLBACK CGridCtrl::pfnCellTextCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { UNUSED_ALWAYS(lParamSort); CGridCellBase* pCell1 = (CGridCellBase*)lParam1; CGridCellBase* pCell2 = (CGridCellBase*)lParam2; if (!pCell1 || !pCell2) return 0; return _tcscmp(pCell1->GetText(), pCell2->GetText()); } int CALLBACK CGridCtrl::pfnCellNumericCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { UNUSED_ALWAYS(lParamSort); CGridCellBase* pCell1 = (CGridCellBase*)lParam1; CGridCellBase* pCell2 = (CGridCellBase*)lParam2; if (!pCell1 || !pCell2) return 0; int nValue1 = _ttol(pCell1->GetText()); int nValue2 = _ttol(pCell2->GetText()); if (nValue1 < nValue2) return -1; else if (nValue1 == nValue2) return 0; else return 1; } // private recursive sort implementation BOOL CGridCtrl::SortItems(PFNLVCOMPARE pfnCompare, int nCol, BOOL bAscending, LPARAM data, int low, int high) { ///-AfxMessageBox("SortItems3"); if (nCol >= GetColumnCount()) return FALSE; if (high == -1) high = GetRowCount() - 1; int lo = low; int hi = high; if (hi <= lo) return FALSE; //LPARAM midItem = GetItemData((lo + hi)/2, nCol); LPARAM pMidCell = (LPARAM)GetCell((lo + hi) / 2, nCol); // loop through the list until indices cross while (lo <= hi) { // Find the first element that is greater than or equal to the partition // element starting from the left Index. if (bAscending) while (lo < high && pfnCompare((LPARAM)GetCell(lo, nCol), (LPARAM)pMidCell, data) < 0) ++lo; else while (lo < high && pfnCompare((LPARAM)GetCell(lo, nCol), pMidCell, data) > 0) ++lo; // Find an element that is smaller than or equal to the partition // element starting from the right Index. if (bAscending) while (hi > low && pfnCompare((LPARAM)GetCell(hi, nCol), pMidCell, data) > 0) --hi; else while (hi > low && pfnCompare((LPARAM)GetCell(hi, nCol), pMidCell, data) < 0) --hi; // If the indexes have not crossed, swap if the items are not equal if (lo <= hi) { // swap only if the items are not equal if (pfnCompare((LPARAM)GetCell(lo, nCol), (LPARAM)GetCell(hi, nCol), data) != 0) { for (int col = 0; col < GetColumnCount(); col++) { CGridCellBase *pCell = GetCell(lo, col); SetCell(lo, col, GetCell(hi, col)); SetCell(hi, col, pCell); } UINT nRowHeight = m_arRowHeights[lo]; m_arRowHeights[lo] = m_arRowHeights[hi]; m_arRowHeights[hi] = nRowHeight; } ++lo; --hi; } } // If the right index has not reached the left side of array // must now sort the left partition. if (low < hi) SortItems(pfnCompare, nCol, bAscending, data, low, hi); // If the left index has not reached the right side of array // must now sort the right partition. if (lo < high) SortItems(pfnCompare, nCol, bAscending, data, lo, high); return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CGridCtrl data functions BOOL CGridCtrl::SetItem(const GV_ITEM* pItem) { if (!pItem || GetVirtualMode()) return FALSE; CGridCellBase* pCell = GetCell(pItem->row, pItem->col); if (!pCell) return FALSE; SetModified(TRUE, pItem->row, pItem->col); if (pItem->mask & GVIF_TEXT) pCell->SetText(pItem->strText); if (pItem->mask & GVIF_PARAM) pCell->SetData(pItem->lParam); if (pItem->mask & GVIF_IMAGE) pCell->SetImage(pItem->iImage); if (pItem->mask & GVIF_STATE) SetItemState(pItem->row, pItem->col, pItem->nState); if (pItem->mask & GVIF_FORMAT) pCell->SetFormat(pItem->nFormat); if (pItem->mask & GVIF_BKCLR) pCell->SetBackClr(pItem->crBkClr); if (pItem->mask & GVIF_FGCLR) pCell->SetTextClr(pItem->crFgClr); if (pItem->mask & GVIF_FONT) pCell->SetFont(&(pItem->lfFont)); if (pItem->mask & GVIF_MARGIN) pCell->SetMargin(pItem->nMargin); return TRUE; } BOOL CGridCtrl::GetItem(GV_ITEM* pItem) { if (!pItem) return FALSE; CGridCellBase* pCell = GetCell(pItem->row, pItem->col); if (!pCell) return FALSE; if (pItem->mask & GVIF_TEXT) pItem->strText = GetItemText(pItem->row, pItem->col); if (pItem->mask & GVIF_PARAM) pItem->lParam = pCell->GetData();; if (pItem->mask & GVIF_IMAGE) pItem->iImage = pCell->GetImage(); if (pItem->mask & GVIF_STATE) pItem->nState = pCell->GetState(); if (pItem->mask & GVIF_FORMAT) pItem->nFormat = pCell->GetFormat(); if (pItem->mask & GVIF_BKCLR) pItem->crBkClr = pCell->GetBackClr(); if (pItem->mask & GVIF_FGCLR) pItem->crFgClr = pCell->GetTextClr(); if (pItem->mask & GVIF_FONT) memcpy(&(pItem->lfFont), pCell->GetFont(), sizeof(LOGFONT)); if (pItem->mask & GVIF_MARGIN) pItem->nMargin = pCell->GetMargin(); return TRUE; } BOOL CGridCtrl::SetItemText(int nRow, int nCol, LPCTSTR str) { if (GetVirtualMode()) return FALSE; CGridCellBase* pCell = GetCell(nRow, nCol); if (!pCell) return FALSE; pCell->SetText(str); SetModified(TRUE, nRow, nCol); return TRUE; } #if !defined(_WIN32_WCE) || (_WIN32_WCE >= 210) // EFW - 06/13/99 - Added to support printf-style formatting codes BOOL CGridCtrl::SetItemTextFmt(int nRow, int nCol, LPCTSTR szFmt, ...) { if (GetVirtualMode()) return FALSE; CString strText; va_list argptr; CGridCellBase* pCell = GetCell(nRow, nCol); if (!pCell) return FALSE; // Format the message text va_start(argptr, szFmt); strText.FormatV(szFmt, argptr); va_end(argptr); pCell->SetText(strText); SetModified(TRUE, nRow, nCol); return TRUE; } // EFW - 06/13/99 - Added to support string resource ID. Supports // a variable argument list too. BOOL CGridCtrl::SetItemTextFmtID(int nRow, int nCol, UINT nID, ...) { if (GetVirtualMode()) return FALSE; CString strFmt, strText; va_list argptr; CGridCellBase* pCell = GetCell(nRow, nCol); if (!pCell) return FALSE; // Format the message text va_start(argptr, nID); VERIFY(strFmt.LoadString(nID)); strText.FormatV(strFmt, argptr); va_end(argptr); pCell->SetText(strText); SetModified(TRUE, nRow, nCol); return TRUE; } #endif BOOL CGridCtrl::SetItemData(int nRow, int nCol, LPARAM lParam) { if (GetVirtualMode()) return FALSE; CGridCellBase* pCell = GetCell(nRow, nCol); if (!pCell) return FALSE; pCell->SetData(lParam); SetModified(TRUE, nRow, nCol); return TRUE; } LPARAM CGridCtrl::GetItemData(int nRow, int nCol) const { CGridCellBase* pCell = GetCell(nRow, nCol); if (!pCell) return (LPARAM)0; return pCell->GetData(); } BOOL CGridCtrl::SetItemImage(int nRow, int nCol, int iImage) { if (GetVirtualMode()) return FALSE; CGridCellBase* pCell = GetCell(nRow, nCol); if (!pCell) return FALSE; pCell->SetImage(iImage); SetModified(TRUE, nRow, nCol); return TRUE; } int CGridCtrl::GetItemImage(int nRow, int nCol) const { CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return -1; return pCell->GetImage(); } BOOL CGridCtrl::SetItemState(int nRow, int nCol, UINT state) { BOOL bSelected = IsCellSelected(nRow, nCol); // If the cell is being unselected, remove it from the selected list if (bSelected && !(state & GVIS_SELECTED)) { CCellID cell; DWORD key = MAKELONG(nRow, nCol); if (m_SelectedCellMap.Lookup(key, (CCellID&)cell)) { m_SelectedCellMap.RemoveKey(key); //chn add for (int i = 0; i < m_nArrayListChoose.GetCount(); i++) { if (m_nArrayListChoose.GetAt(i) == nRow) { m_nArrayListChoose.RemoveAt(i); break; } } } } // If cell is being selected, add it to the list of selected cells else if (!bSelected && (state & GVIS_SELECTED)) { CCellID cell(nRow, nCol); m_SelectedCellMap.SetAt(MAKELONG(nRow, nCol), cell); //chn add bool bExist = false; for (int i = 0; i < m_nArrayListChoose.GetCount(); i++) { if (m_nArrayListChoose.GetAt(i) == nRow) { bExist = true; break; } } if (!bExist) m_nArrayListChoose.Add(nRow); } if (GetVirtualMode()) return FALSE; CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return FALSE; // Set the cell's state pCell->SetState(state); return TRUE; } UINT CGridCtrl::GetItemState(int nRow, int nCol) const { CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return 0; return pCell->GetState(); } BOOL CGridCtrl::SetItemFormat(int nRow, int nCol, UINT nFormat) { if (GetVirtualMode()) return FALSE; CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return FALSE; pCell->SetFormat(nFormat); return TRUE; } UINT CGridCtrl::GetItemFormat(int nRow, int nCol) const { CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return 0; return pCell->GetFormat(); } BOOL CGridCtrl::SetItemBkColour(int nRow, int nCol, COLORREF cr /* = CLR_DEFAULT */) { if (GetVirtualMode()) return FALSE; CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return FALSE; pCell->SetBackClr(cr); return TRUE; } COLORREF CGridCtrl::GetItemBkColour(int nRow, int nCol) const { CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return 0; return pCell->GetBackClr(); } BOOL CGridCtrl::SetItemFgColour(int nRow, int nCol, COLORREF cr /* = CLR_DEFAULT */) { if (GetVirtualMode()) return FALSE; CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return FALSE; pCell->SetTextClr(cr); return TRUE; } COLORREF CGridCtrl::GetItemFgColour(int nRow, int nCol) const { CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return 0; return pCell->GetTextClr(); } BOOL CGridCtrl::SetItemFont(int nRow, int nCol, const LOGFONT* plf) { if (GetVirtualMode()) return FALSE; CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return FALSE; pCell->SetFont(plf); return TRUE; } const LOGFONT* CGridCtrl::GetItemFont(int nRow, int nCol) { CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return GetDefaultCell(nRow < GetFixedRowCount(), nCol < GetFixedColumnCount())->GetFont(); return pCell->GetFont(); } BOOL CGridCtrl::IsItemEditing(int nRow, int nCol) { CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return FALSE; return pCell->IsEditing(); } //////////////////////////////////////////////////////////////////////////////////// // Row/Column size functions long CGridCtrl::GetVirtualWidth() const { long lVirtualWidth = 0; int iColCount = GetColumnCount(); for (int i = 0; i < iColCount; i++) lVirtualWidth += m_arColWidths[i]; return lVirtualWidth; } long CGridCtrl::GetVirtualHeight() const { long lVirtualHeight = 0; int iRowCount = GetRowCount(); for (int i = 0; i < iRowCount; i++) lVirtualHeight += m_arRowHeights[i]; return lVirtualHeight; } int CGridCtrl::GetRowHeight(int nRow) const { ASSERT(nRow >= 0 && nRow < m_nRows); if (nRow < 0 || nRow >= m_nRows) return -1; return m_arRowHeights[nRow]; } int CGridCtrl::GetColumnWidth(int nCol) const { ASSERT(nCol >= 0 && nCol < m_nCols); if (nCol < 0 || nCol >= m_nCols) return -1; return m_arColWidths[nCol]; } BOOL CGridCtrl::SetRowHeight(int nRow, int height) { ASSERT(nRow >= 0 && nRow < m_nRows && height >= 0); if (nRow < 0 || nRow >= m_nRows || height < 0) return FALSE; m_arRowHeights[nRow] = height; ResetScrollBars(); return TRUE; } BOOL CGridCtrl::SetColumnWidth(int nCol, int width) { ASSERT(nCol >= 0 && nCol < m_nCols && width >= 0); if (nCol < 0 || nCol >= m_nCols || width < 0) return FALSE; m_arColWidths[nCol] = width; ResetScrollBars(); return TRUE; } int CGridCtrl::GetFixedRowHeight() const { int nHeight = 0; for (int i = 0; i < m_nFixedRows; i++) nHeight += GetRowHeight(i); return nHeight; } int CGridCtrl::GetFixedColumnWidth() const { int nWidth = 0; for (int i = 0; i < m_nFixedCols; i++) nWidth += GetColumnWidth(i); return nWidth; } BOOL CGridCtrl::AutoSizeColumn(int nCol, UINT nAutoSizeStyle /*=GVS_DEFAULT*/, BOOL bResetScroll /*=TRUE*/) { ASSERT(nCol >= 0 && nCol < m_nCols); if (nCol < 0 || nCol >= m_nCols) return FALSE; // Skip hidden columns when autosizing if (GetColumnWidth(nCol) <= 0) return FALSE; CSize size; CDC* pDC = GetDC(); if (!pDC) return FALSE; int nWidth = 0; ASSERT(GVS_DEFAULT <= nAutoSizeStyle && nAutoSizeStyle <= GVS_BOTH); if (nAutoSizeStyle == GVS_DEFAULT) nAutoSizeStyle = GetAutoSizeStyle(); int nStartRow = (nAutoSizeStyle & GVS_HEADER) ? 0 : GetFixedRowCount(); int nEndRow = (nAutoSizeStyle & GVS_DATA) ? GetRowCount() - 1 : GetFixedRowCount() - 1; if (GetVirtualMode()) SendCacheHintToParent(CCellRange(nStartRow, nCol, nEndRow, nCol)); for (int nRow = nStartRow; nRow <= nEndRow; nRow++) { CGridCellBase* pCell = GetCell(nRow, nCol); if (pCell) size = pCell->GetCellExtent(pDC); if (size.cx > nWidth) nWidth = size.cx; } if (GetVirtualMode()) SendCacheHintToParent(CCellRange(-1, -1, -1, -1)); m_arColWidths[nCol] = nWidth; ReleaseDC(pDC); if (bResetScroll) ResetScrollBars(); return TRUE; } BOOL CGridCtrl::AutoSizeRow(int nRow, BOOL bResetScroll /*=TRUE*/) { ASSERT(nRow >= 0 && nRow < m_nRows); if (nRow < 0 || nRow >= m_nRows) return FALSE; // Skip hidden rows when autosizing if (GetRowHeight(nRow) <= 0) return FALSE; CSize size; CDC* pDC = GetDC(); if (!pDC) return FALSE; int nHeight = 0; int nNumColumns = GetColumnCount(); if (GetVirtualMode()) SendCacheHintToParent(CCellRange(nRow, 0, nRow, nNumColumns)); for (int nCol = 0; nCol < nNumColumns; nCol++) { CGridCellBase* pCell = GetCell(nRow, nCol); if (pCell) size = pCell->GetCellExtent(pDC); if (size.cy > nHeight) nHeight = size.cy; } m_arRowHeights[nRow] = nHeight; if (GetVirtualMode()) SendCacheHintToParent(CCellRange(-1, -1, -1, -1)); ReleaseDC(pDC); if (bResetScroll) ResetScrollBars(); return TRUE; } void CGridCtrl::AutoSizeColumns(UINT nAutoSizeStyle /*=GVS_DEFAULT*/) { int nNumColumns = GetColumnCount(); for (int nCol = 0; nCol < nNumColumns; nCol++) { // Skip hidden columns when autosizing if (GetColumnWidth(nCol) > 0) AutoSizeColumn(nCol, nAutoSizeStyle, FALSE); } ResetScrollBars(); } void CGridCtrl::AutoSizeRows() { int nNumRows = GetRowCount(); for (int nRow = 0; nRow < nNumRows; nRow++) { // Skip hidden rows when autosizing if (GetRowHeight(nRow) > 0) AutoSizeRow(nRow, FALSE); } ResetScrollBars(); } // sizes all rows and columns // faster than calling both AutoSizeColumns() and AutoSizeRows() void CGridCtrl::AutoSize(UINT nAutoSizeStyle /*=GVS_DEFAULT*/) { CDC* pDC = GetDC(); if (!pDC) return; int nNumColumns = GetColumnCount(); int nCol, nRow; ASSERT(GVS_DEFAULT <= nAutoSizeStyle && nAutoSizeStyle <= GVS_BOTH); if (nAutoSizeStyle == GVS_DEFAULT) nAutoSizeStyle = GetAutoSizeStyle(); int nStartRow = (nAutoSizeStyle & GVS_HEADER) ? 0 : GetFixedRowCount(); int nEndRow = (nAutoSizeStyle & GVS_DATA) ? GetRowCount() - 1 : GetFixedRowCount() - 1; if (GetVirtualMode()) SendCacheHintToParent(CCellRange(nStartRow, 0, nEndRow, nNumColumns)); // Row initialisation - only work on rows whose height is > 0 for (nRow = nStartRow; nRow <= nEndRow; nRow++) { if (GetRowHeight(nRow) > 0) m_arRowHeights[nRow] = 1; } CSize size; for (nCol = 0; nCol < nNumColumns; nCol++) { // Don't size hidden columns or rows if (GetColumnWidth(nCol) > 0) { // Skip columns that are hidden, but now initialize m_arColWidths[nCol] = 0; for (nRow = nStartRow; nRow <= nEndRow; nRow++) { if (GetRowHeight(nRow) > 0) { CGridCellBase* pCell = GetCell(nRow, nCol); if (pCell) size = pCell->GetCellExtent(pDC); if (size.cx > (int)m_arColWidths[nCol]) m_arColWidths[nCol] = size.cx; if (size.cy > (int)m_arRowHeights[nRow]) m_arRowHeights[nRow] = size.cy; } } } } if (GetVirtualMode()) SendCacheHintToParent(CCellRange(-1, -1, -1, -1)); ReleaseDC(pDC); ResetScrollBars(); Refresh(); } // Expands the columns to fit the screen space. If bExpandFixed is FALSE then fixed // columns will not be affected void CGridCtrl::ExpandColumnsToFit(BOOL bExpandFixed /*=TRUE*/) { if (bExpandFixed) { if (GetColumnCount() <= 0) return; } else { if (GetColumnCount() <= GetFixedColumnCount()) return; } EnableScrollBars(SB_HORZ, FALSE); CRect rect; GetClientRect(rect); int nFirstColumn = (bExpandFixed) ? 0 : GetFixedColumnCount(); int col = 0; int nNumColumnsAffected = 0; for (col = nFirstColumn; col < GetColumnCount(); col++) { if (m_arColWidths[col] > 0) nNumColumnsAffected++; } if (nNumColumnsAffected <= 0) return; long virtualWidth = GetVirtualWidth(); int nDifference = rect.Width() - (int)virtualWidth; int nColumnAdjustment = nDifference / nNumColumnsAffected; for (col = nFirstColumn; col < GetColumnCount(); col++) { if (m_arColWidths[col] > 0) m_arColWidths[col] += nColumnAdjustment; } if (nDifference > 0) { int leftOver = nDifference % nNumColumnsAffected; for (int nCount = 0, col = nFirstColumn; (col < GetColumnCount()) && (nCount < leftOver); col++, nCount++) { if (m_arColWidths[col] > 0) m_arColWidths[col] += 1; } } else { int leftOver = (-nDifference) % nNumColumnsAffected; for (int nCount = 0, col = nFirstColumn; (col < GetColumnCount()) && (nCount < leftOver); col++, nCount++) { if (m_arColWidths[col] > 0) m_arColWidths[col] -= 1; } } Refresh(); ResetScrollBars(); } void CGridCtrl::ExpandLastColumn() { if (GetColumnCount() <= 0) return; // Search for last non-hidden column int nLastColumn = GetColumnCount() - 1; while (m_arColWidths[nLastColumn] <= 0) nLastColumn--; if (nLastColumn <= 0) return; EnableScrollBars(SB_HORZ, FALSE); CRect rect; GetClientRect(rect); long virtualWidth = GetVirtualWidth(); int nDifference = rect.Width() - (int)virtualWidth; if (nDifference > 0) { //if (GetVirtualHeight() > rect.Height()) // nDifference -= GetSystemMetrics(SM_CXVSCROLL); m_arColWidths[nLastColumn] += nDifference; Refresh(); } ResetScrollBars(); } // Expands the rows to fit the screen space. If bExpandFixed is FALSE then fixed // rows will not be affected void CGridCtrl::ExpandRowsToFit(BOOL bExpandFixed /*=TRUE*/) { if (bExpandFixed) { if (GetRowCount() <= 0) return; } else { if (GetRowCount() <= GetFixedRowCount()) return; } EnableScrollBars(SB_VERT, FALSE); CRect rect; GetClientRect(rect); int nFirstRow = (bExpandFixed) ? 0 : GetFixedRowCount(); int row = 0; int nNumRowsAffected = 0; for (row = nFirstRow; row < GetRowCount(); row++) { if (m_arRowHeights[row] > 0) nNumRowsAffected++; } if (nNumRowsAffected <= 0) return; long virtualHeight = GetVirtualHeight(); int nDifference = rect.Height() - (int)virtualHeight; int nRowAdjustment = nDifference / nNumRowsAffected; for (row = nFirstRow; row < GetRowCount(); row++) { if (m_arRowHeights[row] > 0) m_arRowHeights[row] += nRowAdjustment; } if (nDifference > 0) { int leftOver = nDifference % nNumRowsAffected; for (int nCount = 0, row = nFirstRow; (row < GetRowCount()) && (nCount < leftOver); row++, nCount++) { if (m_arRowHeights[row] > 0) m_arRowHeights[row] += 1; } } else { int leftOver = (-nDifference) % nNumRowsAffected; for (int nCount = 0, row = nFirstRow; (row < GetRowCount()) && (nCount < leftOver); row++, nCount++) { if (m_arRowHeights[row] > 0) m_arRowHeights[row] -= 1; } } Refresh(); ResetScrollBars(); } // Expands the cells to fit the screen space. If bExpandFixed is FALSE then fixed // cells will not be affected void CGridCtrl::ExpandToFit(BOOL bExpandFixed /*=TRUE*/) { ExpandColumnsToFit(bExpandFixed); // This will remove any existing horz scrollbar ExpandRowsToFit(bExpandFixed); // This will remove any existing vert scrollbar ExpandColumnsToFit(bExpandFixed); // Just in case the first adjustment was with a vert // scrollbar in place Refresh(); } ///////////////////////////////////////////////////////////////////////////////////// // Attributes void CGridCtrl::SetVirtualMode(BOOL bVirtual) { DeleteAllItems(); m_bVirtualMode = bVirtual; // Force some defaults here. if (m_bVirtualMode) { SetEditable(FALSE); SetHeaderSort(FALSE); SetAutoSizeStyle(GVS_HEADER); SetFixedColumnSelection(FALSE); SetFixedRowSelection(FALSE); } } void CGridCtrl::SetGridLines(int nWhichLines /*=GVL_BOTH*/) { m_nGridLines = nWhichLines; Refresh(); } void CGridCtrl::SetListMode(BOOL bEnableListMode /*=TRUE*/) { ResetSelectedRange(); SetSortColumn(-1); m_bListMode = bEnableListMode; SetFixedRowSelection(FALSE); Refresh(); } void CGridCtrl::SetSortColumn(int nCol) { if (m_nSortColumn >= 0) InvalidateCellRect(0, m_nSortColumn); m_nSortColumn = nCol; if (nCol >= 0) InvalidateCellRect(0, nCol); } BOOL CGridCtrl::IsCellFixed(int nRow, int nCol) { return (nRow < GetFixedRowCount() || nCol < GetFixedColumnCount()); } void CGridCtrl::SetModified(BOOL bModified /*=TRUE*/, int nRow /*=-1*/, int nCol /*=-1*/) { // Cannot guarantee sorting anymore... if (nCol < 0 || nCol == GetSortColumn()) SetSortColumn(-1); if (nRow >= 0 && nCol >= 0) { if (bModified) { SetItemState(nRow, nCol, GetItemState(nRow, nCol) | GVIS_MODIFIED); m_bModified = TRUE; } else SetItemState(nRow, nCol, GetItemState(nRow, nCol) & ~GVIS_MODIFIED); } else m_bModified = bModified; if (!m_bModified) { for (int row = 0; row < GetRowCount(); row++) for (int col = 0; col < GetColumnCount(); col++) SetItemState(row, col, GetItemState(row, col) & ~GVIS_MODIFIED); } } BOOL CGridCtrl::GetModified(int nRow /*=-1*/, int nCol /*=-1*/) { if (nRow >= 0 && nCol >= 0) return ((GetItemState(nRow, nCol) & GVIS_MODIFIED) == GVIS_MODIFIED); else return m_bModified; } ///////////////////////////////////////////////////////////////////////////////////// // GridCtrl cell visibility tests and invalidation/redraw functions void CGridCtrl::Refresh() { if (GetSafeHwnd() && m_bAllowDraw) Invalidate(); } // EnsureVisible supplied by Roelf Werkman void CGridCtrl::EnsureVisible(int nRow, int nCol) { if (!m_bAllowDraw) return; CRect rectWindow; /* // set the scroll to the approximate position of row (Nigel Page-Jones) int nPos = (int)((float)nRow / GetRowCount() * 1000); float fPos = (float)nPos / 1000; SCROLLINFO scrollInfo; GetScrollInfo(SB_VERT, &scrollInfo); scrollInfo.nPos = (int)(scrollInfo.nMax * fPos); SetScrollInfo(SB_VERT, &scrollInfo, FALSE); GetClientRect(rectWindow); // redraw cells if necessary (Nigel Page-Jones) CCellID idTopLeft = GetTopleftNonFixedCell(FALSE); CCellID idNewTopLeft = GetTopleftNonFixedCell(TRUE); if (idNewTopLeft != idTopLeft) { rectWindow.top = GetFixedRowHeight(); InvalidateRect(rectWindow); } */ // We are gonna send some scroll messages, which will steal the focus // from it's rightful owner. Squirrel it away ourselves so we can give // it back. (Damir) CWnd* pFocusWnd = GetFocus(); CCellRange VisibleCells = GetVisibleNonFixedCellRange(); int right = nCol - VisibleCells.GetMaxCol(); int left = VisibleCells.GetMinCol() - nCol; int down = nRow - VisibleCells.GetMaxRow(); int up = VisibleCells.GetMinRow() - nRow; int iColumnStart; int iRowStart; iColumnStart = VisibleCells.GetMaxCol() + 1; while (right > 0) { if (GetColumnWidth(iColumnStart) > 0) SendMessage(WM_HSCROLL, SB_LINERIGHT, 0); right--; iColumnStart++; } iColumnStart = VisibleCells.GetMinCol() - 1; while (left > 0) { if (GetColumnWidth(iColumnStart) > 0) SendMessage(WM_HSCROLL, SB_LINELEFT, 0); left--; iColumnStart--; } iRowStart = VisibleCells.GetMaxRow() + 1; while (down > 0) { if (GetRowHeight(iRowStart) > 0) SendMessage(WM_VSCROLL, SB_LINEDOWN, 0); down--; iRowStart++; } iRowStart = VisibleCells.GetMinRow() - 1; while (up > 0) { if (GetRowHeight(iRowStart) > 0) SendMessage(WM_VSCROLL, SB_LINEUP, 0); up--; iRowStart--; } // Move one more if we only see a snall bit of the cell CRect rectCell; if (!GetCellRect(nRow, nCol, rectCell)) { pFocusWnd->SetFocus(); return; } GetClientRect(rectWindow); // The previous fix was fixed properly by Martin Richter while (rectCell.right > rectWindow.right && rectCell.left > GetFixedColumnWidth()) { SendMessage(WM_HSCROLL, SB_LINERIGHT, 0); if (!GetCellRect(nRow, nCol, rectCell)) { pFocusWnd->SetFocus(); return; } } while (rectCell.bottom > rectWindow.bottom && rectCell.top > GetFixedRowHeight()) { SendMessage(WM_VSCROLL, SB_LINEDOWN, 0); if (!GetCellRect(nRow, nCol, rectCell)) { pFocusWnd->SetFocus(); return; } } // restore focus to whoever owned it pFocusWnd->SetFocus(); } BOOL CGridCtrl::IsCellEditable(CCellID &cell) const { return IsCellEditable(cell.row, cell.col); } BOOL CGridCtrl::IsCellEditable(int nRow, int nCol) const { return IsEditable() && ((GetItemState(nRow, nCol) & GVIS_READONLY) != GVIS_READONLY); } BOOL CGridCtrl::IsCellSelected(CCellID &cell) const { return IsCellSelected(cell.row, cell.col); } BOOL CGridCtrl::IsCellSelected(int nRow, int nCol) const { if (GetVirtualMode()) { if (!IsSelectable()) return FALSE; CCellID cell; DWORD key = MAKELONG(nRow, nCol); return (m_SelectedCellMap.Lookup(key, (CCellID&)cell)); } else return IsSelectable() && ((GetItemState(nRow, nCol) & GVIS_SELECTED) == GVIS_SELECTED); } BOOL CGridCtrl::IsCellVisible(CCellID cell) { return IsCellVisible(cell.row, cell.col); } BOOL CGridCtrl::IsCellVisible(int nRow, int nCol) { if (!IsWindow(m_hWnd)) return FALSE; int x, y; CCellID TopLeft; if (nCol >= GetFixedColumnCount() || nRow >= GetFixedRowCount()) { TopLeft = GetTopleftNonFixedCell(); if (nCol >= GetFixedColumnCount() && nCol < TopLeft.col) return FALSE; if (nRow >= GetFixedRowCount() && nRow < TopLeft.row) return FALSE; } CRect rect; GetClientRect(rect); if (nCol < GetFixedColumnCount()) { x = 0; for (int i = 0; i <= nCol; i++) { if (x >= rect.right) return FALSE; x += GetColumnWidth(i); } } else { x = GetFixedColumnWidth(); for (int i = TopLeft.col; i <= nCol; i++) { if (x >= rect.right) return FALSE; x += GetColumnWidth(i); } } if (nRow < GetFixedRowCount()) { y = 0; for (int i = 0; i <= nRow; i++) { if (y >= rect.bottom) return FALSE; y += GetRowHeight(i); } } else { if (nRow < TopLeft.row) return FALSE; y = GetFixedRowHeight(); for (int i = TopLeft.row; i <= nRow; i++) { if (y >= rect.bottom) return FALSE; y += GetRowHeight(i); } } return TRUE; } BOOL CGridCtrl::InvalidateCellRect(const CCellID& cell) { return InvalidateCellRect(cell.row, cell.col); } BOOL CGridCtrl::InvalidateCellRect(const int row, const int col) { if (!::IsWindow(GetSafeHwnd()) || !m_bAllowDraw) return FALSE; if (!IsValid(row, col)) return FALSE; if (!IsCellVisible(row, col)) return FALSE; CRect rect; if (!GetCellRect(row, col, rect)) return FALSE; rect.right++; rect.bottom++; InvalidateRect(rect, TRUE); return TRUE; } BOOL CGridCtrl::InvalidateCellRect(const CCellRange& cellRange) { ASSERT(IsValid(cellRange)); if (!::IsWindow(GetSafeHwnd()) || !m_bAllowDraw) return FALSE; CCellRange visibleCellRange = GetVisibleNonFixedCellRange().Intersect(cellRange); CRect rect; if (!GetCellRangeRect(visibleCellRange, rect)) return FALSE; rect.right++; rect.bottom++; InvalidateRect(rect, TRUE); return TRUE; } ///////////////////////////////////////////////////////////////////////////// // CGridCtrl Mouse stuff // Handles mouse wheel notifications // Note - if this doesn't work for win95 then use OnRegisteredMouseWheel instead #if !defined(_WIN32_WCE) && (_MFC_VER >= 0x0421) BOOL CGridCtrl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) { // A m_nRowsPerWheelNotch value less than 0 indicates that the mouse // wheel scrolls whole pages, not just lines. if (m_nRowsPerWheelNotch == -1) { int nPagesScrolled = zDelta / 120; if (nPagesScrolled > 0) for (int i = 0; i < nPagesScrolled; i++) PostMessage(WM_VSCROLL, SB_PAGEUP, 0); else for (int i = 0; i > nPagesScrolled; i--) PostMessage(WM_VSCROLL, SB_PAGEDOWN, 0); } else { int nRowsScrolled = m_nRowsPerWheelNotch * zDelta / 120; if (nRowsScrolled > 0) for (int i = 0; i < nRowsScrolled; i++) PostMessage(WM_VSCROLL, SB_LINEUP, 0); else for (int i = 0; i > nRowsScrolled; i--) PostMessage(WM_VSCROLL, SB_LINEDOWN, 0); } return CWnd::OnMouseWheel(nFlags, zDelta, pt); } #endif // !defined(_WIN32_WCE) && (_MFC_VER >= 0x0421) void CGridCtrl::OnMouseMove(UINT /*nFlags*/, CPoint point) { CRect rect; GetClientRect(rect); #ifndef GRIDCONTROL_NO_DRAGDROP // If outside client area, return (unless we are drag n dropping) if (m_MouseMode != MOUSE_DRAGGING && !rect.PtInRect(point)) return; #endif // Sometimes a MOUSEMOVE message can come after the left buttons // has been let go, but before the BUTTONUP message hs been processed. // We'll keep track of mouse buttons manually to avoid this. // All bMouseButtonDown's have been replaced with the member m_bLMouseButtonDown // BOOL bMouseButtonDown = ((nFlags & MK_LBUTTON) == MK_LBUTTON); // If the left mouse button is up, then test to see if row/column sizing is imminent if (!m_bLMouseButtonDown || (m_bLMouseButtonDown && m_MouseMode == MOUSE_NOTHING)) { if (m_bAllowColumnResize && MouseOverColumnResizeArea(point)) { if (m_MouseMode != MOUSE_OVER_COL_DIVIDE) { #ifndef _WIN32_WCE_NO_CURSOR SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE)); #endif m_MouseMode = MOUSE_OVER_COL_DIVIDE; } } else if (m_bAllowRowResize && MouseOverRowResizeArea(point)) { if (m_MouseMode != MOUSE_OVER_ROW_DIVIDE) { #ifndef _WIN32_WCE_NO_CURSOR SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENS)); #endif m_MouseMode = MOUSE_OVER_ROW_DIVIDE; } } else if (m_MouseMode != MOUSE_NOTHING) { #ifndef _WIN32_WCE_NO_CURSOR SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); #endif m_MouseMode = MOUSE_NOTHING; } if (m_MouseMode == MOUSE_NOTHING) { CGridCellBase* pCell = NULL; CCellID idCurrentCell; if (!GetVirtualMode() || m_bTitleTips) { // Let the cell know that a big fat cursor is currently hovering // over it. idCurrentCell = GetCellFromPt(point); pCell = GetCell(idCurrentCell.row, idCurrentCell.col); if (pCell) pCell->OnMouseOver(); } #ifndef GRIDCONTROL_NO_TITLETIPS // Titletips anyone? anyone? if (m_bTitleTips) { CRect TextRect, CellRect; if (pCell) { LPCTSTR szTipText = pCell->GetTipText(); if (!m_bRMouseButtonDown && szTipText && szTipText[0] && !pCell->IsEditing() && GetCellRect(idCurrentCell.row, idCurrentCell.col, &TextRect) && pCell->GetTipTextRect(&TextRect) && GetCellRect(idCurrentCell.row, idCurrentCell.col, CellRect)) { //TRACE0("Showing TitleTip\n"); m_TitleTip.Show(TextRect, pCell->GetTipText(), 0, CellRect, pCell->GetFont(), GetTitleTipTextClr(), GetTitleTipBackClr()); } } } #endif } m_LastMousePoint = point; return; } if (!IsValid(m_LeftClickDownCell)) { m_LastMousePoint = point; return; } // If the left mouse button is down, then process appropriately if (m_bLMouseButtonDown) { switch (m_MouseMode) { case MOUSE_SELECT_ALL: break; case MOUSE_SELECT_COL: case MOUSE_SELECT_ROW: case MOUSE_SELECT_CELLS: { CCellID idCurrentCell = GetCellFromPt(point); if (!IsValid(idCurrentCell)) return; if (idCurrentCell != GetFocusCell()) { OnSelecting(idCurrentCell); // EFW - BUG FIX - Keep the appropriate cell row and/or // column focused. A fix in SetFocusCell() will place // the cursor in a non-fixed cell as needed. if ((idCurrentCell.row >= m_nFixedRows && idCurrentCell.col >= m_nFixedCols) || m_MouseMode == MOUSE_SELECT_COL || m_MouseMode == MOUSE_SELECT_ROW) { SetFocusCell(idCurrentCell); } } break; } case MOUSE_SIZING_COL: { CDC* pDC = GetDC(); if (!pDC) break; CRect oldInvertedRect(m_LastMousePoint.x, rect.top, m_LastMousePoint.x + 2, rect.bottom); pDC->InvertRect(&oldInvertedRect); CRect newInvertedRect(point.x, rect.top, point.x + 2, rect.bottom); pDC->InvertRect(&newInvertedRect); ReleaseDC(pDC); } break; case MOUSE_SIZING_ROW: { CDC* pDC = GetDC(); if (!pDC) break; CRect oldInvertedRect(rect.left, m_LastMousePoint.y, rect.right, m_LastMousePoint.y + 2); pDC->InvertRect(&oldInvertedRect); CRect newInvertedRect(rect.left, point.y, rect.right, point.y + 2); pDC->InvertRect(&newInvertedRect); ReleaseDC(pDC); } break; #ifndef GRIDCONTROL_NO_DRAGDROP case MOUSE_PREPARE_EDIT: case MOUSE_PREPARE_DRAG: m_MouseMode = MOUSE_PREPARE_DRAG; OnBeginDrag(); break; #endif } } m_LastMousePoint = point; } // Returns the point inside the cell that was clicked (coords relative to cell top left) CPoint CGridCtrl::GetPointClicked(int nRow, int nCol, const CPoint& point) { CPoint PointCellOrigin; if (!GetCellOrigin(nRow, nCol, &PointCellOrigin)) return CPoint(0, 0); CPoint PointClickedCellRelative(point); PointClickedCellRelative -= PointCellOrigin; return PointClickedCellRelative; } void CGridCtrl::OnLButtonDblClk(UINT nFlags, CPoint point) { //TRACE0("CGridCtrl::OnLButtonDblClk\n"); CCellID cell = GetCellFromPt(point); if (!IsValid(cell)) { //ASSERT(FALSE); return; } #ifdef _WIN32_WCE if (MouseOverColumnResizeArea(point)) #else if (m_MouseMode == MOUSE_OVER_COL_DIVIDE) #endif { CPoint start; if (!GetCellOrigin(0, cell.col, &start)) return; if (point.x - start.x < m_nResizeCaptureRange) // Clicked right of border cell.col--; // ignore columns that are hidden and look left towards first visible column BOOL bFoundVisible = FALSE; while (cell.col >= 0) { if (GetColumnWidth(cell.col) > 0) { bFoundVisible = TRUE; break; } cell.col--; } if (!bFoundVisible) return; AutoSizeColumn(cell.col, GetAutoSizeStyle()); Invalidate(); } #ifdef _WIN32_WCE else if (MouseOverRowResizeArea(point)) #else else if (m_MouseMode == MOUSE_OVER_ROW_DIVIDE) #endif { CPoint start; if (!GetCellOrigin(0, cell.col, &start)) return; if (point.y - start.y < m_nResizeCaptureRange) // Clicked below border cell.row--; // ignore rows that are hidden and look up towards first visible row BOOL bFoundVisible = FALSE; while (cell.row >= 0) { if (GetRowHeight(cell.row) > 0) { bFoundVisible = TRUE; break; } cell.row--; } if (!bFoundVisible) return; AutoSizeRow(cell.row); Invalidate(); } else if (m_MouseMode == MOUSE_NOTHING) { CPoint pointClickedRel; pointClickedRel = GetPointClicked(cell.row, cell.col, point); CGridCellBase* pCell = NULL; if (IsValid(cell)) pCell = GetCell(cell.row, cell.col); // Clicked in the text area? Only then will cell selection work BOOL bInTextArea = FALSE; if (pCell) { CRect rectCell; if (GetCellRect(cell.row, cell.col, rectCell) && pCell->GetTextRect(rectCell)) bInTextArea = rectCell.PtInRect(point); } if (cell.row >= m_nFixedRows && IsValid(m_LeftClickDownCell) && cell.col >= m_nFixedCols && bInTextArea) { OnEditCell(cell.row, cell.col, pointClickedRel, VK_LBUTTON); } else if (m_bListMode) { if (!IsValid(cell)) return; if (cell.row >= m_nFixedRows && cell.col >= m_nFixedCols && bInTextArea) OnEditCell(cell.row, cell.col, pointClickedRel, VK_LBUTTON); } if (IsValid(cell)) { CGridCellBase* pCell = GetCell(cell.row, cell.col); if (pCell) pCell->OnDblClick(pointClickedRel); SendMessageToParent(cell.row, cell.col, NM_DBLCLK); } } CWnd::OnLButtonDblClk(nFlags, point); } void CGridCtrl::OnLButtonDown(UINT nFlags, CPoint point) { #ifdef GRIDCONTROL_USE_TITLETIPS // EFW - Bug Fix m_TitleTip.Hide(); // hide any titletips #endif // TRACE0("CGridCtrl::OnLButtonDown\n"); // CWnd::OnLButtonDown(nFlags, point); SetFocus(); m_bLMouseButtonDown = TRUE; m_LeftClickDownPoint = point; m_LeftClickDownCell = GetCellFromPt(point); if (!IsValid(m_LeftClickDownCell)) return; // If the SHIFT key is not down, then the start of the selection area should be the // cell just clicked. Otherwise, keep the previous selection-start-cell so the user // can add to their previous cell selections in an intuitive way. If no selection- // start-cell has been specified, then set it's value here and now. if ((nFlags & MK_SHIFT) != MK_SHIFT) m_SelectionStartCell = m_LeftClickDownCell; else { if (!IsValid(m_SelectionStartCell)) m_SelectionStartCell = m_idCurrentCell; } EndEditing(); // tell the cell about it CGridCellBase* pCell = GetCell(m_LeftClickDownCell.row, m_LeftClickDownCell.col); if (pCell) pCell->OnClickDown(GetPointClicked(m_LeftClickDownCell.row, m_LeftClickDownCell.col, point)); // Clicked in the text area? Only then will cell selection work BOOL bInTextArea = FALSE; if (pCell) { CRect rectCell; if (GetCellRect(m_LeftClickDownCell.row, m_LeftClickDownCell.col, rectCell) && pCell->GetTextRect(rectCell)) { bInTextArea = rectCell.PtInRect(point); } } // If the user clicks on the current cell, then prepare to edit it. // (If the user moves the mouse, then dragging occurs) if (m_LeftClickDownCell == m_idCurrentCell && !(nFlags & MK_CONTROL) && bInTextArea && IsCellEditable(m_LeftClickDownCell)) { m_MouseMode = MOUSE_PREPARE_EDIT; return; } // If the user clicks on a selected cell, then prepare to drag it. // (If the user moves the mouse, then dragging occurs) else if (IsCellSelected(m_LeftClickDownCell)) { // If control is pressed then unselect the cell or row (depending on the list mode) if (nFlags & MK_CONTROL) { SetFocusCell(m_LeftClickDownCell); if (GetListMode()) SelectRows(m_LeftClickDownCell, TRUE, FALSE); else SelectCells(m_LeftClickDownCell, TRUE, FALSE); return; } #ifndef GRIDCONTROL_NO_DRAGDROP else if (m_bAllowDragAndDrop) m_MouseMode = MOUSE_PREPARE_DRAG; #endif } else if (m_MouseMode != MOUSE_OVER_COL_DIVIDE && m_MouseMode != MOUSE_OVER_ROW_DIVIDE) { SetFocusCell(-1, -1); if (GetRowCount() > GetFixedRowCount() && GetColumnCount() > GetFixedColumnCount()) SetFocusCell(max(m_LeftClickDownCell.row, m_nFixedRows), max(m_LeftClickDownCell.col, m_nFixedCols)); } SetCapture(); if (m_MouseMode == MOUSE_NOTHING) { if (m_bAllowColumnResize && MouseOverColumnResizeArea(point)) { if (m_MouseMode != MOUSE_OVER_COL_DIVIDE) { #ifndef _WIN32_WCE_NO_CURSOR SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE)); #endif m_MouseMode = MOUSE_OVER_COL_DIVIDE; } } else if (m_bAllowRowResize && MouseOverRowResizeArea(point)) { if (m_MouseMode != MOUSE_OVER_ROW_DIVIDE) { #ifndef _WIN32_WCE_NO_CURSOR SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENS)); #endif m_MouseMode = MOUSE_OVER_ROW_DIVIDE; } } // else if (m_MouseMode != MOUSE_NOTHING) //{ // SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); // m_MouseMode = MOUSE_NOTHING; //} } if (m_MouseMode == MOUSE_OVER_COL_DIVIDE) // sizing column { m_MouseMode = MOUSE_SIZING_COL; // Kludge for if we are over the last column... if (GetColumnWidth(GetColumnCount() - 1) < m_nResizeCaptureRange) { CRect VisRect; GetVisibleNonFixedCellRange(VisRect); if (abs(point.x - VisRect.right) < m_nResizeCaptureRange) m_LeftClickDownCell.col = GetColumnCount() - 1; } CPoint start; if (!GetCellOrigin(0, m_LeftClickDownCell.col, &start)) return; if (!m_bHiddenColUnhide) { // ignore columns that are hidden and look left towards first visible column BOOL bLookForVisible = TRUE; BOOL bIsCellRightBorder = point.x - start.x >= m_nResizeCaptureRange; if (bIsCellRightBorder && m_LeftClickDownCell.col + 1 >= GetColumnCount()) { // clicked on last column's right border // if last column is visible, don't do anything if (m_LeftClickDownCell.col >= 0) bLookForVisible = FALSE; } if (bLookForVisible) { // clicked on column divider other than last right border BOOL bFoundVisible = FALSE; int iOffset = 1; if (bIsCellRightBorder) iOffset = 0; while (m_LeftClickDownCell.col - iOffset >= 0) { if (GetColumnWidth(m_LeftClickDownCell.col - iOffset) > 0) { bFoundVisible = TRUE; break; } m_LeftClickDownCell.col--; } if (!bFoundVisible) return; } } CRect rect; GetClientRect(rect); CRect invertedRect(point.x, rect.top, point.x + 2, rect.bottom); CDC* pDC = GetDC(); if (pDC) { pDC->InvertRect(&invertedRect); ReleaseDC(pDC); } // If we clicked to the right of the colimn divide, then reset the click-down cell // as the cell to the left of the column divide - UNLESS we clicked on the last column // and the last column is teensy (kludge fix) if (point.x - start.x < m_nResizeCaptureRange) { if (m_LeftClickDownCell.col < GetColumnCount() - 1 || GetColumnWidth(GetColumnCount() - 1) >= m_nResizeCaptureRange) { if (!GetCellOrigin(0, --m_LeftClickDownCell.col, &start)) return; } } // Allow a cell resize width no greater than that which can be viewed within // the grid itself int nMaxCellWidth = rect.Width() - GetFixedColumnWidth(); rect.left = start.x + 1; rect.right = rect.left + nMaxCellWidth; ClientToScreen(rect); #ifndef _WIN32_WCE_NO_CURSOR ClipCursor(rect); #endif } else if (m_MouseMode == MOUSE_OVER_ROW_DIVIDE) // sizing row { m_MouseMode = MOUSE_SIZING_ROW; // Kludge for if we are over the last column... if (GetRowHeight(GetRowCount() - 1) < m_nResizeCaptureRange) { CRect VisRect; GetVisibleNonFixedCellRange(VisRect); if (abs(point.y - VisRect.bottom) < m_nResizeCaptureRange) m_LeftClickDownCell.row = GetRowCount() - 1; } CPoint start; if (!GetCellOrigin(m_LeftClickDownCell, &start)) return; if (!m_bHiddenRowUnhide) { // ignore rows that are hidden and look up towards first visible row BOOL bLookForVisible = TRUE; BOOL bIsCellBottomBorder = point.y - start.y >= m_nResizeCaptureRange; if (bIsCellBottomBorder && m_LeftClickDownCell.row + 1 >= GetRowCount()) { // clicked on last row's bottom border // if last row is visible, don't do anything if (m_LeftClickDownCell.row >= 0) bLookForVisible = FALSE; } if (bLookForVisible) { // clicked on row divider other than last bottom border BOOL bFoundVisible = FALSE; int iOffset = 1; if (bIsCellBottomBorder) iOffset = 0; while (m_LeftClickDownCell.row - iOffset >= 0) { if (GetRowHeight(m_LeftClickDownCell.row - iOffset) > 0) { bFoundVisible = TRUE; break; } m_LeftClickDownCell.row--; } if (!bFoundVisible) return; } } CRect rect; GetClientRect(rect); CRect invertedRect(rect.left, point.y, rect.right, point.y + 2); CDC* pDC = GetDC(); if (pDC) { pDC->InvertRect(&invertedRect); ReleaseDC(pDC); } // If we clicked below the row divide, then reset the click-down cell // as the cell above the row divide - UNLESS we clicked on the last row // and the last row is teensy (kludge fix) if (point.y - start.y < m_nResizeCaptureRange) // clicked below border { if (m_LeftClickDownCell.row < GetRowCount() - 1 || GetRowHeight(GetRowCount() - 1) >= m_nResizeCaptureRange) { if (!GetCellOrigin(--m_LeftClickDownCell.row, 0, &start)) return; } } int nMaxCellHeight = rect.Height() - GetFixedRowHeight(); rect.top = start.y + 1; rect.bottom = rect.top + nMaxCellHeight; ClientToScreen(rect); #ifndef _WIN32_WCE_NO_CURSOR ClipCursor(rect); #endif } else #ifndef GRIDCONTROL_NO_DRAGDROP if (m_MouseMode != MOUSE_PREPARE_DRAG) // not sizing or editing -- selecting #endif { SendMessageToParent(m_LeftClickDownCell.row, m_LeftClickDownCell.col, GVN_SELCHANGING); // If Ctrl pressed, save the current cell selection. This will get added // to the new cell selection at the end of the cell selection process m_PrevSelectedCellMap.RemoveAll(); if (nFlags & MK_CONTROL) { for (POSITION pos = m_SelectedCellMap.GetStartPosition(); pos != NULL;) { DWORD key; CCellID cell; m_SelectedCellMap.GetNextAssoc(pos, key, (CCellID&)cell); m_PrevSelectedCellMap.SetAt(key, cell); } } if (m_LeftClickDownCell.row < GetFixedRowCount()) OnFixedRowClick(m_LeftClickDownCell); else if (m_LeftClickDownCell.col < GetFixedColumnCount()) OnFixedColumnClick(m_LeftClickDownCell); else { m_MouseMode = m_bListMode ? MOUSE_SELECT_ROW : MOUSE_SELECT_CELLS; OnSelecting(m_LeftClickDownCell); m_nTimerID = SetTimer(WM_LBUTTONDOWN, m_nTimerInterval, 0); } } m_LastMousePoint = point; } void CGridCtrl::OnLButtonUp(UINT nFlags, CPoint point) { // TRACE0("CGridCtrl::OnLButtonUp\n"); CWnd::OnLButtonUp(nFlags, point); m_bLMouseButtonDown = FALSE; #ifndef _WIN32_WCE_NO_CURSOR ClipCursor(NULL); #endif if (GetCapture()->GetSafeHwnd() == GetSafeHwnd()) { ReleaseCapture(); KillTimer(m_nTimerID); m_nTimerID = 0; } CPoint pointClickedRel; pointClickedRel = GetPointClicked(m_idCurrentCell.row, m_idCurrentCell.col, point); // m_MouseMode == MOUSE_PREPARE_EDIT only if user clicked down on current cell // and then didn't move mouse before clicking up (releasing button) if (m_MouseMode == MOUSE_PREPARE_EDIT) { OnEditCell(m_idCurrentCell.row, m_idCurrentCell.col, pointClickedRel, VK_LBUTTON); } #ifndef GRIDCONTROL_NO_DRAGDROP // m_MouseMode == MOUSE_PREPARE_DRAG only if user clicked down on a selected cell // and then didn't move mouse before clicking up (releasing button) else if (m_MouseMode == MOUSE_PREPARE_DRAG) { CGridCellBase* pCell = GetCell(m_idCurrentCell.row, m_idCurrentCell.col); if (pCell) pCell->OnClick(GetPointClicked(m_idCurrentCell.row, m_idCurrentCell.col, point)); SendMessageToParent(m_LeftClickDownCell.row, m_LeftClickDownCell.col, NM_CLICK); SendMessageToParent(m_LeftClickDownCell.row, m_LeftClickDownCell.col, GVN_SELCHANGING); ResetSelectedRange(); SendMessageToParent(m_idCurrentCell.row, m_idCurrentCell.col, GVN_SELCHANGED); } #endif else if (m_MouseMode == MOUSE_SIZING_COL) { CRect rect; GetClientRect(rect); CRect invertedRect(m_LastMousePoint.x, rect.top, m_LastMousePoint.x + 2, rect.bottom); CDC* pDC = GetDC(); if (pDC) { pDC->InvertRect(&invertedRect); ReleaseDC(pDC); } if (m_LeftClickDownPoint != point && (point.x != 0 || point.y != 0)) // 0 pt fix by email1@bierling.net { CPoint start; if (!GetCellOrigin(m_LeftClickDownCell, &start)) return; int nColumnWidth = max(point.x - start.x, m_bAllowColHide ? 0 : 1); SetColumnWidth(m_LeftClickDownCell.col, nColumnWidth); ResetScrollBars(); Invalidate(); } } else if (m_MouseMode == MOUSE_SIZING_ROW) { CRect rect; GetClientRect(rect); CRect invertedRect(rect.left, m_LastMousePoint.y, rect.right, m_LastMousePoint.y + 2); CDC* pDC = GetDC(); if (pDC) { pDC->InvertRect(&invertedRect); ReleaseDC(pDC); } if (m_LeftClickDownPoint != point && (point.x != 0 || point.y != 0)) // 0 pt fix by email1@bierling.net { CPoint start; if (!GetCellOrigin(m_LeftClickDownCell, &start)) return; int nRowHeight = max(point.y - start.y, m_bAllowRowHide ? 0 : 1); SetRowHeight(m_LeftClickDownCell.row, nRowHeight); ResetScrollBars(); Invalidate(); } } else { SendMessageToParent(m_idCurrentCell.row, m_idCurrentCell.col, GVN_SELCHANGED); CGridCellBase* pCell = GetCell(m_idCurrentCell.row, m_idCurrentCell.col); if (pCell) pCell->OnClick(GetPointClicked(m_idCurrentCell.row, m_idCurrentCell.col, point)); SendMessageToParent(m_LeftClickDownCell.row, m_LeftClickDownCell.col, NM_CLICK); } m_MouseMode = MOUSE_NOTHING; #ifndef _WIN32_WCE_NO_CURSOR SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); #endif if (!IsValid(m_LeftClickDownCell)) return; CWnd *pOwner = GetOwner(); if (pOwner && IsWindow(pOwner->m_hWnd)) pOwner->PostMessage(WM_COMMAND, MAKELONG(GetDlgCtrlID(), BN_CLICKED), (LPARAM)GetSafeHwnd()); } #ifndef _WIN32_WCE void CGridCtrl::OnRButtonDown(UINT nFlags, CPoint point) { CWnd::OnRButtonDown(nFlags, point); m_bRMouseButtonDown = TRUE; #ifdef GRIDCONTROL_USE_TITLETIPS //TRACE0("Hiding TitleTip\n"); m_TitleTip.Hide(); // hide any titletips #endif } // EFW - Added to forward right click to parent so that a context // menu can be shown without deriving a new grid class. void CGridCtrl::OnRButtonUp(UINT nFlags, CPoint point) { CWnd::OnRButtonUp(nFlags, point); m_bRMouseButtonDown = FALSE; CCellID FocusCell; FocusCell = GetCellFromPt(point); EndEditing(); // Auto-destroy any InPlaceEdit's // If not a valid cell, pass -1 for row and column if (!IsValid(FocusCell)) SendMessageToParent(-1, -1, NM_RCLICK); else { SetFocusCell(-1, -1); SetFocusCell(max(FocusCell.row, m_nFixedRows), max(FocusCell.col, m_nFixedCols)); // tell the cell about it CGridCellBase* pCell = GetCell(FocusCell.row, FocusCell.col); if (pCell) pCell->OnRClick(GetPointClicked(FocusCell.row, FocusCell.col, point)); SendMessageToParent(FocusCell.row, FocusCell.col, NM_RCLICK); } } #endif #if !defined(_WIN32_WCE_NO_PRINTING) && !defined(GRIDCONTROL_NO_PRINTING) ///////////////////////////////////////////////////////////////////////////// // CGridCtrl printing // EFW - New print margin support functions void CGridCtrl::SetPrintMarginInfo(int nHeaderHeight, int nFooterHeight, int nLeftMargin, int nRightMargin, int nTopMargin, int nBottomMargin, int nGap) { // If any parameter is -1, keep the existing setting if (nHeaderHeight > -1) m_nHeaderHeight = nHeaderHeight; if (nFooterHeight > -1) m_nFooterHeight = nFooterHeight; if (nLeftMargin > -1) m_nLeftMargin = nLeftMargin; if (nRightMargin > -1) m_nRightMargin = nRightMargin; if (nTopMargin > -1) m_nTopMargin = nTopMargin; if (nBottomMargin > -1) m_nBottomMargin = nBottomMargin; if (nGap > -1) m_nGap = nGap; } void CGridCtrl::GetPrintMarginInfo(int &nHeaderHeight, int &nFooterHeight, int &nLeftMargin, int &nRightMargin, int &nTopMargin, int &nBottomMargin, int &nGap) { nHeaderHeight = m_nHeaderHeight; nFooterHeight = m_nFooterHeight; nLeftMargin = m_nLeftMargin; nRightMargin = m_nRightMargin; nTopMargin = m_nTopMargin; nBottomMargin = m_nBottomMargin; nGap = m_nGap; } void CGridCtrl::Print(CPrintDialog* pPrntDialog /*=NULL*/) { CDC dc; if (pPrntDialog == NULL) { CPrintDialog printDlg(FALSE); if (printDlg.DoModal() != IDOK) // Get printer settings from user return; dc.Attach(printDlg.GetPrinterDC()); // attach a printer DC } else dc.Attach(pPrntDialog->GetPrinterDC()); // attach a printer DC dc.m_bPrinting = TRUE; CString strTitle; strTitle.LoadString(AFX_IDS_APP_TITLE); if (strTitle.IsEmpty()) { CWnd *pParentWnd = GetParent(); while (pParentWnd) { pParentWnd->GetWindowText(strTitle); if (strTitle.GetLength()) // can happen if it is a CView, CChildFrm has the title break; pParentWnd = pParentWnd->GetParent(); } } DOCINFO di; // Initialise print doc details memset(&di, 0, sizeof(DOCINFO)); di.cbSize = sizeof(DOCINFO); di.lpszDocName = strTitle; BOOL bPrintingOK = dc.StartDoc(&di); // Begin a new print job CPrintInfo Info; Info.m_rectDraw.SetRect(0, 0, dc.GetDeviceCaps(HORZRES), dc.GetDeviceCaps(VERTRES)); OnBeginPrinting(&dc, &Info); // Initialise printing for (UINT page = Info.GetMinPage(); page <= Info.GetMaxPage() && bPrintingOK; page++) { dc.StartPage(); // begin new page Info.m_nCurPage = page; OnPrint(&dc, &Info); // Print page bPrintingOK = (dc.EndPage() > 0); // end page } OnEndPrinting(&dc, &Info); // Clean up after printing if (bPrintingOK) dc.EndDoc(); // end a print job else dc.AbortDoc(); // abort job. dc.Detach(); // detach the printer DC } ///////////////////////////////////////////////////////////////////////////// // CGridCtrl printing overridables - for Doc/View print/print preview framework // EFW - Various changes in the next few functions to support the // new print margins and a few other adjustments. void CGridCtrl::OnBeginPrinting(CDC *pDC, CPrintInfo *pInfo) { // OnBeginPrinting() is called after the user has committed to // printing by OK'ing the Print dialog, and after the framework // has created a CDC object for the printer or the preview view. // This is the right opportunity to set up the page range. // Given the CDC object, we can determine how many rows will // fit on a page, so we can in turn determine how many printed // pages represent the entire document. ASSERT(pDC && pInfo); if (!pDC || !pInfo) return; // Get a DC for the current window (will be a screen DC for print previewing) CDC *pCurrentDC = GetDC(); // will have dimensions of the client area if (!pCurrentDC) return; CSize PaperPixelsPerInch(pDC->GetDeviceCaps(LOGPIXELSX), pDC->GetDeviceCaps(LOGPIXELSY)); CSize ScreenPixelsPerInch(pCurrentDC->GetDeviceCaps(LOGPIXELSX), pCurrentDC->GetDeviceCaps(LOGPIXELSY)); // Create the printer font int nFontSize = -10; CString strFontName = _T("Arial"); m_PrinterFont.CreateFont(nFontSize, 0, 0, 0, FW_NORMAL, 0, 0, 0, DEFAULT_CHARSET, OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE, strFontName); CFont *pOldFont = pDC->SelectObject(&m_PrinterFont); // Get the average character width (in GridCtrl units) and hence the margins m_CharSize = pDC->GetTextExtent(_T("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSATUVWXYZ"), 52); m_CharSize.cx /= 52; int nMargins = (m_nLeftMargin + m_nRightMargin)*m_CharSize.cx; // Get the page sizes (physical and logical) m_PaperSize = CSize(pDC->GetDeviceCaps(HORZRES), pDC->GetDeviceCaps(VERTRES)); if (m_bWysiwygPrinting) { m_LogicalPageSize.cx = ScreenPixelsPerInch.cx * m_PaperSize.cx / PaperPixelsPerInch.cx * 3 / 4; m_LogicalPageSize.cy = ScreenPixelsPerInch.cy * m_PaperSize.cy / PaperPixelsPerInch.cy * 3 / 4; } else { m_PaperSize = CSize(pDC->GetDeviceCaps(HORZRES), pDC->GetDeviceCaps(VERTRES)); m_LogicalPageSize.cx = GetVirtualWidth() + nMargins; #ifdef _WIN32_WCE m_LogicalPageSize.cy = (m_LogicalPageSize.cx * m_PaperSize.cy) / m_PaperSize.cx; #else m_LogicalPageSize.cy = MulDiv(m_LogicalPageSize.cx, m_PaperSize.cy, m_PaperSize.cx); #endif } m_nPageHeight = m_LogicalPageSize.cy - GetFixedRowHeight() - (m_nHeaderHeight + m_nFooterHeight + 2 * m_nGap)*m_CharSize.cy; // Get the number of pages. Assumes no row is bigger than the page size. int nTotalRowHeight = 0; m_nNumPages = 1; for (int row = GetFixedRowCount(); row < GetRowCount(); row++) { nTotalRowHeight += GetRowHeight(row); if (nTotalRowHeight > m_nPageHeight) { m_nNumPages++; nTotalRowHeight = GetRowHeight(row); } } // now, figure out how many additional pages must print out if rows ARE bigger // than page size int iColumnOffset = 0; int i1; for (i1 = 0; i1 < GetFixedColumnCount(); i1++) { iColumnOffset += GetColumnWidth(i1); } m_nPageWidth = m_LogicalPageSize.cx - iColumnOffset - nMargins; m_nPageMultiplier = 1; if (m_bWysiwygPrinting) { int iTotalRowWidth = 0; for (i1 = GetFixedColumnCount(); i1 < GetColumnCount(); i1++) { iTotalRowWidth += GetColumnWidth(i1); if (iTotalRowWidth > m_nPageWidth) { m_nPageMultiplier++; iTotalRowWidth = GetColumnWidth(i1); } } m_nNumPages *= m_nPageMultiplier; } // Set up the print info pInfo->SetMaxPage(m_nNumPages); pInfo->m_nCurPage = 1; // start printing at page# 1 ReleaseDC(pCurrentDC); pDC->SelectObject(pOldFont); } void CGridCtrl::OnPrint(CDC *pDC, CPrintInfo *pInfo) { if (!pDC || !pInfo) return; //CRect rcPage(pInfo->m_rectDraw); CFont *pOldFont = pDC->SelectObject(&m_PrinterFont); // Set the page map mode to use GridCtrl units, and setup margin pDC->SetMapMode(MM_ANISOTROPIC); pDC->SetWindowExt(m_LogicalPageSize); pDC->SetViewportExt(m_PaperSize); pDC->SetWindowOrg(-m_nLeftMargin * m_CharSize.cx, 0); // Header pInfo->m_rectDraw.top = 0; pInfo->m_rectDraw.left = 0; pInfo->m_rectDraw.right = m_LogicalPageSize.cx - (m_nLeftMargin + m_nRightMargin) * m_CharSize.cx; pInfo->m_rectDraw.bottom = m_nHeaderHeight * m_CharSize.cy; PrintHeader(pDC, pInfo); pDC->OffsetWindowOrg(0, -m_nHeaderHeight * m_CharSize.cy); // Gap between header and column headings pDC->OffsetWindowOrg(0, -m_nGap * m_CharSize.cy); pDC->OffsetWindowOrg(0, -GetFixedRowHeight()); // We need to find out which row to start printing for this page. int nTotalRowHeight = 0; UINT nNumPages = 1; m_nCurrPrintRow = GetFixedRowCount(); // Not only the row, but we need to figure out column, too // Can print 4 pages, where page 1 and 2 represent the same rows but // with different WIDE columns. // // ....... // .1 .2 . If representing page 3 --> iPageIfIgnoredWideCols = 2 // ....... iWideColPageOffset = 0 // .3 .4 . If representing page 2 --> iPageIfIgnoredWideCols = 1 // ....... iWideColPageOffset = 1 int iPageIfIgnoredWideCols = pInfo->m_nCurPage / m_nPageMultiplier; int iWideColPageOffset = pInfo->m_nCurPage - (iPageIfIgnoredWideCols * m_nPageMultiplier); if (iWideColPageOffset > 0) iPageIfIgnoredWideCols++; if (iWideColPageOffset == 0) iWideColPageOffset = m_nPageMultiplier; iWideColPageOffset--; // calculate current print row based on iPageIfIgnoredWideCols while (m_nCurrPrintRow < GetRowCount() && (int)nNumPages < iPageIfIgnoredWideCols) { nTotalRowHeight += GetRowHeight(m_nCurrPrintRow); if (nTotalRowHeight > m_nPageHeight) { nNumPages++; if ((int)nNumPages == iPageIfIgnoredWideCols) break; nTotalRowHeight = GetRowHeight(m_nCurrPrintRow); } m_nCurrPrintRow++; } m_nPrintColumn = GetFixedColumnCount(); int iTotalRowWidth = 0; int i1, i2; // now, calculate which print column to start displaying for (i1 = 0; i1 < iWideColPageOffset; i1++) { for (i2 = m_nPrintColumn; i2 < GetColumnCount(); i2++) { iTotalRowWidth += GetColumnWidth(i2); if (iTotalRowWidth > m_nPageWidth) { m_nPrintColumn = i2; iTotalRowWidth = 0; break; } } } PrintRowButtons(pDC, pInfo); // print row buttons on each page int iColumnOffset = 0; for (i1 = 0; i1 < GetFixedColumnCount(); i1++) { iColumnOffset += GetColumnWidth(i1); } // Print the column headings pInfo->m_rectDraw.bottom = GetFixedRowHeight(); if (m_nPrintColumn == GetFixedColumnCount()) { // have the column headings fcn draw the upper left fixed cells // for the very first columns, only pDC->OffsetWindowOrg(0, +GetFixedRowHeight()); m_nPageWidth += iColumnOffset; m_nPrintColumn = 0; PrintColumnHeadings(pDC, pInfo); m_nPageWidth -= iColumnOffset; m_nPrintColumn = GetFixedColumnCount(); pDC->OffsetWindowOrg(-iColumnOffset, -GetFixedRowHeight()); } else { // changed all of this here to match above almost exactly same pDC->OffsetWindowOrg(0, +GetFixedRowHeight()); m_nPageWidth += iColumnOffset; // print from column 0 ... last column that fits on the current page PrintColumnHeadings(pDC, pInfo); m_nPageWidth -= iColumnOffset; pDC->OffsetWindowOrg(-iColumnOffset, -GetFixedRowHeight()); } if (m_nCurrPrintRow >= GetRowCount()) return; // Draw as many rows as will fit on the printed page. // Clip the printed page so that there is no partially shown // row at the bottom of the page (the same row which will be fully // shown at the top of the next page). BOOL bFirstPrintedRow = TRUE; CRect rect; rect.bottom = -1; while (m_nCurrPrintRow < GetRowCount()) { rect.top = rect.bottom + 1; rect.bottom = rect.top + GetRowHeight(m_nCurrPrintRow) - 1; if (rect.bottom > m_nPageHeight) break; // Gone past end of page rect.right = -1; // modified to allow printing of wide grids on multiple pages for (int col = m_nPrintColumn; col < GetColumnCount(); col++) { rect.left = rect.right + 1; rect.right = rect.left + GetColumnWidth(col) - 1; if (rect.right > m_nPageWidth) break; CGridCellBase* pCell = GetCell(m_nCurrPrintRow, col); if (pCell) pCell->PrintCell(pDC, m_nCurrPrintRow, col, rect); if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_HORZ) { int Overlap = (col == 0) ? 0 : 1; pDC->MoveTo(rect.left - Overlap, rect.bottom); pDC->LineTo(rect.right, rect.bottom); if (m_nCurrPrintRow == 0) { pDC->MoveTo(rect.left - Overlap, rect.top); pDC->LineTo(rect.right, rect.top); } } if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_VERT) { int Overlap = (bFirstPrintedRow) ? 0 : 1; pDC->MoveTo(rect.right, rect.top - Overlap); pDC->LineTo(rect.right, rect.bottom); if (col == 0) { pDC->MoveTo(rect.left, rect.top - Overlap); pDC->LineTo(rect.left, rect.bottom); } } } m_nCurrPrintRow++; bFirstPrintedRow = FALSE; } // Footer pInfo->m_rectDraw.bottom = m_nFooterHeight * m_CharSize.cy; pDC->SetWindowOrg(-m_nLeftMargin * m_CharSize.cx, -m_LogicalPageSize.cy + m_nFooterHeight * m_CharSize.cy); PrintFooter(pDC, pInfo); // SetWindowOrg back for next page pDC->SetWindowOrg(0, 0); pDC->SelectObject(pOldFont); } // added by M.Fletcher 12/17/00 void CGridCtrl::PrintFixedRowCells(int nStartColumn, int nStopColumn, int& row, CRect& rect, CDC *pDC, BOOL& bFirst) { // print all cells from nStartColumn to nStopColumn on row for (int col = nStartColumn; col < nStopColumn; col++) { rect.left = rect.right + 1; rect.right = rect.left + GetColumnWidth(col) - 1; if (rect.right > m_nPageWidth) break; CGridCellBase* pCell = GetCell(row, col); if (pCell) pCell->PrintCell(pDC, row, col, rect); if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_HORZ) { int Overlap = (col == 0) ? 0 : 1; pDC->MoveTo(rect.left - Overlap, rect.bottom); pDC->LineTo(rect.right, rect.bottom); if (row == 0) { pDC->MoveTo(rect.left - Overlap, rect.top); pDC->LineTo(rect.right, rect.top); } } if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_VERT) { int Overlap = (row == 0) ? 0 : 1; pDC->MoveTo(rect.right, rect.top - Overlap); pDC->LineTo(rect.right, rect.bottom); if (bFirst) { pDC->MoveTo(rect.left - 1, rect.top - Overlap); pDC->LineTo(rect.left - 1, rect.bottom); bFirst = FALSE; } } } // end of column cells loop } // end of CGridCtrl::PrintFixedRowCells void CGridCtrl::PrintColumnHeadings(CDC *pDC, CPrintInfo* /*pInfo*/) { CFont *pOldFont = pDC->SelectObject(&m_PrinterFont); CRect rect; rect.bottom = -1; BOOL bFirst = TRUE; BOOL bOriginal; // modified to allow column hdr printing of multi-page wide grids for (int row = 0; row < GetFixedRowCount(); row++) { rect.top = rect.bottom + 1; rect.bottom = rect.top + GetRowHeight(row) - 1; rect.right = -1; // if printColumn > fixedcolumncount we are on page 2 or more // lets printout those fixed cell headings again the 1 or more that would be missed // added by M.Fletcher 12/17/00 if (m_nPrintColumn >= GetFixedColumnCount()) { bOriginal = bFirst; // lets print the missing fixed cells on left first out to last fixed column PrintFixedRowCells(0, GetFixedColumnCount(), row, rect, pDC, bFirst); bFirst = bOriginal; } // now back to normal business print cells in heading after all fixed columns PrintFixedRowCells(m_nPrintColumn, GetColumnCount(), row, rect, pDC, bFirst); } // end of Row Loop pDC->SelectObject(pOldFont); } // end of CGridCtrl::PrintColumnHeadings /***************************************************************************** Prints line of row buttons on each page of the printout. Assumes that the window origin is setup before calling *****************************************************************************/ void CGridCtrl::PrintRowButtons(CDC *pDC, CPrintInfo* /*pInfo*/) { CFont *pOldFont = pDC->SelectObject(&m_PrinterFont); CRect rect; rect.right = -1; BOOL bFirst = TRUE; for (int iCol = 0; iCol < GetFixedColumnCount(); iCol++) { rect.left = rect.right + 1; rect.right = rect.left + GetColumnWidth(iCol) - 1; rect.bottom = -1; for (int iRow = m_nCurrPrintRow; iRow < GetRowCount(); iRow++) { rect.top = rect.bottom + 1; rect.bottom = rect.top + GetRowHeight(iRow) - 1; if (rect.bottom > m_nPageHeight) break; CGridCellBase* pCell = GetCell(iRow, iCol); if (pCell) pCell->PrintCell(pDC, iRow, iCol, rect); if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_HORZ) { int Overlap = (iCol == 0) ? 0 : 1; pDC->MoveTo(rect.left - Overlap, rect.bottom); pDC->LineTo(rect.right, rect.bottom); if (bFirst) { pDC->MoveTo(rect.left - Overlap, rect.top - 1); pDC->LineTo(rect.right, rect.top - 1); bFirst = FALSE; } } if (m_nGridLines == GVL_BOTH || m_nGridLines == GVL_VERT) { int Overlap = (iRow == 0) ? 0 : 1; pDC->MoveTo(rect.right, rect.top - Overlap); pDC->LineTo(rect.right, rect.bottom); if (iCol == 0) { pDC->MoveTo(rect.left, rect.top - Overlap); pDC->LineTo(rect.left, rect.bottom); } } } } pDC->SelectObject(pOldFont); } void CGridCtrl::PrintHeader(CDC *pDC, CPrintInfo *pInfo) { // print App title on top right margin CString strRight; strRight.LoadString(AFX_IDS_APP_TITLE); // print parent window title in the centre (Gert Rijs) CString strCenter; CWnd *pParentWnd = GetParent(); while (pParentWnd) { pParentWnd->GetWindowText(strCenter); if (strCenter.GetLength()) // can happen if it is a CView, CChildFrm has the title break; pParentWnd = pParentWnd->GetParent(); } CFont BoldFont; LOGFONT lf; //create bold font for header and footer VERIFY(m_PrinterFont.GetLogFont(&lf)); lf.lfWeight = FW_BOLD; VERIFY(BoldFont.CreateFontIndirect(&lf)); CFont *pNormalFont = pDC->SelectObject(&BoldFont); int nPrevBkMode = pDC->SetBkMode(TRANSPARENT); CRect rc(pInfo->m_rectDraw); if (!strCenter.IsEmpty()) pDC->DrawText(strCenter, &rc, DT_CENTER | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER); if (!strRight.IsEmpty()) pDC->DrawText(strRight, &rc, DT_RIGHT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER); pDC->SetBkMode(nPrevBkMode); pDC->SelectObject(pNormalFont); BoldFont.DeleteObject(); // draw ruled-line across top pDC->SelectStockObject(BLACK_PEN); pDC->MoveTo(rc.left, rc.bottom); pDC->LineTo(rc.right, rc.bottom); } //print footer with a line and date, and page number void CGridCtrl::PrintFooter(CDC *pDC, CPrintInfo *pInfo) { // page numbering on left CString strLeft; strLeft.Format(_T("Page %d of %d"), pInfo->m_nCurPage, pInfo->GetMaxPage()); // date and time on the right CString strRight; COleDateTime t = COleDateTime::GetCurrentTime(); strRight = t.Format(_T("%c")); CRect rc(pInfo->m_rectDraw); // draw ruled line on bottom pDC->SelectStockObject(BLACK_PEN); pDC->MoveTo(rc.left, rc.top); pDC->LineTo(rc.right, rc.top); CFont BoldFont; LOGFONT lf; //create bold font for header and footer m_PrinterFont.GetLogFont(&lf); lf.lfWeight = FW_BOLD; BoldFont.CreateFontIndirect(&lf); CFont *pNormalFont = pDC->SelectObject(&BoldFont); int nPrevBkMode = pDC->SetBkMode(TRANSPARENT); // EFW - Bug fix - Force text color to black. It doesn't always // get set to a printable color when it gets here. pDC->SetTextColor(RGB(0, 0, 0)); if (!strLeft.IsEmpty()) pDC->DrawText(strLeft, &rc, DT_LEFT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER); if (!strRight.IsEmpty()) pDC->DrawText(strRight, &rc, DT_RIGHT | DT_SINGLELINE | DT_NOPREFIX | DT_VCENTER); pDC->SetBkMode(nPrevBkMode); pDC->SelectObject(pNormalFont); BoldFont.DeleteObject(); } void CGridCtrl::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/) { m_PrinterFont.DeleteObject(); } #endif // !defined(_WIN32_WCE_NO_PRINTING) && !defined(GRIDCONTROL_NO_PRINTING) #ifndef _WIN32_WCE ///////////////////////////////////////////////////////////////////////////// // CGridCtrl persistance BOOL CGridCtrl::Save(LPCTSTR filename, TCHAR chSeparator/*=_T(',')*/) { CStdioFile File; CFileException ex; CString strSeparator(chSeparator); if (!File.Open(filename, CFile::modeWrite | CFile::modeCreate | CFile::typeText, &ex)) { ex.ReportError(); return FALSE; } TRY { int nNumColumns = GetColumnCount(); for (int i = 0; i < GetRowCount(); i++) { for (int j = 0; j < nNumColumns; j++) { File.WriteString(GetItemText(i, j)); File.WriteString((j == (nNumColumns - 1)) ? _T("\n") : strSeparator); } } File.Close(); } CATCH(CFileException, e) { AfxMessageBox(_T("Unable to save grid list")); return FALSE; } END_CATCH return TRUE; } BOOL CGridCtrl::Load(LPCTSTR filename, TCHAR chSeparator/*=_T(',')*/) { if (GetVirtualMode()) return FALSE; TCHAR *token, *end; TCHAR buffer[1024]; CStdioFile File; CFileException ex; if (!File.Open(filename, CFile::modeRead | CFile::typeText)) { ex.ReportError(); return FALSE; } DeleteAllItems(); TRY { // Read Header off file File.ReadString(buffer, 1024); // Get first token for (token = buffer, end = buffer; *end && (*end != chSeparator) && (*end != _T('\n')); end++) ; if ((*end == _T('\0')) && (token == end)) token = NULL; *end = _T('\0'); while (token) { InsertColumn(token); // Get next token for (token = ++end; *end && (*end != chSeparator) && (*end != _T('\n')); end++) ; if ((*end == _T('\0')) && (token == end)) token = NULL; *end = _T('\0'); } // Read in rest of data int nItem = 0; while (File.ReadString(buffer, 1024)) { // Get first token for (token = buffer, end = buffer; *end && (*end != chSeparator) && (*end != _T('\n')); end++) ; if ((*end == _T('\0')) && (token == end)) token = NULL; *end = _T('\0'); int nSubItem = 0; while (token) { if (!nSubItem) InsertRow(token); else SetItemText(nItem, nSubItem, token); // Get next token for (token = ++end; *end && (*end != chSeparator) && (*end != _T('\n')); end++) ; if ((*end == _T('\0')) && (token == end)) token = NULL; *end = _T('\0'); nSubItem++; } nItem++; } AutoSizeColumns(GetAutoSizeStyle()); File.Close(); } CATCH(CFileException, e) { AfxMessageBox(_T("Unable to load grid data")); return FALSE; } END_CATCH return TRUE; } #endif ///////////////////////////////////////////////////////////////////////////// // CGridCtrl overrideables #ifndef GRIDCONTROL_NO_DRAGDROP // This is no longer needed since I've changed to OLE drag and drop - but it's // still cool code. :) CImageList* CGridCtrl::CreateDragImage(CPoint *pHotSpot) { CDC* pDC = GetDC(); if (!pDC) return NULL; CRect rect; CCellID cell = GetFocusCell(); if (!GetCellRect(cell.row, cell.col, rect)) return NULL; // Translate coordinate system rect.BottomRight() = CPoint(rect.Width(), rect.Height()); rect.TopLeft() = CPoint(0, 0); *pHotSpot = rect.BottomRight(); // Create a new imagelist (the caller of this function has responsibility // for deleting this list) CImageList* pList = new CImageList; if (!pList || !pList->Create(rect.Width(), rect.Height(), ILC_MASK, 1, 1)) { if (pList) delete pList; return NULL; } // Create mem DC and bitmap CDC MemDC; CBitmap bm; MemDC.CreateCompatibleDC(pDC); bm.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height()); CBitmap* pOldBitmap = MemDC.SelectObject(&bm); MemDC.SetWindowOrg(0, 0); // Draw cell onto bitmap in memDC CGridCellBase* pCell = GetCell(cell.row, cell.col); if (pCell) pCell->Draw(&MemDC, cell.row, cell.col, rect, FALSE); // Clean up MemDC.SelectObject(pOldBitmap); ReleaseDC(pDC); // Add the bitmap we just drew to the image list. pList->Add(&bm, GetDefaultCell(FALSE, FALSE)->GetBackClr()); bm.DeleteObject(); return pList; } #endif void CGridCtrl::OnFixedRowClick(CCellID& cell) { if (!IsValid(cell)) return; if (GetHeaderSort()) { //AfxMessageBox("IF"); CWaitCursor waiter; if (cell.col == GetSortColumn()) SortItems(cell.col, !GetSortAscending()); else SortItems(cell.col, TRUE); Invalidate(); } if (GetFixedRowSelection()) { if (cell.col < GetFixedColumnCount()) { m_MouseMode = MOUSE_SELECT_ALL; OnSelecting(cell); } else { m_MouseMode = MOUSE_SELECT_COL; OnSelecting(cell); } } } void CGridCtrl::OnFixedColumnClick(CCellID& cell) { if (!IsValid(cell)) return; // if (m_bListMode && (GetItemState(cell.row, m_nFixedCols) & GVNI_SELECTED)) // { // OnEditCell(cell.row, cell.col, VK_LBUTTON); // return; // } if (GetFixedColumnSelection()) { if (cell.row < GetFixedRowCount()) { m_MouseMode = MOUSE_SELECT_ALL; OnSelecting(cell); } else { m_MouseMode = MOUSE_SELECT_ROW; OnSelecting(cell); } } } // Gets the extent of the text pointed to by str (no CDC needed) // By default this uses the selected font (which is a bigger font) CSize CGridCtrl::GetTextExtent(int nRow, int nCol, LPCTSTR str) { CGridCellBase* pCell = GetCell(nRow, nCol); if (!pCell) return CSize(0, 0); else return pCell->GetTextExtent(str); } // virtual void CGridCtrl::OnEditCell(int nRow, int nCol, CPoint point, UINT nChar) { #ifndef GRIDCONTROL_NO_TITLETIPS m_TitleTip.Hide(); // hide any titletips #endif // Can we do it? CCellID cell(nRow, nCol); if (!IsValid(cell) || !IsCellEditable(nRow, nCol)) return; // Can we see what we are doing? EnsureVisible(nRow, nCol); if (!IsCellVisible(nRow, nCol)) return; // Where, exactly, are we gonna do it?? CRect rect; if (!GetCellRect(cell, rect)) return; // Check we can edit... if (SendMessageToParent(nRow, nCol, GVN_BEGINLABELEDIT) >= 0) { // Let's do it... CGridCellBase* pCell = GetCell(nRow, nCol); if (pCell) pCell->Edit(nRow, nCol, rect, point, IDC_INPLACE_CONTROL, nChar); } } // virtual void CGridCtrl::EndEditing() { CCellID cell = GetFocusCell(); if (!IsValid(cell)) return; CGridCellBase *pCell = GetCell(cell.row, cell.col); if (pCell) pCell->EndEdit(); } // virtual void CGridCtrl::OnEndEditCell(int nRow, int nCol, CString str) { CString strCurrentText = GetItemText(nRow, nCol); if (strCurrentText != str) { SetItemText(nRow, nCol, str); if (ValidateEdit(nRow, nCol, str) && SendMessageToParent(nRow, nCol, GVN_ENDLABELEDIT) >= 0) { SetModified(TRUE, nRow, nCol); RedrawCell(nRow, nCol); } else { SetItemText(nRow, nCol, strCurrentText); } } CGridCellBase* pCell = GetCell(nRow, nCol); if (pCell) pCell->OnEndEdit(); } // If this returns FALSE then the editing isn't allowed // virtual BOOL CGridCtrl::ValidateEdit(int nRow, int nCol, LPCTSTR str) { CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return TRUE; return pCell->ValidateEdit(str); } // virtual CString CGridCtrl::GetItemText(int nRow, int nCol) const { if (nRow < 0 || nRow >= m_nRows || nCol < 0 || nCol >= m_nCols) return _T(""); CGridCellBase* pCell = GetCell(nRow, nCol); ASSERT(pCell); if (!pCell) return _T(""); return pCell->GetText(); } int CGridCtrl::GetSelCount() { return m_nArrayListChoose.GetCount(); } int CGridCtrl::GetSelData(int iIndex) { return m_nArrayListChoose[iIndex]; }