How do I get the tabbed dialog effect on my own custom tabbed dialog?


CJ observed that the standard tabbed dialogs provide an effect on the tab pages and want to know how to get the tabbed dialog effect on custom dialogs. fds242 noted this as well and wanted to know why the automatic tabbed dialog effect doesn't kick in until you put a static control on the child dialog box.

Let's look at the first question first. To get the tabbed dialog effect, you can call Enable­Theme­Dialog­Texture with the ETDT_ENABLE­TAB flag. The best time to do this is in the WM_INIT­DIALOG handler, but you can also do it immediately after the dialog has been created. (Basically, you want to do this before the dialog paints for the first time, so as to avoid flicker.)

Here's a sample program that shows a dummy dialog with the tabbed dialog texture enabled.

// Hereby incorporated by reference:
// dialog template and DlgProc function from this sample program
// Comctl32 version 6 manifest from this sample program

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
    return DialogBox(hinst, MAKEINTRESOURCE(1), 0, DlgProc);
}

If you run this program, you get the expected dialog box without the tabbed dialog effect. But you can turn on the effect by calling the Enable­Theme­Dialog­Texture function:

#include <uxtheme.h>

 case WM_INITDIALOG:
  CheckRadioButton(hdlg, 100, 102, 100);
  EnableThemeDialogTexture(hdlg, ETDT_ENABLETAB);
  return TRUE;

Now, when you run the program, you get the tabbed dialog effect. It looks kind of weird when it's not in a tabbed dialog, but presumably you're going to put this dialog inside your own custom tabbed dialog control, so everything will look right when it's all finished.

Now the second half of the question: Why doesn't the automatic tabbed dialog effect kick in until you put a static control on the child dialog box?

If you look closely at the ETDT_ENABLE­TAB flag, you'll see that it's really two flags: ETDT_USE­TAB­TEXTURE and ETDT_ENABLE. The first flag says, "I would like to get the tab texture, if enabled"; the second flag says "Enable it." In other words, in order to get the tab texture, the tab texture needs to be both used and enabled.

Originally, ETDT_ENABLE­TAB was just a single bit. Setting the bit turned on the tab texture. But it turns out that some programs didn't look good with the tab texture, and the common reason was that they created a dialog with no standard controls at all and then did custom drawing all over it. Therefore, the algorithm for enabling the tab texture was changed to the two-step version. The property sheet manager turned on the ETDT_USE­TAB­TEXTURE flag, and the button and static controls turned on the ETDT_ENABLE flag. Therefore, if your property sheet page has a button or a static, the second bit got turned on, and the tab texture became visible. On the other hand, if you didn't have any buttons or statics, then the assumption is that you're one of those programs that does custom dialog drawing, and the tab texture remains disabled.

Let's watch it in action:

1 DIALOGEX 32, 32, 200, 76
STYLE DS_MODALFRAME
CAPTION "Sample"
FONT 8, "MS Shell Dlg"
BEGIN
    // nothing!
END

INT_PTR CALLBACK DlgProc(HWND hdlg, UINT uMsg,
                         WPARAM wParam, LPARAM lParam)
{
 switch (uMsg) {
 case WM_INITDIALOG:
  return TRUE;
 }
 return FALSE;
}


int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
 PROPSHEETPAGE psp = { sizeof(psp) };
 psp.hInstance = hinst;
 psp.pszTemplate = MAKEINTRESOURCE(1);
 psp.pfnDlgProc = DlgProc2;

 PROPSHEETHEADER psh = { sizeof(psh) };
 psh.dwFlags = PSH_PROPSHEETPAGE;
 psh.nPages = 1;
 psh.ppsp = &psp;
 return PropertySheet(&psh);
}

If you run this program, you'll see that there is no tabbed dialog texture. As we saw earlier, the reason there is no tabbed dialog texture is that the system is afraid that you're one of those programs that custom-draws their dialog boxes, so it's staying out of your way.

But add this line:

 case WM_INITDIALOG:
  EnableThemeDialogTexture(hdlg, ETDT_ENABLETAB);
  return TRUE;

The property sheet manager was afraid to give you that texture by default, but adding that line just adds the texture manually.

This time, when you run the program, you get the happy tabbed dialog texture because you added it explicitly.

I will leave you to answer fds242's final question: "Why do Windows Task Manager's tab pages still have the gray background."

Comments (13)
  1. Dan Bugglin says:

    To be clear this seems to be talking about XP's Task Manager.  Vista's is probably fixed (7's definitely is, 8 is just a completely new Task Manager).

    I assume Task Manager wasn't doing tabs right, didn't have these flags set and since it wasn't a dialog it wouldn't get them automatically.  At least I assume it's not a propertysheet dialog, it has a bunch of extra bits those don't have like a status bar.

    It could also have something to do with the undocumented feature where you double click to hide everything except the active tab pane.

  2. jon says:

    One thing to be aware of is that, at least under XP and possibly later versions, the tab texture has a finite height (~460px from memory) and Windows won't draw anything below it if your dialog is taller than the texture.

  3. Leo Davidson says:

    I've always assumed Task Manager didn't use the tab texture on Windows XP because, as Jon says just above, the texture stops painting beyond a certain height and Task Manager is resizable.

    (It was a bit of a fail designing a UI theme element without resizing or even just large windows in mind, really. Especially when part of the OS itself required it.)

    With Aero, the texture is a solid white colour and will fill the entire area it is assigned to, so the problem isn't there. (In both cases, you can get decent results by drawing the texture yourself and either repeating it — not so great when it's XP's gradient — or flipping it upside down on alternate rows.)

  4. Christian says:

    If someone actually runs these programs, can he post screenshots? That would be great!

  5. Ian says:

    It took me a while to work out what Raymond was writing about. We're talking about the subtle gradient background from white at the top to just off-white at the bottom that is seen in tabbed dialogs on XP. I wouldn't have called it a 'texture' although if you blow it up in an image editor and enhance the contrast you can just about see the texture caused by what looks like (and might actually be) dithering. You might never even notice that there is a gradient unless you draw a control with a white background near the bottom of the tab page and discover that the white background of the control doesn't quite match the background colour of the page. (I ran into this problem because the standard TLabel control in Delphi couldn't be drawn with a transparent background, so getting the background to match the tab page background involved jumping through hoops.)

  6. Instead of using property sheet, I use the common tab control (SysTabControl32) and put it on a dialog with a few static controls & radio buttons (that is to say, the tab control and the other controls are siblings window), then on WM_INITDIALOG I move these static/radio controls on top of the tab control. The problem is: the static control is rendered with gray background while the tab control is rendered with white texture. Does anyone know why?

    See img38.imageshack.us/…/60378192.png

  7. Ian says:

    Correction: My memory let me down. It wasn't a static that couldn't be made transparent. It was a read-only edit box with no border that was supposed to look like a static label but respond to select and copy. (Just like the one at the top of the Shortcut tab on file properties, which of course proves it can be done.)

  8. @Ian See the picture above in my comments, I suspect we are seeing the same problem. However, it took me a while to find out this: Instead of moving control on the tab, I create another dialog with needed controls (static/radio/etc) as the child window of the main window, then move the dialog at the proper location (use TabCtrl_AdjustRect to make sure it centered at the middle of tab, make the dialog looks like it is embeded inside the tab control). I also enable the common control 6, and use EnableThemeDialogTexture for the "embeded" dialog, after doing these, the tab control is rendered with white texture, and the radio/static control inside it looks fine!

    See this:

    img233.imageshack.us/…/51044293.png

    Actually, this is the exact same window hierarchy that property sheet uses.

    However, if I create a custom control (my own registered window class) as the holder window (like a dialog, contains other child window), EnableThemeDialogTexture won't work. It seems to me that EnableThemeDialogTexture  only works for dialog! So how do I get the custom control with the tab texture enabled?

  9. I submitted my code here:

    codeviewer.org/…/code:2b5e

    Or as a backup link, here:

    http://pastebin.com/Gyhwy97t

    The code above creates a dialog with a tab control with 3 pages on it, the first page has one static control, the second page has one "embeded" dialog, the third one has a custom control as the panel.

    Only the page with the dialog (with EnableThemeDialogTexture called on) works fine (the white texture is used), on the other pages the static control including radio button are not drawn "transparently".

  10. Rick C says:

    Looks like you've got some function inconsistency somewhere:

    psp.pfnDlgProc = DlgProc2;

    There's no DlgProc2 defined, so I assume you want to use DlgProc again.

    Two questions:  would you include necessary #include lines with sample code like this?  I guess not doing so has the advantage that I have to go out to MSDN and figure out the right ones and then sometimes I read the docs, but it would be easier without it.  Relatedly, would you consider adding either a list of necessary link libraries to your sample code, or appropriate #pragma comments with the libraries listed, for the same reason?

  11. ChrisR says:

    @Holy.Cow:  Actually the static control on the first tab *is* being drawn transparently.  However it is being drawn transparently over *it's parent*, which is the main dialog box, and which has a gray background.

    To see that this is indeed the case, add "EnableThemeDialogTexture(hdlg, ETDT_ENABLETAB);" directly after "g_hDlg = hdlg;" in the WM_INITDIALOG case of DlgProc.  You will see that the gradients don't quite line up but it is pretty obvious what is happening then.

    Your custom window class test is suffering from the same issue:  The static text is drawing onto the custom window class's background, which happens to be flat gray.  If you add WS_EX_TRANSPARENT to g_hCustomPanel you can see that the custom panel is then "drawn" correctly, but its static text child is still drawn incorrectly.

    The root cause of all this confusion is that those controls are probably drawing into their parents' DC.  If the parent is not correct or is not doing the correct painting, you won't get what you want.

  12. @ChrisR: Thank you for the detailed explanations! Now it's very clear for me to understand the problem for the custom window, but I still have no clue how to get the child controls on my custom window drawn transparently, any idea?

    By the way, on the second page (with a dialog on it), if the "embeded" dialog is not resized to fit the page, you will notice that the gradient filling is not "correctly" fit the tab on Windows XP.

  13. All right, I tried "SetBkMode(hDC, TRANSPARENT);" in response to WM_CTLCOLORSTATIC message in the window procedure of the custom window, it seems to work fine, for now.

Comments are closed.