Skip to content

Commit 58be37c

Browse files
committed
Fix CodeSnip crash after resume from hiberanation.
It seems that the problem is that, sometimes (not always) Windows recreates the tree view displayed in the overview pane and all its nodes. The tree view nodes are custom classes that have a property that reference an `IView` instance relating to the displayed items. Unfortunately when Windows recreates the nodes the `IView` property is set to `nil`. This explains the nil `IView` references that have been causing the access violation. The solution used in the fix is to handle the Windows messages sent when the computer hibernates and resumes. On hibernation the state of the tree view is recorded. On restoration we assume that the tree view is corrupted and so forcibly rebuild it and restore the saved state. There is a problem thought. The message we handle is issued twice after resuming from hibernation. There is no easy way to tell which message has been issued. Therefore the tree view is rebuilt twice. There is not much performance penalty to this, so we can let it go. The potential problem is that if the tree view is recreated it happens after the 1st message and before the 2nd. Should, for example, the message only be triggered once then the bug will be back! Even after all this it is possible that the program will redraw the tree view before the `IView` instances are restored. I've added code to the tree node custom drawing code to test if a node's `IView` instance is nil. This leads to some nodes not being drawn correctly. However, this doesn't matter because the tree view is forcibly redrawn again after the `IView` instances are restored. All in all, I've not totally happy with this solution, which is more of a work around than a fix, but it's the best I can come up with without completely revising the overview frame code. Fixes #70
1 parent 0908e3b commit 58be37c

File tree

3 files changed

+64
-4
lines changed

3 files changed

+64
-4
lines changed

Src/FmMain.pas

+22-1
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,11 @@ TMainForm = class(THelpAwareForm)
522522
/// <summary>Object that manages favourites.</summary>
523523
fFavouritesMgr: TFavouritesManager;
524524

525+
/// <summary>Handles the <c>WM_POWERBROADCAST</c> messages to detect and
526+
/// respond to hibernation messages.</summary>
527+
/// <remarks>This is necessary as part of the fix for an obscure bug. See
528+
/// https://github.com/delphidabbler/codesnip/issues/70</remarks>
529+
procedure WMPowerBroadcast(var Msg: TMessage); message WM_POWERBROADCAST;
525530
/// <summary>Displays view item given by TViewItemAction instance
526531
/// referenced by Sender and adds to history list.</summary>
527532
procedure ActViewItemExecute(Sender: TObject);
@@ -1324,7 +1329,6 @@ procedure TMainForm.FormDestroy(Sender: TObject);
13241329
// fStatusBarMgr MUST be nilled: otherwise it can be called after status bar
13251330
// control has been freed and so cause AV when trying to use the control
13261331
FreeAndNil(fStatusBarMgr);
1327-
13281332
end;
13291333

13301334
procedure TMainForm.FormResize(Sender: TObject);
@@ -1582,5 +1586,22 @@ procedure TMainForm.splitVertCanResize(Sender: TObject;
15821586
Accept := False;
15831587
end;
15841588

1589+
procedure TMainForm.WMPowerBroadcast(var Msg: TMessage);
1590+
begin
1591+
// Sometimes when the computer is resumed from hibernation the tree view in
1592+
// the overview frame is destroyed and recreated by Windows. Unfortunately the
1593+
// IView instances associated with the recreated tree nodes are lost.
1594+
// Attempting to read those (now nil) IView instances was resulting in an
1595+
// access violation.
1596+
case Msg.WParam of
1597+
PBT_APMSUSPEND:
1598+
// Get ready for isolation
1599+
fMainDisplayMgr.PrepareForHibernate;
1600+
PBT_APMPOWERSTATUSCHANGE:
1601+
// Restore from hibernation: ensure the IView instances are recreeated
1602+
fMainDisplayMgr.RestoreFromHibernation;
1603+
end;
1604+
end;
1605+
15851606
end.
15861607

Src/FrOverview.pas

+13-3
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ TTVDraw = class(TSnippetsTVDraw)
8686
@return True if node is a section header, False if not.
8787
}
8888
end;
89+
8990
var
9091
fTVDraw: TTVDraw; // Object that renders tree view nodes
9192
fNotifier: INotifier; // Notifies app of user initiated events
@@ -966,7 +967,12 @@ function TOverviewFrame.TTVDraw.IsSectionHeadNode(
966967
ViewItem: IView; // view item represented by node
967968
begin
968969
ViewItem := (Node as TViewItemTreeNode).ViewItem;
969-
Result := ViewItem.IsGrouping;
970+
// Workaround for possibility that ViewItem might be nil when restarting after
971+
// hibernation.
972+
if Assigned(ViewItem) then
973+
Result := ViewItem.IsGrouping
974+
else
975+
Result := False;
970976
end;
971977

972978
function TOverviewFrame.TTVDraw.IsUserDefinedNode(
@@ -979,8 +985,12 @@ function TOverviewFrame.TTVDraw.IsUserDefinedNode(
979985
ViewItem: IView; // view item represented by node
980986
begin
981987
ViewItem := (Node as TViewItemTreeNode).ViewItem;
982-
// TODO -cBug: Exception reported as issue #70 seems to be triggered here
983-
Result := ViewItem.IsUserDefined;
988+
// Workaround for possibility that ViewItem might be nil when restarting after
989+
// hibernation.
990+
if Assigned(ViewItem) then
991+
Result := ViewItem.IsUserDefined
992+
else
993+
Result := False;
984994
end;
985995

986996
end.

Src/UMainDisplayMgr.pas

+29
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,23 @@ TMainDisplayMgr = class(TObject)
291291

292292
/// <summary>Prepares display ready for database to be reloaded.</summary>
293293
procedure PrepareForDBReload;
294+
295+
/// <summary>Gets the overview frame prepared for program hibernation.
296+
/// </summary>
297+
/// <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;
310+
294311
end;
295312

296313

@@ -566,6 +583,12 @@ procedure TMainDisplayMgr.PrepareForDBViewChange(View: IView);
566583
fPendingViewChange := True;
567584
end;
568585

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+
569592
procedure TMainDisplayMgr.RedisplayOverview;
570593
begin
571594
(fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True);
@@ -593,6 +616,12 @@ procedure TMainDisplayMgr.ReStart;
593616
(fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True);
594617
end;
595618

619+
procedure TMainDisplayMgr.RestoreFromHibernation;
620+
begin
621+
(fOverviewMgr as IOverviewDisplayMgr).Display(Query.Selection, True);
622+
(fOverviewMgr as IOverviewDisplayMgr).RestoreTreeState;
623+
end;
624+
596625
procedure TMainDisplayMgr.SelectAll;
597626
begin
598627
// Only details pane supports text selection

0 commit comments

Comments
 (0)