Skip to content

Commit ca42523

Browse files
committed
Revise hibernate bug fix (issue #70)
Following a discussion in the comments the DelphiDabbler Blog post at https://tinyurl.com/mrp76mdy it seems that was not a good idea to rely upon handling WM_POWERBROADCAST's PBT_APMPOWERSTATUSCHANGE event to restore the overview pane's tree view nodes to the expected state after Windows has recreated the tree view in an invalid state. So I've modified the code to only rely on the PBT_APMSUSPEND event of WM_POWERBROADCAST and not PBT_APMPOWERSTATUSCHANGE. PBT_APMSUSPEND is handled to prepare for hibernation by not only saving the tree view's state (as per the previous fix) but also setting an event handler that gets called only when the tree view's window gets recreated by Windows AND the treeview contains nodes with nil IView pointers. When called, the event handler rebuilds the tree view with nodes containing valid IView references. The problem is that the event needs to be triggered from the TTreeView.CreateWnd method that gets called when Windows recreates the tree view. Since TTreeView exposes no suitable events, the only way is to inject a suitable event using a nasty hack. Not good practise. Note that all the methods that depend on the hack have been given names beginning with "_HACK_" to make it obvious where the naughtiness lies.
1 parent e5c0ce6 commit ca42523

File tree

4 files changed

+87
-33
lines changed

4 files changed

+87
-33
lines changed

Src/FmMain.pas

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -524,9 +524,10 @@ TMainForm = class(THelpAwareForm)
524524

525525
/// <summary>Handles the <c>WM_POWERBROADCAST</c> messages to detect and
526526
/// respond to hibernation messages.</summary>
527-
/// <remarks>This is necessary as part of the fix for an obscure bug. See
527+
/// <remarks>!! HACK necessary as part of the fix for an obscure bug. See
528528
/// https://github.com/delphidabbler/codesnip/issues/70</remarks>
529529
procedure WMPowerBroadcast(var Msg: TMessage); message WM_POWERBROADCAST;
530+
530531
/// <summary>Displays view item given by TViewItemAction instance
531532
/// referenced by Sender and adds to history list.</summary>
532533
procedure ActViewItemExecute(Sender: TObject);
@@ -1587,18 +1588,15 @@ procedure TMainForm.splitVertCanResize(Sender: TObject;
15871588

15881589
procedure TMainForm.WMPowerBroadcast(var Msg: TMessage);
15891590
begin
1591+
// !! HACK
15901592
// Sometimes when the computer is resumed from hibernation the tree view in
15911593
// the overview frame is destroyed and recreated by Windows. Unfortunately the
15921594
// IView instances associated with the recreated tree nodes are lost.
15931595
// Attempting to read those (now nil) IView instances was resulting in an
15941596
// access violation.
15951597
case Msg.WParam of
15961598
PBT_APMSUSPEND:
1597-
// Get ready for isolation
1598-
fMainDisplayMgr.PrepareForHibernate;
1599-
PBT_APMPOWERSTATUSCHANGE:
1600-
// Restore from hibernation: ensure the IView instances are recreeated
1601-
fMainDisplayMgr.RestoreFromHibernation;
1599+
fMainDisplayMgr._HACK_PrepareForHibernate;
16021600
end;
16031601
end;
16041602

Src/FrOverview.pas

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,29 @@ interface
2626

2727
type
2828

29+
// !! HACK
30+
// Horrible hack to expose CreateWnd for overiding TTreeView.CreateWnd for the
31+
// existing TTreeView component of TOverviewFrame. The hack avoids having to
32+
// remove the component and replacing it with a descendant class that is
33+
// manually constructed at run time.
34+
// This is here to enable the tree view to be recreated with correctly
35+
// instantiated TViewItemTreeNode nodes after Windows recreates the tree
36+
// behind the scenes after resuming from hibernation.
37+
// I am deeply ashamed of this hack.
38+
TTreeView = class(ComCtrls.TTreeView)
39+
strict private
40+
var
41+
_HACK_fOnAfterCreateNilViews: TNotifyEvent;
42+
protected
43+
procedure CreateWnd; override;
44+
public
45+
/// <summary>!! HACK. Event triggered after the inherited CreateWnd is
46+
/// called. Only called if the tree view has nil references to IView
47+
/// objects.</summary>
48+
property _HACK_OnAfterCreateNilViews: TNotifyEvent
49+
read _HACK_fOnAfterCreateNilViews write _HACK_fOnAfterCreateNilViews;
50+
end;
51+
2952
{
3053
TOverviewFrame:
3154
Titled frame that displays lists of snippets grouped in various ways and
@@ -214,6 +237,10 @@ TTVDraw = class(TSnippetsTVDraw)
214237
procedure RestoreTreeState;
215238
{Restores last saved treeview expansion state from memory.
216239
}
240+
/// <summary>!! HACK: Sets an event handler on the tree view to work
241+
/// around a bug that can occur after resuming from hibernation.</summary>
242+
/// <remarks>Method of IOverviewDisplayMgr.</remarks>
243+
procedure _HACK_SetHibernateHandler(const AHandler: TNotifyEvent);
217244
{ IPaneInfo }
218245
function IsInteractive: Boolean;
219246
{Checks if the pane is currently interactive with user.
@@ -955,6 +982,12 @@ procedure TOverviewFrame.UpdateTreeState(const State: TTreeNodeAction);
955982
end;
956983
end;
957984

985+
procedure TOverviewFrame._HACK_SetHibernateHandler(
986+
const AHandler: TNotifyEvent);
987+
begin
988+
tvSnippets._HACK_OnAfterCreateNilViews := AHandler;
989+
end;
990+
958991
{ TOverviewFrame.TTVDraw }
959992

960993
function TOverviewFrame.TTVDraw.IsSectionHeadNode(
@@ -993,5 +1026,24 @@ function TOverviewFrame.TTVDraw.IsUserDefinedNode(
9931026
Result := False;
9941027
end;
9951028

1029+
{ TTreeView }
1030+
1031+
procedure TTreeView.CreateWnd;
1032+
var
1033+
HasNilViews: Boolean;
1034+
Node: TTreeNode;
1035+
begin
1036+
inherited;
1037+
HasNilViews := False;
1038+
for Node in Items do
1039+
begin
1040+
HasNilViews := not Assigned((Node as TViewItemTreeNode).ViewItem);
1041+
if HasNilViews then
1042+
Break;
1043+
end;
1044+
if HasNilViews and Assigned(_HACK_fOnAfterCreateNilViews) then
1045+
_HACK_fOnAfterCreateNilViews(Self);
1046+
end;
1047+
9961048
end.
9971049

Src/IntfFrameMgrs.pas

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
44
* obtain one at https://mozilla.org/MPL/2.0/
55
*
6-
* Copyright (C) 2005-2021, Peter Johnson (gravatar.com/delphidabbler).
6+
* Copyright (C) 2005-2025, Peter Johnson (gravatar.com/delphidabbler).
77
*
88
* Declares interfaces, constants and enumerations required to manage various
99
* parts of CodeSnip's UI.
@@ -19,6 +19,7 @@ interface
1919
uses
2020
// Delphi
2121
SHDocVw, ActiveX,
22+
Classes, // !! For HACK
2223
// Project
2324
Browser.IntfDocHostUI, DB.USnippet, Compilers.UGlobals, UCommandBars, UView;
2425

@@ -145,6 +146,9 @@ interface
145146
/// <summary>Restore expand / collapse state of treeview to last save
146147
/// state.</summary>
147148
procedure RestoreTreeState;
149+
/// <summary>!! HACK: Sets an event handler on the tree view to work
150+
/// around a bug that can occur after resuming from hibernation.</summary>
151+
procedure _HACK_SetHibernateHandler(const AHandler: TNotifyEvent);
148152
end;
149153

150154
type

Src/UMainDisplayMgr.pas

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ TMainDisplayMgr = class(TObject)
165165
procedure DisplayViewItem(ViewItem: IView; Mode: TDetailPageDisplayMode);
166166
overload;
167167

168+
/// <summary>!! HACK event handle to redisplay the overview pane treeview.
169+
/// Called only if Windows has mysteriously recreated the treeview and lost
170+
/// necessary object references.</summary>
171+
procedure _HACK_HibernateHandler(Sender: TObject);
172+
168173
public
169174
/// <summary>Object contructor. Sets up object to work with given frame
170175
/// manager objects.</summary>
@@ -292,21 +297,12 @@ TMainDisplayMgr = class(TObject)
292297
/// <summary>Prepares display ready for database to be reloaded.</summary>
293298
procedure PrepareForDBReload;
294299

295-
/// <summary>Gets the overview frame prepared for program hibernation.
296-
/// </summary>
300+
/// <summary>!!HACK: gets the overview frame prepared for program
301+
/// hibernation.</summary>
297302
/// <remarks>Saves the overview tree view state ready for restoring after
298-
/// hibernation.</remarks>
299-
procedure PrepareForHibernate;
300-
301-
/// <summary>Restores the overview's tree view to have the correct IView
302-
/// instances after hibernation restores the previously saved state.
303-
/// </summary>
304-
/// <remarks>Sometimes, Windows quietly recreates the node of the tree view
305-
/// after resuming from hibernation, without restoring the associated IView
306-
/// instances, leading to access violations. This method should be called
307-
/// after resuming from hibernation to recreate the tree view with the
308-
/// correct IView instances.</remarks>
309-
procedure RestoreFromHibernation;
303+
/// hibernation if Windows has recreated the overview pane's treeview,
304+
/// losing necessary IView object references..</remarks>
305+
procedure _HACK_PrepareForHibernate;
310306

311307
end;
312308

@@ -583,12 +579,6 @@ procedure TMainDisplayMgr.PrepareForDBViewChange(View: IView);
583579
fPendingViewChange := True;
584580
end;
585581

586-
procedure TMainDisplayMgr.PrepareForHibernate;
587-
begin
588-
// simply save the state of the overview tree view ready for later restoration
589-
(fOverviewMgr as IOverviewDisplayMgr).SaveTreeState;
590-
end;
591-
592582
procedure TMainDisplayMgr.RedisplayOverview;
593583
begin
594584
(fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True);
@@ -616,12 +606,6 @@ procedure TMainDisplayMgr.ReStart;
616606
(fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True);
617607
end;
618608

619-
procedure TMainDisplayMgr.RestoreFromHibernation;
620-
begin
621-
(fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True);
622-
(fOverviewMgr as IOverviewDisplayMgr).RestoreTreeState;
623-
end;
624-
625609
procedure TMainDisplayMgr.SelectAll;
626610
begin
627611
// Only details pane supports text selection
@@ -720,5 +704,21 @@ procedure TMainDisplayMgr.UpdateOverviewTreeState(const State: TTreeNodeAction);
720704
(fOverviewMgr as IOverviewDisplayMgr).UpdateTreeState(State);
721705
end;
722706

707+
procedure TMainDisplayMgr._HACK_HibernateHandler(Sender: TObject);
708+
begin
709+
(fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True);
710+
(fOverviewMgr as IOverviewDisplayMgr).RestoreTreeState;
711+
// disable this handler until next resume from hibernation
712+
(fOverviewMgr as IOverviewDisplayMgr)._HACK_SetHibernateHandler(nil);
713+
end;
714+
715+
procedure TMainDisplayMgr._HACK_PrepareForHibernate;
716+
begin
717+
(fOverviewMgr as IOverviewDisplayMgr).SaveTreeState;
718+
(fOverviewMgr as IOverviewDisplayMgr)._HACK_SetHibernateHandler(
719+
_HACK_HibernateHandler
720+
);
721+
end;
722+
723723
end.
724724

0 commit comments

Comments
 (0)