You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现类似Delphi IDE的FMX TSelection 8个抓取手柄功能?

扩展FMX的TSelection实现8个单方向调整手柄

完全可以通过扩展TSelection类来实现类似Delphi IDE的8个单方向调整手柄功能,不需要依赖边角手柄同时改变宽高。下面是具体的实现思路和关键步骤:

核心实现思路

FMX的TSelection默认仅渲染4个边角手柄,且拖动时会同时修改控件的宽高。要实现8个单方向手柄,需要重写TSelection的绘制逻辑、鼠标命中检测和拖动调整逻辑,将手柄拆分为8个对应单一调整方向的交互点。

关键步骤与代码示例

1. 创建TSelection子类

定义继承自TSelection的子类,新增手柄位置枚举、激活手柄记录、手柄位置计算等逻辑:

type
  THandlePosition = (hpTopLeft, hpTopCenter, hpTopRight,
                     hpMiddleLeft, hpMiddleRight,
                     hpBottomLeft, hpBottomCenter, hpBottomRight);

  T8HandleSelection = class(TSelection)
  private
    FActiveHandle: THandlePosition;
    FOriginalBounds: TRectF;
    function GetHandleRect(const APosition: THandlePosition): TRectF;
  protected
    procedure Paint; override;
    function HitTest(const P: TPointF): Boolean; override;
    procedure MouseDown(const Button: TMouseButton; const Shift: TShiftState; const P: TPointF); override;
    procedure MouseMove(const Shift: TShiftState; const P: TPointF); override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

2. 实现手柄位置计算

编写GetHandleRect方法,根据选中控件的边界,计算每个手柄的矩形区域(以8x8的小方块为例):

function T8HandleSelection.GetHandleRect(const APosition: THandlePosition): TRectF;
const
  HANDLE_SIZE = 8;
var
  Bounds: TRectF;
begin
  Bounds := SelectedObject.AbsoluteRect;
  case APosition of
    hpTopLeft: Result := RectF(Bounds.Left - HANDLE_SIZE/2, Bounds.Top - HANDLE_SIZE/2,
                               Bounds.Left + HANDLE_SIZE/2, Bounds.Top + HANDLE_SIZE/2);
    hpTopCenter: Result := RectF(Bounds.CenterPoint.X - HANDLE_SIZE/2, Bounds.Top - HANDLE_SIZE/2,
                                 Bounds.CenterPoint.X + HANDLE_SIZE/2, Bounds.Top + HANDLE_SIZE/2);
    hpTopRight: Result := RectF(Bounds.Right - HANDLE_SIZE/2, Bounds.Top - HANDLE_SIZE/2,
                                Bounds.Right + HANDLE_SIZE/2, Bounds.Top + HANDLE_SIZE/2);
    hpMiddleLeft: Result := RectF(Bounds.Left - HANDLE_SIZE/2, Bounds.CenterPoint.Y - HANDLE_SIZE/2,
                                  Bounds.Left + HANDLE_SIZE/2, Bounds.CenterPoint.Y + HANDLE_SIZE/2);
    hpMiddleRight: Result := RectF(Bounds.Right - HANDLE_SIZE/2, Bounds.CenterPoint.Y - HANDLE_SIZE/2,
                                   Bounds.Right + HANDLE_SIZE/2, Bounds.CenterPoint.Y + HANDLE_SIZE/2);
    hpBottomLeft: Result := RectF(Bounds.Left - HANDLE_SIZE/2, Bounds.Bottom - HANDLE_SIZE/2,
                                  Bounds.Left + HANDLE_SIZE/2, Bounds.Bottom + HANDLE_SIZE/2);
    hpBottomCenter: Result := RectF(Bounds.CenterPoint.X - HANDLE_SIZE/2, Bounds.Bottom - HANDLE_SIZE/2,
                                    Bounds.CenterPoint.X + HANDLE_SIZE/2, Bounds.Bottom + HANDLE_SIZE/2);
    hpBottomRight: Result := RectF(Bounds.Right - HANDLE_SIZE/2, Bounds.Bottom - HANDLE_SIZE/2,
                                   Bounds.Right + HANDLE_SIZE/2, Bounds.Bottom + HANDLE_SIZE/2);
  end;
end;

3. 重写绘制逻辑

在Paint方法中循环绘制8个手柄,模拟Delphi IDE的样式:

procedure T8HandleSelection.Paint;
var
  Pos: THandlePosition;
  HandleRect: TRectF;
begin
  inherited; // 可选:保留原有的选中边框,仅添加手柄
  Canvas.BeginScene;
  try
    Canvas.Fill.Color := TAlphaColors.White;
    Canvas.Stroke.Color := TAlphaColors.Black;
    Canvas.Stroke.Thickness := 1;
    for Pos := Low(THandlePosition) to High(THandlePosition) do
    begin
      HandleRect := GetHandleRect(Pos);
      Canvas.FillRect(HandleRect, 0, 0, AllCorners, 1);
      Canvas.DrawRect(HandleRect, 0, 0, AllCorners, 1);
    end;
  finally
    Canvas.EndScene;
  end;
end;

4. 实现鼠标交互逻辑

  • HitTest:检测鼠标是否命中某个手柄
function T8HandleSelection.HitTest(const P: TPointF): Boolean;
var
  Pos: THandlePosition;
begin
  Result := False;
  for Pos := Low(THandlePosition) to High(THandlePosition) do
  begin
    if GetHandleRect(Pos).Contains(P) then
    begin
      FActiveHandle := Pos;
      Result := True;
      Break;
    end;
  end;
end;
  • MouseDown:记录初始边界,用于拖动计算
procedure T8HandleSelection.MouseDown(const Button: TMouseButton; const Shift: TShiftState; const P: TPointF);
begin
  inherited;
  if Assigned(SelectedObject) then
    FOriginalBounds := SelectedObject.AbsoluteRect;
end;
  • MouseMove:根据激活的手柄,仅调整单一方向的尺寸
procedure T8HandleSelection.MouseMove(const Shift: TShiftState; const P: TPointF);
var
  DeltaX, DeltaY: Single;
begin
  inherited;
  if not Assigned(SelectedObject) then Exit;

  case FActiveHandle of
    hpTopLeft, hpTopCenter, hpTopRight:
      begin
        DeltaY := P.Y - FOriginalBounds.Top;
        SelectedObject.Position.Y := P.Y;
        SelectedObject.Height := FOriginalBounds.Height - DeltaY;
      end;
    hpMiddleLeft:
      begin
        DeltaX := P.X - FOriginalBounds.Left;
        SelectedObject.Position.X := P.X;
        SelectedObject.Width := FOriginalBounds.Width - DeltaX;
      end;
    hpMiddleRight:
      begin
        SelectedObject.Width := P.X - FOriginalBounds.Left;
      end;
    hpBottomLeft:
      begin
        DeltaX := P.X - FOriginalBounds.Left;
        SelectedObject.Position.X := P.X;
        SelectedObject.Width := FOriginalBounds.Width - DeltaX;
      end;
    hpBottomCenter:
      begin
        SelectedObject.Height := P.Y - FOriginalBounds.Top;
      end;
    hpBottomRight:
      begin
        SelectedObject.Width := P.X - FOriginalBounds.Left;
        SelectedObject.Height := P.Y - FOriginalBounds.Top;
      end;
  end;
end;

替换默认TSelection

在自定义设计器或控件选择逻辑中,将默认的TSelection实例替换为T8HandleSelection,即可启用8个单方向调整手柄。


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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 23:22:32