I have the following code in a unit that I include as the last unit in my uses list
{ TFormHelper }
procedure TForm.WMMoving(var aMessage: TWMMoving);
var
rec: ^TRect;
wrk: TRect;
begin
wrk := GetWorkArea;
rec := Pointer(aMessage.DragRect);
if rec^.Left < wrk.Left then
begin
rec^.Right := rec^.Right - (rec^.Left - wrk.Left);
rec^.Left := wrk.Left;
end
else if rec^.Right > wrk.Right then
begin
rec^.Left := rec^.Left - (rec^.Right - wrk.Right);
rec^.Right := wrk.Right;
end;
if rec^.Top < wrk.Top then
begin
rec^.Bottom := rec^.Bottom - (rec^.Top - wrk.Top);
rec^.Top := wrk.Top;
end
else if rec^.Bottom > wrk.Bottom then
begin
rec^.Top := rec^.Top - (rec^.Bottom - wrk.Bottom);
rec^.Bottom := wrk.Bottom;
end;
end;
It is supposed to check if a form is inside the working window of my main form and if not then it should move the form to a valid position.
In the form I want to check I put
SendMessage(Handle, WM_MOVING, 0, 0);
in the FormShow event, but it has no effect.
I know the function works because if I try to drag the form with the mouse it is moved to a valid position at once.
So my question is: how can I force the code to run when form is shown?
To run code when a form is shown, put code in the OnShow event handler.
That's not your problem, though. Your problem is that your code has no effect. The wm_Moving message is normally sent while a window is being moved by the user, as during a drag operation. The OS continually sends the message to ask the form where it's allowed to go, including tentative window coordinates where the window will be moved to. The window responds to the message by adjusting the proposed window bounds, and then the OS either moves the window to those new coordinates or changes the drag rectangle (depending on whether the "full window drag" option is set).
Merely sending a lone wm_Moving message doesn't do any of that, though. You're essentially asking the form where it wants to go, but since you're playing the role of the OS in this scenario, you still need to act on the results you get and actually move the window. The wm_Moving message is a notification, not a command; it has no inherent side effects of its own.
Related
I found (in Delphi 2010) that shortcuts always end up on first form (as owned by main form) that has that action, but not the currently focused form. My TMainFrm owns several TViewFrm. Each has a TActionManager with the same TActons.
I see some ways out, but wonder whats the best fix.. (and not a bad hack)
The forms are navigated using a tabset which calls their Hide() and Show(). I'd did not expect hidden forms to receive keypresses. Am i doing something wrong?
It seems that action shortcuts are always start at the main form, and using TCustomForm.IsShortCut() get distributed to owned forms. I see no logic there to respect hidden windows, should i override it and have it trigger the focused form first?
Disabling all TActions in TViewFrm.Hide() .. ?
Moving the TActionToolBar to TMainFrm but that is a pit of snakes and last resort.
I have found a workaround thats good enough for me; my main form now overrides TCustomForm.IsShortcut() and first checks visible windows from my list of editor tabs.
A list which i conveniently already have, so this might not work for everyone.
// Override TCustomForm and make it check the currently focused tab/window first.
function TFormMain.IsShortCut(var Message: TWMKey): Boolean;
function DispatchShortCut(const Owner: TComponent) : Boolean; // copied function unchanged
var
I: Integer;
Component: TComponent;
begin
Result := False;
{ Dispatch to all children }
for I := 0 to Owner.ComponentCount - 1 do
begin
Component := Owner.Components[I];
if Component is TCustomActionList then
begin
if TCustomActionList(Component).IsShortCut(Message) then
begin
Result := True;
Exit;
end
end
else
begin
Result := DispatchShortCut(Component);
if Result then
Break;
end
end;
end;
var
form : TForm;
begin
Result := False;
// Check my menu
Result := Result or (Menu <> nil) and (Menu.WindowHandle <> 0) and
Menu.IsShortCut(Message);
// Check currently focused form <------------------- (the fix)
for form in FEditorTabs do
if form.Visible then
begin
Result := DispatchShortCut(form);
if Result then Break;
end;
// ^ wont work using GetActiveWindow() because it always returns Self.
// Check all owned components/forms (the normal behaviour)
if not Result then
Result := inherited IsShortCut(Message);
end;
Another solution would be to change DispatchShortCut() to check for components being visible and/or enabled, but that might impact more than i'd like. I wonder whether the original code architects had a reason not to -- by design. Best would be have it called twice: first to give priority to visible+enabled components, and second call as fallback to normal behavior.
Currently in my application the user is able to create windows (no controls on the as of yet) which are docked to a pagecontrol. Each editor has a set of toolboxes associated with them. This set is reset upon editor construction and modified as toolboxes are closed and opened. When the page control changes tab the state of the toolboxes is saved, they are closed and then the toolboxes for the new tab are restored.
My application is crashing (seemingly randomly at first) when you change tab. I believe it is when you change tab very fast (the speed of a double click) the processing of the toolboxes in the PageControl.OnChange event doesnt complete before the next PageControl.OnChange event starts and I end up with two events running in 'parallel'. My questions are:
1) Does this sound like a plausible theory? I am aware there is a GUI processing thread does delphi work by dispatching new threads to deal with events?
2) How would you suggest going about fixing this? Hold the messages until the first method finishes (it is a very quick method so there shouldnt be any use lag really)?
Here is some code incase anyone would like to look at the problem from another angle.
This is the code for the onchange event.
procedure TMainForm.PageDockingAreaChange(Sender: TObject);
var
count : integer;
// Shortcut variables
FormShortcut : TForm;
WelcomeShortcut : TWelcomePageForm;
BaseEditorShortcut : TBaseEditor;
begin
// Set nil values
FormShortcut := nil;
WelcomeShortcut := nil;
BaseEditorShortcut := nil;
// Create shortcut value
FormShortcut := Self.GetActiveForm;
if (FormShortcut is TWelcomePageForm) then WelcomeShortcut := TWelcomePageForm(FormShortcut);
if (FormShortcut is TBaseEditor) then BaseEditorShortcut := TBaseEditor(FormShortcut);
// Hide all tabs when welcome page is visible
if (WelcomeShortcut <> nil) then
begin
// Clear any existing toolboxes
Self.ToolboxClearAll;
end;
{endif}
// Try to execute toolbox setup
try
// Load toolbox state
Self.ToolboxSetup(BaseEditorShortcut);
except
// Display fatal error to the user
ShowMessage('Fatal Error : Unable to load toolboxes.');
end;
// Update last active editor
if (BaseEditorShortcut = nil) then
Self.LastActiveEditor := nil
else
Self.LastActiveEditor := BaseEditorShortcut;
{endif}
end;
This method closes all toolboxes and frees them in turn.
procedure TMainForm.ToolboxClearAll;
var
count : integer;
begin
// Save toolbox state if needed
if Assigned(Self.LastActiveEditor) then
Self.LastActiveEditor.SaveToolboxState;
// Close and free all toolboxes
for count := 0 to length(Self.ActiveToolboxes)-1 do
Self.ToolboxCloseAndFree(Self.ActiveToolboxes[count]);
// Reset array size (should already be done though)
SetLength(Self.ActiveToolboxes, 0);
end;
I'm having trouble with the following scenario:
2 Edit's
Type something in Edit1 and press TAB, focus goes to Edit2
Edit1.OnExit -> show a Form with a message "Processing..." (makes a lengthy validation)
After the form closes, the focus on Edit2 seems to be "crashed"...
- the hole TEXT in Edit2 isn't selected
- the carret isn't flashing
Example:
Create a new form
Put 2 edits
Set this as OnExit event in Edit1:
procedure TForm1.Edit1Exit(Sender: TObject);
begin
with TForm.CreateNew(self) do
try
Width := 100;
Height := 50;
Position := poMainFormCenter;
show;
sleep(200);
finally
Free;
end;
end;
Run the application
Set focus in the Edit1 and press TAB
I'm using:
Delphi 7 Enterprise
Windows 7 x64
This is a known problem. Windows has problems when you change focus before it's completed the last focus change (eg., focus starts changing from Edit1 to Edit2, but Edit1.OnExit does something to change focus to another control or form.
This happens, for instance, when apps try to do validations in an OnExit event and then try to return focus to the original control when the validation fails.
The easiest solution is to post a message to your form handle in the OnExit instead, and handle the focus change need there. It will fire once the target control gets the input focus, and Windows doesn't get confused.
const
UM_EDIT1_EXITED = WM_USER + 1;
type
TForm1=class(TForm)
...
private
procedure UMEdit1Exited(var Msg: TMessage); message UM_EDIT1_EXITED;
end;
implementation
procedure TForm1.Edit1Exit(Sender: TObject);
begin
PostMessage(Handle, UM_EDIT1_EXITED, 0, 0);
end;
procedure TForm1.UMEdit1Exited(var Msg: TMessage);
begin
// Show your other form here
end;
From an old Borland NG post by Dr. Peter Below of TeamB:
here is my general sermon on the "show dialog from OnExit" problem:
If an OnExit handler is triggered (which happens in response to the
Windows
message WM_KILLFOCUS) Windows is in the midst of a focus change. If you do
something in the handler that causes another focus change (like popping up
a message box or doing a SetFocus call) Windows gets terribly confused.
The
missing cursor is a symptom of that.
If you have to display a message to your user from an OnExit handler, do
it
this way:
Define a constant for a user message somewhere in the INterface
section
of your unit, above the type declaration for your form
'Const
UM_VALIDATE = WM_USER + 200;'
Give your Form a handler for this message, best placed in the private
section of the class declaration:
Procedure UMValidate( Var Msg: TMessage ); message UM_VALIDATE;
Post a UM_VALIDATE message to the form from the OnExit handler if
the contents of the field are not ok. You can pass additional
information in the wparam and lparam parameters of the message, e.g.
an error number and the Sender object. In fact you could do the whole
validation in the UMValidate handler!
I'm not sure precisely what's going on here, but it looks like the order of processing of messages is a bit messed up. Instead of killing your other form with Free, use Release and the focus will behave as you desire.
Another option is to use ShowModal instead of Show. Normally you show a processing dialog modally because you don't want the user making modifications to the main form whilst you are processing. If you do that then you can carry on using Free.
I am currently writing a windowing system for an existing Delphi application.
Currently, the program consists of a number of full-sized forms which are shown modally in the order they are required and none of which can be moved by the user. My aim is to allow all of these forms to be moveable. Previously forms were stacked on top of each other but since none could be moved the background forms were not visible to the user. My solution so far has been to hide the 'parent' form when opening a new child, and reshowing it when that child is closed.
Unfortunately since each child is called with showModal, the call the make the parent form visible does not come until after the modal process has completed and hence after the child form has been hidden so the user sees a split second flash where no form is visible.
Is there a way I can prevent the modal forms from being hidden automatically after their process has completed? This would allow me to manually hide them once the parent form is visible again. I have tried to schedule this in the FormHide event of each child form but this does not work as a child form is also hidden when opening one of its own children.
EDIT:
Here is what I have so far based of Remy's advice below
procedure openModalChild(child: TForm; parent: TForm);
var
WindowList: Pointer;
SaveFocusCount: Integer;
SaveCursor: TCursor;
SaveCount: Integer;
ActiveWindow: HWnd;
Result: integer;
begin
CancelDrag;
with child do begin
Application.ModalStarted;
try
ActiveWindow := GetActiveWindow;
WindowList := DisableTaskWindows(0);
//set the window to fullscreen if required
setScreenMode(child);
try
Show; //show the child form
try
SendMessage(Handle, CM_ACTIVATE, 0, 0);
ModalResult := 0;
repeat
Application.HandleMessage;
//if Forms.Application.FTerminate then ModalResult := mrCancel else
if ModalResult <> 0 then closeModal(child as TCustomForm);
until ModalResult <> 0;
Result := ModalResult;
SendMessage(Handle, CM_DEACTIVATE, 0, 0);
if GetActiveWindow <> Handle then ActiveWindow := 0;
finally
parent.Show;
Hide;
end;
finally
EnableTaskWindows(WindowList);
parent.Show; //reshow the parent form
if ActiveWindow <> 0 then SetActiveWindow(ActiveWindow);
end;
finally
Application.ModalFinished;
end;
end;
end;
This works well but the only problem is the active repeat loop never breaks, even after the child has been escaped and so the parent form is never reshown.
Is there any way I can resolve this?
ShowModal() explicitally calls Show() just before entering its modal processing loop, and explicitally calls Hide() immediately after exiting the loop. You cannot change that without altering the code in the VCL's Forms.pas source file.
If you need finer control over the windows, without editing VCL source code, then don't use ShowModal() at all. Use Show(), Hide(), DisableTaskWindows(), and EnableTaskWindows() yourself as needed. I would sugest you look at Forms.pas to see how they are used. Copy the implementation of ShowModal() into your own function, then you can customize it as needed.
In my Delphi form's OnShow method, I determine that a dialog must be opened automatically once the form is opened - and I should be able to do this by simulating a click on a menuitem.
However, calling menuitem.Click brings up the dialog before the main form has opened - which is not what I want.
I expect that should do what I want, but I cannot find what parameters to pass for "wparam" to send the click to my menuitem.
PostMessage(handle, WM_COMMAND, wparam, 0)
The MSDN WM_COMMAND docs talk about IDM_* identifiers, but how does that appear in Delphi?
(I know this is a very old question but despite being resolved in some way the real question has really gone unanswered.)
--
The command item identifier of a 'TMenuItem' is in the Command property. According to WM_COMMAND's documentation the high word of 'wParam' would be '0' and the low word would be the menu identifier;
PostMessage(Handle, WM_COMMAND, MakeWParam(MyMenuItem.Command, 0), 0);
or simply;
PostMessage(Handle, WM_COMMAND, MyMenuItem.Command, 0);
With a popup menu item there would be a slight difference: the VCL handles popup menus' messages with a different utility window. The global PopupList variable has the handle to it in its Window property;
PostMessage(PopupList.Window, WM_COMMAND, MyPopupMenuItem.Command, 0);
Perhaps you can try to open the dialog in the OnActivate event ?
I am not really sure if the OnActivate gets fired again other than when the form is shown but if it does you can use :
procedure TForm1.FormActivate(Sender: TObject);
begin
Form2.ShowModal;
Self.OnActivate := nil;
end;
Wouldn't you have to do this with a one-time timer, if you want the form to appear as per a normal Show/ShowModal, get drawn (etc) fully, and then immediately do something else?
tmrKickOff : a TTimer, 100 ms interval, disabled at design time,
fires off a 'tmrKickOffTimer' event.
in form create,
tmrKickOff.Enabled:=false; //just in case something happened in IDE
in form show, at end of all other stuff;
tmrKickOff.Enabled:=true;
in tmrKickOffTimer
begin
tmrKickOffTimer.Enabled:=false;
menuItemClick(nil);
end;
with apologies for style, form and any error-trapping :-)
Alternatively, handle the Application.OnIdle event with something along the lines of ...
if not DialogDone then
begin
MyDialogForm.ShowModal; // or menuItem.Click ....
DialogDone := true;
end;
OnIdle won't fire (for the first time) until the Form is shown and the message queue is empty.
I don't think you can send a message directly to your menu item, but you can just post it to the main window and show your dialog from there. I do this and it works great so that the dialog box (in my case, a login prompt) appears on top of the main window to avoid confusion.
-Mark
procedure WMPostStartup(var Message: TMessage); message WM_POSTSTARTUP;
procedure TMainForm.WMPostStartup(var Message: TMessage);
begin
Self.Refresh;
// Now show the dialog box.
end;
One method I have used, which is very simular to MarkF's solution, is to create a new user defined message type and send a message using that type to yourself when you determine that you need to perform this other process after your main form displays:
const
wm_SpecialProc = wm_User + 1;
procedure TForm1.WMSpecialProc(var Message:tMessage); message wm_SpecialProc;
begin
Form2.ShowModal;
end;
procedure TForm1.OnShow(Sender:tObject);
begin
if true then
PostMessage(Application.MainForm.Handle,wm_SpecialProc,0,0);
end;
The nice thing about this method is that you are in control of the message generation, so can populate ANY lparam or wparam you want to later use by your handler. I sent the message directly through the application.mainform but you could also just say handle for the current form.