| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛 | 
共有 680 人关注过本帖
标题:探究grid控件的原理
只看楼主 加入收藏
sam_jiang
Rank: 10Rank: 10Rank: 10
等 级:贵宾
威 望:14
帖 子:1281
专家分:1687
注 册:2021-10-13
收藏
 问题点数:0 回复次数:3 
探究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
收到的鲜花
  • trial03 于 2026-07-29 10:04 送鲜花  1朵  
搜索更多相关主题的帖子: THIS IF PROCEDURE INTEGER 绘制 
2026-07-26 11:41
schtg
Rank: 13Rank: 13Rank: 13Rank: 13
来 自:USA
等 级:贵宾
威 望:67
帖 子:2796
专家分:4967
注 册:2012-2-29
收藏
 得分:0 
2026-07-26 16:44
dengsheng
Rank: 2
等 级:论坛游民
帖 子:16
专家分:15
注 册:2019-9-1
收藏
 得分:0 
很好的思路扩展,所有功能都得自已写过就是了
2026-07-27 17:16
sam_jiang
Rank: 10Rank: 10Rank: 10
等 级:贵宾
威 望:14
帖 子:1281
专家分:1687
注 册:2021-10-13
收藏
 得分:0 
回复 3楼 dengsheng
如果想锻炼自己的编程能力,重写或改造现成控件是一个非常好且有效的方法
2026-07-29 16:32
快速回复:探究grid控件的原理
数据加载中...
 
   
关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.039807 second(s), 11 queries.
Copyright©2004-2026, BCCN.NET, All Rights Reserved