views:

101

answers:

1

I have an app with a TPageControl on the main form. The pagecontrol has several tabs. The app can be minimized to a tray icon. Sometimes after running minimized for a while, when I restore the main window (via a right-mouse click on the tray icon), the tab that was last displayed is displayed, but I can't select any other tabs!

If I click on another tab, the appearance changes so that tab then appears to be the active one (i.e the tab itself moves to the front of the row of tabs), but the body of the tab remains as it was. I also have menu items and shortcut keys to select the other tabs and they behave the same. If I type Alt-O (options) the options tab at the top becomes active but I can't see what is on the body of that tab - I still see the other tab's contents.

I have verified that focus moves off the first tab when I click on another tab and moves back when I click on that tab.

I haven't yet established if the behaviour is confined to a particular tab as it takes a while for it to happen.

Any ideas?

Update

Interesting note. I have established that the problem occurs under these circumstances. The app is started, then minimized to the tray. An alert condition is detected, pops up a window and restores the main window (this is intended behaviour of the app). It is at this point the fault is observed - i.e. I cant see the other tabs when I click on them.

  • Start app. Tab 1 is displayed
  • Minimize app. to tray
  • Wait for popup to show, main form is restored
  • Click on Tab 2 FAULT OBSERVED (Tab 2 body does not display)
  • Put breakpoint in TWinControl.CreateHandle
  • Click on Tab 3 - breaks
  • Run - does not show Tab 3 body
  • Click on Tab 1 - does not break
  • Click on Tab 3 - does not break
  • Click on Tab 4 - breaks
  • Run - does not show Tab 4 body
  • Click on Tab 1, 2, 3, 4 - does not break

So it seems the tabs are creating their handles the first time they are clicked on, and from that point on they think they exist, but they don't show. If the popup is disabled the fault is not observed. The popup is triggered from an Application.OnIdle task.

Another update: Some progress. After poking around on the web I made some changes.

I removed the following code:

procedure RestoreMainWindow ;

begin
MainForm.WindowState := wsNormal ;
MainForm.visible := true ;
Application.Restore ;
Application.BringToFront ;
ShowWindow (Application.Handle, SW_SHOW) ;  { show the taskbar button }
end ;

and replaced it with:

procedure RestoreMainWindow ;

begin
MainForm.Show () ;
MainForm.WindowState := wsNormal ;
Application.BringToFront () ;
ShowWindow (Application.Handle, SW_SHOW) ;  { show the taskbar button }
end ;

I removed:

procedure TTADMainForm.SendToTray (Sender: TObject) ;

begin
MainForm.visible := false ;
ShowWindow (Application.Handle, SW_HIDE) ;  { hide the taskbar button }
end ;
...
Application.OnMinimize := SendToTray ;    

and replaced it with:

procedure TTADMainForm.ApplicationEvents1Minimize(Sender: TObject) ;

begin
Hide();
WindowState := wsMinimized ;
TrayIcon1.Visible := True;
end ;

and the problem seems to have gone. HOWEVER. Now I can minimize the app after startup, the popup occurs and shows modally, the main form shows, all the tabs display and work. BUT. I can't minimize the form again. The OnMinimize handler doesn't get triggered after the first time. Grrrrr.

I still can't fathom why it works now, which is a little worrying. And how do I get it to minimize again??

+3  A: 

Working entirely from 5 years ago memory, but here goes:

TPageControl uses a different window handle for each page within it. The tab bar is its own window handle, and the TPageControl is responsible for listening to tab changes and making the corresponding hide/show of pages. So, when you click on a tab and the tab jumps to the front of the pack, the TPageControl is supposed to hide the current page window and show the page window corresponding to the selected tab.

Normally, VCL controls don't create their window handle until it is actually needed - when it's actually shown, for example. This reduces window handle consumption. Critically important in Windows 3.1 and Win95, but not so critical in today's NT based 32 bit OS's.

To minimize resource load and startup time, TPageControl doesn't create window handles for all its hidden pages when the control is created. The page window handles will be created when they are first shown.

There are a few possibilities for why the page is not being drawn when the tab is clicked:

  1. Exhausting the GDI window handle pool. Extremely unlikely unless you're on a 16 bit Windows OS. (Win 3.1 or Win95)
  2. Memory leak that causes your app to spill into the swap file and thrash the hard disk. The app will grind to a near halt and look like it's frozen, with burps of UI activity every now and then.
  3. Window handles being created on a background thread that has no message loop. Are you doing anything in background threads? Touching a VCL control in a background thread can cause the window handle to be created prematurely, and the window handle will be bound to the thread it was created on. If that thread has no message loop, then that window handle will never receive any messages, so it will never draw itself on screen.

No. 3 is your most likely culprit. So, what are you doing in that background thread? ;>

dthorpe
Thanks for the detailed answer. There is no thrashing evident and after the event has occurred the app behaves exactly as I would expect as far as the controls I can see are concerned. There is a background thread - but I don't believe it touches the VCL. Would you expect all pages on the control (there are 7) to become unresponsive? If the windows handles in a background thread scenario is the cuplrit, would an emergency work-around be to programmatically select each page in turn on startup?
You'll need to check the docs or the VCL source code for TPageControl to see if it destroys page window handles when it hides the pages. I can't remember if it does or not. Probably did in the Win3.1 days, but no longer needed in Win32.
dthorpe
If it does not destroy the window handle when a page is hidden, then yes, touching all the page handles at startup would force all the handles to be created. However, be careful here because if you have a dozen tab pages and each page has 20 controls on it, creating all those handles will take a chunk o time. That's why we changed TPageControl to be lazy-load - the Delphi IDE's options dialog was taking too long to load! ;>
dthorpe
Look carefully at how the background thread interacts with the UI or foreground thread. If it calls a function that calls a function that calls a function that assigns a string to a Button.Caption, that happens in the context of the background thread and can force the Button control to create its window handle right then and there. If the only communication between threads is via PostMessage or setting fields in a shared data structure, then your background thread is full isolated.
dthorpe
You might be onto something. Where could I put a breakpoint to establish this. Presumably a particular piece of code executes when the background thread interacts illegally with the UI and if I could trap that it can give me a clue about where or when it's happening.
Set a breakpoint in TWinControl.CreateHandle or similar function - something that is part of the window handle creation step. You'll probably want to start your app up first and then set the breakpoint since this will be a very busy area of the code. The run your background thread and see if your breakpoint in create handle gets hit. If it does, look at the register window or the thread window to see what the current thread handle is, and compare that to your background thread. If it matches, look at the call stack to see how you got there from the background thread.
dthorpe
See my edit to the question
Still more info: The sequence of events to guarantee the fault is: Start app, minimize to tray, trigger OnIdle hander, which calls Application.Restore, Application.BringToFront, AlertForm.ShowModal, close alert form after delay. PageControl is dead. If I leave out the Application.Restore and Application.BringToFront the app behaves.
See my further edits