基于当前主题更改控件模板

4

很不幸,我不得不覆盖我的应用程序中一个TabControl的控制模板,因为我需要对外观进行一些微小的修改,否则无法完成。

<TabControl Name="specialTabControl" Template="{StaticResource SpecialTabControlTemplate} />". 

我所有的一切看起来都很好,直到我切换操作系统的主题。现在,我的 TabControl 看起来非常糟糕,因为它使用了错误主题的控件模板(我基于该主题提取的控件模板,使用 Blend)。所以,我想找到一种方法来为这个特殊的 TabControl 提供 4 个控件模板(luna、aero、xp、classic),并根据当前操作系统主题进行选择。
那么,在基于当前主题提供和应用不同的自定义控件模板时,如何为 specialTabControl 进行设置,使得当用户更改操作系统的主题时,specialTabControl 将会切换到我为该主题提供的控件模板?
请注意,我还有其他在应用程序中的 TabControl,它们没有覆盖控件模板,并且应始终具有该主题的标准控件模板。

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - springy76
你也可以为所有类型的控件编写模板,这样你的应用程序和所有控件在所有主题中看起来都是一样的 - 无论将来有什么新主题。我想这就是微软办公套件所做的。 - Lumo
1个回答

0

我觉得你需要看一下WPF应用程序中的主题。程序集可以包含以下行:

[assembly:ThemeInfo(ResourceDictionaryLocation.SourceAssembly, ResourceDictionaryLocation.SourceAssembly)]

这意味着应用程序的系统主题位于您的程序集中(在/themes文件夹中)。主题的名称必须符合系统主题的规范...例如:

The Aero theme (Windows Vista and Windows 7): themes\Aero.NormalColor.xaml
The default Windows XP theme: themes\Luna.NormalColor.xaml
The olive green Windows XP theme: themes\Luna.Homestead.xaml
The Windows Classic theme: themes\Classic.xaml

当用户更改系统的主题时,WPF应用程序将自动下载您的主题。因此,您可以为每个系统主题设置控件模板。更多信息请参考:
"Adam Nathan. WPF 4 Unleashed". 第14章。
主题化WPF应用程序: http://blogs.infosupport.com/theming-wpf-applications/ 希望对您有所帮助。
*编辑*
我找到了一个有趣的示例,涉及实际更改主题的内容。

http://northhorizon.net/2010/how-to-actually-change-the-system-theme-in-wpf/

您可以设置显式样式,它不会响应系统中的皮肤更改:

<Style x:Key="ExplicitGreenButtonStyle" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
   <Setter Property="Background" Value="Green" />
   <Setter Property="Foreground" Value="White" />
</Style>

因此,隐式样式将响应系统皮肤的更改:

<Style x:Key="ImplicitGreenButtonStyle" TargetType="Button">
    <Setter Property="Background" Value="Green" />
    <Setter Property="Foreground" Value="White" />
</Style>

此外,在示例中包含了一个有用的代码,位于ThemeHelper中,其中包含一些主题函数。

* 编辑 #2 *

如果我理解正确,首先需要获取系统主题名称。这个操作可以通过几种方式来完成。

第一种方法是使用来自库“UxTheme.dll”的Win32函数,例如GetCurrentThemeName()

[DllImport("uxtheme.dll", CharSet = CharSet.Auto)]
public static extern int GetCurrentThemeName(StringBuilder pszThemeFileName, int dwMaxNameChars, StringBuilder pszColorBuff, int dwMaxColorChars, StringBuilder pszSizeBuff, int cchMaxSizeChars);

StringBuilder stringThemeName = new StringBuilder(260);
StringBuilder stringColorName = new StringBuilder(260);
StringBuilder stringSizeName = new StringBuilder(260);

Int32 s = GetCurrentThemeName(stringThemeName, 260, stringColorName, 260, stringSizeName, 260);

但是我发现这个函数无法正确接收一些系统主题名称,例如“Classic”。所以我尝试了另一种方法。

第二种方法是从注册表中获取主题名称。在路径“HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\LastTheme”中包含当前系统主题。因此,我们需要通过窗口钩子拦截“更改系统主题事件”。

以下是一个带有几个按钮的示例,其样式取决于系统主题:

添加到窗口:

SourceInitialized="Window_SourceInitialized"

样式:

<Style x:Key="DefaultStyle" BasedOn="{StaticResource {x:Type Button}}" TargetType="{x:Type Button}">
    <Setter Property="Background" Value="CadetBlue" />
</Style>

<Style x:Key="LunaStyle" TargetType="{x:Type Button}">
    <Setter Property="Background" Value="Blue" />
    <Setter Property="Foreground" Value="White" />
</Style>

<Style x:Key="ClassicStyle" TargetType="{x:Type Button}">
    <Setter Property="Background" Value="Gray" />
    <Setter Property="Foreground" Value="Black" />
</Style>

主网格:

<Grid>
    <Button Style="{StaticResource DefaultStyle}" Content="Default button" Width="100" Height="30" VerticalAlignment="Top" HorizontalAlignment="Left" />
    <Button Name="ChangeButtonStyle" Content="Changes style" Width="100" Height="30" VerticalAlignment="Top" HorizontalAlignment="Right" />
    <TextBlock Name="CurrentTheme" FontSize="16" Text="Null" Width="150" Height="30" HorizontalAlignment="Center" VerticalAlignment="Top" />
</Grid>

在代码中:
拦截“更改系统主题事件”:
private IntPtr hwnd;
private HwndSource hsource;

private void Window_SourceInitialized(object sender, EventArgs e)
{
    if ((hwnd = new WindowInteropHelper(this).Handle) == IntPtr.Zero)
    {
        throw new InvalidOperationException("Could not get window handle.");
    }

    hsource = HwndSource.FromHwnd(hwnd);
    hsource.AddHook(WndProc);
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case 0x31A:          // Define this as WM_DWMCOMPOSITIONCHANGED for Windows 7
        case 0x31E:          // Define this as WM_THEMECHANGED

        // Action on the change system theme
        GetThemeName(SubKey, Value); 

        return IntPtr.Zero;

        default:

        return IntPtr.Zero;
     }
}

获取系统主题名称并设置样式:

public string SubKey = "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\LastTheme";
public string Value = "ThemeFile";

private void GetThemeName(string OpenKey, string Value)
{
    RegistryKey pRegKey = Registry.CurrentUser;
    pRegKey = pRegKey.OpenSubKey(OpenKey);
    Object val = pRegKey.GetValue(Value);
    string NameThemeFile = val as string;

    if (NameThemeFile.IndexOf("Luna") != -1)
    {
        ChangeButtonStyle.Style = this.FindResource("LunaStyle") as Style;
        CurrentTheme.Text = "Luna";
    }

    if (NameThemeFile.IndexOf("Classic") != -1)
    {
        ChangeButtonStyle.Style = this.FindResource("ClassicStyle") as Style;
        CurrentTheme.Text = "Classic";
    }
}

这种方法不是最好的,但可以开始。


但是我如何在一个TabControl中使用来自WPF本身的默认主题,而在另一个TabControl中使用基于当前操作系统主题切换的自定义主题呢? - bitbonk
谢谢您提供了更新的详细答案,但我认为您还没有完全理解我的问题。我不想为整个应用程序设置主题,也不想有固定的主题/样式。我想为一个 TabControl 提供 4 个控件模板(luna、classic、aero、xp),并且每当操作系统主题更改时,它都应该切换到该控件模板。在同一应用程序中,还有一些 TabControl 没有自定义控件模板,只需像常规选项卡控件一样使用当前操作系统主题的样式即可。 - bitbonk
我已经更新了我的问题,并提供了一个更好的解释。 - bitbonk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接