Delphi中如何禁用与重新启用TAdvStringGrid的鼠标点击?
针对你遇到的多线程填充网格时,鼠标点击导致数据错乱的问题,直接设置Enabled := False会让控件变灰影响视觉体验,这里有几个更贴合需求的解决方案:
方案1:标志位+鼠标事件拦截
你可以在Form中设置一个布尔标记,标记当前是否处于数据填充状态,然后在网格的鼠标点击事件里判断这个标记,若处于填充状态就直接终止事件处理。
代码修改步骤:
首先在Form类中添加私有变量和事件声明:
private FIsPopulating: Boolean; // 标记是否正在填充数据 FActiveThreads: Integer; // 统计活跃线程数 procedure StringGridMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure DecrementThreadCount;
然后在Form初始化时绑定事件并初始化变量:
procedure TForm1.FormCreate(Sender: TObject); begin StringGrid1.OnMouseDown := StringGridMouseDown; FIsPopulating := False; FActiveThreads := 0; end;
实现鼠标点击拦截逻辑:
procedure TForm1.StringGridMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if FIsPopulating then begin // 阻止后续鼠标点击处理 Abort; // 可选:给用户提示 // ShowMessage('数据加载中,请稍候'); end; end;
修改按钮点击事件,启动线程前标记填充状态:
procedure TForm1.Button1Click(Sender: TObject); begin FIsPopulating := True; FActiveThreads := 8; // 对应你创建的8个线程数 TThread_populate_stringgrid.Create('Andrei', form1.StringGrid1); TThread_populate_stringgrid.Create('Matei', form1.StringGrid1); TThread_populate_stringgrid.Create('Iulia', form1.StringGrid1); TThread_populate_stringgrid.Create('Petru', form1.StringGrid1); TThread_populate_stringgrid.Create('Gheorghe', form1.StringGrid1); TThread_populate_stringgrid.Create('Tudor', form1.StringGrid1); TThread_populate_stringgrid.Create('Cineva', form1.StringGrid1); TThread_populate_stringgrid.Create('Altcine', form1.StringGrid1); end;
实现线程计数递减逻辑,所有线程完成后恢复鼠标交互:
procedure TForm1.DecrementThreadCount; begin Dec(FActiveThreads); if FActiveThreads = 0 then FIsPopulating := False; // 所有填充完成,允许鼠标操作 end;
最后修改线程的Execute方法,完成后通知主线程更新计数:
procedure TThread_populate_stringgrid.Execute; begin Synchronize( procedure begin f_stringgrid_to_populate.cells[0,0]:='DATE'; f_stringgrid_to_populate.cells[1,0]:='NAME'; f_stringgrid_to_populate.cells[2,0]:='ADRESS'; f_stringgrid_to_populate.cells[3,0]:='CITY'; f_stringgrid_to_populate.cells[4,0]:='COUNTRY'; f_stringgrid_to_populate.Cols[0].Add(FormatDatetime('dd-mm-yyyy hh:mm:ss', Now)); f_stringgrid_to_populate.Cols[1].Add(f_name); f_stringgrid_to_populate.Cols[2].Add('58 RED ROAD'); f_stringgrid_to_populate.Cols[3].Add('ENGLAND'); f_stringgrid_to_populate.Cols[3].Add('UK'); end ); // 线程结束后通知主线程递减计数 Synchronize(procedure begin TForm1(form1).DecrementThreadCount; end); end;
方案2:子类化控件拦截鼠标消息
如果不想依赖事件绑定,可以通过子类化StringGrid,直接拦截所有鼠标相关的系统消息,在填充期间忽略这些消息。
代码示例:
在Form类中添加变量保存原窗口过程:
private FOrgGridWndProc: TWndMethod; FIsPopulating: Boolean; FActiveThreads: Integer; procedure GridWndProc(var Msg: TMessage); procedure DecrementThreadCount;
在Form创建和销毁时处理子类化:
procedure TForm1.FormCreate(Sender: TObject); begin FOrgGridWndProc := StringGrid1.WindowProc; StringGrid1.WindowProc := GridWndProc; FIsPopulating := False; FActiveThreads := 0; end; procedure TForm1.FormDestroy(Sender: TObject); begin // 恢复原窗口过程 StringGrid1.WindowProc := FOrgGridWndProc; end;
实现自定义窗口过程拦截鼠标消息:
procedure TForm1.GridWndProc(var Msg: TMessage); const // 需要拦截的鼠标消息列表 MouseMessages: array[0..4] of Cardinal = (WM_LBUTTONDOWN, WM_LBUTTONUP, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_MOUSEWHEEL); var i: Integer; begin if FIsPopulating then begin // 检查是否是鼠标消息,是则直接返回不处理 for i := Low(MouseMessages) to High(MouseMessages) do begin if Msg.Msg = MouseMessages[i] then Exit; end; end; // 非拦截消息交给原窗口过程处理 FOrgGridWndProc(Msg); end;
后续的线程计数和标志位逻辑和方案1一致即可。
方案3:用EnableWindow保持视觉状态(快速实现)
如果只是不想让控件变灰但需要阻止交互,可以调用Windows API的EnableWindow,它会禁用控件输入但保留原有外观(和Enabled := False的灰化效果不同)。
使用示例:
// 开始填充时禁用输入 EnableWindow(StringGrid1.Handle, False); // 所有线程完成后恢复输入 EnableWindow(StringGrid1.Handle, True);
你可以结合方案1的线程计数逻辑,在Button1Click中调用禁用代码,在DecrementThreadCount中当计数归零时调用恢复代码。
另外提醒下:你当前的多线程代码中,多个线程同时向Cols添加数据,虽然Synchronize会把操作放到主线程执行,但建议统一管理填充顺序,或者用临界区确保数据操作的原子性,进一步避免潜在冲突。
内容的提问来源于stack exchange,提问作者user2858981

