如何像表单设计器一样将控件置于设计状态模式?
Posted
技术标签:
【中文标题】如何像表单设计器一样将控件置于设计状态模式?【英文标题】:How to put controls into a Design State Mode just like the Form Designer does? 【发布时间】:2015-06-12 06:03:50 【问题描述】:这个问题让我困惑了一段时间,也许答案很简单,或者它涉及更多的 VCL 黑客或魔法来完成我正在寻找的东西,但无论哪种方式,我都不知道如何来解决我的问题。
如果您查看 Delphi 表单设计器,您会发现当鼠标移到它们上方时,没有任何控件动画,它们也无法接收焦点或输入(例如,您无法在 TEdit 中键入、单击 TCheckBox 或移动 TScrollBar等),只有在运行时控件才能正常运行并响应用户交互。
我想知道如何在运行时为任何控件实现这种类型的行为,例如将控件设置为设计器状态模式?但是,控件还应该响应鼠标事件,例如 OnMouseDown
、OnMouseMove
、OnMouseUp
等,以便在需要时可以移动它们并调整它们的大小。
这是我管理的最接近的:
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 的动画绘制仍然处于活动状态:
但实际上应该是:
从上面两张图可以看出,只有TCheckBox控件可以交互。
是否有隐藏在某个地方的过程可以更改控件的状态?或者也许是更合适的方法来实现这一目标?到目前为止,我设法得到的代码只会带来更多问题。
将控件设置为Enabled := False
也不是我要寻找的答案,是的,行为有点相同,但当然控件的绘制方式不同,以显示它们被禁用,这不是我想要的。
【问题讨论】:
你试过csDesigning in ComponentState
吗?
我对您的问题有了更好的理解,ComponentState
不一定是解决方案。
@JerryDodge 我看了ControlStyle
和ControlState
无法得到任何工作,除非我做错了,而且我认为csDesigning 是只读的,以后需要再看一遍现在从我的机器上。
【参考方案1】:
您要查找的不是控件本身的功能,而是表单设计器本身的实现。在设计时,用户输入在任何给定控件处理之前被截获。 VCL 定义了一个CM_DESIGNHITTEST
消息,以允许每个控件指定它是否希望在设计时接收用户输入(例如,允许列表/网格列标题的可视化调整大小)。这是一项可选功能。
不过,您可以将所需的控件放在无边框的TPanel
上,然后根据需要简单地启用/禁用TPanel
本身。这将有效地启用/禁用其子控件的所有用户输入和动画。此外,当TPanel
被禁用时,子控件不会将自己呈现为禁用状态。
【讨论】:
我想你是对的,我认为我过于关注控制级别而没有考虑到 Delphi 表单设计器可能会拦截这些消息。使用像 TPanel 这样的容器控件也是一个好主意,只是这样会禁用所有子控件 - 而不是特定的子控件。 设计器不会“包装”每个控件。设计器将 IDesignerHook 接口的实现“附加”到设计的 TCustomForm。在 VCL 内部,每个控件将通过拥有 TCustomForm 的 IDesignerHook.IsDesignMsg() 引导每个 Windows 消息。如果该函数返回 True,则该消息已被设计者“处理”。这包括鼠标和键盘消息。 我发现使用 TPanel 的问题是当Enabled := False
面板不响应诸如 OnMouseDown 之类的事件
对,因为它被禁用了,所以它不会接收任何用户输入。但是,为什么面板需要 OnMouseDown 呢?您只是将它用作容器来帮助您启用/禁用子控件。
好吧,你总是可以编写一个实现Vcl.Forms.IDesignerHook
的类,并在运行时将其分配给父Form的Designer
属性,然后在ComponentState
属性中启用csDesigning
标志每个所需的控件,然后这些控件将调用您的 IsDesignMsg()
方法,就像它们在设计时为实际的表单设计器所做的那样。然后你可以对他们做任何你想做的事情。【参考方案2】:
我不确定这是否是您所追求的,但 Greatis 有一个表单设计器组件。见:http://www.greatis.com/delphicb/formdes/
【讨论】:
也许这是一个评论【参考方案3】:Remy Lebeau 关于将控件放入容器(例如 TPanel)然后将面板设置为 Enabled := False
的回答确实将控件置于我正在寻找的状态。我还发现覆盖控件WM_HITTEST
会将控件置于相同的状态,例如它们不接收焦点并且不能与之交互。这两个的问题是控件仍然需要能够响应MouseDown
、MouseMove
和MouseUp
事件等,但它们不再不能。
Remy 还建议编写一个类并实现 Vcl.Forms.IDesignerHook
,我还没有尝试过,因为它可能需要太多工作才能满足我的需要。
无论如何,在玩了很多之后,我找到了另一种替代方法,它涉及使用PaintTo
将控件绘制到画布上。我做的步骤如下:
Canvas
创建自定义TPanel
在 FormCreate
创建自定义面板并将其与客户端对齐
在运行时向表单添加控件(将自定义面板置于最前面)
在自定义面板上调用控件PaintTo
方法Canvas
这实际上是在创建组件并使用表单作为父级,我们的自定义面板位于顶部。然后将控件绘制到面板画布上,使其看起来好像控件在面板上,而实际上它位于不受干扰的表单下方。
因为控件位于面板下方,为了让它们响应MouseDown
、MouseMove
和MouseUp
等事件,我覆盖了面板中的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
事件,那是因为我们实际上并没有查看控件,而是查看控件的图形副本。
【讨论】:
以上是关于如何像表单设计器一样将控件置于设计状态模式?的主要内容,如果未能解决你的问题,请参考以下文章