Click here to Skip to main content
15,884,176 members
Articles / Desktop Programming / WTL

A fast and lightweight cell control

Rate me:
Please Sign up or sign in to vote.
4.42/5 (31 votes)
11 Mar 2008CPOL1 min read 90.9K   4.5K   81  
A fast and lightweight cell control for displaying tabular data. The cell is a custom control derived from ATL::CWindow.
// MyCell - version 1.0
// Written by Yanxueming <yanxm2003@hotmail.com>
// Copyright (C) 2006-2006
// All rights reserved.
//
// The code and information is provided "as-is" without
// warranty of any kind, either expressed or implied.
#include "stdafx.h"
#include <atltypes.h>
#include <atlgdi.h>
#include "../include/GridBase.h"
#include "../include/RgnLight.h"
#include "../include/msg.h"
namespace mycell{
	void GetWndUpdateRgn(HWND hWnd, CRgnLight& rgnValue)
	{
		HRGN hRgn = CreateRectRgn(0, 0, 0, 0);
		if (hRgn)
		{
			switch (GetUpdateRgn(hWnd, hRgn, FALSE))
			{
			case SIMPLEREGION:
			case COMPLEXREGION:
				rgnValue.FromGdi(hRgn);
			}
			DeleteObject(hRgn);
		}
	}
	BOOL GridBase::IsRowFullVisible(int row)
	{
		RowHeader& rh=get_RowHeader();
		int const topRow=rh.get_TopRow();
		int const bottomRow=rh.get_BottomRow();
		if(row<topRow || row>bottomRow)
			return FALSE;
		if(row==bottomRow){
			RECT const rc=GetCellRect(row,HEADER_COL);
			RECT rCli;
			GetClientRect(&rCli);
			return rc.bottom<=rCli.bottom;
		}else
			return TRUE;
	}
	BOOL GridBase::IsColFullVisible(int col)
	{
		ColHeader& ch=get_ColHeader();
		int const leftCol=ch.get_LeftCol();
		int const rightCol=ch.get_RightCol();
		if(col<leftCol || col>rightCol)
			return FALSE;
		if(col==rightCol){
			RECT const rc=GetCellRect(HEADER_COL,col);
			RECT rCli;
			GetClientRect(&rCli);
			return rc.right<=rCli.right;
		}else
			return TRUE;
	}
	LRESULT GridBase::OnPaint(UINT,WPARAM,LPARAM,BOOL&)
	{
		//GetUpdateRect(&rcClip_);
		AtlTrace(_T("\nGridBase::OnPaint BeginPaint"));
		RecalcVisibleMergeCells();
		CRgnLight rgnClip;
		GetWndUpdateRgn(m_hWnd,rgnClip);
		rgnClip.Optimize();

		CPaintDC dc(m_hWnd);
		RECT rcli;
		GetClientRect(&rcli);
		DrawEnvironment de;
		render_.draw(dc,&rcli,&rgnClip,de);
		/*
#ifdef _DEBUG
		int i=0;
#endif
		for (LPCRECT pRect = rgnClip.GetFirst();pRect; pRect = CRgnLight::GetNext(pRect))
		{
			// paint the rectangle piece of your shape
#ifdef _DEBUG
			AtlTrace(_T("\nGridBase::OnPaint UpdateRect[%d](%d,%d,%d,%d)"),++i,pRect->left,pRect->top,pRect->right,pRect->bottom);
#endif
			//de.lprcClip=pRect;
			render_.draw(dc,&rcli,pRect,de);
		}
		AtlTrace(_T("\nGridBase::OnPaint EndPaint tickcount=%d"),GetTickCount());
		*/
		return 0;
	}

	int GridBase::GetCellText(int row,int col,LPTSTR buf)
	{
		buf[0]=0;
		SendMessageToListener(row,col,OCN_GETCELLTEXT,(LPARAM)buf);
		/*
		RowHeader& rh=get_RowHeader();
		ColHeader& ch=get_ColHeader();
		TCHAR sz[100],sz1[100];
		rh.get_text(row,sz);
		ch.get_text(col,sz1);
		wsprintf(buf,_T("%s%s"),sz1,sz);
		*/
		for(int i=0;i!=MAX_CELLTEXT && buf[i];++i)
			;
		return i;
	}
	// Sends a message to the listener in the form of a WM_NOTIFY message with
	// a NM_GRIDVIEW structure attached
	LRESULT GridBase::SendMessageToListener(int nRow, int nCol, int nNotifyCode,LPARAM lParam) const
	{
		HWND const hWndListener=get_listener();
		if (!(hWndListener && ::IsWindow(hWndListener) && IsWindow() ))
			return 0;
		//_ASSERT(hWndListener!=m_hWnd);
		//if(hWndListener==m_hWnd) return 1;
		NM_GRIDVIEW nmgv;
		nmgv.row         = nRow;
		nmgv.col	      = nCol;
		nmgv.lParam		  = lParam;	
		nmgv.hdr.hwndFrom = m_hWnd;
		nmgv.hdr.idFrom   = GetDlgCtrlID();
		nmgv.hdr.code     = nNotifyCode;
		return ::SendMessage(hWndListener,WM_NOTIFY,nmgv.hdr.idFrom, (LPARAM)&nmgv);
	}
	RECT GridBase::get_ScrollRect()
	{
		RECT rc;
		GetClientRect(&rc);
		if(get_ShowRowHeader()){
			RowHeader& rh=get_RowHeader();
			rc.left=rh.get_width();
		}
		if(get_ShowColHeader()){
			ColHeader& ch=get_ColHeader();
			rc.top=ch.get_height();
		}
		return rc;
	}
	int GridBase::GetScrollPos32(int nBar, BOOL bGetTrackPos)
	{
		SCROLLINFO si;
		si.cbSize = sizeof(SCROLLINFO);
		if (bGetTrackPos){
			si.fMask = SIF_TRACKPOS;
			if (GetScrollInfo(nBar, &si))
				return si.nTrackPos;
		}else {
			si.fMask = SIF_POS;
			if (GetScrollInfo(nBar, &si))
				return si.nPos;
		}
		return 0;
	}

	void GridBase::ResetScrollBars(BOOL bResetHCrollBar,BOOL bResetVScrollBar)
	{
		HWND const hWnd=m_hWnd;
		CRect rect;	
		::GetClientRect(hWnd,rect);
		if(get_ShowRowHeader())
			rect.left=get_RowHeader().get_width();
		if(get_ShowColHeader())
			rect.top=get_ColHeader().get_height();
		if (rect.left >= rect.right || rect.top >= rect.bottom)
			return;

		SCROLLINFO si;
		si.cbSize = sizeof(SCROLLINFO);
		si.fMask = SIF_PAGE | SIF_RANGE | SIF_POS;

		if(bResetVScrollBar)
		{
			si.nMin		= 0;
			int const heights=get_RowHeader().get_heights();
			if(heights>rect.Height()){
				si.nPage	= rect.Height();
				si.nMax		= heights;
				int const oldTopRow=get_RowHeader().get_TopRow();
				si.nPos		= get_RowHeader().get_DiffHeights(0,get_RowHeader().get_TopRow());
			}else{
				si.nPage	= 0;
				si.nPos		= 0;
				si.nMax		= 0;
			}
			::SetScrollInfo(hWnd,SB_VERT, &si, TRUE);
		}
		if(bResetHCrollBar)
		{
			si.nMin = 0;
			int const widths=get_ColHeader().get_widths();
			if(widths>rect.Width()){
				si.nPage	= rect.Width();
				si.nMax		= widths;
				si.nPos		= get_ColHeader().get_DiffWidths(0,get_ColHeader().get_LeftCol());
			}else{
				si.nPage	= 0;
				si.nPos		= 0;
				si.nMax		= 0;
			}
			::SetScrollInfo(hWnd,SB_HORZ, &si, TRUE);
		}
	}
	void GridBase::EnsureVisible(int row,int col,BOOL bPartial)
	{
		RowHeader& rh=rowHeader_;
		ColHeader& ch=colHeader_;
		int const topRow=rh.get_TopRow();
		int const bottomRow=rh.get_BottomRow();
		int const leftCol=ch.get_LeftCol();
		int const rightCol=ch.get_RightCol();
		if(row<topRow){
			int const diff=rh.get_DiffHeights(row,topRow);
			RECT rc;
			GetClientRect(&rc);
			get_ContentRect(&rc);
			rc.left=0;
			ScrollWindow(0,diff,&rc,&rc);
			int nScrollPos=GetScrollPos(SB_VERT);
			SetScrollPos(SB_VERT,nScrollPos-diff);
			rh.put_TopRow(row,&rc);
		}else if(row>bottomRow){
			RECT rc;
			GetClientRect(&rc);
			get_ContentRect(&rc);
			rc.left=0;
			int const height=rc.bottom-rc.top;
			int newTopRow=row,hi=0;
			for(;newTopRow!=topRow;--newTopRow){
				hi+=rh.get_RowHeight(newTopRow);
				if(hi>height){
					break;
				}
			}
			++newTopRow;
			int const diff=rh.get_DiffHeights(topRow,newTopRow);
			ScrollWindow(0,-diff,&rc,&rc);
			int nScrollPos=GetScrollPos(SB_VERT);
			SetScrollPos(SB_VERT,nScrollPos+diff);
			rh.put_TopRow(newTopRow,&rc);
		}else if(row==bottomRow && !IsRowFullVisible(row)){
			SendMessage(WM_VSCROLL,SB_LINEDOWN,TRUE);
		}
		if(col<leftCol){
			int const diff=ch.get_DiffWidths(col,leftCol);
			RECT rc;
			GetClientRect(&rc);
			get_ContentRect(&rc);
			rc.top=0;
			ScrollWindow(diff,0,&rc,&rc);
			int nScrollPos=GetScrollPos(SB_HORZ);
			SetScrollPos(SB_HORZ,nScrollPos-diff);
			ch.put_LeftCol(col,&rc);
		}else if(col>rightCol){
			RECT rc;
			GetClientRect(&rc);
			get_ContentRect(&rc);
			rc.top=0;
			int const width=rc.right-rc.left;
			int newLeftCol=col,wi=0;
			for(;newLeftCol!=leftCol;--newLeftCol){
				wi+=ch.get_ColWidth(newLeftCol);
				if(wi>width){
					break;
				}
			}
			++newLeftCol;
			int const diff=ch.get_DiffWidths(leftCol,newLeftCol);
			ScrollWindow(-diff,0,&rc,&rc);
			int nScrollPos=GetScrollPos(SB_HORZ);
			SetScrollPos(SB_HORZ,nScrollPos+diff);
			ch.put_LeftCol(newLeftCol,&rc);
		}else if(rightCol==col){
			if(!IsColFullVisible(col))
				SendMessage(WM_HSCROLL,SB_LINERIGHT,TRUE);
		}
		if(topRow!=rh.get_TopRow() || leftCol!=ch.get_LeftCol()){
			RecalcVisibleMergeCells();
			UpdateWindow();
		}
	}
	void GridBase::put_RowHeight(int row,int newHeight)
	{
		RowHeader& rh=get_RowHeader();
		int const oldTopRow=rh.get_TopRow();
		int const oldBottomRow=rh.get_BottomRow();
		if(row>=oldTopRow && row<=oldBottomRow){
			int const oldHeight=rh.get_RowHeight(row);
			int const delta=newHeight-oldHeight;
			RECT rCli;GetClientRect(&rCli);
			CRect const rcCell=GetCellRect(row,IGNOR_COL);
			rh.put_RowHeight(row,newHeight);
			RECT const rScroll=get_ScrollRect();
			AtlTrace(_T("\nGrid::put_RowHeight cause bottomRow from %d"),rh.get_BottomRow());
			rh.validate_BottomRow(&rScroll);
			AtlTrace(_T(" to %d"),rh.get_BottomRow());
			rCli.top=min(rcCell.bottom,rcCell.top+newHeight);
			ScrollWindow(0,delta,&rCli,&rCli);
			//UpdateWindow();

			rCli.top=rcCell.top;
			rCli.bottom=rcCell.top+newHeight;
			InvalidateRect(&rCli,FALSE);
			VisibleMergeCellMgr& vmc=GetVisibleMergeCellMgr();
			vmc.InvalidRect(&rCli);
			UpdateWindow();
		}else
			rh.put_RowHeight(row,newHeight);
		ResetScrollBars(FALSE,TRUE);
	}

	//void Grid::ColumnAutoFit(int col)
	//{
	//	CClientDC dc(m_hWnd);
	//	ColumnAutoFitPolicy afp(this,col);
	//	int const nWidth=GetAutoFit<ColumnAutoFitPolicy>(&afp,dc);
	//	if(nWidth>0){
	//		put_ColWidth(col,nWidth);
	//	}
	//}
	void GridBase::put_ColWidth(int col,int newWidth)
	{
		ColHeader& ch=get_ColHeader();
		int const oldLeftCol=ch.get_LeftCol();
		int const oldRightCol=ch.get_RightCol();
		if(col>=oldLeftCol && col<=oldRightCol){
			int const oldWidth=ch.get_ColWidth(col);
			int const delta=newWidth-oldWidth;
			RECT rCli;GetClientRect(&rCli);
			CRect const rcCell=GetCellRect(IGNOR_ROW,col);
			ch.put_ColWidth(col,newWidth);
			RECT const rScroll=get_ScrollRect();
			AtlTrace(_T("\nGrid::put_ColWidth cause rightCol from %d"),ch.get_RightCol());
			ch.validate_RightCol(&rScroll);
			AtlTrace(_T(" to %d"),ch.get_RightCol());
			rCli.left=min(rcCell.right,rcCell.left+newWidth);
			ScrollWindow(delta,0,&rCli,&rCli);
			//UpdateWindow();

			rCli.left=rcCell.left;
			rCli.right=rcCell.left+newWidth;
			InvalidateRect(&rCli,FALSE);

			VisibleMergeCellMgr& vmc=GetVisibleMergeCellMgr();
			vmc.InvalidRect(&rCli);

			UpdateWindow();
		}else
			ch.put_ColWidth(col,newWidth);
		ResetScrollBars(TRUE,FALSE);
	}
	void GridBase::CopyToClipboard(int y1,int x1,int y2,int x2)
	{
		CWaitCursor wc;
//#ifdef UNICODE
//		std::wstring vecstr;
//#else
//		std::string vecstr;
//#endif
		//vector<wchar_t> vecstr;
		//vecstr.resize((y2-y1+1)*(x2-x1+1));
		std::wstring vecstr;
#ifndef UNICODE
		WCHAR wtext[MAX_CELLTEXT];
#endif
		TCHAR text[MAX_CELLTEXT];
		_ASSERT(y1<=y2);
		_ASSERT(x1<=x2);
		if(y1<0)
			y1=0;
		if(x1<0)
			x1=0;
		for(;y1<=y2;++y1)
		{
			for(int x=x1;x<=x2;++x)
			{
				int const nCount=GetCellText(y1,x,text);
				if(nCount>0){
#ifdef UNICODE
					vecstr+=text;
#else
					wtext[0]='\0';
					int lRet=MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,text,nCount,wtext,MAX_CELLTEXT);
					if(lRet>0){
						if(lRet>=MAX_CELLTEXT)
							lRet=MAX_CELLTEXT-1;
						wtext[lRet]='\0';
						for(int i=0;i!=lRet;++i)
						{
							if(wtext[i]=='\t')
								wtext[i]=' ';
						}
						vecstr+=wtext;
					}
#endif
				}
				vecstr+=0x09;
			}
			vecstr[vecstr.size()-1]=0x0D;
			vecstr+=0x0A;
		}
		if(!vecstr.empty()){
			if(::OpenClipboard(0)){
				EmptyClipboard();
//#ifdef UNICODE
				HGLOBAL hglb=GlobalAlloc(GMEM_MOVEABLE,(vecstr.size()+1)*sizeof(wchar_t));
				wchar_t* lpstr=(wchar_t*)GlobalLock(hglb);
				wcscpy(lpstr,vecstr.c_str());
				GlobalUnlock(hglb);
				SetClipboardData(CF_UNICODETEXT,hglb);
//#else
//				HGLOBAL hglb=GlobalAlloc(GMEM_MOVEABLE,(vecstr.size()+1)*sizeof(char));
//				char* lpstr=(char*)GlobalLock(hglb);
//				strcpy(lpstr,vecstr.c_str());
//				GlobalUnlock(hglb);
//				SetClipboardData(CF_TEXT,hglb);
//#endif
				CloseClipboard();
				GlobalFree(hglb);
				CloseClipboard();
			}else{
				FormatMessage(m_hWnd);
			}
		}
	}
	void GridBase::RowHeaderAutoFit()
	{
		CClientDC dc(m_hWnd);
		CRect const rcContent=get_ContentRect();
		RowHeaderAutoFitPolicy afp(this);
		int nWidth=GetAutoFit<RowHeaderAutoFitPolicy>(&afp,dc,rcContent.Width()-1);
		if(nWidth>0){
			RowHeader& rh=get_RowHeader();
			int const oldWidth=rh.get_width();
			nWidth=max(nWidth,rh.get_MinWidth());
			int const xDelta=nWidth-oldWidth;
			if(xDelta){
				rh.put_width(nWidth);
				if(IsWindow()){
					RECT rcContent=get_ContentRect();
					rcContent.top=0;
					ScrollWindow(xDelta,0,&rcContent,&rcContent);
					rcContent.right=rcContent.left;
					rcContent.left=0;
					InvalidateRect(&rcContent,FALSE);
					UpdateWindow();
				}
			}
		}
	}
	BOOL GridBase::delete_row(int row)
	{
		BOOL bAllowDelete=TRUE;
		SendMessageToListener(row,-1,OCN_DELETEROW,(LPARAM)&bAllowDelete);
		if(!bAllowDelete)
			return FALSE;
		RECT const rcContent=get_ContentRect();
		RowHeader& rh=get_RowHeader();
		int const topRow=rh.get_TopRow();
		int const bottomRow=rh.get_BottomRow();
		int const rowHeight=rh.get_RowHeight(row);
		rh.delete_row(row,&rcContent);
		if(row>=topRow && row<=bottomRow){
			int const newTopRow=rh.get_TopRow();
			RECT rcScroll={0,rcContent.top+rh.get_DiffHeights(topRow,row),rcContent.right,rcContent.bottom};
			//if(row!=bottomRow){
				ScrollWindow(0,-rowHeight,&rcScroll,&rcScroll);
			//}
			if(topRow!=newTopRow){
				rcScroll.top=rcContent.top;
				int const yDelta=rh.get_DiffHeights(newTopRow,topRow);
				ScrollWindow(0,yDelta,&rcScroll,&rcScroll);
				ResetScrollBars(FALSE,TRUE);
			}
			UpdateWindow();
		}else{
			SCROLLINFO si;
			si.cbSize = sizeof(SCROLLINFO);
			si.fMask  = SIF_RANGE;
			GetScrollInfo(SB_VERT,&si);
			si.nMax  -= rowHeight;
			SetScrollInfo(SB_VERT, &si);
		}
		return TRUE;
	}
	void GridBase::put_rows(int rows)
	{
		_ASSERT(rows>=0);
		RowHeader& rh=get_RowHeader();
		int const topRow=rh.get_TopRow();
		int const bottomRow=rh.get_BottomRow();
		rh.put_rows(rows);
		if(IsWindow()){
			RECT rcContent=get_ContentRect();
			if(bottomRow>=rows)
				rh.put_BottomRow(rows>0?rows-1:0,&rcContent,TRUE);
			rh.validate_BottomRow(&rcContent);

			rcContent.left=0;
			int const newTopRow=rh.get_TopRow();
			if(newTopRow==topRow){
				int const newBottomRow=rh.get_BottomRow();
				int const minBottomRow=min(bottomRow,newBottomRow);
				rcContent.top=rcContent.top+rh.get_DiffHeights(rh.get_TopRow(),minBottomRow+1);
				InvalidateRect(&rcContent,FALSE);
			}else{
				RECT rcScroll={0,rcContent.top,rcContent.right,rcContent.bottom};
				int diff=rh.get_DiffHeights(newTopRow,topRow);
				ScrollWindow(0,diff,&rcScroll,&rcScroll);
				int const minBottomRow=min(bottomRow,rh.get_BottomRow());
				rcContent.top=rcContent.top+rh.get_DiffHeights(rh.get_TopRow(),minBottomRow+1);
				InvalidateRect(&rcContent,FALSE);
			}

			//Selection& s=get_Selection();
			CellRange const cr=get_Selection();
			//s.get_Range(cr.first.col,cr.first.row,cr.second.col,cr.second.row);
			CellRange const oldSeletion=cr;
			//cr.Normalize();

			if(cr.first.row>rows-1){
				put_Selection(cr.first.col,rows-1,cr.second.col,rows-1);
			}else if(cr.second.col>rows-1)
				selection_.put_Range(cr.first.col,cr.first.row,cr.second.col,rows-1);
			CellID activeCell;
			selection_.GetActiveCell(activeCell.row,activeCell.col);
			if(activeCell.row>rows-1){
				selection_.SetActiveCell(rows-1,activeCell.col);
			}
			selection_.InvalidateWindow(activeCell,oldSeletion);
			UpdateWindow();
			ResetScrollBars(FALSE,TRUE);
			_ASSERT(newTopRow<rows);
		}
	}
	void GridBase::put_cols(int cols)
	{
		_ASSERT(cols>=0);
		ColHeader& ch=get_ColHeader();
		int const leftCol=ch.get_LeftCol();
		int const rightCol=ch.get_RightCol();
		ch.put_cols(cols);
		if(IsWindow()){
			RECT rcContent=get_ContentRect();
			if(rightCol>=cols)
				ch.put_RightCol(cols-1,&rcContent,TRUE);
			if(cols>0){
				ch.validate_RightCol(&rcContent);
				rcContent.top=0;
				int const newLeftCol=ch.get_LeftCol();
				if(newLeftCol==leftCol){
					int const minRightCol=min(rightCol,ch.get_RightCol());
					rcContent.left=rcContent.left+ch.get_DiffWidths(ch.get_LeftCol(),minRightCol+1);
					InvalidateRect(&rcContent,FALSE);
				}else{
					RECT rcScroll={rcContent.left,0,rcContent.right,rcContent.bottom};
					int diff=ch.get_DiffWidths(newLeftCol,leftCol);
					ScrollWindow(diff,0,&rcScroll,&rcScroll);
					int const minRightCol=min(rightCol,ch.get_RightCol());
					rcContent.left=rcContent.left+ch.get_DiffWidths(ch.get_LeftCol(),minRightCol+1);
					InvalidateRect(&rcContent,FALSE);
				}

				Selection& s=selection_;//get_Selection();
				CellRange cr;
				s.get_Range(cr.first.col,cr.first.row,cr.second.col,cr.second.row);
				CellRange const oldSeletion=cr;
				cr.Normalize();

				if(cr.first.col>cols-1){
					s.put_Range(cols-1,cr.first.row,cols-1,cr.second.row);
				}else if(cr.second.col>cols-1)
					s.put_Range(cr.first.col,cr.first.row,cols-1,cr.second.row);
				CellID activeCell;
				s.GetActiveCell(activeCell.row,activeCell.col);
				if(activeCell.col>cols-1){
					s.SetActiveCell(activeCell.row,cols-1);
				}
				s.InvalidateWindow(activeCell,oldSeletion);
				_ASSERT(newLeftCol<cols);
			}else{
				rcContent.top=0;
				InvalidateRect(&rcContent,FALSE);
			}
			UpdateWindow();
			ResetScrollBars(TRUE,FALSE);
		}
	}
}//namespace mycell

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
China China
My name is Yanxueming,i live in Chengdu China.Graduated from UESTC in 1999.

Comments and Discussions