- Windows/Apps/UWP/Design and UI/ Controls
XAML的资源一般指的是那些可以被共享的字符串,样式、模板、笔刷、动画等等。资源的主要组织方式是ResourceDictionary。把资源放在ResourceDictionary内,然后设置一个x:Key,就可以通过StaticResource或者ThemeResource这些XAML标签扩展来引用。ResourceDictionary又可以进一步分为MergedDictionaries,用以整合其他XAML文件中定义的ResourceDictionary;以及ThemeDictionaries,用于整合和主题相关的资源。
XAML的资源跟Visual Studio支持的.resw资源文件不是一回事儿。
资源必须是可以被共享的对象,像控件(controls)、形状(shapes)以及其他一些不能被共享的FrameworkElements,是不能放在ResouceDictionary中被复用的。
ResourceDictionary里面的资源必须有键值,但是有一些特殊情况:
- Style和ControlTemplate需要指定一个TargetType。如果x:Key没有指定的情况下,这个TargetType的对象会被作为键值使用。 DataTempplate也是如此。
- x:Name可以用来替代x:Key。但是x:Name会在XAML对应的后部代码中生成一个相应的字段。这个字段在加载的时候需要初始化,会导致额外的开销。
StaticResource标签扩展会通过x:Key或者x:Name来找到指定名字的资源。但是呢,如果元素上没有设置Style或者ContentTemplate或者ItemTemplate,XAML框架也会去查找隐性的样式和模板资源(也就是只指定了TargetType的资源)。
下面例子中,Page.Resources中定义的Style的键值默认为typeof(Button),所以会被接下来定义的Button所使用。
<Page
x:Class="MSDNSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style TargetType="Button">
<Setter Property="Background" Value="Red"/>
</Style>
</Page.Resources>
<Grid>
<!-- This button will have a red background. -->
<Button Content="Button" Height="100" VerticalAlignment="Center" Width="100"/>
</Grid>
</Page>
可以在后部代码中通过代码的形式查找资源,可是和StaticeResource标记扩展不同,代码形式的查找不会在查完Page.Resources之后又去查找Application.Resources.
下面是一个例子:
<Page
x:Class="MSDNSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Style TargetType="Button" x:Key="redButtonStyle">
<Setter Property="Background" Value="red"/>
</Style>
</Page.Resources>
</Page>
<Application
x:Class="MSDNSample.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:SpiderMSDN">
<Application.Resources>
<Style TargetType="Button" x:Key="appButtonStyle">
<Setter Property="Background" Value="red"/>
</Style>
</Application.Resources>
</Application>
// 只查Page.Resources
MainPage::MainPage()
{
InitializeComponent();
Windows::UI::Xaml::Style style = Resources().TryLookup(winrt::box_value(L"redButtonStyle")).as<Windows::UI::Xaml::Style>();
}
// 只查Application.Resources
MainPage::MainPage()
{
InitializeComponent();
Windows::UI::Xaml::Style style = Application::Current().Resources()
.TryLookup(winrt::box_value(L"appButtonStyle"))
.as<Windows::UI::Xaml::Style>();
}
对于Application.Resources,你可以在代码向其添加样式,但是有两点需要注意:
- 必须在样式被使用前添加
- 不能再App的构造函数中添加
所以,一个好的时间点是在Application.OnLaunched的时候添加:
// App.cpp
void App::OnLaunched(LaunchActivatedEventArgs const& e)
{
Frame rootFrame{ nullptr };
auto content = Window::Current().Content();
if (content)
{
rootFrame = content.try_as<Frame>();
}
// Do not repeat app initialization when the Window already has content,
// just ensure that the window is active
if (rootFrame == nullptr)
{
Windows::UI::Xaml::Media::SolidColorBrush brush{ Windows::UI::ColorHelper::FromArgb(255, 0, 255, 0) };
Resources().Insert(winrt::box_value(L"brush"), winrt::box_value(brush));
// … Other code that VS generates for you …
值得注意的是,每个FrameworkElement都有Resources下属,可以往里面添加资源。
<Page
x:Class="MSDNSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<x:String x:Key="greeting">Hello world</x:String>
</Page.Resources>
<StackPanel>
<!-- Displays "Hello world" -->
<TextBlock x:Name="textBlock1" Text="{StaticResource greeting}"/>
<Border x:Name="border">
<Border.Resources>
<x:String x:Key="greeting">Hola mundo</x:String>
</Border.Resources>
<!-- Displays "Hola mundo" -->
<TextBlock x:Name="textBlock2" Text="{StaticResource greeting}"/>
</Border>
<!-- Displays "Hola mundo", set in code. -->
<TextBlock x:Name="textBlock3"/>
</StackPanel>
</Page>
通过ResourceDictionary.MergedDictionaries,引用其他XAML文件中的ResourceDictionary,从而增加了ResourceDictionary的可组织性。
<!-- Dictionary1.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MSDNSample">
<SolidColorBrush x:Key="brush" Color="Red"/>
</ResourceDictionary>
<Page
x:Class="MSDNSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"/>
</ResourceDictionary.MergedDictionaries>
<x:String x:Key="greeting">Hello world</x:String>
</ResourceDictionary>
</Page.Resources>
<TextBlock Foreground="{StaticResource brush}" Text="{StaticResource greeting}" VerticalAlignment="Center"/>
</Page>
事实上,当往Page.Resources添加资源的时候,XAML框架会隐式地帮你定义一个ResourceDictionary对象。而上面地例子中ResourceDictionary对象则是显示定义的。可以看到,在ResourceDictionary.MergedDictionaries之后,依然可以指定额外的资源。
从资源查找的角度,MergedDictionarie中的资源的优先级排在ResourceDictionary中定义的资源之后。如果有多个MergedDictionarie,那么查找顺序和它们在ResourceDictionary中出现的顺序相反。在下面的例子中,Dictionary2.xaml中的资源会优先于Dictionary1.xaml中的资源:
<Page
x:Class="MSDNSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Dictionary1.xaml"/>
<ResourceDictionary Source="Dictionary2.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Page.Resources>
<TextBlock Foreground="{StaticResource brush}" Text="greetings!" VerticalAlignment="Center"/>
</Page>
同一个ResourceDictionary指定的资源的键值必须是不一样的。但是这个要求不会引申到ResourceDictionary所引用的MergedDictionaries中。
除了StaticResource外,ResourceDictionary里面还有ThemeResouce,定义在ThemeDictionaries中。后者作为主题资源,可以随主题变化而改变。ThemeDictionaries中的资源必须指定x:Key的值为Default、Dark、Light或者HighContrast。
下面是一个例子:
<!-- Dictionary1.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MSDNSample">
<SolidColorBrush x:Key="brush" Color="Red"/>
</ResourceDictionary>
<!-- Dictionary2.xaml -->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:MSDNSample">
<SolidColorBrush x:Key="brush" Color="blue"/>
</ResourceDictionary>
<Page
x:Class="MSDNSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.ThemeDictionaries>
<ResourceDictionary Source="Dictionary1.xaml" x:Key="Light"/>
<ResourceDictionary Source="Dictionary2.xaml" x:Key="Dark"/>
</ResourceDictionary.ThemeDictionaries>
</ResourceDictionary>
</Page.Resources>
<TextBlock Foreground="{StaticResource brush}" Text="hello world" VerticalAlignment="Center"/>
</Page>
可以查看\(Program Files)\Windows Kits\10\DesignTime\CommonConfiguration\Neutral\UAP\<SDK version>\Generic.xaml
来得知默认的主题定义是什么样子的。和Generic.xaml同目录的还有一个 themeresources.xaml文件,内容来自于Generic.xaml,但是不包含default control templates。在Visual Studio中如果拷贝某个元素的控件或者模板,那么这些拷贝的来源就是来自于上面的上述的xaml文件。
XAML框架在查找资源的时候,会沿着对象树往上,对于路径上的每个对象,都会去看看它的Resources下属是否存在,以及是否包含想要的资源。找到目标资源,则停止查找,否则就会一直往上,直到触达树的根节点。如果没有在对象树中找到目标资源,下一步是查找Application.Resources。最后一步是查找Platform资源(其中定义了所有控件的默认样式和模板)。从技术上讲,Platform资源是作为一个MergedDictionaries被引用的。
如果上述所有的查找都失败了,那么会抛出XAML parsing error/exception。通常情况下在XAML编译的时候能够发现这些错误,但是有些时候只有在运行时才能发现。
XAML中的资源必须先定义再使用。所以App级别的资源无法引用Page级别的资源。这种前向引用(Forward Reference)是不支持的。在组织XAML资源的时候必须考虑到这一点。
然后XAML中的资源必须是可共享的。XAML资源会被对象树中不同的的节点引用。下列资源是可共享的:
- 样式和模板
- 笔刷和颜色
- 动画以及Storyboard
- 变换(GeneralTransform)
- Matrix以及Matrix3D
- Point
- 一些UI相关的结构,比如Thickness和CornerRadius
- XAML intrinsic 数据类型(x:Boolean、x:String、x:Double、x:Int32等等)
自定义的对象也可以作为资源,比如对IValueConverter的实现。自定义的对象必须有一个默认构造函数,并且不能是UIElement的派生类型。(UIElement被设计为不可共享)。
对于UserControl,有定义范围(definition scope)和使用范围(usage scope)的区分。定义范围不能访问app级别资源;而使用范围可以访问app级别资源。
如果使用XamlReader.Load来加载资源,那么需要注意的是,加载的时候不考虑其他ResourceDictionary,甚至不考虑Application.Resources,也不考虑{ThemeResource}。
在代码中访问XAML资源的时候,查找的范围不会从临近资源跨越到App资源,这一点和XAML的行为是不一致的。但是MergedDictionaries所引用的资源是可以被查找到的,这一点和XAML的行为是一致的。可以在代码中往ResourceDictionary添加资源,但是这是发生在XAML加载之后,所以添加的资源不会影响已经使用了的资源。
XAML的ResourceDictionary可能一开始会用到需要本地化的字符串。随着项目进展,需要把这些字符串移到项目中,设定其XUIDValue.PropertyName。然后再XAML中使用x:Uid directive来索引。
少数情况下,可能需要对XAML的资源查找行为进行自定义。可以实现CustomXamlResourceLoader来达到此目的。可以通过CustomResource标记扩展,而不是StaticResource和ThemeResource来访问自定义加载的资源。
(完)
2021-01-24更新
从代码中获取系统默认颜色
Application.Current.Resources["SystemAccentColor"]
- https://stackoverflow.com/questions/35573255/get-uwp-theme-colors-in-c-sharp-code
- https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.application.requestedtheme?view=winrt-19041
- https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.elementtheme?view=winrt-19041
(更新完)