禁用表单仍然允许子控件接收输入 [英] Disabling the form still allow childs controls to receive input

查看:120
本文介绍了禁用表单仍然允许子控件接收输入的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述


$ b我在delphi最后几天遇到了很多令人头痛的事情,我试图做的很简单,阻止了某个点的界面。 $ b

但是简单的说,我无法弄清楚为什么某些设计是允许的,所以要澄清:



1)创建一个项目< 2)在表单中放置编辑和按钮,编辑的标签顺序必须首先



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

 启用:= False; 

4)配置按钮的OnClick事件并写入:

  ShowMessage('这是对的)'; 

基本上就是这样,现在编译,重点将在编辑,按Tab和表单将按照我们的要求被禁用,所以相应地按照选项卡顺序,下一个控件来获取关注点是按钮(但是我们禁用了表单),现在按空格,消息应该出现。



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



thx提前。

解决方案

p>两个 TButton TEdit TWinControl 后裔 - 这意味着他们是窗口控件。当他们被创建时,它们被分配自己的 HWND ,并且操作系统在他们有焦点时直接向他们发送消息。禁用其包含的表单可防止主窗体接收输入消息或接收焦点,但如果已经具有输入焦点,则不会禁用任何其他窗口控件。



如果这些控件没有输入焦点,则当用户输入(点击,标签键等)指定时,包含表单有责任将输入焦点传输给他们。如果表单被禁用,并且这些控件没有被聚焦,则表单将不会接收到允许它转移焦点的输入消息。然而,如果焦点被转移到窗口控件,则所有用户输入都直接转到该控件,即使它们的父控件的窗口被禁用,它们实际上也是它们自己单独的窗口。



我不知道你观察到的行为是一个错误 - 这可能不是预期的,但它是标准行为。通常不希望禁用一个窗口也会禁用同一应用程序中的其他窗口。



问题是有两个单独的层次结构。在VCL级别上,Button是一个子控件,并具有一个父级(表单)。然而,在操作系统级别上,这两个都是单独的窗口,并且操作系统不知道(组件级别)父/子关系。这将是一个类似的情况:

  procedure TForm1.Button1Click(Sender:TObject); 
var
form2:TForm1;
begin
self.Enabled:= false;
form2:= TForm1.Create(self);
try
form2.ShowModal;
finally
form2.Free;
结束
结束

你真的希望 form2 被禁用当它被显示时,只是因为它的 TComponent 所有者是 Form1 ?当然不是窗口控件是非常相同的。



Windows本身也可以具有父/子关系,但这与组件所有权(VCL父/子)是分开的,并不一定以相同的方式表现。 从MSDN


系统将子窗口的输入消息直接传递到
子窗口;消息不会通过父窗口传递。
唯一的例外是如果子窗口被
EnableWindow功能禁用
。在这种情况下,系统会将任何输入的
消息传递到子窗口到父窗口
。这允许父窗口检查输入消息
,并在必要时启用子窗口。


强调我的 - 如果您禁用子窗口,然后其消息将路由到父级,以便机会检查并对其执行操作。相反的情况是不正确的 - 残疾的父母不会阻止孩子接收消息。



一个相当乏味的解决方法可能是使自己的一套 TWinControl s表现如下:

  TSafeButton = class(TButton)
protected
程序WndProc(var Msg:TMessage);覆盖
结束

{...}

程序TSafeButton.WndProc(var Msg:TMessage);
函数ParentForm(AControl:TWinControl):TWinControl;
开始
如果分配(AControl)和(AControl是TForm)然后
result:= AControl
else
如果分配(AControl.Parent)然后
result:= ParentForm(AControl.Parent)
else result:= nil;
结束
begin
如果已分配(ParentForm(self))和(不是ParentForm(self).Enabled)然后
Msg.Result:= 0
else
继承;
结束

这将爬上VCL父树,直到找到一个表单 - 如果是,并且表单被禁用那么它也拒绝对窗口控件的输入。凌乱,可能会更有选择性(也许一些消息不应该被忽略...),但这将是可以起作用的开始。



进一步挖掘,这似乎与与文档有所不同


一次只能有一个窗口可以接收键盘输入;那个窗口是
表示有键盘焦点。如果一个应用程序使用
EnableWindow函数来禁用键盘对焦窗口,窗口
除了被禁用外,还会失去键盘焦点。 EnableWindow
然后将键盘焦点设置为NULL,这意味着没有窗口具有焦点。
如果子窗口或其他后代窗口具有键盘焦点,则当父窗口为
禁用时,
后代窗口失去焦点。有关详细信息,请参阅键盘输入。


这似乎不会发生,甚至显式地将按钮的窗口设置为一个小孩:

  oldParent:= WinAPI.Windows.SetParent(Button1.Handle,Form1.Handle); 
//这里,其实oldParent = Form1.Handle,所以父/子HWND
//关系是默认的。

更多(为了重​​复) - 相同的场景编辑选项卡专注于按钮,退出处理程序启用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建议应该为空)。

  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
如果不启用,(Parent<> nil)则为RemoveFocus(False);
// ^^如果表单本身被禁用,则为False!
如果HandleAllocated而不是(csDesigning在ComponentState)然后
EnableWindow(WindowHandle,Enabled);
结束
procedure TWinControl.RemoveFocus(Remove:Boolean);
var
表单:TCustomForm;
begin
Form:= GetParentForm(Self);
如果Form<> nil然后Form.DefocusControl(Self,Remove);
end

其中

  procedure TCustomForm.DefocusControl(Control:TWinControl; Remove:Boolean); 
begin
如果Remove和Control.ContainsControl(FFocusedControl)然后
FFocusedControl:= Control.Parent;
如果Control.ContainsControl(FActiveControl)然后SetActiveControl(nil);
结束

这部分解释了上述观察到的行为 - 焦点移动到父控件,主动控制失去焦点。它仍然不能解释为什么EnableWindow无法将焦点杀死到按钮的子窗口。这确实开始似乎是一个WinAPI问题...


I'm having a lot of headache in the last days with delphi, what im trying to do is a lot simple, block the interface at somepoint and enable after some other point.

But as simply as it sound i couldn't figure out why somethings are allowed by design, so to clarify:

1) create a project

2) in the form put a edit and a button, tab order of the edit must be first

3) configure the OnExit event of the edit and write:

Enabled := False; 

4) configure the OnClick event of the button and write:

ShowMessage('this is right?');

basically this is it, now compile, the focus it will be at the edit, press tab and the form will be disabled as we demanded, so accordingly to the tab order the next control to gain focus is the button (but we disabled the form), now press space and the message should come up.

so the question is: is this right? whats the logical explanation to this behaviour?

thx in advance.

解决方案

Both TButton and TEdit are TWinControl descendents - this means that they are windowed controls. When they are created they are allocated their own HWND and the operating system posts messages to them directly when they have focus. Disabling their containing form prevents the main form from receiving input messages or from receiving focus but it does not disable any other windowed control if it already has input focus.

If these controls do not have input focus, it is responsibility of the containing form to transfer input focus to them when user input (click, tab key, etc) dictates. If the form is disabled and these controls are not focused then the form will not receive the input messages that would allow it to transfer focus. If focus is transferred to a windowed control, however, then all user input goes directly to that control, even if their parent control's window is disabled - they are in fact their own separate windows.

I'm not sure the behaviour you have observed is a bug - it is perhaps not expected, but it is standard behaviour. There is generally no expectation that disabling one window will also disable others within the same application.

The problem is that there are two separate hierarchies in play. On the VCL level, the Button is a child control and has a parent (the form). On the OS level, however, both are separate windows and the (component level) parent/child relationship is not know to the OS. This would be a similar situation :

procedure TForm1.Button1Click(Sender: TObject);
var
  form2 : TForm1;
begin
  self.Enabled := false;
  form2 := TForm1.Create(self);
  try
    form2.ShowModal;
  finally
    form2.Free;
  end;
end;

Would you really expect form2 to be disabled when it was shown, simply because its TComponent owner is Form1? Surely not. Windowed controls are much the same.

Windows themselves can also have a parent/child relationship, but this is separate from component ownership (VCL parent/child) and does not necessarily behave in the same way. From MSDN:

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

Emphasis mine - if you disable a child window then its messages will be routed to the parent for an opportunity to inspect and act upon them. The reverse is not true - a disabled parent will not prevent a child from receiving messages.

A rather tedious workaround could be to make your own set of TWinControls that behave like this :

 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;

This walks up the VCL parent tree until it finds a form - if it does and the form is disabled then it rejects input to the windowed control as well. Messy, and probably could be more selective (maybe some messages should not be ignored...) but it would be the start of something that could work.

Digging further, this does seem to be at odds with the documentation :

Only one window at a time can receive keyboard input; that window is said to have the keyboard focus. If an application uses the EnableWindow function to disable a keyboard-focus window, the window loses the keyboard focus in addition to being disabled. EnableWindow then 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 is disabled. For more information, see Keyboard Input.

This does not seem to happen, even explicitly setting the button's window to be a child with :

 oldParent := WinAPI.Windows.SetParent(Button1.Handle, Form1.Handle);
 // here, in fact, oldParent = Form1.Handle, so parent/child HWND
 // relationship is correct by default.

A bit more (for repro) - same scenario Edit tabs focus to button, exit handler enables TTimer. Here the form is disabled, but the button retains focus even though this seems to confirm that Form1's HWND is indeed the parent window of the button and it should lose focus.

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;

In the case where we move the button into a panel, everything seems to work (mostly) as expected. The panel is disabled and the button loses focus, but focus then moves to the parent form (WinAPI suggests it should be 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;

Part of the problem seems to be here - it looks like the top form itself is taking responsibility for defocusing controls. This works except when the form itself is the one being disabled :

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

Where

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;

This partially explains the above observed behaviour - focus moves to the parent control and the active control loses focus. It still doesn't explain why the 'EnableWindow` fails to kill focus to the button's child window. This does start to seem like a WinAPI problem...

这篇关于禁用表单仍然允许子控件接收输入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆