Remove focus from a Delphi component [duplicate] - delphi

I have a DB component which DataLink.UpdateRecord is called when it receives CM_EXIT message. This message is sent when it loses focus. When I click post button, it doesn't lose focus and value is not written to datasource. How can I reach an effect of component losing focus without switching it to other one?

You could use:
procedure TCustomForm.DefocusControl(Control: TWinControl; Removing: Boolean);

We accomplish this by setting the Self.ActiveControl := nil. That causes all of the exit events to fire. In our case we wanted to also re-focus back to the control after the save took place. That required a few extra checks to ensure we had a good control that could accept focus.
procedure TSaleEditor.SaveCurrentState();
var
SavedActiveControl: TWinControl;
AlternateSavedControl: TWinControl;
begin
// Force the current control to exit and save any state.
if Self.ActiveControl <> nil then
begin
SavedActiveControl := Self.ActiveControl;
// We may have an inplace grid editor as the current control. In that case we
// will not be able to reset it as the active control. This will cause the
// Scroll box to scroll to the active control, which will be the lowest tab order
// control. Our "real" controls have names, where the dynamic inplace editor do not
// find an Alternate control to set the focus by walking up the parent list until we
// find a named control.
AlternateSavedControl := SavedActiveControl;
while (AlternateSavedControl.Name = '') and (AlternateSavedControl.Parent <> nil) do
begin
AlternateSavedControl := AlternateSavedControl.Parent;
end;
Self.ActiveControl := nil;
// If the control is a radio button then do not re-set focus
// because if you are un-selecting the radio button this will automatically
// re-select it again
if (SavedActiveControl.CanFocus = true) and
((SavedActiveControl is TcxRadioButton) = false) then
begin
Self.ActiveControl := SavedActiveControl;
end
else if (AlternateSavedControl.CanFocus = true) and
((AlternateSavedControl is TcxRadioButton) = false) then
begin
Self.ActiveControl := AlternateSavedControl;
end;
end;
end;

Have a look at TCustomForm.FocusControl. You can't make it lose focus without switching focus to something else, but you can switch and then immediately switch back, which would probably work.

There is a SetFocus function in windows unit. Try this:
Windows.SetFocus(0);

Related

Disable/Enable Control onPaint event , but Changes not reflecting

I am trying to Enable/Disable control in OnPaint event, but Changes are not getting reflected. How ever if toggle with other application changes getting reflected(using ALT+TAB)
procedure TfrmBase.FormPaint(Sender: TObject);
var
...
...
begin
flg := False;
for i := ComponentCount - 1 downto 0 do
begin
Temp := Components[i];
if (Temp is TToolButton) then
begin
(Temp as TToolButton).Enabled := SomeFuncWhichReturnBoolean;
end
else if (Temp is TButton) then
(Temp as TButton).Enabled := SomeFuncWhichReturnBoolean ;
end;
end;
Please suggest
OnPaint is for painting, and it's not the right moment to change states. Doing this will at best trigger another paint, or at worst it won't. So either the code doesn't work, or it works inefficiently. Moreover, Paint isn't called all the time. Even when you move the form around there is no guarantee that it will be repainted. So, as a trigger, this is a very unreliable event.
Instead, toggle the control when it it added to or removed from DisableControlList. Changing Enabled of the control should trigger a repaint, so you don't have to worry about that part.
You failed to mention what kind of list this is, but maybe it has an OnChange event you can use, or you can wrap it or inherit from it to implement the toggle without making it the responsibility of the procedure that added the control to the list. The code you now have, should be in that OnChange event.
Generally spoken, there is a time to change the states and there is a time to paint the current states. Don't mix them up.
Each button represents an action that will happen when you press the button and this action is maybe allowed or not.
Delphi has a TActionList where you can manage actions. Each action has an OnExecute (what should happen) and an OnUpdate event. This OnUpdate event is a perfect place to enable or disable the action.
procedure TFoo.BarActionExecute(Sender:TObject);
begin
DoBarAction();
end;
procedure TFoo.BarActionUpdate(Sedner:TObject);
begin
(Sender as TAction).Enabled := CanDoBarAction();
end;
Just wire up all the buttons with the actions from your TActionList

How to prevent focused control from scrolling when the mouse isn't over it?

Refer to this prior related question. While the answers there do work, I have further issues when it comes to certain types of controls such as a TDBGrid. If the TDBGrid currently has focus, but the mouse is pointed over another control to scroll, the TDBGrid scrolls anyway, thus resulting in two different controls scrolling at the same time. This happens in every single solution which I've found so far related to scrolling the control underneath the mouse.
How can I prevent this behavior and ensure that only the control under the mouse scrolls, and nothing else?
This code works fine for me.
procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
var
Control: TWinControl;
begin
if Msg.message = WM_MOUSEWHEEL then
begin
// Find control at mouse cursor
Control := FindVCLWindow(Msg.pt);
if Assigned(Control) then
begin
// Try to scroll
if Control.Perform(CM_MOUSEWHEEL, Msg.wParam, Msg.lParam) <> 0 then
Handled := True
else
// If no scroll was performed by control,
// then detrmine if message control is at mouse cursor.
// If not, then supress message
if Control.Handle <> Msg.hwnd then
Handled := True;
end;
end;
end;

Disabling the form still allow childs controls to receive input

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 known 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...

How do I know when a control can be focused?

I have my own Treeview control derived from TCustomTreeView.
I have added some of my own procedures to the class such as adding nodes. When this procedure is called at runtime I wanted the newly added node to be selected and for the Treeview to be focused so the new node is highlighted.
Here is a extract:
procedure TMyTreeView.AddGroup(AName: string);
var
Node: TTreeNode;
Obj: TGroup;
procedure AddToTree;
begin
Obj := TGroup.Create(AName);
FGroups.Add(Obj);
Node := Items.AddObject(Node, AName, Obj);
with Node do
begin
ImageIndex := 0;
SelectedIndex := 0;
end;
Selected := Node;
SetFocus;
end;
begin
Node := nil;
AddToTree;
end;
The above works but I am facing the common error message when calling from the Forms OnCreate event:
Cannot focus a disabled or invisible window
I know you can use the OnActivate event or just don't use OnCreate at all which will not result in the error, but anyone else who may use the component may not realise this.
So I wanted to know if there is a way to determine if my Treeview (or any control) is able to receive the focus, then I could add a little checking of my own, something like:
if ControlIsFocusable then
begin
Selected := Node;
SetFocus;
end;
I know there is the Loaded procedure you can override which tells us when the control is loaded but then that would only work on first run. If the control became hidden by the user at runtime (or was not visible to begin with) the Cannot focus a disabled or invisible window error is still going to show up.
The dirty way to do it when not run in the debugger is:
try
Selected := Node;
SetFocus;
except
end;
But that defeats the purpose and I hate handling errors in this way.
So basically I wanted to know if there was a way to determine if a control can receive focus, so that we can set the focus to it?
I'm not going to answer the question that you asked, because I think you are doing this wrong.
The control should not call SetFocus on itself. I can imagine no scenario where that is the correct behaviour. The form or application or framework should determine focus. Not the control.
Imagine what happens when you have a form with two such controls? Imagine using the keyboard to focus a button, which you then press with the SPACE bar. If the action attached to the button calls your control's method which then changes the focus, you've just gone against the platform UI guidelines. You control now places a severe burden on any application that attempts to consume it.

How to remove focus from currently focused component?

I have a DB component which DataLink.UpdateRecord is called when it receives CM_EXIT message. This message is sent when it loses focus. When I click post button, it doesn't lose focus and value is not written to datasource. How can I reach an effect of component losing focus without switching it to other one?
You could use:
procedure TCustomForm.DefocusControl(Control: TWinControl; Removing: Boolean);
We accomplish this by setting the Self.ActiveControl := nil. That causes all of the exit events to fire. In our case we wanted to also re-focus back to the control after the save took place. That required a few extra checks to ensure we had a good control that could accept focus.
procedure TSaleEditor.SaveCurrentState();
var
SavedActiveControl: TWinControl;
AlternateSavedControl: TWinControl;
begin
// Force the current control to exit and save any state.
if Self.ActiveControl <> nil then
begin
SavedActiveControl := Self.ActiveControl;
// We may have an inplace grid editor as the current control. In that case we
// will not be able to reset it as the active control. This will cause the
// Scroll box to scroll to the active control, which will be the lowest tab order
// control. Our "real" controls have names, where the dynamic inplace editor do not
// find an Alternate control to set the focus by walking up the parent list until we
// find a named control.
AlternateSavedControl := SavedActiveControl;
while (AlternateSavedControl.Name = '') and (AlternateSavedControl.Parent <> nil) do
begin
AlternateSavedControl := AlternateSavedControl.Parent;
end;
Self.ActiveControl := nil;
// If the control is a radio button then do not re-set focus
// because if you are un-selecting the radio button this will automatically
// re-select it again
if (SavedActiveControl.CanFocus = true) and
((SavedActiveControl is TcxRadioButton) = false) then
begin
Self.ActiveControl := SavedActiveControl;
end
else if (AlternateSavedControl.CanFocus = true) and
((AlternateSavedControl is TcxRadioButton) = false) then
begin
Self.ActiveControl := AlternateSavedControl;
end;
end;
end;
Have a look at TCustomForm.FocusControl. You can't make it lose focus without switching focus to something else, but you can switch and then immediately switch back, which would probably work.
There is a SetFocus function in windows unit. Try this:
Windows.SetFocus(0);

Resources