Silverlight 动态主题
本文介绍了如何动态应用主题到 Silverlight 应用程序。
目录
引言
在本文中,我们将了解如何创建一个真正动态的、支持主题的 Silverlight 应用程序。为了能够构建配套项目,您需要满足以下要求:
- 安装 Microsoft Visual Studio 2010。
- CodePlex 网站上的 Silverlight Toolkit。
- Microsoft Downloads 提供的最新 Silverlight 4 主题(JetPack、Accent Color、Windows 7 和 Cosmopolitan)。
我们将根据下图所示的截图,在一个页面上测试动态主题。
如何动态更改主题
我们希望使用现有工具包主题,让用户轻松更改应用程序的外观和感觉。使用现有工具包主题有两种方法:
- 引用内置主题
- 将原始主题嵌入到项目中
1. 引用内置主题
此技术有一个重要的优点:您只需引用主题程序集和您使用的控件(Core、SDK 或 Silverlight Toolkit)。不同的控件来自不同的程序集,但您不必加载所有控件,只需加载您正在使用的控件。这样可以减小应用程序的体积,并且使项目更容易导航。缺点是无法修改内置主题。如果您打算创建自定义控件或想调整现有控件的外观,您就无法做到。或者,您可以通过显式样式覆盖特定控件的主题来实现,但这不再是动态方式了。
网上有很多文章描述如何使用内置主题。我们在这里不再赘述,直接跳到第二种技术。
2. 将原始主题嵌入到项目中
使用此方法将使我们能够完全控制现有的 Silverlight 主题。为此,我们需要将原始 Silverlight 主题添加到项目中。此方法的主要问题在于,您必须引用包含嵌入的原始主题中的控件的程序集。对于小型应用程序,这可能无法接受,但对于大型应用程序,大多数控件程序集已经引用,不会增加额外负担。
新的 Silverlight 4 主题结构良好,每个主题有六个易于理解的文件:
- Brushes.xaml(主题画笔定义)
- Fonts.xaml(主题字体定义)
- CoreStyles.xaml(核心 Silverlight 控件,如
Button
、TextBox
、ComboBox
、ListBox
等) - SDKStyles.xaml(
TabControl
、TreeView
、DataGrid
、DatePicker
等) - ToolkitStyles.xaml(
Accordion
、ContextMenu
、Chart
、BusyIndicator
等) - Styles.xaml(用于单个主题应用程序布局的样式)
此外,我们在同一主题级别添加了 CustomStyles.xaml,并使用它来定义自定义控件的样式(在本文中,只是一个页面背景控件)。
对于每个主题,都有一个容器(一个 MergedDictionaries
),其中上述资源被嵌入为 ResourceDictionary
。下面是 AccentColor.xaml 内容的快照:
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/SLDynamicThemes2;component/Assets/Themes/AccentColor/CoreStyles.xaml" />
<ResourceDictionary Source="/SLDynamicThemes2;component/Assets/Themes/AccentColor/SDKStyles.xaml" />
<ResourceDictionary Source="/SLDynamicThemes2;component/Assets/Themes/AccentColor/ToolkitStyles.xaml" />
<ResourceDictionary Source="/SLDynamicThemes2;component/Assets/Themes/AccentColor/Styles.xaml" />
<ResourceDictionary Source="/SLDynamicThemes2;component/Assets/Themes/AccentColor/CustomStyles.xaml" />
</ResourceDictionary.MergedDictionaries>
要在运行时动态更改主题,我们将利用工具包的 Theme
控件。根据应用程序的要求,可以选择两种选项。第一种(最简单直接)由 Michael Epner 在文章评论中提出,适用于需要更改整个应用程序主题的情况。它也更优雅,因为无需使用最初作为主题化子窗口唯一方法的 ChildWindow
变通方法。第二种方法(原始文章)必须用于需要部分应用程序主题化时(即,不是整个应用程序,而是其中的一部分或几部分)。下面您可以看到这两种选项。
2.a. 完整的应用程序主题化
最简单的解决方案是利用 Theme.SetApplicationThemeUri
静态函数,该函数将负责更改整个应用程序主题,而无需采取额外的变通方法。无需将 Theme
控件包含到 MainPage
的布局根目录,也不需要创建任何自定义 ChildWindow
来启用子窗口主题化。只需调用 Theme.SetApplicationThemeUri
,新的外观将立即显示。
void ThemeButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
if (button.Tag != null)
{
string themeName = button.Tag.ToString();
if (!String.IsNullOrEmpty(themeName))
{
Uri themeUri = new Uri(string.Format(@"/SLDynamicThemes2;component/" +
s@"Assets/Themes/{0}", themeName), UriKind.Relative);
Theme.SetApplicationThemeUri(App.Current, themeUri);
}
}
}
2.b. 部分应用程序主题化
要在运行时动态更改主题,我们将利用工具包的 Theme
控件,并将其添加到您想应用 Silverlight 主题的页面中。在我们的示例中,Theme
控件仅添加到 MainPage.xaml,并将整个应用程序布局包含在其边界内。
<toolkit:Theme x:Name="ThemeContainer" ThemeUri="/SLDynamicThemes2;component/Assets/Themes/JetPack.xaml">
<controls:PageBackground HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<toolkit:DockPanel LastChildFill="True">
<StackPanel Orientation ="Horizontal" toolkit:DockPanel.Dock="Top"></StackPanel>
<Grid>
<application:Home x:Name="HomePage"/>
</Grid>
</toolkit:DockPanel>
</controls:PageBackground>
</toolkit:Theme>
为了修改主题,我们唯一需要做的就是在 MainPage.cs 中将 ThemeContainer
的 ThemeUri
属性更改为所需的值。
void ThemeButton_Click(object sender, RoutedEventArgs e)
{
Button button = sender as Button;
if (button.Tag != null)
{
string themeName = button.Tag.ToString();
if (!String.IsNullOrEmpty(themeName))
{
Uri themeUri = new Uri(string.Format(@"/SLDynamicThemes2;" +
@"component/Assets/Themes/{0}", themeName), UriKind.Relative);
ThemeContainer.ThemeUri = currentThemeUri = themeUri;
}
}
}
主页面顶部的四个按钮(参见第一张截图)定义了标识要应用主题的 Tag
属性。点击时,ThemeContainer
的 ThemeUri
将相应地更改,新主题将应用于整个应用程序。
ChildWindow 中的动态主题
在解释有关主题化 ChildWindow
控件的所有内容之前,我必须在此包含 Liviu Catrina 的一个观察:目前,只有 ChildWindow
的 **内容** 可以主题化,而子窗口本身的外观不能。Silverlight Toolkit 中存在一个限制,阻止了 ChildWindow
控件的主题化(即使原始主题包含子窗口的样式)。您可以在官方 Silverlight 论坛上阅读 Justin Angel(工具包的创建者之一)提供的简短解释。
如果您遵循上面描述的第一种方法 - 2.a. 完整的应用程序主题化,则无需进一步调整 ChildWindow
。主题更改将自动应用于应用程序包含的任何子窗口。对于第二种实现 - 2.b. 部分应用程序主题化,我们将不得不准备我们的子窗口,以便使它们能够主题化。
即使我们仔细地将应用程序内容包含在 Theme
控件中,ChildWindow
仍然会保持在它之外,并且更改主题不会在其中得到反映。接下来,我们将提出一个解决方案,确保主题也能应用于 ChildWindow
(参见下图)。
可以通过创建一个自定义类(在我们的示例中是 CustomChildWindow
)来启用 ChildWindow
主题,在该类中我们将重写 OnApplyTemplate()
函数。我们将期望所有派生自 CustomChildWindow
的子窗口都拥有一个名为 ThemeContainer
的 Theme
控件。其思想是记住应用程序的当前 ThemeUri
,并在显示任何子窗口时使用它。
public class CustomChildWindow : ChildWindow
{
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
Theme themeContainer = (Theme)(this).FindName("ThemeContainer");
if (themeContainer != null)
{
App app = (App)App.Current;
MainPage mainPage = app.RootVisual as MainPage;
themeContainer.ThemeUri = mainPage.CurrentThemeUri;
}
}
}
当新创建的 ChildWindow
显示时,我们将检查并应用保存在主页面中的 CurrentThemeUri
。下面您可以看到我们使用的子窗口的 XAML 代码片段。
<toolkit:Theme x:Name="ThemeContainer">
<Grid x:Name="LayoutRoot" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<appControls:PageBackground HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Name="ThemeNameTextBlock" Loaded="ThemeNameTextBlock_Loaded"></TextBlock>
</StackPanel>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Bottom">
<Button Name="OK" Click="Button_Click" Margin="0,0,10,0">OK</Button>
<Button Name="Cancel" Click="Button_Click" Margin="0,0,10,0">Cancel</Button>
</StackPanel>
</appControls:PageBackground>
</Grid>
</toolkit:Theme>
ThemeContainer
控件包含我们子窗口中的所有内容,任何子控件都将继承当前应用程序主题的外观和感觉。
结论
在本文中,我们详细介绍了一种可用于在 Silverlight 应用程序中启用动态主题的方法。它使您可以完全控制现有主题,并允许您使用自己的自定义控件对其进行扩展。
我想感谢大家提出的宝贵建议和见解。您的帮助改进了原始文档,使其更加完善。