【问题标题】:change control template based on current theme根据当前主题更改控制模板
【发布时间】:2013-04-25 12:16:19
【问题描述】:

不幸的是,我不得不在我的应用程序中覆盖 TabControls 之一的控制模板,因为我需要对外观进行一些细微的修改,而这是其他方式无法完成的。

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

在我切换操作系统的主题之前,一切看起来都很好。现在我的TabControl看起来完全是垃圾,因为它使用了错误主题的控制模板(我的控制模板基于的主题,用 Blend 提取)。

所以我想需要找到一种方法来为需要根据当前操作系统主题选择的特殊TabControl 提供 4 个控制模板(luna、aero、xp、经典)。

那么如何根据当前主题为specialTabControl提供和应用不同的自定义控件模板,这样当用户切换操作系统的主题时,specialTabControl会切换到我为该主题提供的控制模板?

请注意,我在应用程序中还有其他 TabControls 没有覆盖控件模板,并且应该始终具有该主题的标准控件模板。

【问题讨论】:

  • 每次有人建议分配一个新的 ControlTemplate 来解决一些问题时,我都会因为这个问题而停止阅读,这总是会立即出现在我的脑海中。
  • 您还可以为所有类型的控件编写模板,以便您的应用程序及其所有控件在所有主题中看起来都一样——无论将来会有什么新主题。这就是例如我猜是 Microsoft Office。

标签: wpf themes controltemplate


【解决方案1】:

我认为您需要查看 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 应用程序会自动下载您的主题。相应地,您可以为每个系统主题设置控制模板。更多信息请参见:

“亚当·内森。WPF 4 释放”。第 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);

但是我这个功能没有正确接收到一些系统主题名称,比如“经典”。所以我尝试了不同的方法。

第二种方法是从注册表中获取主题名称。在路径 "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 使用自定义主题(根据当前操作系统主题切换)?
  • 感谢您更新的详细答案,但我认为您还没有完全理解我的问题。我不想为整个应用程序设置主题,也不想有固定的主题/样式。我想为 one TabControl 提供 4 个控制模板(luna、classic、aero、xp),并且只要更改 OS 主题,它就应该切换到该控制模板。在同一个应用程序中,还有TabControls 没有自定义控件模板,并且应该像使用当前操作系统主题样式的常规选项卡控件一样。
  • 我更新了我的问题,希望有更好的解释。
猜你喜欢
  • 1970-01-01
  • 2012-11-24
  • 2016-07-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多