探究grid控件的原理
我们都使用过grid控件,这是一个非常强大的控件,使用过的都知道,对它是又爱又恨。给它设置一个数据源,显示成千上万条记录,毫无压力,速度奇快无比,反观如果使用msflexgrid控件来显示数据,一旦数据量大了,分分钟卡死!
究其原因,主要是msflexgrid控件是用一个个控件来显示承载数据的,有1万条记录,就有几万个控件(视字段数而定)),内存开销由此可见。。。
而grid是只是把数据画在控件上,一个grid控件几个字段就几个columns,每个columns就一个控件(一般是文本框)),这个控件只有在你编辑的时候可见。
grid控件能容纳多少条记录,它就调用渲染多少条记录,其他的全部放在缓冲,需要时再刷新,所以它体积很小,运行速度很快。
我们通常有使用表格的形式来输入数据表的需求,但是grid的特性往往满足不了,它要求你先绑定数据源,而我们表还没有建立。
针对这点,我挺喜欢access,它可以籍由输入建立数据表,就是在建立表之前就开始输入数据,像做excel表格一样,之后再保存为数据表。
而vfp没有可以与之相匹配的工具或手段,于是便一直有打算自己手搓一个。。。
一直与豆包,DS,KIMI聊天,探究grid控件的原理,现在总算知道了。。。
核心就是,你在grid控件上点击,它会计算你点击所在的哪个单元格,再把编辑框显示在那个单元格,激活给你输入使用。
输入完成后,它再把你输入的文字,画在单元格所在的位置,移动编辑框到下一行,或者激活相邻的column的编辑框。
然后,在你完成某些操作后,不断地paint重绘grid区域。
今天我就是先实现这个基础功能,供大家参考理解,以便做出自己钟意的表格输入控件,作为抛砖引玉吧。。。
效果图:
附上代码:
程序代码:
* ============================================================
* 文件名: DrawGrid.PRG
* 功能: 画布式 Grid 控件 - 使用绘制而非堆砌控件
* 优化: 移除重复的 Refresh/GridPaint 调用
* ============================================================
#DEFINE DT_LEFT 0x00000000
#DEFINE DT_CENTER 0x00000001
#DEFINE DT_RIGHT 0x00000002
#DEFINE DT_VCENTER 0x00000004
#DEFINE DT_BOTTOM 0x00000008
#DEFINE DT_SINGLELINE 0x00000020
#DEFINE DT_END_ELLIPSIS 0x00008000
#DEFINE TRANSPARENT 1
#DEFINE DEFAULT_CHARSET 1
* ============================================================
* DrawGrid 类 - 画布式表格控件
* ============================================================
DEFINE CLASS DrawGrid AS Container
*--- 外观属性
BackColor = RGB(255, 255, 255)
BorderWidth = 1
BorderColor = RGB(200, 200, 200)
*--- 表格结构
RowCount = 10
ColCount = 5
HeaderHeight = 28
RowHeight = 26
ColWidths = ""
*--- 数据
DataSource = ""
Fields = ""
*--- 状态
SelectedRow = 0
SelectedCol = 0
EditRow = 0
EditCol = 0
lIsEditing = .F.
lIsPainting = .F. && 防止递归绘制
*--- 内部数组
DIMENSION aColWidths[1]
DIMENSION aHeaders[1]
DIMENSION aFieldNames[1]
nTotalWidth = 0
nTotalHeight = 0
lInitialized = .F.
*--- 唯一编辑控件 - 初始时完全隐藏并置于左上角外
ADD OBJECT oEditBox AS TextBox WITH ;
Visible = .F., ;
BorderStyle = 1, ;
SpecialEffect = 0, ;
Height = 22, ;
Left = -100, ;
Top = -100, ;
Width = 10, ;
BackColor = RGB(255, 255, 220), ;
ForeColor = RGB(0, 0, 0)
*========== 初始化 ==========
PROCEDURE Init
THIS.SetupColumns()
THIS.SetupEditBox()
THIS.Declare()
THIS.lInitialized = .T.
THIS.lIsEditing = .F.
THIS.lIsPainting = .F.
THIS.EditRow = 0
THIS.EditCol = 0
* 确保编辑框初始隐藏
THIS.oEditBox.Visible = .F.
THIS.oEditBox.Left = -100
THIS.oEditBox.Top = -100
RETURN .T.
ENDPROC
PROCEDURE Declare
*========== 先声明 Windows API ==========
DECLARE INTEGER GetDC IN user32 INTEGER hWnd
DECLARE INTEGER ReleaseDC IN user32 INTEGER hWnd, INTEGER hDC
DECLARE INTEGER CreateSolidBrush IN gdi32 INTEGER crColor
DECLARE INTEGER FillRect IN user32 INTEGER hDC, STRING @lpRect, INTEGER hBrush
DECLARE INTEGER DeleteObject IN gdi32 INTEGER hObject
DECLARE INTEGER CreateFont IN gdi32 ;
INTEGER nHeight, INTEGER nWidth, INTEGER nEscapement, INTEGER nOrientation, ;
INTEGER fnWeight, INTEGER fdwItalic, INTEGER fdwUnderline, INTEGER fdwStrikeOut, ;
INTEGER fdwCharSet, INTEGER fdwOutputPrecision, INTEGER fdwClipPrecision, ;
INTEGER fdwQuality, INTEGER fdwPitchAndFamily, STRING lpszFace
DECLARE INTEGER SelectObject IN gdi32 INTEGER hdc, INTEGER hgdiobj
DECLARE INTEGER SetTextColor IN gdi32 INTEGER hdc, INTEGER crColor
DECLARE INTEGER SetBkMode IN gdi32 INTEGER hdc, INTEGER iBkMode
DECLARE INTEGER CreatePen IN gdi32 INTEGER fnPenStyle, INTEGER nWidth, INTEGER crColor
DECLARE INTEGER MoveToEx IN gdi32 INTEGER hdc, INTEGER X, INTEGER Y, INTEGER lpPoint
DECLARE INTEGER LineTo IN gdi32 INTEGER hdc, INTEGER nXEnd, INTEGER nYEnd
DECLARE INTEGER DrawTextW IN user32 ;
INTEGER hDC, STRING lpchText, INTEGER cchText, STRING @lprc, INTEGER format
DECLARE INTEGER InvalidateRect IN user32 INTEGER hWnd, STRING @lpRect, INTEGER bErase
DECLARE INTEGER UpdateWindow IN user32 INTEGER hWnd
ENDPROC
*========== 设置列 ==========
PROCEDURE SetupColumns
LOCAL lnFCount, i
IF THIS.ColCount < 1
THIS.ColCount = 5
ENDIF
DIMENSION THIS.aColWidths[THIS.ColCount]
DIMENSION THIS.aFieldNames[THIS.ColCount]
DIMENSION THIS.aHeaders[THIS.ColCount]
IF NOT EMPTY(THIS.DataSource) AND USED(THIS.DataSource)
lnFCount = FCOUNT(THIS.DataSource)
IF EMPTY(THIS.Fields)
LOCAL lcFieldList
lcFieldList = ""
FOR i = 1 TO lnFCount
lcFieldList = lcFieldList + FIELD(i, THIS.DataSource) + IIF(i < lnFCount, ",", "")
ENDFOR
THIS.Fields = lcFieldList
THIS.ColCount = lnFCount
DIMENSION THIS.aColWidths[THIS.ColCount]
DIMENSION THIS.aFieldNames[THIS.ColCount]
DIMENSION THIS.aHeaders[THIS.ColCount]
ENDIF
SELECT (THIS.DataSource)
THIS.RowCount = RECCOUNT()
ENDIF
IF NOT EMPTY(THIS.Fields)
LOCAL lcFields, lcName, lnPos, lnCol
lcFields = THIS.Fields + ","
lnCol = 1
THIS.ColCount = OCCURS(",", THIS.Fields) + 1
DIMENSION THIS.aFieldNames[THIS.ColCount]
DIMENSION THIS.aHeaders[THIS.ColCount]
DIMENSION THIS.aColWidths[THIS.ColCount]
lnCol = 1
DO WHILE "," $ lcFields AND lnCol <= THIS.ColCount
lnPos = AT(",", lcFields)
lcName = ALLTRIM(LEFT(lcFields, lnPos - 1))
lcName = CHRTRAN(lcName, CHR(0)+CHR(9)+CHR(10)+CHR(13), "")
THIS.aFieldNames[lnCol] = lcName
THIS.aHeaders[lnCol] = lcName
lcFields = SUBSTR(lcFields, lnPos + 1)
lnCol = lnCol + 1
ENDDO
ENDIF
IF THIS.ColCount = 0
THIS.ColCount = 5
DIMENSION THIS.aFieldNames[THIS.ColCount]
DIMENSION THIS.aHeaders[THIS.ColCount]
DIMENSION THIS.aColWidths[THIS.ColCount]
FOR i = 1 TO THIS.ColCount
THIS.aFieldNames[i] = "Col" + ALLTRIM(STR(i))
THIS.aHeaders[i] = "Col" + ALLTRIM(STR(i))
ENDFOR
ENDIF
IF EMPTY(THIS.ColWidths)
LOCAL lnDefaultWidth
lnDefaultWidth = INT(THIS.Width / THIS.ColCount)
IF lnDefaultWidth < 60
lnDefaultWidth = 80
ENDIF
FOR i = 1 TO THIS.ColCount
THIS.aColWidths[i] = lnDefaultWidth
ENDFOR
ELSE
LOCAL lcWidths, lnPos2, lnCol2, lnVal
lcWidths = THIS.ColWidths + ","
lnCol2 = 1
DIMENSION THIS.aColWidths[THIS.ColCount]
DO WHILE "," $ lcWidths AND lnCol2 <= THIS.ColCount
lnPos2 = AT(",", lcWidths)
lnVal = VAL(LEFT(lcWidths, lnPos2 - 1))
THIS.aColWidths[lnCol2] = IIF(lnVal > 30, lnVal, 80)
lcWidths = SUBSTR(lcWidths, lnPos2 + 1)
lnCol2 = lnCol2 + 1
ENDDO
DO WHILE lnCol2 <= THIS.ColCount
THIS.aColWidths[lnCol2] = 80
lnCol2 = lnCol2 + 1
ENDDO
ENDIF
THIS.nTotalWidth = 0
FOR i = 1 TO THIS.ColCount
THIS.nTotalWidth = THIS.nTotalWidth + THIS.aColWidths[i]
ENDFOR
THIS.nTotalHeight = THIS.HeaderHeight + THIS.RowCount * THIS.RowHeight
THIS.Width = THIS.nTotalWidth
THIS.Height = THIS.nTotalHeight
ENDPROC
*========== 设置编辑框 ==========
PROCEDURE SetupEditBox
BINDEVENT(THIS.oEditBox, "LostFocus", THIS, "EditBoxLostFocus", 1)
BINDEVENT(THIS.oEditBox, "KeyPress", THIS, "EditBoxKeyPress", 1)
ENDPROC
*========== 主绘制方法 ==========
PROCEDURE GridPaint
* 防止递归绘制
IF THIS.lIsPainting
RETURN
ENDIF
THIS.lIsPainting = .T.
TRY
* 清屏并绘制
THIS.DrawRect(0, 0, THIS.Width, THIS.Height, THIS.BackColor)
THIS.DrawHeaders()
THIS.DrawCells()
THIS.DrawGridLines()
FINALLY
THIS.lIsPainting = .F.
ENDTRY
ENDPROC
*========== 刷新(外部调用入口)==========
PROCEDURE Refresh
IF NOT THIS.lInitialized
RETURN
ENDIF
* 如果正在编辑,先结束编辑状态(但不重复绘制)
IF THIS.lIsEditing
THIS.SaveEditValue()
THIS.HideEditBoxNoPaint() && 不绘制,由后续 GridPaint 统一绘制
ENDIF
THIS.GridPaint()
ENDPROC
*========== 绘制表头 ==========
PROCEDURE DrawHeaders
LOCAL lnX, i
lnX = 0
THIS.DrawRect(0, 0, THIS.nTotalWidth, THIS.HeaderHeight, RGB(230, 230, 230))
FOR i = 1 TO THIS.ColCount
THIS.DrawText(lnX + 4, 4, THIS.aHeaders[i], ;
THIS.aColWidths[i] - 8, THIS.HeaderHeight - 8, ;
RGB(0, 0, 0), 11, "宋体", .T.)
lnX = lnX + THIS.aColWidths[i]
ENDFOR
ENDPROC
*========== 绘制单元格 ==========
PROCEDURE DrawCells
LOCAL lnRow, lnCol, lnX, lnY, lcValue, lcField
FOR lnRow = 1 TO THIS.RowCount
lnY = THIS.HeaderHeight + (lnRow - 1) * THIS.RowHeight
lnX = 0
* 行背景
IF lnRow = THIS.SelectedRow
THIS.DrawRect(0, lnY, THIS.nTotalWidth, THIS.RowHeight, RGB(180, 210, 255))
ELSE
IF MOD(lnRow, 2) = 0
THIS.DrawRect(0, lnY, THIS.nTotalWidth, THIS.RowHeight, RGB(248, 248, 248))
ELSE
THIS.DrawRect(0, lnY, THIS.nTotalWidth, THIS.RowHeight, RGB(255, 255, 255))
ENDIF
ENDIF
FOR lnCol = 1 TO THIS.ColCount
lcField = THIS.aFieldNames[lnCol]
lcValue = THIS.GetCellValue(lnRow, lcField)
* 只有在 正在编辑 且 行列匹配 时才跳过绘制
IF NOT (THIS.lIsEditing AND lnRow = THIS.EditRow AND lnCol = THIS.EditCol)
THIS.DrawText(lnX + 4, lnY + 3, lcValue, ;
THIS.aColWidths[lnCol] - 8, THIS.RowHeight - 6, ;
RGB(0, 0, 0), 10, "宋体", .F.)
ENDIF
lnX = lnX + THIS.aColWidths[lnCol]
ENDFOR
ENDFOR
ENDPROC
*--- 绘制网格线 ---
PROCEDURE DrawGridLines
LOCAL i, lnX, lnY
lnX = 0
FOR i = 1 TO THIS.ColCount - 1
lnX = lnX + THIS.aColWidths[i]
THIS.DrawLine(lnX, 0, lnX, THIS.nTotalHeight, RGB(200, 200, 200))
ENDFOR
THIS.DrawLine(0, THIS.HeaderHeight, THIS.nTotalWidth, THIS.HeaderHeight, RGB(160, 160, 160))
FOR i = 1 TO THIS.RowCount
lnY = THIS.HeaderHeight + i * THIS.RowHeight
THIS.DrawLine(0, lnY, THIS.nTotalWidth, lnY, RGB(220, 220, 220))
ENDFOR
ENDPROC
*========== 获取窗口句柄 ==========
PROCEDURE GetHWnd
LOCAL loObj
loObj = THIS
DO WHILE TYPE("loObj.HWnd") <> "N" AND NOT ISNULL(loObj.Parent)
loObj = loObj.Parent
ENDDO
RETURN IIF(TYPE("loObj.HWnd") = "N", loObj.HWnd, 0)
ENDPROC
*========== 获取控件在表单中的偏移 ==========
PROCEDURE GetControlOffset
LPARAMETERS taOffset
taOffset[1] = THIS.Left
taOffset[2] = THIS.Top
ENDPROC
*========== MakeRect ==========
PROCEDURE MakeRect(tnLeft, tnTop, tnRight, tnBottom)
RETURN BINTOC(tnLeft, "4RS") + BINTOC(tnTop, "4RS") + ;
BINTOC(tnRight, "4RS") + BINTOC(tnBottom, "4RS")
ENDPROC
*========== 绘制矩形 ==========
PROCEDURE DrawRect(tnX, tnY, tnW, tnH, tnColor)
LOCAL lnHDC, lnBrush, lnHWnd, lcRect, laOffset[2]
lnHWnd = THIS.GetHWnd()
IF lnHWnd = 0
RETURN
ENDIF
DIMENSION laOffset[2]
THIS.GetControlOffset(@laOffset)
lnHDC = GetDC(lnHWnd)
lnBrush = CreateSolidBrush(tnColor)
lcRect = THIS.MakeRect(tnX + laOffset[1], tnY + laOffset[2], ;
tnX + tnW + laOffset[1], tnY + tnH + laOffset[2])
FillRect(lnHDC, lcRect, lnBrush)
DeleteObject(lnBrush)
ReleaseDC(lnHWnd, lnHDC)
ENDPROC
*========== 绘制文字 ==========
PROCEDURE DrawText(tnX, tnY, tcText, tnW, tnH, tnColor, tnSize, tcFont, tlBold)
LOCAL lnHDC, lnFont, lnHWnd, lcRect, laOffset[2], lcUnicodeText
lnHWnd = THIS.GetHWnd()
IF lnHWnd = 0
RETURN
ENDIF
DIMENSION laOffset[2]
THIS.GetControlOffset(@laOffset)
lnHDC = GetDC(lnHWnd)
lnFont = CreateFont(-tnSize, 0, 0, 0, IIF(tlBold, 700, 400), 0, 0, 0, ;
DEFAULT_CHARSET, 0, 0, 0, 0, tcFont)
SelectObject(lnHDC, lnFont)
SetTextColor(lnHDC, tnColor)
SetBkMode(lnHDC, TRANSPARENT)
lcUnicodeText = STRCONV(tcText, 5)
LOCAL lnLeft, lnTop, lnRight, lnBottom
lnLeft = tnX + laOffset[1]
lnTop = tnY + laOffset[2]
lnRight = lnLeft + MAX(tnW, 10)
lnBottom = lnTop + MAX(tnH, tnSize + 4)
lcRect = THIS.MakeRect(lnLeft, lnTop, lnRight, lnBottom)
DrawTextW(lnHDC, lcUnicodeText, LEN(lcUnicodeText)/2, lcRect, ;
DT_END_ELLIPSIS + DT_VCENTER + DT_SINGLELINE + DT_LEFT)
DeleteObject(lnFont)
ReleaseDC(lnHWnd, lnHDC)
ENDPROC
*========== 绘制线条 ==========
PROCEDURE DrawLine(tnX1, tnY1, tnX2, tnY2, tnColor)
LOCAL lnHDC, lnPen, lnHWnd, laOffset[2]
lnHWnd = THIS.GetHWnd()
IF lnHWnd = 0
RETURN
ENDIF
DIMENSION laOffset[2]
THIS.GetControlOffset(@laOffset)
lnHDC = GetDC(lnHWnd)
lnPen = CreatePen(0, 1, tnColor)
SelectObject(lnHDC, lnPen)
MoveToEx(lnHDC, tnX1 + laOffset[1], tnY1 + laOffset[2], 0)
LineTo(lnHDC, tnX2 + laOffset[1], tnY2 + laOffset[2])
DeleteObject(lnPen)
ReleaseDC(lnHWnd, lnHDC)
ENDPROC
*========== 强制刷新指定区域 ==========
PROCEDURE InvalidateArea(tnX, tnY, tnW, tnH)
LOCAL lnHWnd, laOffset[2], lnAbsX, lnAbsY, lcRect
lnHWnd = THIS.GetHWnd()
IF lnHWnd = 0
RETURN
ENDIF
DIMENSION laOffset[2]
THIS.GetControlOffset(@laOffset)
lnAbsX = tnX + laOffset[1]
lnAbsY = tnY + laOffset[2]
lcRect = BINTOC(lnAbsX, "4RS") + BINTOC(lnAbsY, "4RS") + ;
BINTOC(lnAbsX + tnW, "4RS") + BINTOC(lnAbsY + tnH, "4RS")
InvalidateRect(lnHWnd, @lcRect, 1)
UpdateWindow(lnHWnd)
ENDPROC
*========== 显示编辑框 ==========
PROCEDURE ShowEditBox(tnRow, tnCol)
LOCAL lnX, lnY, lnW, lnH, lcValue, lcField, i
* 先确保之前的编辑状态完全结束(但不触发绘制)
IF THIS.lIsEditing
THIS.SaveEditValue()
THIS.HideEditBoxNoPaint()
ENDIF
lnX = 0
FOR i = 1 TO tnCol - 1
lnX = lnX + THIS.aColWidths[i]
ENDFOR
lnY = THIS.HeaderHeight + (tnRow - 1) * THIS.RowHeight
lnW = THIS.aColWidths[tnCol]
lnH = THIS.RowHeight
lcField = THIS.aFieldNames[tnCol]
lcValue = THIS.GetCellValue(tnRow, lcField)
WITH THIS.oEditBox
.Left = lnX + 1
.Top = lnY + 1
.Width = lnW - 2
.Height = lnH - 2
.Value = lcValue
.FontSize = 10
.FontName = "宋体"
.Visible = .T.
.SetFocus()
ENDWITH
THIS.EditRow = tnRow
THIS.EditCol = tnCol
THIS.lIsEditing = .T.
ENDPROC
*========== 数据获取 ==========
PROCEDURE GetCellValue(tnRow, tcField)
LOCAL lcValue
IF EMPTY(THIS.DataSource) OR NOT USED(THIS.DataSource)
RETURN "R" + ALLTRIM(STR(tnRow)) + "-" + tcField
ENDIF
SELECT (THIS.DataSource)
IF RECCOUNT() < tnRow
RETURN ""
ENDIF
GO tnRow
LOCAL lcField
lcField = ALLTRIM(tcField)
IF EMPTY(FIELD(lcField, THIS.DataSource))
RETURN "?" + lcField + "?"
ENDIF
TRY
lcValue = EVALUATE(lcField)
lcValue = IIF(ISNULL(lcValue), "", TRANSFORM(lcValue))
CATCH
lcValue = ""
ENDTRY
RETURN lcValue
ENDPROC
*========== 鼠标交互 ==========
PROCEDURE MouseDown
LPARAMETERS nButton, nShift, nXCoord, nYCoord
NODEFAULT
* 如果正在编辑,先保存并结束编辑(不触发绘制,后面统一绘制)
IF THIS.lIsEditing
THIS.SaveEditValue()
THIS.HideEditBoxNoPaint()
ENDIF
LOCAL lnGridX, lnGridY
lnGridX = nXCoord - THIS.Left
lnGridY = nYCoord - THIS.Top
IF lnGridX < 0 OR lnGridY < 0 OR lnGridX > THIS.Width OR lnGridY > THIS.Height
RETURN
ENDIF
IF lnGridY <= THIS.HeaderHeight
RETURN
ENDIF
LOCAL lnRow, lnCol, lnAccumWidth, i
lnRow = INT((lnGridY - THIS.HeaderHeight) / THIS.RowHeight) + 1
IF lnRow < 1 OR lnRow > THIS.RowCount
RETURN
ENDIF
lnAccumWidth = 0
lnCol = 0
FOR i = 1 TO THIS.ColCount
lnAccumWidth = lnAccumWidth + THIS.aColWidths[i]
IF lnGridX <= lnAccumWidth
lnCol = i
EXIT
ENDIF
ENDFOR
IF lnCol = 0
RETURN
ENDIF
* 更新选中状态
THIS.SelectedRow = lnRow
THIS.SelectedCol = lnCol
* 重绘(清除旧编辑框残留)
THIS.GridPaint()
* 显示编辑框
THIS.ShowEditBox(lnRow, lnCol)
ENDPROC
*========== Container 失去焦点 ==========
PROCEDURE LostFocus
* 当 Grid (Container) 失去焦点时
IF THIS.lIsEditing
THIS.SaveEditValue()
THIS.HideEditBoxNoPaint()
THIS.GridPaint()
ENDIF
ENDPROC
*========== 隐藏编辑框(不触发绘制)==========
PROCEDURE HideEditBoxNoPaint
IF NOT THIS.lIsEditing
RETURN
ENDIF
* 记录编辑框位置(用于刷新区域)
LOCAL lnOldLeft, lnOldTop, lnOldWidth, lnOldHeight
WITH THIS.oEditBox
lnOldLeft = .Left
lnOldTop = .Top
lnOldWidth = .Width
lnOldHeight = .Height
ENDWITH
* 清除状态
THIS.lIsEditing = .F.
THIS.EditRow = 0
THIS.EditCol = 0
* 隐藏编辑框
THIS.oEditBox.Visible = .F.
THIS.oEditBox.Left = -1000
THIS.oEditBox.Top = -1000
* 强制刷新编辑框所在区域
IF lnOldLeft >= 0 AND lnOldTop >= 0 AND lnOldWidth > 0 AND lnOldHeight > 0
THIS.InvalidateArea(lnOldLeft, lnOldTop, lnOldWidth, lnOldHeight)
ENDIF
ENDPROC
*========== 隐藏编辑框(带绘制)==========
PROCEDURE HideEditBox
THIS.HideEditBoxNoPaint()
THIS.GridPaint()
ENDPROC
PROCEDURE EditBoxLostFocus
* 编辑框失去焦点时,保存数据并结束编辑
IF THIS.lIsEditing
THIS.SaveEditValue()
THIS.HideEditBox() && 带绘制
ENDIF
ENDPROC
PROCEDURE EditBoxKeyPress
LPARAMETERS nKeyCode, nShiftAltCtrl
DO CASE
CASE nKeyCode = 13 && Enter
THIS.SaveEditValue()
THIS.HideEditBox() && 带绘制
IF THIS.SelectedRow < THIS.RowCount
THIS.SelectedRow = THIS.SelectedRow + 1
THIS.ShowEditBox(THIS.SelectedRow, THIS.SelectedCol)
ENDIF
CASE nKeyCode = 27 && Esc - 取消编辑,不保存
THIS.HideEditBox() && 带绘制
CASE nKeyCode = 9 && Tab
THIS.SaveEditValue()
THIS.HideEditBox() && 带绘制
IF THIS.SelectedCol < THIS.ColCount
THIS.SelectedCol = THIS.SelectedCol + 1
THIS.ShowEditBox(THIS.SelectedRow, THIS.SelectedCol)
ENDIF
ENDCASE
ENDPROC
*========== 保存数据 ==========
PROCEDURE SaveEditValue
IF NOT THIS.lIsEditing OR THIS.EditRow = 0 OR THIS.EditCol = 0
RETURN
ENDIF
IF EMPTY(THIS.DataSource) OR NOT USED(THIS.DataSource)
RETURN
ENDIF
LOCAL lcField, lcValue, lcType, lcExpr
lcField = ALLTRIM(THIS.aFieldNames[THIS.EditCol])
lcValue = ALLTRIM(THIS.oEditBox.Value)
SELECT (THIS.DataSource)
IF RECCOUNT() >= THIS.EditRow
GO THIS.EditRow
IF NOT EMPTY(FIELD(lcField, THIS.DataSource))
* 获取字段类型
lcType = TYPE(lcField)
* 根据字段类型转换数据
DO CASE
CASE lcType = "N"
lcExpr = VAL(lcValue)
CASE lcType = "I"
lcExpr = INT(VAL(lcValue))
CASE lcType = "L"
lcExpr = IIF(UPPER(lcValue) = "T" OR UPPER(lcValue) = "Y" OR UPPER(lcValue) = "是", .T., .F.)
CASE lcType = "D"
TRY
lcExpr = CTOD(lcValue)
CATCH
lcExpr = {}
ENDTRY
CASE lcType = "T"
TRY
lcExpr = CTOT(lcValue)
CATCH
lcExpr = DATETIME()
ENDTRY
OTHERWISE
lcExpr = lcValue
ENDCASE
TRY
REPLACE &lcField WITH lcExpr
CATCH
TRY
REPLACE &lcField WITH lcValue
CATCH
ENDTRY
ENDTRY
ENDIF
ENDIF
ENDPROC
*========== 公共方法 ==========
PROCEDURE SetDataSource(tcCursor)
THIS.DataSource = tcCursor
IF USED(tcCursor)
SELECT (tcCursor)
THIS.RowCount = RECCOUNT()
THIS.SetupColumns()
ENDIF
THIS.GridPaint()
ENDPROC
PROCEDURE AddRow
IF EMPTY(THIS.DataSource) OR NOT USED(THIS.DataSource)
RETURN
ENDIF
SELECT (THIS.DataSource)
APPEND BLANK
THIS.RowCount = RECCOUNT()
THIS.GridPaint()
ENDPROC
PROCEDURE DeleteRow(tnRow)
IF EMPTY(THIS.DataSource) OR NOT USED(THIS.DataSource)
RETURN
ENDIF
IF tnRow > 0 AND tnRow <= RECCOUNT()
SELECT (THIS.DataSource)
GO tnRow
DELETE
THIS.RowCount = RECCOUNT()
IF THIS.SelectedRow = tnRow
THIS.SelectedRow = 0
THIS.SelectedCol = 0
ENDIF
THIS.GridPaint()
ENDIF
ENDPROC
ENDDEFINE
* ============================================================
* 测试表单
* ============================================================
DEFINE CLASS TestForm AS Form
Caption = "画布式 Grid 控件演示"
Width = 650
Height = 480
BackColor = RGB(240, 240, 240)
ADD OBJECT oGrid AS DrawGrid
PROCEDURE Load
CREATE CURSOR testdata (;
姓名 C(20), ;
年龄 N(3), ;
部门 C(30), ;
电话 C(20), ;
备注 C(50) ;
)
INSERT INTO testdata VALUES ("张三", 28, "技术部", "13800138001", "高级工程师")
INSERT INTO testdata VALUES ("李四", 32, "销售部", "13900139002", "销售经理")
INSERT INTO testdata VALUES ("王五", 25, "人事部", "13700137003", "专员")
INSERT INTO testdata VALUES ("赵六", 35, "财务部", "13600136004", "主管")
INSERT INTO testdata VALUES ("钱七", 29, "技术部", "13500135005", "工程师")
INSERT INTO testdata VALUES ("孙八", 31, "市场部", "13400134006", "经理")
INSERT INTO testdata VALUES ("周九", 27, "运营部", "13300133007", "专员")
INSERT INTO testdata VALUES ("吴十", 33, "技术部", "13200132008", "架构师")
ENDPROC
PROCEDURE Init
WITH THIS.oGrid
.DataSource = "testdata"
.ColWidths = "80,80,100,120,150"
.Fields = "姓名,年龄,部门,电话,备注"
.Left = 20
.Top = 20
.Width = 500
.Visible = .T.
.SetupColumns()
ENDWITH
ENDPROC
*--- 表单移动时刷新 Grid(仅重绘,不触发编辑结束)
PROCEDURE Move
THISFORM.oGrid.GridPaint()
ENDPROC
PROCEDURE Activate
* 激活时确保编辑框隐藏并重绘
THISFORM.oGrid.HideEditBoxNoPaint()
THISFORM.oGrid.GridPaint()
*thisform.ogrid.refresh()
ENDPROC
PROCEDURE Destroy
IF USED("testdata")
USE IN testdata
ENDIF
ENDPROC
ENDDEFINE
* ============================================================
* 运行测试
* ============================================================
* 在命令行输入下面的代码,或保存为prg文件,并运行它。
*!* SET PROCEDURE TO drawgrid ADDITIVE
*!* oform = CREATEOBJECT("TestForm")
*!* oform.Show
在命令窗口一次输入:
程序代码:
SET PROCEDURE TO drawgrid addi
oform=CREATEOBJECT("testform")
oform.Show









