【问题标题】:How to switch an Application between Themed and not Themed at run-time?如何在运行时在主题和非主题之间切换应用程序?
【发布时间】:2011-05-22 13:53:15
【问题描述】:

非常类似于“项目|选项|应用程序|启用运行时主题”复选框,但在运行时是动态的。
[针对 Win XP 或 Win 7 的 Delphi XE]

到目前为止,我尝试使用 uxTheme.SetWindowTheme 玩了一下,但没有成功......

【问题讨论】:

    标签: delphi windows-7 themes delphi-xe xp-theme


    【解决方案1】:

    为了补充 Rob Kennedy 的答案,您必须以这种方式使用 SetThemeAppProperties

    uses
     UxTheme;
    
    procedure DisableThemesApp;
    begin
      SetThemeAppProperties(0);
      SendMessage(Application.Handle,WM_THEMECHANGED,0,0);
      SendMessage(Application.MainForm.Handle,CM_RECREATEWND,0,0);
    end;
    
    procedure EnableThemesApp;
    begin
      SetThemeAppProperties(STAP_ALLOW_NONCLIENT or STAP_ALLOW_CONTROLS or STAP_ALLOW_WEBCONTENT);
      SendMessage(Application.Handle,WM_THEMECHANGED,0,0);
      SendMessage(Application.MainForm.Handle,CM_RECREATEWND,0,0);
    end;
    

    要确定您的控件是否有主题,您可以使用GetThemeAppProperties 函数。

    var
      Flag : DWORD;
    begin
      Flag:=GetThemeAppProperties;
      if (Flag and STAP_ALLOW_CONTROLS)<>0 then //if the controls are themed
      begin
    
      end;
    end;
    

    更新

    由于为您描述的问题,我检查了UxTheme 单元的代码,我发现问题与UseThemes 函数有关。所以我写了这个小补丁(使用 Andreas Hausladen 开发的函数来修补 HookProcUnHookProcGetActualAddr),它在我的测试中运行良好。让我知道是否也适合你。

    您必须在使用列表中包含 PatchUxTheme。并调用函数 DisableThemesAppEnableThemesApp

    unit PatchUxTheme;
    
    interface
    
    
    procedure EnableThemesApp;
    procedure DisableThemesApp;
    
    
    implementation
    
    uses
    Controls,
    Forms,
    Messages,
    UxTheme,
    Sysutils,
    Windows;
    
    type
      TJumpOfs = Integer;
      PPointer = ^Pointer;
    
      PXRedirCode = ^TXRedirCode;
      TXRedirCode = packed record
        Jump: Byte;
        Offset: TJumpOfs;
      end;
    
      PAbsoluteIndirectJmp = ^TAbsoluteIndirectJmp;
      TAbsoluteIndirectJmp = packed record
        OpCode: Word;
        Addr: PPointer;
      end;
    
    var
     UseThemesBackup: TXRedirCode;
    
    function GetActualAddr(Proc: Pointer): Pointer;
    begin
      if Proc <> nil then
      begin
        if (Win32Platform = VER_PLATFORM_WIN32_NT) and (PAbsoluteIndirectJmp(Proc).OpCode = $25FF) then
          Result := PAbsoluteIndirectJmp(Proc).Addr^
        else
          Result := Proc;
      end
      else
        Result := nil;
    end;
    
    
    procedure HookProc(Proc, Dest: Pointer; var BackupCode: TXRedirCode);
    var
      n: DWORD;
      Code: TXRedirCode;
    begin
      Proc := GetActualAddr(Proc);
      Assert(Proc <> nil);
      if ReadProcessMemory(GetCurrentProcess, Proc, @BackupCode, SizeOf(BackupCode), n) then
      begin
        Code.Jump := $E9;
        Code.Offset := PAnsiChar(Dest) - PAnsiChar(Proc) - SizeOf(Code);
        WriteProcessMemory(GetCurrentProcess, Proc, @Code, SizeOf(Code), n);
      end;
    end;
    
    procedure UnhookProc(Proc: Pointer; var BackupCode: TXRedirCode);
    var
      n: Cardinal;
    begin
      if (BackupCode.Jump <> 0) and (Proc <> nil) then
      begin
        Proc := GetActualAddr(Proc);
        Assert(Proc <> nil);
        WriteProcessMemory(GetCurrentProcess, Proc, @BackupCode, SizeOf(BackupCode), n);
        BackupCode.Jump := 0;
      end;
    end;
    
    function UseThemesH:Boolean;
    Var
     Flag : DWORD;
    begin
      Flag:=GetThemeAppProperties;
      if ( (@IsAppThemed<>nil) and (@IsThemeActive<>nil) ) then
        Result := IsAppThemed and IsThemeActive and ((Flag and STAP_ALLOW_CONTROLS)<>0)
      else
        Result := False;
    end;
    
    procedure HookUseThemes;
    begin
      HookProc(@UxTheme.UseThemes, @UseThemesH, UseThemesBackup);
    end;
    
    procedure UnHookUseThemes;
    begin
      UnhookProc(@UxTheme.UseThemes, UseThemesBackup);
    end;
    
    
    Procedure DisableThemesApp;
    begin
      SetThemeAppProperties(0);
      SendMessage(Application.Handle,WM_THEMECHANGED,0,0);
      SendMessage(Application.MainForm.Handle,CM_RECREATEWND,0,0);
    end;
    
    Procedure EnableThemesApp;
    begin
      SetThemeAppProperties(STAP_ALLOW_NONCLIENT or STAP_ALLOW_CONTROLS or STAP_ALLOW_WEBCONTENT);
      SendMessage(Application.Handle,WM_THEMECHANGED,0,0);
      SendMessage(Application.MainForm.Handle,CM_RECREATEWND,0,0);
    end;
    
    initialization
     HookUseThemes;
    finalization
     UnHookUseThemes;
    end.
    

    【讨论】:

    • @RRUZ。到达那里,但还没有完全... CM_RECREATEWND 肯定需要看到任何东西(尽管我会避免它,因为它可以带来 Combos、ListViews 的讨厌的副作用......)。删除主题时仍然存在问题,SpeedButtons 消失,PageControls 在更改选项卡时未重新绘制,以及 Grids 显示混乱。原因之一可能是因为 IsAppThemed and IsThemeActive 仍然返回 True,这会在尝试绘制时混淆 VCL...
    • @François,如果您从控制面板全局更改主题设置,您是否会看到类似的问题?
    • @Rob。非常好的问题。删除控制面板中的主题(到 Windows 经典版)并没有那么糟糕。在这种情况下,唯一的问题似乎是网格单元格的绘制。 SpeedButtons 和 PageControls 行为正确。现在有趣的部分是在控制面板中进行更改并使用上面的代码在应用程序中进行更改都可以正常工作(每个人似乎都表现得很好)。
    • @Rob。更有趣的是,当我使用代码从控制面板和应用程序中关闭主题时,如果我在应用程序中重新启用 first 然后在控制面板中,主题会回来,而如果我执行控制面板 1st然后在应用程序中使用代码,主题不会回来。
    • @RRUZ。似乎是一个可行的解决方案。删除主题后,TToolBars 仍然有些行为不端,但一些摆弄可能会修复它。虽然它可能不是解决方案,因为当您从控制面板中删除主题时它不会做任何事情......而且顺便说一句,Delphi 本身也不能很好地处理。 ;-) 谢谢罗德里戈(和罗伯)!
    【解决方案2】:

    【讨论】:

    • 嗯。似乎它不适用于我在家里的 D2010。 SetThemeAppProperties(0) 似乎没有任何明显的效果。 IsAppThemed and IsThemeActive 仍然返回 True,带或不带 WM_THEMECHANGED 或调用 ThemeServices.ApplyThemeChange。明天我会在工作中尝试更多使用 Delphi XE...
    【解决方案3】:

    对于我的一个项目,我使用了这样的东西:

    Procedure RemoveTheme(Const Controls : Array Of HWnd; Const Redraw : Boolean = True);
    Var
      I : Integer;
    Begin
      If IsAppThemed And IsThemeActive Then Try
        I := 0;
        While (I < Length(Controls)) Do Begin
          If (Controls[I] > 0) And IsWindow(Controls[I]) Then SetWindowTheme(Controls[I], '', '');
          If Redraw Then Begin
            InvalidateRect(Controls[I], Nil, True);
            UpdateWindow(Controls[I]);
          End;
          Inc(I);
        End;
      Except
      End;
    End;
    

    像这样使用: RemoveTheme([Edit1.Handle, Edit2.Handle]);

    【讨论】:

    • 谢谢,但在我的情况下它不起作用。 (a) 您需要向下递归容器(面板、框、选项卡/页面控件...),(b) 不是 WinControls 的控件(如 SpeedButtons 之类的图形控件...)未处理,(c) 对话框未由应用程序定义(windows.MessageBox...)无论如何都会获得主题,(d)由 VCL 绘制的控件(如网格)部分更改(滚动条由 Windows 更改,单元格未由 VCL 更改)。我宁愿设置一个全局标志并告诉 Windows/主题管理器/VCL 这个应用程序不是主题。如果可能的话....
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-16
    • 2012-09-26
    • 2021-01-06
    • 1970-01-01
    • 2018-09-03
    • 1970-01-01
    相关资源
    最近更新 更多