WPF桌面端开发6-Window.Resources

前言

在Web前端开发中有这么几种元素

  • HTML+模板
  • CSS
  • JS

WPF和HTML页面类似用XML进行页面编程,那么上述的模板CSSJS放在哪呢?

答案就是Window.Resources

什么叫WPF的资源(Resource)?

资源是保存在可执行文件中的一种不可执行数据。在WPF的资源中,几乎可以包含图像、字符串等所有的任意CLR对象,只要对象有一个默认的构造函数和独立的属性。

也就是说,应用程序中非程序代码的内容,比如点阵图、颜色、字型、动画/影片档以及字符串常量值,可将它们从程序中独立出来,单独包装成”资源(Resource)”。

静态资源(Static Resource),动态资源(Dynamic Resources)。

这两者的区别是:

  • 静态资源在第一次编译后即确定其对象或值,之后不能对其进行修改。

  • 动态资源则是在运行时决定,当运行过程中真正需要时,才到资源目标中查找其值。因此,我们可以动态地修改它。由于动态资源的运行时才能确定其值,因此效率比静态资源要低。

资源的范围(层级)

WPF提供一个封装和存取资源(resource)的机制,我们可将资源建立在应用程序的不同范围上。WPF中,资源定义的位置决定了该资源的可用范围。资源可以定义在如下范围中:

  1. 物件级:此时,资源只能套用在这个Object物件,或套用至该物件的子物件。
  2. 文件级:如果将资源定义在Window或Page层级的XAML档中,那么可以套用到这个文件中的所有物件。
  3. 应用程序级:如果我们将资源定义在App.xaml 中,那么,就可以将资源套用到应用程序内的任何地方。
  4. 字典级:当我们把资源封装成一个资源字典, 定义到一个ResourceDictionary的XAML文件时,就可以在另一个应用程序中重复使用。

每一个框架级元素(FrameworkElement 或者FrameworkContentElement)都有一个资源属性。每一个在资源字典中的资源都有一个唯一不重复的键值(key),在标签中使用x:Key属性来标识它。一般地,键值是一个字符串,但你也可以用合适的扩展标签来设置为其他对象类型。非字符键值资源使用于特定的WPF区域,尤其是风格、组件资源,以及样式数据等。

StaticResource和DynamicResource区别

StaticResource和DynamicResource都是WPF中用来引用资源的方式,下面是它们的区别:

  • StaticResource:在应用程序启动时解析,即在XAML文件的解析期间被确定。一旦确定,它就会成为那个控件的属性值,而不会再改变。如果资源被修改了,这些修改不会反映到使用StaticResource的控件上。这种方式启动速度快,但是没有动态性。
  • DynamicResource:在运行时解析,即在XAML文件的解析期间不被确定。如果资源发生更改,则这些更改将自动更新到使用DynamicResource的控件上。这种方式可以在运行期间动态更新资源,但会在应用程序启动时相对较慢。

在使用ItemsControl的ItemTemplate时,建议使用StaticResource,因为ItemTemplate一般是在控件的生命周期内只需要创建一次,且不需要随着用户交互而动态改变。而DynamicResource则适用于需要动态更改的资源,如界面主题等。

StaticResource的适用场合

StaticResource用于在XAML中创建的一个静态资源,只会在应用程序启动时被创建,占用一定的内存空间,在应用程序整个运行期间不会被释放,所以适用于以下场合:

  1. 常用的样式和控件,如按钮、标签等,因为它们在应用程序中会被多次使用,使用StaticResource可以提高性能和减少内存消耗。

  2. 程序中不会改变的资源,如图标、字体等,使用StaticResource可以加快应用程序启动速度。

  3. 在同一页面中多次使用的资源,如背景颜色、字体等,使用StaticResource可以避免重复定义。

总之,StaticResource适用于那些没必要动态修改的资源,使用起来更加简单高效。

DynamicResource的适用场合

在WPF中,DynamicResource 适用于那些资源可能在运行时被更改或者需要动态更新的情况,而 StaticResource 则用于那些在加载时已经确定好的资源。

因此,在以下情况下使用 DynamicResource 更为合适:

  1. 在运行时更改应用程序主题或控件样式时。
  2. 当应用程序需要动态更改资源时。
  3. 当你想要动态更改控件某些属性时,例如控件的前景色、背景色等。

需要注意的是,使用 DynamicResource 会导致一个稍微较慢的应用程序启动和加载时间,因为这些资源可能需要在运行时计算而不是在编译时确定。

因此,只有在确实需要它们动态更新的情况下才使用 DynamicResource,否则应该优先考虑 StaticResource。当然,在实际应用过程中,也可以采用两者混合的方式,根据具体情况决定。

资源的查询方式

Static Resource的查询

  1. 查找使用该资源的元素的Resource字典;
  2. 顺着逻辑树向上查找父元素的资源字典,直到根节点;
  3. 查找Application资源;
  4. 不支持向前引用,即:不能引用在引用点之后才定义的资源。

Dynamic Resource的查询

  1. 查找使用该资源的元素的Resource字典;
    如果元素定义了一个Style 属性,将查找Style中的资源字典;如果元素定义了一个Template属性,将查找FrameworkTemplate中的资源字典。
  2. 顺逻辑树向上查找父元素的资源字典,直到根节点;
  3. 查找Application资源;
  4. 查找当前激活状态下的Theme资源字典;
  5. 查找系统资源。

模板

模板的使用有先后顺序,后面的模板才能使用前面的模板。

DataTemplate

设置列表的模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<Window.Resources>
<DataTemplate x:Key="MyItemTemplate">
<Grid>
<Border
Height="46"
Background="#ffffff"
CornerRadius="4">
<TextBlock
Margin="10,0,0,0"
VerticalAlignment="Center"
Text="{Binding name}" />
</Border>
</Grid>
</DataTemplate>

<DataTemplate x:Key="MyItemsPanel">
<StackPanel />
</DataTemplate>
</Window.Resources>

使用方式

1
2
3
4
5
6
7
8
9
10
11
12
<ListBox
x:Name="audio_list_box"
Grid.Row="0"
Margin="10,10"
Background="#f3f3f3"
BorderThickness="0"
FocusVisualStyle="{DynamicResource MyFocusVisualStyle}"
ItemContainerStyle="{DynamicResource MyItemContainerStyle}"
ItemTemplate="{DynamicResource MyItemTemplate}"
ItemsPanel="{Binding MyItemsPanel}"
ItemsSource="{Binding MYAudioDevices}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" />

ControlTemplate

1
2
3
4
5
6
7
8
9
10
<Window.Resources>
<ControlTemplate x:Key="ListBoxTmp">
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Hidden">
<StackPanel
IsItemsHost="True"
Orientation="Vertical"
ScrollViewer.CanContentScroll="False" />
</ScrollViewer>
</ControlTemplate>
</Window.Resources>

CSS

Style的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<Window.Resources>
<SolidColorBrush x:Key="Item.SelectedActive.Border" Color="Red" />

<Style x:Key="MyItemContainerStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Padding" Value="0,0,0,0" />
<Setter Property="Margin" Value="0,0,0,6" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<Border
x:Name="Bd"
Padding="{TemplateBinding Padding}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="true">
<ContentPresenter
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource Item.SelectedActive.Border}" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="False" />
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource Item.SelectedActive.Border}" />
</MultiTrigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="Selector.IsSelectionActive" Value="True" />
<Condition Property="IsSelected" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="Bd" Property="BorderBrush" Value="{StaticResource Item.SelectedActive.Border}" />
</MultiTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="MyFocusVisualStyle" TargetType="{x:Type Control}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<Grid />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>

使用方式

1
2
3
4
5
6
7
8
9
10
11
12
<ListBox
x:Name="audio_list_box"
Grid.Row="0"
Margin="10,10"
Background="#f3f3f3"
BorderThickness="0"
FocusVisualStyle="{DynamicResource MyFocusVisualStyle}"
ItemContainerStyle="{DynamicResource MyItemContainerStyle}"
ItemTemplate="{DynamicResource MyItemTemplate}"
ItemsPanel="{Binding MyItemsPanel}"
ItemsSource="{Binding MYAudioDevices}"
ScrollViewer.HorizontalScrollBarVisibility="Disabled" />

Style的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<Window.Resources>
<Style x:Key="style_button" TargetType="Button">
<Style.Setters>
<Setter Property="BorderThickness" Value="0" />
</Style.Setters>
<Style.Triggers />
</Style>

<Style
x:Key="style_close_button"
BasedOn="{StaticResource style_button}"
TargetType="Button">
<Style.Setters>
<Setter Property="BorderThickness" Value="0" />
</Style.Setters>
<Style.Triggers />
</Style>
</Window.Resources>

JS

这个就是引用C#写的类

IValueConverter

下面的示例主要是根据绑定的值控制组件显示隐藏

1
2
3
<Window.Resources>
<util:ZJGenericTypeConverter x:Key="anyTypeConverter" />
</Window.Resources>

对应的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;

namespace ZJClassTool.Utils
{
public class ZJGenericTypeConverter : IValueConverter
{
/// <summary>
/// 正向键值对字典
/// </summary>
private Dictionary<string, string> Alias { get; set; }

/// <summary>
/// 反向键值对字典
/// </summary>
private Dictionary<string, string> BackAlias { get; set; }

private string aliasStrTemp = "";

/// <summary>
/// 解析转换规则
/// </summary>
/// <param name="aliasStr">规则字符串</param>
private void ParseAliasByStr(string aliasStr)
{
if (aliasStrTemp == aliasStr)
return;
aliasStrTemp = aliasStr;
Alias = new Dictionary<string, string>();
BackAlias = new Dictionary<string, string>();

string content = aliasStr;

Alias = new Dictionary<string, string>();
string[] arr1 = content.Split('|');
foreach (string item in arr1)
{
string[] arr2 = item.Split(':');
var key = arr2[0];
if (key == "true")
{
key = "True";
}
else if (key == "false")
{
key = "False";
}
var value = arr2[1];
if (key != "other")
{
BackAlias.Add(value, key);
}

Alias.Add(key, value);
}
}

private object ConvertCommon(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture, bool isBack)
{
if (value == null || string.IsNullOrWhiteSpace(parameter.ToString()))
return null;
object ret = value;//如果没有匹配返回传入的值

ParseAliasByStr(parameter.ToString());
Dictionary<string, string> alias;
if (isBack)
alias = BackAlias;
else
alias = Alias;

//绑定的值
string bindingValue = value.ToString();

if (alias.ContainsKey(bindingValue))
ret = StringToTargetType(alias[bindingValue], targetType);
else if (alias.ContainsKey("other"))
ret = StringToTargetType(alias["other"], targetType);
else if (alias.ContainsKey("else"))
ret = StringToTargetType(alias["else"], targetType);

return ret;
}

/// <summary>
/// 字符串转换成目标类型,如需添加一个目标类型只需在该方法中添加一个类型判断之后转换
/// </summary>
/// <param name="strValue"></param>
/// <param name="targetType"></param>
/// <returns></returns>
private object StringToTargetType(string strValue, Type targetType)
{
object ret = null;
//目标类型 string
if (targetType == typeof(string) || targetType == typeof(char))
{
ret = strValue;
}
//目标类型 char
if (targetType == typeof(char))
{
if (strValue.Length == 1)
ret = strValue;
}
//目标类型 int
if (targetType == typeof(int))
{
int temp;
if (int.TryParse(strValue, out temp))
ret = temp;
else
ret = 0;
}
//目标类型 double
if (targetType == typeof(double))
{
double temp;
if (double.TryParse(strValue, out temp))
ret = temp;
else
ret = 0;
}
//目标类型 float
if (targetType == typeof(float))
{
float temp;
if (float.TryParse(strValue, out temp))
ret = temp;
else
ret = 0;
}
//目标类型 decimal
if (targetType == typeof(decimal))
{
decimal temp;
if (decimal.TryParse(strValue, out temp))
ret = temp;
else
ret = 0;
}
//目标类型 bool? bool
if (targetType == typeof(bool?) || targetType == typeof(bool))
{
bool temp;
if (bool.TryParse(strValue, out temp))
ret = temp;
else
ret = false;
}

//目标类型 Visibility
if (targetType == typeof(Visibility))
{
switch (strValue.ToLower())
{
case "collapsed":
ret = Visibility.Collapsed;
break;

case "hidden":
ret = Visibility.Hidden;
break;

case "visible":
ret = Visibility.Visible;
break;

default:
ret = Visibility.Visible;
break;
}
}
return ret;
}

public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ConvertCommon(value, targetType, parameter, culture, false);
}

public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return ConvertCommon(value, targetType, parameter, culture, true);
}
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<views:ZJClippingBorder CornerRadius="4" Visibility="{Binding IsStart, Converter={StaticResource anyTypeConverter}, ConverterParameter='false:Visible|true:Collapsed'}">
<Button
Width="100"
Height="46"
Click="start_Click"
Style="{StaticResource style_close_button}">
开始
</Button>
</views:ZJClippingBorder>
<views:ZJClippingBorder CornerRadius="4" Visibility="{Binding IsStart, Converter={StaticResource anyTypeConverter}, ConverterParameter='true:Visible|false:Collapsed'}">
<Button
Width="100"
Height="46"
Click="start_Click"
Style="{StaticResource style_close_button}">
结束
</Button>
</views:ZJClippingBorder>