Getting access violation error in FormClose using VCL Themes - delphi

I developed an application in VCL which is using VCL Themes. This application requires TPageControl and inner(child) forms in it.
Each child form has same way in OnClose: Parent.Destroy;
MsgResp := MessageDlg('Closing info....', mtWarning, [mbYes, mbNo, mbCancel], 0);
case MsgResp of
mrYes:
begin
DoSomething; {Save something}
Parent.Destroy;
end;
mrNo:
begin
Parent.Destroy;
end;
mrCancel:
begin
Exit;
end;
end;
If I set a theme to application like Sapphire Kamri (or something else), I get access violation error in destroying parent component. But if I use default style (Windows), this code works fine.

This is entirely to be expected. Your code is just as broken without VCL styles, but you get away with it.
The problem is the calls to Parent.Destroy. When these occur, the parent is destroyed, and so are all of its children, including the control that owns the code seen in the question. When the call to Parent.Destroy returns, execution continues in methods of objects that have been destroyed. That is the source of the runtime error.
You need to schedule the destruction to happen after the OnClose event handler has completed. The VCL Release method exists for this very purpose.

Related

pmAuto ModalPopupMode proper use or bug workaround

I'm having problems using TApplication.ModalPopupMode=pmAuto and I was wondering if my problems were caused by my usage of pmAuto or a bug in delphi.
Simple use case:
Form1(MainForm) and Form3 are permanent forms. (Created in the dpr)
Form2 is created when needed and
freed afterward.
Form3 contains a TComboBox with X items.
Sequence of actions :
Form1 create and show Form2 modal.
Form2 show form3 modal.
Close Form3
Close and free Form2
Show Form3 <---- The TComboBox now contains 0 items.
I use ComboBox as an example, but I guess any controls that saves information in the DestroyWnd procedure and restore it in the CreateWnd procedure isn't working right. I tested TListBox and it displays the same behavior too.
Is it a known fact that one shouldn't mix permanent and temporary form when ModalPopupMode is pmAuto?
If not, is there any known workaround for this problem?
If it's a bug, is this fixed in more recent version of Delphi? (I'm using XE4)
It is not really a bug, just a quirk in how the various windows interact with each other when dealing with modality.
When Form3 is first created, TComboBox.CreateWnd() is called during DFM streaming. When Form3.ShowModal() is called for the first time, Form3 calls RecreateWnd() on itself if its PopupMode is pmNone and Application.ModalPopupMode is not pmNone. OK, so TComboBox.DestroyWnd() gets called, saving the items, then TComboBox.CreateWnd() gets called, restoring the items. Recreating the TComboBox's window during ShowModal() is not ideal, but it works this time.
When Form3.ShowModal() is called the second time, TComboBox.CreateWnd() is called again without a previous call to TComboBox.DestroyWnd()! Since the items have not been saved, they cannot be restored. That is why the TComboBox is empty.
But why does this happen? When Form2 is freed, Form3's window is still associated with Form2's window. The first call to Form3.ShowModal set Form2's window as Form3's parent/owner window. When you close a TForm, it is merely hidden, its window still exists. So, when Form2 and Form3 are closed, they still exist and are linked together, and then when Form2 is destroyed, all of its child and owned windows get destroyed. TComboBox receives a WM_NCDESTROY message, resetting its Handle to 0 without notifying the rest of its code that the window is being destroyed. Thus, TComboBox does not have a chance to save its current items because DestroyWnd() is not called. DestroyWnd() is called only when the VCL itself is destroying the window, not when the OS destroys it.
Now, how can you fix this? You will have to destroy the TComboBox's window, triggering its DestroyWnd() method, before freeing Form2. The trick is that TComboBox.DestroyWnd() will save the items only if the csRecreating flag is enabled in the TComboBox.ControlState property. There are a few different ways you can accomplish that:
call TWinControl.UpdateRecreatingFlag() and TWinControl.DestroyHandle() directly. They are both protected, so you can use an accessor class to reach them:
type
TComboBoxAccess = class(TComboBox)
end;
Form2 := TForm2.Create(nil);
try
Form2.ShowModal;
finally
with TComboBoxAccess(Form3.ComboBox1) do
begin
UpdateRecreatingFlag(True);
DestroyHandle;
UpdateRecreatingFlag(False);
end;
Frm.Free;
end;
Form3.ShowModal;
call TWinControl.RecreateWnd() directly. It is also protected, so you can use an accessor class to reach it:
type
TComboBoxAccess = class(TComboBox)
end;
Form2 := TForm2.Create(nil);
try
Form2.ShowModal;
finally
TComboBoxAccess(Form3.ComboBox1).RecreateWnd;
Frm.Free;
end;
Form3.ShowModal;
The TComboBox window is not actually be created until the next time it is needed, in the subsequent ShowModal().
send the TComboBox window a CM_DESTROYHANDLE message and let TWinControl handle everything for you:
Form2 := TForm2.Create(nil);
try
Form2.ShowModal;
finally
if Form3.ComboBox1.HandleAllocated then
SendMessage(Form3.ComboBox1.Handle, CM_DESTROYHANDLE, 1, 0);
Frm.Free;
end;
Form3.ShowModal;
CM_DESTROYHANDLE is used internally by TWinControl.DestroyHandle() when destroying child windows. When a TWinControl component receives that message, it calls UpdateRecreatingFlag() and DestroyHandle() on itself.
Based on Remy's excellent answer I implemented something that fixes these issues in the whole application. You will need to descend all your modal forms from a custom TForm descendant - TMyModalForm in my example (which, IMO, is always a good practice anyway). All modal forms in my application descend from this. Please notice that I also set PopupMode to pmAuto in CreateParams() before calling the inherited method. This prevents the z-order problem when showing modal windows, but also causes the window handle problem described in your question. Also, I just broadcast the CM_DESTROYHANDLE if action is caHide. This skips an unnecessary notification for MDI child windows and modal windows which are destroyed on close.
BTW, for future reference, this issue still exists in Delphi 10.2.3 Tokyo.
type
TMyModalForm = class(TForm)
protected
procedure DoClose(var Action: TCloseAction); override;
procedure CreateParams(var Params: TCreateParams); override;
end;
procedure TMyModalForm.DoClose(var Action: TCloseAction);
var
Msg: TMessage;
begin
inherited DoClose(Action);
if Action = caHide then
begin
FillChar(Msg, SizeOf(Msg), 0);
Msg.Msg := CM_DESTROYHANDLE;
Msg.WParam := 1;
Broadcast(Msg);
end;
end;
procedure TMyModalForm.CreateParams(var Params: TCreateParams);
begin
PopupMode := pmAuto;
inherited;
end;
end;

How to check if the Application.MainForm is valid?

How can I be sure that in some point of my VCL application lifetime the Application.MainForm is valid so I could post a message to it (from a MadExcept ExceptionHandler).
This could be at any point (in the context of any thread) in my application (also initialization, finalization etc...)
I was thinking:
if Assigned(Application)
and (not Application.Terminated)
and Assigned(Application.MainForm)
and Application.MainForm.HandleAllocated then
begin
PostMessage(Application.MainForm.Handle, MyMessage, 0, 0);
end;
Is this correct?
How can I be sure that in some point of my VCL application lifetime the Application.MainForm is valid so I could post a message to it.
OK.
This could be at any point (in the context of any thread) in my application (also initialization, finalization etc...)
Uh oh.
....
Is this correct?
No it certainly is not. Your code can never be made threadsafe because it is not permitted to access VCL objects from outside the main thread.
In your particular case consider the following sequence of events:
You perform your tests in the if culminating in your evaluating Application.MainForm.HandleAllocated as True. Ignore for a moment the fact that you are doing this outside the main thread.
You then set about preparing the call to PostMessage. But at this very instance, the main form is destroyed.
By the time your thread gets round to accessing Application.MainForm, it has gone.
You are going to need to work a little harder here. You'll need to do something like this:
// interface section of some unit
procedure RegisterMainFormHandle(Wnd: HWND);
procedure UnregisterMainFormHandle;
procedure PostMessageToMainForm(...);
// implementation section
var
MainFormHandleLock: TCriticalSection;
MainFormHandle: HWND;
procedure RegisterMainFormHandle(Wnd: HWND);
begin
MainFormHandleLock.Acquire;
try
MainFormHandle := Wnd;
finally
MainFormHandleLock.Release;
end;
end;
procedure UnregisterMainFormHandle;
begin
MainFormHandleLock.Acquire;
try
MainFormHandle := 0;
finally
MainFormHandleLock.Release;
end;
end;
procedure PostMessageToMainForm(...);
begin
MainFormHandleLock.Acquire;
try
if MainFormHandle <> 0 then
PostMessage(MainFormHandle, ...)
finally
MainFormHandleLock.Release;
end;
end;
You also need to create and destroy the critical section, but I assume that you know how to do that.
In your main form you override CreateWnd and DestroyWnd and arrange that they call RegisterMainFormHandle and UnregisterMainFormHandle.
Then you can call PostMessageToMainForm from any thread at any time.
Of course, if the main form's window is recreated then you'll lose some messages. Which sounds like it could be a problem. Using AllocateHwnd to have a window whose lifetime you control is usually a better option than using the main form's window like this.
Make some global variable flag = false at the beginning.
Make your mainform turn it to true.
Check that flag to see if main form was already initialised or not yet.
You can make it from such places as mainform's OnActivate event or overridden TMainForm.Loaded method
Similarly when your application would be terminating and the mainform would get hidden (and later even destroyed) - you would reset the flag back to false

OnShown event for TForm?

At program start, in the OnActivate event handler, I need to do something which blocks the program for a few seconds. During this time the form's client area is still not completely painted, which looks ugly for the user. (During this blocked time I don't need the program to respond to clicks or other user actions, so there is no need to put the blocking operation into a thread - I just need the form to be completely painted). So I use TForm.Update and Application-ProcessMessages to update the form before the blocking operation which works very well:
procedure TForm1.FormActivate(Sender: TObject);
begin
Form1.Update;
Application.ProcessMessages;
Sleep(7000);
end;
However, I wonder whether there is not another more elegant solution for this problem. This could be for example a OnShown event implemented in a descendant of TForm which will be fired AFTER the form has been completely painted. How could such an event be implemented?
Your real problem is that you are blocking the UI thread. Simply put, you must never do that. Move the long running task onto a different thread and thus allow the UI to remain responsive.
If you are looking for event which is fired when application finishes loading/repainting you should use TApplication.OnIdle event
http://docwiki.embarcadero.com/Libraries/XE3/en/Vcl.Forms.TApplication.OnIdle
This event is fired once application is read to recieve users input. NOTE this event will be fired every time application becomes idle so you need to implement some controll variable which will tel you when OnIdle even was fired for the first time.
But as David already pointed out it is not good to block your UI (main thread). Why? When you block your main thread the application can't normally process its messages. This could lead to OS recognizing your application as being "Hanged". And aou definitly wanna avoid this becouse it could cause the users to go and forcefully kill your application whihc would probably lead to data loss. Also if you ever wanna design your application for any other platforms than Windows your application might fail the certification proces becouse of that.
In the past a simple PostMessage did the trick.
Essentially you fire it during DoShow of the base form:
procedure TBaseForm.DoShow;
begin
inherited;
PostMessage(Handle, APP_AFTERSHOW, 0, 0);
end;
then catch the msg and create an AfterShow event for all forms inherited from this base form.
But that no longer works, well not if you are skinning and have a good number of VCL controls.
My next trick was to spawn a simple thread in DoShow and check for IsWindowVisible(Handle) and IsWindowEnabled(Handle). That really sped things up it cut 250ms from load time since db opening and other stuff was already in the AfterShow event.
Then finally I thought of madHooks, easy enough to hook the API ShowWindow for my application and fire APP_AFTERSHOW from that.
function ShowWindowCB(hWnd: HWND; nCmdShow: Integer): BOOL; stdcall;
begin
Result := ShowWindowNext(hWnd, nCmdShow);
PostMessage(hWnd, APP_AFTERSHOW, 0, 0);
end;
procedure TBaseForm.Loaded;
begin
inherited;
if not Assigned(Application.MainForm) then // Must be Mainform it gets assigned after creation completes
HookAPI(user32, 'ShowWindow', #ShowWindowCB, #ShowWindowNext);
end;
To get the whole thing to completely paint before AfterShow it still needed a ProcessPaintMessages call
procedure TBaseForm.APPAFTERSHOW(var AMessage: TMessage);
begin
ProcessPaintMessages;
AfterShow;
end;
procedure ProcessPaintMessages; // << not tested, pulled out of code
var
msg: TMsg;
begin
while PeekMessage(msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE) do
DispatchMessage(msg);
end;
My final test was to add a Sleep to the AfterShow event and see the Form fully painted with empty db containers since the AfterShow events had not yet completed.
procedure TMainForm.AfterShow;
begin
inherited;
Sleep(8*1000);
......

Proper handling of modal window "Minimize" behavior

I have the following issue: we're building a rather large application (win32, Delphi 6 Enterprise). In several part of the application, modal windows are used, usually containing the detail of the selection of the main window.
We included a modification of the handling of the WM_SYSCOMMAND messages so that, if the window is modal, then a SW_SHOWMINNOACTIVE message will be sent to the application's main window. This causes the whole application to be minimized instead of just the modal form.
There is, however, an issue happening in a specific case: if the calling window is set to full screen, then upon restoration, the modal window will appear UNDER the (disabled) maximized main window (this seems to happen on Windows 7)
My problem is two fold:
First, I don't seem to get any syscommand message when the application is restored any more so I cannot introduce code to restore the Z-Order because I don't know where to put it.
Second, it seems to me that, if the whole application is minimized, clicking on the app's button in the task bar should restore it in the same state, not with a modal window under it. Is there a way to fix that ?
Edit: we did some additional testing and it seems we can actually detect the problem in the WM_ACTIVATE handler for the main form. We can also identify the modal window at that stage. I cannot, however, find a way to restore it to the top of the Z-Order.
Edit2: here is the code that minimizes the application when the modal form is minimized:
procedure TfmGITForm.WMSysCommand(var Message: TWMSysCommand);
begin
if (fsModal in FormState) or
not Application.MainForm.Visible then
begin
case Message.CmdType of
SC_MINIMIZE:
begin
ShowWindow(Application.Handle, SW_SHOWMINNOACTIVE);
end;
SC_RESTORE:
begin
ShowWindow(Application.Handle, SW_SHOWNORMAL);
inherited;
end;
else
inherited;
end; // case
end
else
inherited;
end;
All our forms descend from that one.
Override the dialog's CreateParams function and set Params.WndParent to the full-screen window (or Owner.Handle if you're owning things properly). The default is Application.Handle, which will cause these kinds of problems. The PopupParent properties introduced in the later Delphi releases does the exact same thing.
This has to do with the Window ghosting by Windows which was introduced in (I think) XP. I have the same issues in a D5 app on these operating systems. Peter Below offered the following work around at the time and it still serves me well:
procedure DisableProcessWindowsGhosting;
type
TDisableProcessWindowsGhostingProc = procedure; stdcall;
const
sUser32 = 'User32.dll';
var
ModH: HMODULE;
_DisableProcessWindowsGhosting: TDisableProcessWindowsGhostingProc;
begin
ModH := GetModuleHandle(sUser32);
if ModH <> 0 then begin
#_DisableProcessWindowsGhosting := nil;
#_DisableProcessWindowsGhosting := GetProcAddress(ModH,
'DisableProcessWindowsGhosting');
if Assigned(_DisableProcessWindowsGhosting) then begin
_DisableProcessWindowsGhosting;
end;
end;
end;
I call it at the beginning of the app's main form's OnCreate handler.

Delphi: How to remove subclasses in reverse order?

Mike Lischke's TThemeServices subclasses Application.Handle, so that it can receive broadcast notifications from Windows (i.e. WM_THEMECHANGED) when theming changes.
It subclasses the Application object's window:
FWindowHandle := Application.Handle;
if FWindowHandle <> 0 then
begin
// If a window handle is given then subclass the window to get notified about theme changes.
{$ifdef COMPILER_6_UP}
FObjectInstance := Classes.MakeObjectInstance(WindowProc);
{$else}
FObjectInstance := MakeObjectInstance(WindowProc);
{$endif COMPILER_6_UP}
FDefWindowProc := Pointer(GetWindowLong(FWindowHandle, GWL_WNDPROC));
SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FObjectInstance));
end;
The subclassed window procdure then does, as it's supposed to, WM_DESTROY message, remove it's subclass, and then pass the WM_DESTROY message on:
procedure TThemeServices.WindowProc(var Message: TMessage);
begin
case Message.Msg of
WM_THEMECHANGED:
begin
[...snip...]
end;
WM_DESTROY:
begin
// If we are connected to a window then we have to listen to its destruction.
SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FDefWindowProc));
{$ifdef COMPILER_6_UP}
Classes.FreeObjectInstance(FObjectInstance);
{$else}
FreeObjectInstance(FObjectInstance);
{$endif COMPILER_6_UP}
FObjectInstance := nil;
end;
end;
with Message do
Result := CallWindowProc(FDefWindowProc, FWindowHandle, Msg, WParam, LParam);
end;
The TThemeServices object is a singleton, destroyed during unit finalization:
initialization
finalization
InternalThemeServices.Free;
end.
And that all works well - as long as TThemeServices is the only guy who ever subclasses the Application's handle.
i have a similar singleton library, that also wants to hook Application.Handle so i can receive broadcasts:
procedure TDesktopWindowManager.WindowProc(var Message: TMessage);
begin
case Message.Msg of
WM_DWMCOLORIZATIONCOLORCHANGED: ...
WM_DWMCOMPOSITIONCHANGED: ...
WM_DWMNCRENDERINGCHANGED: ...
WM_DESTROY:
begin
// If we are connected to a window then we have to listen to its destruction.
SetWindowLong(FWindowHandle, GWL_WNDPROC, Integer(FDefWindowProc));
{$ifdef COMPILER_6_UP}
Classes.FreeObjectInstance(FObjectInstance);
{$else}
FreeObjectInstance(FObjectInstance);
{$endif COMPILER_6_UP}
FObjectInstance := nil;
end;
end;
with Message do
Result := CallWindowProc(FDefWindowProc, FWindowHandle, Msg, WParam, LParam);
And my singleton is similarly removed when the unit finalizes:
initialization
...
finalization
InternalDwmServices.Free;
end.
Now we come to the problem. i can't guarantee the order in which someone might choose to access ThemeServices or DWM, each of which apply their subclass. Nor can i know the order in which Delphi will finalize units.
The subclasses are being removed in the wrong order, and there is a crash on application close.
How to fix? How can i ensure that i keep my subclassing method around long enough until the other guy is done after me is done? (i don't want to leak memory, after all)
See also
Raymond Chen: Safer Subclassing
MSDN: Using Window Procedures
Raymond Chen: When the normal window destruction messages are thrown for a loop
Update: i see Delphi 7 solves the bug by rewriting TApplication. ><
procedure TApplication.WndProc(var Message: TMessage);
...
begin
...
with Message do
case Msg of
...
WM_THEMECHANGED:
if ThemeServices.ThemesEnabled then
ThemeServices.ApplyThemeChange;
...
end;
...
end;
Grrrr
In other words: trying to subclass TApplication was a bug, that Borland fixed when they adopted Mike's TThemeManager.
That very well may mean that there is no way to remove subclasses on TApplication in reverse order. Someone put that in the form of an answer, and i'll accept it.
Change your code to call SetWindowSubclass, as the article you linked to advises. But that only works if everyone uses the same API, so patch Theme Manager to use the same technique. The API was introduced in Windows XP, so there's no danger that it's not available on the systems where it would be needed.
There should be no problem with patching Theme Manager. It's designed to support Windows XP, which Microsoft doesn't support anymore, and to support Delphi 4 through 6, which Borland doesn't support anymore. Since development has ceased on all relevant products, it is safe for you to fork the Theme Manager project without risk of falling behind due to future updates.
You're not really introducing a dependency. Rather, you're fixing a bug that's only present when both window-appearance libraries are in use at the same time. Users of your library don't need to have Theme Manager for yours to work, but if they wish to use both, they need to use Theme Manager with your patches applied. There should be little objection to that since they already have the base version, so it's not like they'd be installing an entirely new library. They'd just be applying a patch and recompiling.
Rather than subclassing the TApplication window, perhaps you can use AllocateHWnd() instead to receive the same broadcasts separately, since it is a top-level window of its own.
I think I would do the following:
Put a reference to ThemeServices in the initialization section of ThemeSrv.pas.
Put a reference to DwmServices in the initialization section of DwmSrv.pas (I'm guess the name of your unit).
Since units are finalised in reverse order from the order of initialisation, your problem will be solved.
Why don't you just use the ApplicationEvents and be done with it. No need to messing around with subclassing.
The other way is to create only one subclass and create multi-notify events and subscribe as many as you wish.
Cheers

Resources