【问题标题】:TSaveDialog fails with client visual styles disabledTSaveDialog 失败,客户端视觉样式被禁用
【发布时间】:2016-03-24 10:44:13
【问题描述】:

我正在尝试在 Delphi XE6 中使用 TSaveDialog

if not SaveDialog1.Execute(0) then
   Exit;

调用立即返回false,不显示任何对话框。我将其追溯到创建 shell Save Dialog COM 对象的行为:

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

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

CoCreateInstance 的调用失败。我创建了最少的代码来重现该问题:

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

它抛出 EOleSysError 异常:

0x80040111:ClassFactory 无法提供请求的类,ClassID:{C0B4E2F3-BA21-4773-8DBA-335EC946EB8B}

我的应用程序正在使用公共控件库的第 6 版 (6.0.7601.18837),但我意识到只有当用户为我的应用程序禁用视觉样式时才会发生这种情况:

我们仍在使用第 6 版的通用控件库,只是 IsAppThemed 返回 false。

注意:我知道很多人错误地认为:

  • Visual Styles API 仅在我们加载了版本 6 的 Comctrl32.dll 时才有效
  • 如果加载了 Comctrl32.dll 版本 6,则 Visual Styles API 将起作用
  • 如果我们不使用 ComCtrl v6,那么这意味着视觉样式被禁用
  • 如果我们使用旧的通用控件库,视觉样式将被禁用

蛮力解决方案是将全局 UseLatestCommonDialogs 设置为 false。

但这很糟糕,因为它只适用于在他们的应用程序中禁用视觉样式的人:

  • 对话框继续在没有视觉样式的操作系统上工作(例如 Windows Server 2008 R2)
  • 对话框在视觉样式关闭的情况下继续工作(例如,Windows 7 视觉样式关闭)

这意味着我不能简单地使用IsAppThemed,因为如果IsThemeActive 为假,它也会返回假。

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

我想我要问的是如何检查 Disble Visual Styles 兼容标志的状态。

我真正要问的是如何使TSaveDialog 在 Delphi 中正常工作(并不暗示读取 compat 标志是解决方案的一部分)。

【问题讨论】:

  • 我希望这不是一个太迟钝的查询,但为什么是“SaveDialog1.Execute(0)”而不是更常见的“SaveDialog1.Execute”?
  • @MartynA 嗯,有三个原因 i) .Execute 没有出现在代码洞察力中 ii) 调用 .Execute 导致窗口归 ApplicationMainHandle 拥有 iii) 实际上我希望对话框拥有我正在查看的表单(例如SaveDialog1.Execute(Self.Handle))。但我不希望人们关注传递给Execute 的参数,所以我将其简化为.Execute(0)。实际上,Delphi 的窗口所有权被打破了,它忽略了所有者——而是将它想要的任何东西都带到了前面。
  • 考虑到这个公认的答案:superuser.com/questions/694734/… 也检查IsCompositionActive 是否合理?只是猜测
  • @fantaghirocco 禁用桌面合成不会禁用视觉样式,反之亦然。

标签: delphi themes delphi-xe6 visual-styles


【解决方案1】:

您肯定不想测试兼容标志。如果您要测试,您想测试该标志控制的内容。在这种情况下,无论主题是否在使用中。如果您打算这样进行测试,那么当满足以下条件时,您应该使用 Vista 风格的对话框:

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

否则,您需要使用旧的 XP 样式对话框。您可以使用以下代码实现这一点:

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

但问题在于,当用户使用 Windows 经典主题运行时,您将禁用新样式对话框。我敢肯定你不想要。

因此您可以采用基于功能的方法。这是尝试使用新样式对话框并在新样式对话框失败时回退到旧样式对话框。所以,尝试创建一个IFileSaveDialog。根据是否成功分配UseLatestCommonDialogs


另一方面,此兼容设置旨在用于在启用主题时无法正常工作的应用程序。您的应用程序在主题下确实可以正常工作,我认为您完全有理由说您的应用程序不支持该特定兼容模式。

您不应该支持兼容模式。例如,如果您停止支持 XP,那么您将不会支持 XP 兼容 shim。

经过反思,这是我对你的建议。干脆什么都不做。如果您的用户询问您的应用程序以这种方式失败,请告诉他们您不支持该兼容模式。让您的应用程序支持兼容模式不是您的工作。

【讨论】:

  • 奇怪的是有时候IsWindowsVistaOrGreater and InitThemeLibrary and UseThemes会返回false,但使用新对话框是正确和正确的。
  • 我希望 Embarcadero 在决定是否使用 IFileDialog 之前,不要再用对各种操作系统/框架功能的六次无用检查来搞乱TOpenDialog/TSaveDialog。每个版本似乎都增加了更多检查。 IFileDialog 是否可用,由操作系统决定。如果UseLatestCommonDialogs 为真,则尝试加载IFileDialog,如果失败则回退到旧对话框。完毕。但他们似乎不想这样做。
  • @Remy 我最初分享了这个观点。但现在我想知道为什么我们需要支持用于预 XP 应用程序的兼容模式。我现在认为我们不必担心现代应用程序中的此类模式是合理的。
  • 现在 Embarcadero 已经正式放弃了对 XP 的支持,即使在 IDE 本身也是如此,我可以看到他们有一天会通过 VCL/RTL 删除旧操作系统版本的兼容性代码。另一方面,对于TOpenDialog/TSaveDialog,某些兼容性代码仍然只是为了支持OnIncludeItem/OnClose/OnShow 事件,这些事件在使用IFileDialog 时不会关联. OnIncludeItem 是有道理的,但我不明白为什么他们从来没有联系过OnShow/OnClose
  • @Remy 我个人使用自己的文件对话框类,这些类直接与 IFileDialog 一起使用。
猜你喜欢
  • 1970-01-01
  • 2012-07-03
  • 1970-01-01
  • 2021-11-10
  • 1970-01-01
  • 2018-02-21
  • 1970-01-01
  • 1970-01-01
  • 2021-07-20
相关资源
最近更新 更多