二、 可编辑表格的初步实现
1、 创建新类CCtrlEditGrid
首先创建一个单文档工程EditGrid。
接着在工程中加入MSFlexGrid控件。这是个ActiveX控件,选择AddToProject的Components and Controls Gallery选项可加入该控件。
然后以MSFlexGrid为基类创建新类CCtrlEditGrid,并添加成员函数void InitGrid()(该函数目前只是空的)和成员变量 CEdit* m_pEdit;
CSpinButtonCtrl* m_pSpinButtonCtrl;以后表格的实体类就是该类。
2、 在工程文件的视图类中显示表格
首先在视图类CEditGridView中添加成员变量CCtrlEditGrid* m_pCtrlEditGrid。
接着添加CEditGridView的消息相应函数OnCreate,在其中创建表格
m_pCtrlEditGrid = newCCtrlEditGrid;
m_pCtrlEditGrid->Create(NULL,WS_CHILD|WS_VISIBLE,CRect(0,0,0,0),this,ID_EDITGRID);
m_pCtrlEditGrid->InitGrid ( );
然后为了和视图的大小保持一致在CEditGridView的消息相应函数OnSize中添加代码
if ( m_pCtrlEditGrid != NULL )
m_pCtrlEditGrid->MoveWindow(0,0,cx,cy);
3、 实现CCtrlEditGrid的InitGrid的函数
InitGrid完成表格的属性设置,表格初始内容的填写,可编辑控件的创建。这里的可编辑控件如CEdit,CComboBox,CSpinButtonCtrl,CDateTimeCtrl……。在本例中只使用CEdit和CSinButtonCtrl的结合这一种。如果表格中不同列之间的编辑控件不同,在程序中可以通过检测列号,来决定使用什么控件,事实上在笔者的项目中不同列之间也是使用不同编辑控件的。在此用一种控件来说明表格编辑的实现方法,读者想换其他的控件也很容易了。
以上三点只是准备阶段,要想使表格编辑,我们还要响应用户的点击单元格事件和离开单元格事件,以使得当用户点击某一单元格时当前单元格处于编辑状态而离开时又处于非编辑状态。MSFLEXGRID控件提供的OnClick和OnLeaveCell事件正好是我们所需要的。由于CCtrlEditGrid不是MFC类,所以不能用类向导来添加事件。只好用手工添加了。
首先在头文件中添加afx_msg void OnLeaveCell();afx_msg void OnClick();接着在CPP文件中添加事件映射表
BEGIN_EVENTSINK_MAP(CCtrlEditGrid, CMSFlexGrid)
//{{AFX_EVENTSINK_MAP(CEditGrid)
ON_EVENT_REFLECT(CCtrlEditGrid, 72 /* LeaveCell */, OnLeaveCell, VTS_NONE)
ON_EVENT_REFLECT(CCtrlEditGrid, -600 /* Click */, OnClick, VTS_NONE)
//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()
(如果用户觉得手工添加时间映射表有困难,可以先在应用程序中添加一个虚设对话框。接着在对话框中插入MSFlexGrid控件。然后使用ClassWizard将事件处理程序写入对话框,接下来就可以参照着对话框编写事件映射表了。记得最后要删除虚设对话框。)
接着添加OnLeaveCell和OnClick的函数体。
OnLeaveCell函数:如果现在m_pEdit是显示的,则说明单元格是在编辑状态,所以要将数据从m_pEdit框读到表格中,然后将m_pEdit和m_pSpinButtonCtrl隐藏。
void CCtrlEditGrid::OnLeaveCell()
{
if ( m_pEdit->IsWindowVisible() )
{
int nCol;
int nRow;
CString strContent;
nCol = GetCol();
nRow = GetRow();
m_pEdit->GetWindowText(strContent);
SetTextMatrix(nRow, nCol, strContent);
m_pEdit->ShowWindow(SW_HIDE);
m_pSpinButtonCtrl->ShowWindow(SW_HIDE);
}
}
OnClick函数:要在点击的单元格中显示m_pEdit和m_pSpinButtonCtrl,,并使输入焦点在m_pEdit中,这里要说明的一点是在计算编辑控件要显示的位置时,如果FlexGrid控件有边框,就应该考虑边框宽度对位置的影响,在本例中我们在InitGrid中设置为无边框,故不用考虑。
void CCtrlEditGrid::OnClick()
{
CDC* pDC = GetDC();
long x = ( GetCellLeft() * pDC -> GetDeviceCaps ( LOGPIXELSX ) ) / 1440;
long y = ( GetCellTop() * pDC -> GetDeviceCaps ( LOGPIXELSY ) ) / 1440;
long cx = ( GetCellWidth() * pDC -> GetDeviceCaps ( LOGPIXELSX ) ) / 1440;
long cy = ( GetCellHeight() * pDC -> GetDeviceCaps ( LOGPIXELSY ) )/ 1440;
ReleaseDC ( pDC );
CString strContent;
strContent = GetText();
m_pEdit->SetWindowText(strContent);
m_pEdit->MoveWindow(x,y,cx,cy,FALSE);
m_pEdit->ShowWindow(SW_SHOW);
m_pEdit->SetFocus ( );
m_pSpinButtonCtrl->SetBuddy (m_pEdit);
m_pSpinButtonCtrl->SetRange32( 0, 100);
m_pSpinButtonCtrl->MoveWindow ( x + cx – 16, y, 16,cy,FALSE );
m_pSpinButtonCtrl->ShowWindow(SW_SHOW);
}
也许OnEnterCell事件可以替代OnClick,但笔者发现用OnEnterCell实现起来会有一个问题:必须快速的点击,否则编辑框出现之后马上消失。所以笔者使用OnClick事件,该事件是在鼠标Up的时候才响应的。
三、 若干问题的出现及解决方案
通过以上的操作,我们可以在表格中点击某一个单元格进行编辑了,似乎我们已经实现了一个可编辑表格的制作。但在随后的测试过程中发现了如下讨厌的问题:
a在当前某个单元格处于可编辑状态而我们试图改变列宽时,发现在单元格上的编辑控件的大小并没有改变。如下
b在当前某个单元格处于可编辑状态而我们试图移动滚动条时,发现在单元格上的编辑控件的光标随之移动到了别的单元格。而且更加严重的是若点击前单元格部分显示,点击后再移动滚动条发现不只是光标移动还有控件本身也移动到了别的单元格中。如下
c在点击上下控件时会触发垂直滚动条的移动,还间接导致b问题的发生。
1、 a问题的解决
首先想到的是在MSDN中寻找MSFLEXGRID的列宽改变的响应事件,很失望没找到。但发现可以采取以下措施:在CEditGridView中的PreTranslateMessage消息响应函数中捕捉鼠标左键是按下状态并且鼠标移动的消息,在这种状态下若发现编辑控件是显示的,就调用CCtrlEditGrid的Onleave函数(开放为PUBLIC)。这样虽然在改变列宽时原来的编辑状态变为了非编辑状态,但避免了显示上不同步改变大小的问题。
BOOLCEditGridView::PreTranslateMessage(MSG* pMsg)
{
if ( ( pMsg->message == WM_MOUSEMOVE ) && ( pMsg->wParam & MK_LBUTTON ) )
{
CWnd* pWnd = FromHandle ( pMsg->hwnd );
if ( pWnd->GetRuntimeClass ( )->IsDerivedFrom ( RUNTIME_CLASS ( CMSFlexGrid ) ) )
{
if ( m_pCtrlEditGrid->m_pEdit->IsWindowVisible() )
m_pCtrlEditGrid->OnLeaveCell();
}
}
returnCView::PreTranslateMessage(pMsg);
}
2、 b问题的解决
首先想到的依然是在MSDN中寻找MSFLEXGRID的列宽改变的响应事件,很幸运找到了。
在CCtrlEditGrid的头文件中添加 afx_msgvoidOnScroll();
在CCtrlEditGrid的CPP文件的事件映射表中添加ON_EVENT_REFLECT(CTaskEditGrid, 73 /* Scroll */, OnScroll, VTS_NONE),
编写OnScroll函数体:若单元格处于编辑状态调用Onleave函数使得编辑控件不可见。
voidCCtrlEditGrid::OnScroll()
{
if ( !m_pEdit->IsWindowVisible ( ) ) return;
else
{
OnLeaveCell();
}
}
3、c问题的解决
该问题的关键是上下控件的VSCROLL事件干扰了FlexGrid的滚动事件响应,只要杜绝上下控件的VSCROLL消息不就行了吗。我们只要改造CSpinButtonCtrl的鼠标左键的响应函数就可以阻止VSCROLL的产生。
首先继承CSpinButtonCtrl产生新类CMySpinButtonCtrl,把原来在CCtrlEditGrid中的成员变量m_pSpinButtonCtrl的类型改为CMySpinButtonCtrl,动态分配时也改为CMySpinButtonCtrl类型。
接着添加CMySpinButtonCtrl的消息响应函数OnLButtonDown,注释掉该函数中默认的一行代码CSpinButtonCtrl::OnLButtonDown(nFlags, point),这样就阻止了VSCROLL消息的传递。
然后判断若点击点在Spin控件的上半部,Edit控件的值就加1,若点击在下半部,Edit控件的值就减1。这样就模拟了原来Spin控件的微调功能。
voidCMySpinButtonCtrl::OnLButtonDown(UINTnFlags, CPointpoint)
{
CStringstrNum;
intnNum;
CMySpinButtonCtrl::GetBuddy()->GetWindowText(strNum);
sscanf ( strNum, "%d", &nNum );
CRectRect;
CMySpinButtonCtrl::GetWindowRect (&Rect);
intnLow,nUpper;
CMySpinButtonCtrl::GetRange(nLow, nUpper);
if(point.y <((Rect.bottom-Rect.top)/2))
{
nNum ++;
if ( nNum > nUpper )
nNum = nUpper;
}
else
{
nNum --;
if ( nNum < nLow )
nNum = nLow;
}
strNum.Format("%d",nNum);
CMySpinButtonCtrl::GetBuddy()->SetWindowText(strNum);
}
四、 总结