Focus control after form is shown - delphi

I have some forms that help me search for a product or a customer. When i open these forms i want the TEdit control that i type into for searching to be focused. I've been using a Timer for that but i've been searching for a more legit way to do this as this causes errors sometimes if the control is told to be focused when the form is not visible yet.
I've tried to use a windows message AfterShow that is called on the end of OnShow event of my Form. It doesn't work as the other simpler solutions of ActiveControl or SetFocus. The window message code is this.
const WM_AFTER_SHOW = WM_USER + 444;
private
procedure WmAfterShow(var Msg: TMessage); message WM_AFTER_SHOW;
procedure Tproducts_edit_form.WmAfterShow(var Msg: TMessage);
begin
self.ActiveControl:= search_txt;
//showmessage(Screen.ActiveControl.Name);
//PostMessage(search_txt.Handle, WM_SETFOCUS, 0, 0);
end;
Strange thing is that if uncomment both the showmessage and the postmessage, the TEdit gets the focus correctly. If i don't, the form opens but the TEdit is not focused even if the Screen.ActiveControl.Name tells me that the control i want has the focus.
Any ideas?

It is correct to use Form.ActiveControl (not Screen.ActiveControl) property to set focus on control, but use it in OnShow, not in OnCreate etc..:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormShow(TObject *Sender)
{
ActiveControl = Edit1;
}
If it doesn't work, maybe because of manual interfering with window message handler, message queue.

The windowsmessage technique is working and the TabOrder of the control to be focused indeed has to be zero.
The problem i had was lying in the DevExpress Bar that my control is docked. The way these bars work makes it impossible to focus a non-DevExpress control that is docked into a DevExpress Bar.

Related

Displaying a disabled modal form

I'm trying to disable a TForm's descendant and showing it as a modal form.
procedure TForm1.Button1Click(Sender: TObject);
var
Frm : TMyForm;
begin
Frm := TMyForm.Create(nil);
try
Frm.Enabled := False;
Frm.ShowModal();
finally
Frm.Free;
end;
end;
At runtime, it raises the following error message:
Cannot make a visible window modal.
The OP wants to display a disabled form modally when the form should be displayed for read-only purposes.
Disabling the form is the wrong thing to do.
How do you display the information? If you are using TEdit, TMemo, or TRichEdit controls, you should simply set them to read only. Otherwise, if you have some combinations of various controls like radio buttons, you should disable each and every such control, not the form itself. I mean, surely you still want the Cancel button to be enabled?
In addition, disabling the form instead of the actual controls will make the controls look enabled, which is very confusing! That's an important point.
So what you need to do is to display the form normally (not disabled!) and then set its controls to their appropriate states when the dialog is shown.
Just to emphasise my point about disabling the form vs its controls, consider this dialog box:
If I do
procedure TCustomViewFrm.FormShow(Sender: TObject);
begin
Enabled := False;
end;
then it looks like this when shown:
As you can see, every control looks very enabled indeed, but no control responds to mouse or keyboard input. This is very confusing and a horribly bad UX.
In fact, you cannot even close the dialog box using its title-bar Close button or Alt+F4. You cannot close it using its system menu, either. In fact, you cannot close it at all, because to close a window, it must respond to user input, and a disabled window doesn't do that. (You cannot move the window, either.)
Instead, if we disable all controls (except the Cancel button),
procedure DisableControl(AControl: TWinControl);
begin
for var i := 0 to AControl.ControlCount - 1 do
begin
if
(AControl.Controls[i] is TCustomButton)
and
(TCustomButton(AControl.Controls[i]).ModalResult = mrCancel)
then
Continue;
if AControl.Controls[i] is TWinControl then
DisableControl(TWinControl(AControl.Controls[i]));
AControl.Controls[i].Enabled := False;
end;
end;
procedure TCustomViewFrm.FormShow(Sender: TObject);
begin
DisableControl(Self);
end;
you get this nice UI:
Not only is it very clear that all controls are disabled, the user can also close the dialog box without killing your application using the Task Manager.

Unusual form's appearance after calling TCustomForm.SetFocusedControl function

First of all, my goal isn't that to set the focus on a control but I'm trying to understand why a form has a different appearance after deactivating and reactivating it.
Here is TCustomForm.SetFocusedControl function's description:
Sets focus to a control on the form.
Use SetFocusedControl to give a control on the form input focus. SetFocusedControl returns false if the Control specified by the Control parameter was already in the process of receiving focus, true otherwise.
Note: A return value of true does not indicate the control has successfully received input focus. If the Control can't have focus (for example, if it is not visible), SetFocusedControl will still return true, indicating that an attempt was made.
I've created a simple test application in order to reproduce the observed behavior:
procedure TForm1.Button1Click(Sender: TObject);
var
Res : Boolean;
begin
Res := Self.SetFocusedControl(Edit2);
if(Self.ActiveControl <> nil) then
begin
Memo1.Text :=
'ActiveControl is ' + Self.ActiveControl.Name + sLineBreak +
'SetFocusedControl result is ' + BoolToStr(Res, True);
end;
end;
Here are steps I followed to reproduce that behavior:
1) At start, the form appears as follows:
2) After clicking on "Button1", this is what I can see:
Memo1.Text reports that Edit2 is the active control but it hasn't the tipical focused appearance (selection and cursor).
Form's caption isn't grayed and clicking on it doesn't cause any change.
3) I've clicked outside the form (on the windows taskbar).
Form's caption became grayed.
4) I've reactivated the form by clicking on its caption:
Edit2 now looks like it has focus.
Could someone explain the differences between the form at point 2 and at point 4? In both cases, Edit2 was the active control and I can't understand that appearance difference.
Further informations:
Tested on Delphi 2007, Windows 10 Pro.
Despite what the documentation says, TForm.SetFocusedControl() does not actually set input focus onto Edit2 (the Win32 SetFocus() function is not called). Button1 receives the input focus when clicked on, and remains in focus after SetFocusControl() is called. That is why Edit2 does not render as focused. If you want to move input focus to Edit2, call Edit2.SetFocus() instead of Self.SetFocusedControl(Edit2).
However, calling SetFocusedControl() does change the VCL's internal state. It sets the Form's ActiveControl and FocusedControl properties, and it sets the global Screen object's ActiveControl, ActiveCustomForm, ActiveForm, and FocusedForm propeties and reorders the entries of its CustomForms and Forms properties.
When the Form is deactivated and then reactivated, the VCL sets input focus to the last known "focused" control, which is now Edit2 instead of Button1.

TListView doesn't hide selection when using explorer style

In Delphi XE4 if you set HideSelection to true and use an explorer style TListView (when the selection rectangle has a gradient background like Windows Explorer) clicking on another control will not hide the selection rectangle. It will stay there as if nothing has happened - it will not even turn into a gray rectangle like normally when the Listview doesn't have focus.
Is this a Delphi bug or a "feature" of the MS Listview control? Are there any known workarounds or fixes for this? It's really annoying...
This is a feature of the underlying control. The delphi code does nothing with the property beyond passing on the LVS_SHOWSELALWAYS list view style to the underlying control.
Initially I was surprised by your question. I've never seen the behaviour that you describe. Upon closer inspection I realise that is because all my list views are virtual. That is they set OwnerData to True and supply content in response to OnData events. Doing that is the only workaround that I know of.
This "feature" is explained by David, and here is a workaround.
By utilizing the OnExit event to save the selection and set selection to nil, you would mimic the wanted behavior. When the ListView is focused, restore the selection.
To make it react on the mouse, make the ListView focused in the OnMouseEnter event.
Type
TForm1 = class(TForm)
...
private
FSelected: TListItem;
...
end;
procedure TForm1.ListView1Enter(Sender: TObject);
begin
if (ListView1.SelCount = 0) and Assigned(FSelected) then
ListView1.Selected := FSelected;
end;
procedure TForm1.ListView1Exit(Sender: TObject);
begin
FSelected := ListView1.Selected;
if Assigned(FSelected) then ListView1.Selected := Nil;
end;
procedure TForm1.ListView1MouseEnter(Sender: TObject);
begin
ListView1.SetFocus;
end;
Having mentioned this solution, why not go for the simple one, set HideSelection = false, and the selected item will turn gray when unfocused, just like Sertac mentioned in a comment.

draw a close button in each Ttabsheet of a TPageControl

I want to implement a close button on a PageControl and I have read this question also How to implement a close button for a TTabsheet of a TPageControl
The thing is I can't figure it out how to implement the code provided in the answer of Ulrichb... are they building a new component descendant from TPageControl or not? if someone could explain where to write that certain code i would be thankfull! I have a single teacher who knows a little bit of delphi at my school but he couldn`t help me out..and I am sorry if this is a silly question but i am new to delphi and programming.
The code in the question you link to does not create a new component. Instead it implements custom drawing by using events of the page control. Specifically these events:
OnDrawTab
OnMouseDown
OnMouseMove
OnMouseLeave
OnMouseUp
You must use the Delphi form designer to connect these event handlers up to the matching events to make the code work.
This approach was probably chosen for simplicity when answering that question but it does not scale to an application with many forms that have page controls. In that situation you would want to derive a new page control component.
If you do that then, rather than using events, you need to override the following methods:
DrawTab
MouseDown
MouseMove
MouseUp
In addition to this you must replicate the OnMouseLeave behaviour. That requires a message handler.
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
....
procedure TMyPageControl.CMMouseLeave(var Message: TMessage);
begin
inherited;
if Message.LParam=0 then
begin
// move OnMouseLeave code here
end;
end;

Avoid window getting focus

I am working on a virtual keyboard the problem is when i press a key on the virtual keyboard the window witch the data needs to be sent loses focus. How can i avoid that ?
When your keyboard form receives focus, part of the message it receives is the handle of the window that lost focus (wParam). Do what you need to do and set the focus back to the window that lost focus.
EDIT: See the documentation on WM_SETFOCUS
EDIT 2:
Also, you could use the following when creating your custom form:
procedure TMainForm.CreateParams(var Params: TCreateParams) ;
//const WS_EX_NOACTIVATE = $8000000;
begin
inherited;
Params.ExStyle := Params.ExStyle + WS_EX_NOACTIVATE;
end;
To prevent your form from activating (taking focus from the other form). Like I alluded to in my comment, you should probably be using non-windowed controls for keys.
The only method I've seen to do what you want is to disable the window with the virtual keyboard EnableWindow(hWnd, FALSE).
Now, if the window is disabled you will not get mouse messages, right? You have to options:
The easy one: Use WM_SETCURSOR. It is sent even to disabled windows, and in the high-order word of lParam you have the identifier of the original message (WM_LBUTTONDOWN, etc.). The coordinates of the cursor can be read using GetMessagePos().
The cool one: Use a windows hook: SetWindowsHookEx(WH_MOUSE, ...). You'll have full control of your mouse messages.
Use a class that does not have the ability to gain keyboard focus, but only responds to mouse input.
Solution: Derive your virtual keyboard from TControl or TGraphicControl, and not from TWinControl or TCustomControl.
Does this help?
procedure WMMouseActivate(var Message: TWMMouseActivate); message WM_MOUSEACTIVATE;
procedure TMyForm.WMMouseActivate(var Message: TWMMouseActivate);
begin
Message.Result := MA_NOACTIVATE;
end;

Resources