gpt4 book ai didi

delphi - 禁用表单仍然允许子控件接收输入

转载 作者:行者123 更新时间:2023-12-03 14:39:00 25 4
gpt4 key购买 nike

在使用 delphi 的最后几天,我很头疼,我想做的很简单,在某个时间点阻止界面并在其他时间点后启用。

但就像听起来一样,我无法弄清楚为什么设计允许某些东西,所以要澄清一下:

1)创建项目

2)在表单中放置一个编辑和一个按钮,编辑的标签顺序必须在前

3)配置编辑写入的OnExit事件:

Enabled := False; 

4)配置按钮的OnClick事件并写入:
ShowMessage('this is right?');

基本上就是这样,现在编译,焦点将在编辑处,按 Tab 键,表单将根据我们的要求被禁用,因此根据 Tab 键顺序,下一个获得焦点的控件是按钮(但我们禁用了表单),现在按空格键,消息应该会出现。

所以问题是:这是对的吗?这种行为的合乎逻辑的解释是什么?

提前谢谢。

最佳答案

两者 TButtonTEditTWinControl后代 - 这意味着它们是窗口控件。当它们被创建时,它们被分配了自己的 HWND当他们获得焦点时,操作系统会直接向他们发送消息。禁用它们的包含表单会阻止主表单接收输入消息或接收焦点,但如果它已经具有输入焦点,则不会禁用任何其他窗口控件。
如果这些控件没有输入焦点,则包含表单有责任在用户输入(单击、Tab 键等)指示时将输入焦点转移给它们。如果表单被禁用并且这些控件未获得焦点,则表单将不会接收允许其转移焦点的输入消息。但是,如果焦点转移到窗口控件,则所有用户输入都会直接转到该控件,即使其父控件的窗口被禁用 - 它们实际上是它们自己的单独窗口。
我不确定您观察到的行为是错误 - 这可能不是预期的,但它是标准行为。通常不会期望禁用一个窗口也会禁用同一应用程序中的其他窗口。
问题是有两个独立的层次结构在起作用。在 VCL 级别,Button 是一个子控件并且有一个父控件(窗体)。然而,在操作系统级别,两者都是单独的窗口,操作系统不知道(组件级别)父/子关系。这将是类似的情况:

procedure TForm1.Button1Click(Sender: TObject);
var
form2 : TForm1;
begin
self.Enabled := false;
form2 := TForm1.Create(self);
try
form2.ShowModal;
finally
form2.Free;
end;
end;
你真的会期待 form2在显示时被禁用,仅仅是因为它的 TComponent店主是 Form1 ?当然不是。窗口控件大致相同。
Windows 本身也可以有父/子关系,但这与组件所有权(VCL 父/子)是分开的,并且不一定以相同的方式运行。 From MSDN :

The system passes a child window's input messages directly to thechild window; the messages are not passed through the parent window.The only exception is if the child window has been disabled by theEnableWindow function. In this case, the system passes any inputmessages that would have gone to the child window to the parent windowinstead. This permits the parent window to examine the input messagesand enable the child window, if necessary.


强调我的 - 如果您禁用子窗口,那么它的消息将被路由到父窗口,以便有机会检查它们并对其采取行动。反之则不然——残疾的 parent 不会阻止 child 接收消息。
一个相当乏味的解决方法可能是制作自己的一套 TWinControl s 的行为是这样的:
 TSafeButton = class(TButton)
protected
procedure WndProc(var Msg : TMessage); override;
end;

{...}

procedure TSafeButton.WndProc(var Msg : TMessage);
function ParentForm(AControl : TWinControl) : TWinControl;
begin
if Assigned(AControl) and (AControl is TForm) then
result := AControl
else
if Assigned(AControl.Parent) then
result := ParentForm(AControl.Parent)
else result := nil;
end;
begin
if Assigned(ParentForm(self)) and (not ParentForm(self).Enabled) then
Msg.Result := 0
else
inherited;
end;
这会沿着 VCL 父树向上走,直到找到一个表单——如果它找到了并且表单被禁用,那么它也会拒绝对窗口控件的输入。凌乱,可能更有选择性(也许有些消息不应该被忽略......)但这将是一些可行的开始。
进一步挖掘,这似乎是不一致的 with the documentation :

Only one window at a time can receive keyboard input; that window issaid to have the keyboard focus. If an application uses theEnableWindow function to disable a keyboard-focus window, the windowloses the keyboard focus in addition to being disabled. EnableWindowthen sets the keyboard focus to NULL, meaning no window has the focus.If a child window, or other descendant window, has the keyboard focus,the descendant window loses the focus when the parent window isdisabled. For more information, see Keyboard Input.


这似乎不会发生,甚至明确地将按钮的窗口设置为具有以下内容的子窗口:
 oldParent := WinAPI.Windows.SetParent(Button1.Handle, Form1.Handle);
// here, in fact, oldParent = Form1.Handle, so parent/child HWND
// relationship is correct by default.
多一点(用于重现) - 相同的场景 Edit选项卡焦点到按钮,退出处理程序启用 TTimer。这里表单被禁用,但按钮保持焦点,即使这似乎确认 Form1 的 HWND 确实是按钮的父窗口,它应该失去焦点。
procedure TForm1.Timer1Timer(Sender: TObject);
var
h1, h2, h3 : cardinal;
begin
h1 := GetFocus; // h1 = Button1.Handle
h2 := GetParent(h1); // h2 = Form1.Handle
self.Enabled := false;
h3 := GetFocus; // h3 = Button1.Handle
end;
的情况下我们将按钮移动到面板 ,一切似乎都按预期工作(大部分)。面板被禁用,按钮失去焦点,但焦点然后移动到父窗体(WinAPI 建议它应该是 NULL)。
procedure TForm1.Timer1Timer(Sender: TObject);
var
h1, h2, h3 : cardinal;
begin
h1 := GetFocus; // h1 = Button1.Handle
h2 := GetParent(h1); // h2 = Panel1.Handle
Panel1.Enabled := false;
h3 := GetFocus; // h3 = Form1.Handle
end;
问题的一部分似乎在这里 - 看起来顶级表单本身正在为散焦控件负责。这有效,除非表单本身被禁用:
procedure TWinControl.CMEnabledChanged(var Message: TMessage);
begin
if not Enabled and (Parent <> nil) then RemoveFocus(False);
// ^^ False if form itself is being disabled!
if HandleAllocated and not (csDesigning in ComponentState) then
EnableWindow(WindowHandle, Enabled);
end;
procedure TWinControl.RemoveFocus(Removing: Boolean);
var
Form: TCustomForm;
begin
Form := GetParentForm(Self);
if Form <> nil then Form.DefocusControl(Self, Removing);
end
哪里
procedure TCustomForm.DefocusControl(Control: TWinControl; Removing: Boolean);
begin
if Removing and Control.ContainsControl(FFocusedControl) then
FFocusedControl := Control.Parent;
if Control.ContainsControl(FActiveControl) then SetActiveControl(nil);
end;
这部分解释了上述观察到的行为 - 焦点移动到父控件,事件控件失去焦点。它仍然没有解释为什么“EnableWindow”无法将焦点移到按钮的子窗口。这确实开始看起来像一个 WinAPI 问题......

关于delphi - 禁用表单仍然允许子控件接收输入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28408953/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com