如何将控件置于设计状态模式中,就像表单设计器一样?

时间:2015-04-07 15:13:19

标签: delphi delphi-xe7

这一段时间以来一直令我困惑,也许答案很简单,或者它可能涉及更多的VCL黑客或魔法来完成我正在寻找的东西,但无论哪种方式我都不知道如何解决我的问题。

如果您查看Delphi表单设计器,您将看到当鼠标移过它们时没有任何控件生成动画,它们也无法接收焦点或输入(例如,您无法键入TEdit,单击TCheckBox或移动TScrollBar等),仅在运行时,控件才能正常运行并响应用户交互。

我想知道如何在运行时将任何控件实现此类行为,例如将控件设置为Designer State Mode?但是,控件仍应响应鼠标事件,例如OnMouseDownOnMouseMoveOnMouseUp等,以便在需要时移动和调整大小,例如。

这是我管理的最接近的地方:

procedure SetControlState(Control: TWinControl; Active: Boolean);
begin
  SendMessage(Control.Handle, WM_SETREDRAW, Ord(Active), 0);
  InvalidateRect(Control.Handle, nil, True);
end;

可以简单地称之为:

procedure TForm1.chkActiveClick(Sender: TObject);
begin
  SetControlState(Button1, chkActive.Checked);
  SetControlState(Button2, chkActive.Checked);
  SetControlState(Edit1, chkActive.Checked);
end;

或者例如,表单上的所有控件:

procedure TForm1.chkActiveClick(Sender: TObject);
var
  I: Integer;
  Ctrl: TWinControl;
begin
  for I := 0 to Form1.ControlCount -1 do
  begin
    if Form1.Controls[I] is TWinControl then
    begin
      Ctrl := TWinControl(Form1.Controls[I]);
      if (Ctrl <> nil) and not (Ctrl = chkActive) then
      begin
        SetControlState(Ctrl, chkActive.Checked);
      end;
    end;
  end;
end;

我注意到的两个问题是,虽然控件确实看起来像设计状态,但某些控件(如TButton)仍然会在其上绘制动画效果。另一个问题是当控件处于设计状态时按下左Alt键会导致它们消失。

所以我的问题是,我如何在运行时将控件置于设计状态模式,就像Delphi表单设计器那样,那些控件没有动画(基于Windows主题)并且无法获得焦点或输入?

为了使这一点更清晰,请根据以上代码示例查看此示例图像,其中控件不再处于活动状态,但TButton的动画颜色仍处于活动状态:

Controls are in Design State mode

但实际应该是:

Controls are in Design State mode

从上面的两张图片中,只能与TCheckBox控件进行交互。

是否有某个程序隐藏在可以改变控件状态的地方?或者也许更合适的方法来实现这一目标?到目前为止我设法得到的代码只会带来更多问题。

将控件设置为Enabled := False也不是我要找的答案,是的,行为有点相同但当然控件的颜色不同以显示它们已被禁用,这不是我要找的

3 个答案:

答案 0 :(得分:5)

您正在寻找的不是控件本身的功能,而是表单设计器本身的实现。在设计时,用户输入在被任何给定控件处理之前被截获。 VCL定义了一条CM_DESIGNHITTEST消息,允许每个控件指定是否要在设计时接收用户输入(例如,允许视图调整列表/网格列标题的大小)。这是一个选择加入功能。

但是,您可以执行的操作是将所需的控件放在无边框TPanel上,然后根据需要启用/禁用TPanel。这将有效地启用/禁用其子控件的所有用户输入和动画。此外,当TPanel被禁用时,子控件不会将自身呈现为禁用。

答案 1 :(得分:1)

Remy Lebeau关于将控件放入容器(如TPanel),然后将面板设置为Enabled := False的答案确实将控件置于我正在寻找的状态。我还发现,覆盖控件WM_HITTEST会将控件置于同一状态,例如,它们不会获得焦点而无法与之交互。这两个问题是控件仍然需要能够响应MouseDownMouseMoveMouseUp事件等,但它们不再能够响应。

Remy还建议编写一个类并实现Vcl.Forms.IDesignerHook,这是我尚未尝试过的东西,因为它可能需要太多的工作来满足我的需求。

无论如何,经过大量的游戏后我发现了另一种替代方式,它涉及使用PaintTo将控件绘制到画布上。我做的步骤如下:

  • 使用展开的TPanel
  • 创建自定义Canvas
  • FormCreate创建自定义面板并将其与客户端对齐
  • 在运行时向表单添加控件(将自定义面板置于前面)
  • 将控件PaintTo方法调用到自定义面板Canvas

这实际上是在创建组件并使用Form作为父级,我们的自定义面板位于顶部。然后将控件绘制到面板画布上,使其看起来好像控件位于面板上,实际上它位于未受干扰的表单下方。

由于控件位于面板下方,为了让他们响应MouseDownMouseMoveMouseUp等事件,我覆盖了面板中的WM_NCHitTest并将结果设置为HTTRANSPARENT

在代码中它看起来像这样:

自定义面板:

type
  TMyPanel = class(TPanel)
  protected
     procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHitTest;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;

    property Canvas;
  end;

{ TMyPanel }

constructor TMyPanel.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);

  Align := alClient;
  BorderStyle := bsNone;
  Caption := '';
end;

destructor TMyPanel.Destroy;
begin
  inherited Destroy;
end;

procedure TMyPanel.WMNCHitTest(var Message: TWMNCHitTest);
begin
  Message.Result := HTTRANSPARENT;
end;

<强> 形式:

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    FMyPanel: TMyPanel;
    procedure ControlMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
  public
    { Public declarations }
  end;

{ TForm1 }

procedure TForm1.FormCreate(Sender: TObject);
begin
  FMyPanel := TMyPanel.Create(nil);
  FMyPanel.Parent := Form1;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FMyPanel.Free;
end;

procedure TForm1.ControlMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Sender is TWinControl then
  begin
    ShowMessage('You clicked: ' + TWinControl(Sender).Name);
  end;
end;

将TButton添加到表单的示例:

procedure TForm1.Button1Click(Sender: TObject);
var
  Button: TButton;
begin
  Button := TButton.Create(Form1);
  Button.Parent := Form1;

  FMyPanel.BringToFront;

  with Button do
  begin
    Caption := 'Button';
    Left := 25;
    Name := 'Button';
    Top  := 15;
    OnMouseDown := ControlMouseDown;

    PaintTo(FMyPanel.Canvas, Left, Top);
    Invalidate;
  end;
end;

如果您尝试运行上述内容,您会看到我们创建的TButton没有动画或获得焦点,但它可以响应我们在上面的代码中附加的MouseDown个事件,因为我们实际上并不是这样看着控件,我们正在查看控件的图形副本。

答案 2 :(得分:0)

我不确定这是不是你想要的,但是Greatis有一个Form Designer组件。请参阅:http://www.greatis.com/delphicb/formdes/