TSaveDialog fails with client visual styles disabled
Asked Answered
S

1

7

I am trying to use a TSaveDialog in Delphi XE6:

if not SaveDialog1.Execute(0) then
   Exit;

The call immediately returns false, without displaying any dialog. I traced it down to the act of creating the shell Save Dialog COM object:

function TCustomFileSaveDialog.CreateFileDialog: IFileDialog;
var
       LGuid: TGUID;
begin
  LGuid := CLSID_FileSaveDialog;

  CoCreateInstance(LGuid, nil, CLSCTX_INPROC_SERVER,
    StringToGUID(SID_IFileSaveDialog), Result);
end;

The call to CoCreateInstance is failing. I created minimal code to reproduce the issue:

procedure TForm1.Button1Click(Sender: TObject);
const
   CLSID_FileSaveDialog: TGUID = '{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}';
begin
   CreateComObject(CLSID_FileSaveDialog);
end;

It throws the EOleSysError exception:

0x80040111: ClassFactory cannot supply requested class, ClassID: {C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}

My application is using Version 6 of the Common Controls library (6.0.7601.18837), but i realized it only happens if the user has disabled visual styles for my application:

enter image description here

We're still using version 6 of the common controls library, just that IsAppThemed returns false.

Note: I know a lot of people mistakenly believe that:

  • Visual Styles API only works if we have version 6 of Comctrl32.dll loaded
  • If version 6 of Comctrl32.dll is loaded, then Visual Styles API will work
  • If we're not using ComCtrl v6 then that means Visual Styles are disabled
  • Visual Styles are disabled if we're using the old common controls library

The brute-force solution is to set the global UseLatestCommonDialogs to false.

But that's pretty bad, as it only applies to people who have disabled visual styles in their application:

  • the dialog continues to work on OS without visual styles (e.g. Windows Server 2008 R2)
  • the dialog continues to work with visual styles turned off (e.g. Windows 7 with visual styles turned off)

This means i cannot simply use IsAppThemed, as that also returns false if IsThemeActive is false.

| IsThemeActive | IsAppThemed | Disable visual styles | Result    |
|---------------|-------------|-----------------------|-----------|
| True          | True        | Unchecked             | Works     |
| True          | False       | Checked               | Fails     |
| False         | False       | Unchecked             | Works     |
| False         | False       | Checked               | Fails     |

What i guess i'm asking is how to check the status of the Disble Visual Styles compat flag.

What i'm really asking is how to make the TSaveDialog work correctly in Delphi (without implying that reading the compat flag is part of the solution).

Sober answered 17/12, 2015 at 20:17 Comment(4)
I hope this isn't too obtuse a query but why "SaveDialog1.Execute(0)" and not the more usual "SaveDialog1.Execute"?Undecided
@Undecided Well there's three reasons for that i) .Execute doesn't show up in code insight ii) Calling .Execute causes the window to be owned by ApplicationMainHandle iii) In reality i want the dialog to be owned to the form i'm looking at (e.g. SaveDialog1.Execute(Self.Handle)). But i didn't want people focusing on the parameter passed to Execute, so i simplified it to .Execute(0). In reality, Delphi's window ownership is broken, and it ignored the owner - instead bringing whatever it wants forward.Sober
taking into account this accepted answer: superuser.com/questions/694734/… is it reasonable to check IsCompositionActive also? Just a guessNewland
@Newland Disabling desktop composition doesn't disable visual styles, or vice versa.Sober
P
5

You surely don't want to test for the compat flag. If you are going to test, you want to test what that flag controls. In this case whether or not themes are in use. If you are going to test like that then you should use the Vista style dialogs when the following condition holds:

IsWindowsVistaOrGreater and Winapi.UxTheme.InitThemeLibrary and Winapi.UxTheme.UseThemes

Otherwise you need to use the old XP style dialogs. You can make this happen with the following code:

UseLatestCommonDialogs := IsWindowsVistaOrGreater and Winapi.UxTheme.InitThemeLibrary 
  and Winapi.UxTheme.UseThemes;

The problem with this though is that you will disable new style dialogs when the user is running with the Windows Classic theme. Which I am sure that you do not want.

So you could take a functionality based approach. That is attempt to use the new style dialogs and fallback on the old style dialog if the new fails. So, attempt to create an IFileSaveDialog. Assign UseLatestCommonDialogs based on whether or not that succeeds.


On the other hand, this compat setting is intended to be used on applications that don't work correctly when themes are enabled. Your application does work correctly under themes and I think it is entirely justifiable for you to say that your application does not support that particular compat mode.

You are not expected to support compatibility modes. For instance, if you stop supporting XP, then you would not be expected to support the XP compat shims.

On reflection that's my advice to you. Simply do nothing. If your users ask about your app failing in that way, tell them that you don't support that compat mode. It is not your job to make your application support compatibility modes.

Phelips answered 17/12, 2015 at 21:13 Comment(5)
The strangeness is that is there are times when IsWindowsVistaOrGreater and InitThemeLibrary and UseThemes will return false, but it is correct and right to use the new dialogs.Sober
I wish Embarcadero would stop cluttering TOpenDialog/TSaveDialog with the half-dozen useless checks for various OS/framework functionalities before deciding whether to use IFileDialog or not. Each release seems to add more checks. Either IFileDialog is available or it is not, let the OS decide. If UseLatestCommonDialogs is true then attempt to load IFileDialog and if it fails then fallback to the old dialog. Done. But they don't seem to want to do that.Upon
@Remy I initially shared that view point. But now I'm wondering why we would need to support a compat mode intended for pre XP apps. I now think it's justifiable for us not to worry about such modes in our modern apps.Phelips
Now that Embarcadero has officially dropped support for XP, even in the IDE itself, I could see them someday going back through the VCL/RTL removing compatibility code for legacy OS versions. On the other hand, for TOpenDialog/TSaveDialog specifically, some of that compatibility code remains just to support the OnIncludeItem/OnClose/OnShow events, which are not hooked up when IFileDialog is being used. OnIncludeItem makes sense, but I never understood why they never hooked up OnShow/OnClose when they could have.Upon
@Remy Personally I use my own file dialog classes that work with IFileDialog directly.Phelips

© 2022 - 2024 — McMap. All rights reserved.