You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何为窗体标题栏、系统菜单及控制按钮设置自定义光标?是否有对应Windows API?

如何为窗体标题栏、系统菜单图标及窗口按钮设置自定义光标?

没错,确实有对应的Windows API可以实现这个需求——毕竟VCL本身并没有提供直接设置标题栏、系统菜单图标以及最小化/最大化/关闭按钮光标的属性,得靠系统API来干预。下面结合你已有的Delphi代码结构,一步步讲具体实现:

核心思路:拦截WM_SETCURSOR消息

当光标移动到窗口的不同区域时,系统会给窗口发送WM_SETCURSOR消息,我们可以在这个消息里判断光标当前落在标题栏的哪个具体区域(比如标题栏本身、系统菜单、最小化按钮等),然后替换成自定义光标。

具体实现步骤

1. 扩展光标常量定义

先把需要用到的标题栏相关光标常量补充到你的定义里:

const
  crOpenCursor = 1;
  crRotateCursor = 2;
  crCursor_Water = 3;
  crTitleBar = 4;       // 标题栏区域光标
  crSysMenu = 5;        // 系统菜单图标光标
  crMinBtn = 6;         // 最小化按钮光标
  crMaxBtn = 7;         // 最大化按钮光标
  crCloseBtn = 8;       // 关闭按钮光标

2. 重写窗体的WM_SETCURSOR消息处理

在你的主窗体类里添加消息处理方法,用来判断光标位置并设置自定义光标:

type
  TFrm_Main = class(TForm)
    // ... 你的已有控件和声明
  private
    procedure SetCursor_For(AControl: TControl; ACursor_FileName: string; const ACurIndex: Integer);
    procedure WMSetCursor(var Msg: TWMSetCursor); message WM_SETCURSOR; // 添加这行
  public
    // ... 已有公共声明
  end;

然后实现这个消息处理方法:

procedure TFrm_Main.WMSetCursor(var Msg: TWMSetCursor);
var
  HitTestResult: Integer;
  TargetCursor: HCURSOR;
begin
  // 先发送WM_NCHITTEST消息,获取光标所在的窗口区域类型
  HitTestResult := SendMessage(Msg.WindowHandle, WM_NCHITTEST, 0, MakeLParam(Msg.XPos, Msg.YPos));
  
  // 根据区域类型匹配对应的自定义光标
  case HitTestResult of
    HTCAPTION:    TargetCursor := Screen.Cursors[crTitleBar];
    HTMENU:       TargetCursor := Screen.Cursors[crSysMenu];
    HTMINBUTTON:  TargetCursor := Screen.Cursors[crMinBtn];
    HTMAXBUTTON:  TargetCursor := Screen.Cursors[crMaxBtn];
    HTCLOSE:      TargetCursor := Screen.Cursors[crCloseBtn];
  else
    // 其他区域交给VCL默认处理
    inherited;
    Exit;
  end;
  
  // 设置自定义光标
  SetCursor(TargetCursor);
  // 告诉系统我们已经处理了这个消息,不用再走默认流程
  Msg.Result := 1;
end;

3. 加载自定义光标到Screen.Cursors数组

你已有SetCursor_For函数给普通控件设置光标,现在可以写一个通用的加载函数,把标题栏相关的光标加载到Screen.Cursors里(这样在消息处理里就能直接调用):

procedure LoadCustomCursor(CurIndex: Integer; CursorFileName: string);
var
  CursorHandle: HCURSOR;
begin
  CursorHandle := LoadCursorFromFile(PChar(CursorFileName));
  if CursorHandle <> 0 then
    Screen.Cursors[CurIndex] := CursorHandle
  else
    RaiseLastOSError; // 加载失败时抛出系统错误信息
end;

然后在窗体的OnCreate事件里调用加载:

procedure TFrm_Main.FormCreate(Sender: TObject);
begin
  // 加载标题栏相关的自定义光标
  LoadCustomCursor(crTitleBar, 'TitleBarCursor.cur');
  LoadCustomCursor(crSysMenu, 'SysMenuCursor.cur');
  LoadCustomCursor(crMinBtn, 'MinBtnCursor.cur');
  LoadCustomCursor(crMaxBtn, 'MaxBtnCursor.cur');
  LoadCustomCursor(crCloseBtn, 'CloseBtnCursor.cur');
  
  // 加载你原来的自定义光标
  LoadCustomCursor(crOpenCursor, 'OpenCursor.cur');
  LoadCustomCursor(crRotateCursor, 'RotateCursor.cur');
  LoadCustomCursor(crCursor_Water, 'WaterCursor.cur');
end;

4. 资源清理(可选但推荐)

如果是从外部文件加载的光标,程序退出时记得释放资源避免内存泄漏:

procedure TFrm_Main.FormDestroy(Sender: TObject);
begin
  DestroyCursor(Screen.Cursors[crTitleBar]);
  DestroyCursor(Screen.Cursors[crSysMenu]);
  DestroyCursor(Screen.Cursors[crMinBtn]);
  DestroyCursor(Screen.Cursors[crMaxBtn]);
  DestroyCursor(Screen.Cursors[crCloseBtn]);
  
  DestroyCursor(Screen.Cursors[crOpenCursor]);
  DestroyCursor(Screen.Cursors[crRotateCursor]);
  DestroyCursor(Screen.Cursors[crCursor_Water]);
end;

几个注意点

  • 64位兼容性:如果你的程序要编译成64位,确保所有API调用都用64位兼容的版本(上面的代码已经兼容,因为用了SendMessageSetCursor这些通用API)。
  • 系统主题干扰:某些特殊的窗口样式或者Windows主题可能会影响WM_NCHITTEST的返回结果,测试时尽量覆盖不同的系统环境。
  • 光标格式:确保你的.cur文件是标准的Windows光标格式,避免加载失败。

内容的提问来源于stack exchange,提问作者Bravesaw

火山引擎 最新活动