如何实现类似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
相关产品推荐
相关产品推荐

