在WPF中开发自定义控件时,可以将控件的默认样式放在以“<控件类型>.Generic.xaml”的形式命名的资源文件中,从而分离各个自定义控件的默认样式的定义,减少单个Generic.xaml文件的复杂度。
但是在Silverlight控件开发时,却发现无法采用上面的方法来实现这一效果,尝试了许久都没有找到其他的办法实现这一效果。郁闷之中,突然想起看一下Silverlight Toolkit中是如何解决这一问题的,结果惊讶的发现它也是将所有的默认样式都堆积在了Generic.xaml一个文件当中,感觉相当的不可思议。但是,仔细一看,发现在Generic.xaml文件的开头有如下的一段话:
XML
<!--
//
WARNING:
//
//
This XAML was automatically generated by merging the individual default
//
styles. Changes to this file may cause incorrect behavior and will be lost
//
if the XAML is regenerated.
-->
这让我又感觉到了希望,于是祭出神器“谷歌”,以上面的话中的一部分为关键字,进行了搜索,果然让我找到了相关的文章,原文地址如下:http://www.jeff.wilcox.name/2009/01/default-style-task/ ,在这里面介绍了如何通过MSBuild的自定义任务来实现将项目中的独立的控件样式在编译阶段合并到一起,文章里面包含了详细的解说和代码,只要按照提示一步步来做就可以了。
如果您对MSBuild比较熟悉,那么按照他的提示,应该就能顺利的完成这一个功能。但是很不幸,我对于MSBuild一窍不通,于是第一遍下来,编译后,什么事情都没有发生。如果你跟我一样对MSBuild不甚了解,但是又想要能够得到这个非常有用的特性,那么希望下面的介绍能够对你有所帮助。关于MSBuild的介绍及相关知识,有兴趣的朋友可以参见MSDN中的相关文章,链接如下:http://msdn.microsoft.com/zh-cn/library/ms171452.aspx 我在这里只介绍如何简单的实现这一功能。
首先,新建一个类库项目,在里面添加文章中提到的两个类,代码如下:
MergeDefaultStylesTask
1
//
(c) Copyright Microsoft Corporation.
2
//
This source is subject to the Microsoft Public License (Ms-PL).
3
//
Please see
http://go.microsoft.com/fwlink/?LinkID=131993
for details.
4
//
All other rights reserved.
5
6
using
System;
7
using
System.Collections.Generic;
8
using
System.Diagnostics.CodeAnalysis;
9
using
System.IO;
10
using
System.Text;
11
using
Microsoft.Build.Framework;
12
using
Microsoft.Build.Utilities;
13
14
namespace
Engineering.Build.Tasks
15
{
16
///
<summary>
17
///
Build task to automatically merge the default styles for controls into
18
///
a single generic.xaml file.
19
///
</summary>
20
public
class
MergeDefaultStylesTask : Task
21
{
22
///
<summary>
23
///
Gets or sets the root directory of the project where the
24
///
generic.xaml file resides.
25
///
</summary>
26
[Required]
27
public
string
ProjectDirectory {
get
;
set
; }
28
29
///
<summary>
30
///
Gets or sets the project items marked with the "DefaultStyle" build
31
///
action.
32
///
</summary>
33
[Required]
34
public
ITaskItem[] DefaultStyles {
get
;
set
; }
35
36
///
<summary>
37
///
Initializes a new instance of the MergeDefaultStylesTask class.
38
///
</summary>
39
public
MergeDefaultStylesTask()
40
{
41
}
42
43
///
<summary>
44
///
Merge the project items marked with the "DefaultStyle" build action
45
///
into a single generic.xaml file.
46
///
</summary>
47
///
<returns>
48
///
A value indicating whether or not the task succeeded.
49
///
</returns>
50
[SuppressMessage(
"
Microsoft.Design
"
,
"
CA1031:DoNotCatchGeneralExceptionTypes
"
, Justification
=
"
Task should not throw exceptions.
"
)]
51
public
override
bool
Execute()
52
{
53
Log.LogMessage(MessageImportance.Low,
"
Merging default styles into generic.xaml.
"
);
54
55
//
Get the original generic.xaml
56
string
originalPath
=
Path.Combine(ProjectDirectory, Path.Combine(
"
themes
"
,
"
generic.xaml
"
));
57
if
(
!
File.Exists(originalPath))
58
{
59
Log.LogError(
"
{0} does not exist!
"
, originalPath);
60
return
false
;
61
}
62
Log.LogMessage(MessageImportance.Low,
"
Found original generic.xaml at {0}.
"
, originalPath);
63
string
original
=
null
;
64
Encoding encoding
=
Encoding.Default;
65
try
66
{
67
using
(StreamReader reader
=
new
StreamReader(File.Open(originalPath, FileMode.Open, FileAccess.Read)))
68
{
69
original
=
reader.ReadToEnd();
70
encoding
=
reader.CurrentEncoding;
71
}
72
}
73
catch
(Exception ex)
74
{
75
Log.LogErrorFromException(ex);
76
return
false
;
77
}
78
79
//
Create the merged generic.xaml
80
List
<
DefaultStyle
>
styles
=
new
List
<
DefaultStyle
>
();
81
foreach
(ITaskItem item
in
DefaultStyles)
82
{
83
string
path
=
Path.Combine(ProjectDirectory, item.ItemSpec);
84
if
(
!
File.Exists(path))
85
{
86
Log.LogWarning(
"
Ignoring missing DefaultStyle {0}.
"
, path);
87
continue
;
88
}
89
90
try
91
{
92
Log.LogMessage(MessageImportance.Low,
"
Processing file {0}.
"
, item.ItemSpec);
93
styles.Add(DefaultStyle.Load(path));
94
}
95
catch
(Exception ex)
96
{
97
Log.LogErrorFromException(ex);
98
}
99
}
100
string
merged
=
null
;
101
try
102
{
103
merged
=
DefaultStyle.Merge(styles).GenerateXaml();
104
}
105
catch
(InvalidOperationException ex)
106
{
107
Log.LogErrorFromException(ex);
108
return
false
;
109
}
110
111
//
Write the new generic.xaml
112
if
(original
!=
merged)
113
{
114
Log.LogMessage(MessageImportance.Low,
"
Writing merged generic.xaml.
"
);
115
116
try
117
{
118
//
Could interact with the source control system / TFS here
119
File.SetAttributes(originalPath, FileAttributes.Normal);
120
Log.LogMessage(
"
Removed any read-only flag for generic.xaml.
"
);
121
122
File.WriteAllText(originalPath, merged, encoding);
123
Log.LogMessage(
"
Successfully merged generic.xaml.
"
);
124
}
125
catch
(Exception ex)
126
{
127
Log.LogErrorFromException(ex);
128
return
false
;
129
}
130
}
131
else
132
{
133
Log.LogMessage(
"
Existing generic.xaml was up to date.
"
);
134
}
135
136
return
true
;
137
}
138
}
139
}
140
DefaultStyle
1
//
(c) Copyright Microsoft Corporation.
2
//
This source is subject to the Microsoft Public License (Ms-PL).
3
//
Please see
http://go.microsoft.com/fwlink/?LinkID=131993
for details.
4
//
All other rights reserved.
5
6
using
System;
7
using
System.Collections.Generic;
8
using
System.Globalization;
9
using
System.IO;
10
using
System.Linq;
11
using
System.Xml.Linq;
12
13
namespace
Engineering.Build
14
{
15
///
<summary>
16
///
DefaultStyle represents the XAML of an individual Control's default
17
///
style (in particular its ControlTemplate) which can be merged with other
18
///
default styles). The XAML must have a ResourceDictionary as its root
19
///
element and be marked with a DefaultStyle build action in Visual Studio.
20
///
</summary>
21
public
partial
class
DefaultStyle
22
{
23
///
<summary>
24
///
Root element of both the default styles and the merged generic.xaml.
25
///
</summary>
26
private
const
string
RootElement
=
"
ResourceDictionary
"
;
27
28
///
<summary>
29
///
Gets or sets the file path of the default style.
30
///
</summary>
31
public
string
DefaultStylePath {
get
;
set
; }
32
33
///
<summary>
34
///
Gets the namespaces imposed on the root element of a default style
35
///
(including explicitly declared namespaces as well as those inherited
36
///
from the root ResourceDictionary element).
37
///
</summary>
38
public
SortedDictionary
<
string
,
string
>
Namespaces {
get
;
private
set
; }
39
40
///
<summary>
41
///
Gets the elements in the XAML that include both styles and shared
42
///
resources.
43
///
</summary>
44
public
SortedDictionary
<
string
, XElement
>
Resources {
get
;
private
set
; }
45
46
///
<summary>
47
///
Gets or sets the history tracking which resources originated from
48
///
which files.
49
///
</summary>
50
private
Dictionary
<
string
,
string
>
MergeHistory {
get
;
set
; }
51
52
///
<summary>
53
///
Initializes a new instance of the DefaultStyle class.
54
///
</summary>
55
protected
DefaultStyle()
56
{
57
Namespaces
=
new
SortedDictionary
<
string
,
string
>
(StringComparer.OrdinalIgnoreCase);
58
Resources
=
new
SortedDictionary
<
string
, XElement
>
(StringComparer.OrdinalIgnoreCase);
59
MergeHistory
=
new
Dictionary
<
string
,
string
>
(StringComparer.OrdinalIgnoreCase);
60
}
61
62
///
<summary>
63
///
Load a DefaultStyle from the a project item.
64
///
</summary>
65
///
<param name="path">
66
///
Path of the default style which is used for reporting errors.
67
///
</param>
68
///
<returns>
The DefaultStyle.
</returns>
69
public
static
DefaultStyle Load(
string
path)
70
{
71
DefaultStyle style
=
new
DefaultStyle();
72
style.DefaultStylePath
=
path;
73
74
string
xaml
=
File.ReadAllText(path);
75
XElement root
=
XElement.Parse(xaml, LoadOptions.PreserveWhitespace);
76
if
(root.Name.LocalName
==
RootElement)
77
{
78
//
Get the namespaces
79
foreach
(XAttribute attribute
in
root.Attributes())
80
{
81
if
(attribute.Name.LocalName
==
"
xmlns
"
)
82
{
83
style.Namespaces.Add(
""
, attribute.Value);
84
}
85
else
if
(attribute.Name.NamespaceName
==
XNamespace.Xmlns.NamespaceName)
86
{
87
style.Namespaces.Add(attribute.Name.LocalName, attribute.Value);
88
}
89
}
90
91
//
Get the styles and shared resources
92
foreach
(XElement element
in
root.Elements())
93
{
94
string
name
=
(element.Name.LocalName
==
"
Style
"
)
?
95
GetAttribute(element,
"
TargetType
"
,
"
Key
"
,
"
Name
"
) :
96
GetAttribute(element,
"
Key
"
,
"
Name
"
);
97
if
(style.Resources.ContainsKey(name))
98
{
99
throw
new
InvalidOperationException(
string
.Format(
100
CultureInfo.InvariantCulture,
101
"
Resource \
"
{
0
}\
"
is used multiple times in {1} (possibly as a Key, Name, or TargetType)!
"
,
102
name,
103
path));
104
}
105
style.Resources.Add(name, element);
106
style.MergeHistory[name]
=
path;
107
}
108
}
109
110
return
style;
111
}
112
113
///
<summary>
114
///
Get the value of the first attribute that is defined.
115
///
</summary>
116
///
<param name="element">
Element with the attributes defined.
</param>
117
///
<param name="attributes">
118
///
Local names of the attributes to find.
119
///
</param>
120
///
<returns>
Value of the first attribute found.
</returns>
121
private
static
string
GetAttribute(XElement element,
params
string
[] attributes)
122
{
123
foreach
(
string
name
in
attributes)
124
{
125
string
value
=
126
(from a
in
element.Attributes()
127
where
a.Name.LocalName
==
name
128
select a.Value)
129
.FirstOrDefault();
130
if
(name
!=
null
)
131
{
132
return
value;
133
}
134
}
135
return
""
;
136
}
137
138
///
<summary>
139
///
Merge a sequence of DefaultStyles into a single style.
140
///
</summary>
141
///
<param name="styles">
Sequence of DefaultStyles.
</param>
142
///
<returns>
Merged DefaultStyle.
</returns>
143
public
static
DefaultStyle Merge(IEnumerable
<
DefaultStyle
>
styles)
144
{
145
DefaultStyle combined
=
new
DefaultStyle();
146
if
(styles
!=
null
)
147
{
148
foreach
(DefaultStyle style
in
styles)
149
{
150
combined.Merge(style);
151
}
152
}
153
return
combined;
154
}
155
156
///
<summary>
157
///
Merge with another DefaultStyle.
158
///
</summary>
159
///
<param name="other">
Other DefaultStyle to merge.
</param>
160
private
void
Merge(DefaultStyle other)
161
{
162
//
Merge or lower namespaces
163
foreach
(KeyValuePair
<
string
,
string
>
ns
in
other.Namespaces)
164
{
165
string
value
=
null
;
166
if
(
!
Namespaces.TryGetValue(ns.Key,
out
value))
167
{
168
Namespaces.Add(ns.Key, ns.Value);
169
}
170
else
if
(value
!=
ns.Value)
171
{
172
other.LowerNamespace(ns.Key);
173
}
174
}
175
176
//
Merge the resources
177
foreach
(KeyValuePair
<
string
, XElement
>
resource
in
other.Resources)
178
{
179
if
(Resources.ContainsKey(resource.Key))
180
{
181
throw
new
InvalidOperationException(
string
.Format(
182
CultureInfo.InvariantCulture,
183
"
Resource \
"
{
0
}\
"
is used by both {1} and {2}!
"
,
184
resource.Key,
185
MergeHistory[resource.Key],
186
other.DefaultStylePath));
187
}
188
Resources[resource.Key]
=
resource.Value;
189
MergeHistory[resource.Key]
=
other.DefaultStylePath;
190
}
191
}
192
193
///
<summary>
194
///
Lower a namespace from the root ResourceDictionary to its child
195
///
resources.
196
///
</summary>
197
///
<param name="prefix">
Prefix of the namespace to lower.
</param>
198
private
void
LowerNamespace(
string
prefix)
199
{
200
//
Get the value of the namespace
201
string
@namespace;
202
if
(
!
Namespaces.TryGetValue(prefix,
out
@namespace))
203
{
204
return
;
205
}
206
207
//
Push the value into each resource
208
foreach
(KeyValuePair
<
string
, XElement
>
resource
in
Resources)
209
{
210
//
Don't push the value down if it was overridden locally or if
211
//
it's the default namespace (as it will be lowered
212
//
automatically)
213
if
(((from e
in
resource.Value.Attributes()
214
where
e.Name.LocalName
==
prefix
215
select e).Count()
==
0
)
&&
216
!
string
.IsNullOrEmpty(prefix))
217
{
218
resource.Value.Add(
new
XAttribute(XName.Get(prefix, XNamespace.Xmlns.NamespaceName), @namespace));
219
}
220
}
221
}
222
223
///
<summary>
224
///
Generate the XAML markup for the default style.
225
///
</summary>
226
///
<returns>
Generated XAML markup.
</returns>
227
public
string
GenerateXaml()
228
{
229
//
Create the ResourceDictionary
230
string
defaultNamespace
=
XNamespace.Xml.NamespaceName;
231
Namespaces.TryGetValue(
""
,
out
defaultNamespace);
232
XElement resources
=
new
XElement(XName.Get(RootElement, defaultNamespace));
233
234
//
Add the shared namespaces
235
foreach
(KeyValuePair
<
string
,
string
>
@namespace
in
Namespaces)
236
{
237
//
The default namespace will be added automatically
238
if
(
string
.IsNullOrEmpty(@namespace.Key))
239
{
240
continue
;
241
}
242
resources.Add(
new
XAttribute(
243
XName.Get(@namespace.Key, XNamespace.Xmlns.NamespaceName),
244
@namespace.Value));
245
}
246
247
//
Add the resources
248
foreach
(KeyValuePair
<
string
, XElement
>
element
in
Resources)
249
{
250
resources.Add(
251
new
XText(Environment.NewLine
+
Environment.NewLine
+
"
"
),
252
new
XComment(
"
"
+
element.Key
+
"
"
),
253
new
XText(Environment.NewLine
+
"
"
),
254
element.Value);
255
}
256
257
resources.Add(
new
XText(Environment.NewLine
+
Environment.NewLine));
258
259
//
Create the document
260
XDocument document
=
new
XDocument(
261
//
TODO: Pull this copyright header from some shared location
262
new
XComment(Environment.NewLine
+
263
"
// (c) Copyright Microsoft Corporation.
"
+
Environment.NewLine
+
264
"
// This source is subject to the Microsoft Public License (Ms-PL).
"
+
Environment.NewLine
+
265
"
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
"
+
Environment.NewLine
+
266
"
// All other rights reserved.
"
+
Environment.NewLine),
267
new
XText(Environment.NewLine
+
Environment.NewLine),
268
new
XComment(Environment.NewLine
+
269
"
// WARNING:
"
+
Environment.NewLine
+
270
"
//
"
+
Environment.NewLine
+
271
"
// This XAML was automatically generated by merging the individual default
"
+
Environment.NewLine
+
272
"
// styles. Changes to this file may cause incorrect behavior and will be lost
"
+
Environment.NewLine
+
273
"
// if the XAML is regenerated.
"
+
Environment.NewLine),
274
new
XText(Environment.NewLine
+
Environment.NewLine),
275
resources);
276
277
return
document.ToString();
278
}
279
280
///
<summary>
281
///
Generate the XAML markup for the default style.
282
///
</summary>
283
///
<returns>
Generated XAML markup.
</returns>
284
public
override
string
ToString()
285
{
286
return
GenerateXaml();
287
}
288
}
289
}
290
在实际操作中,发现当我定义了两个Button的自定义样式,他们的TargetType都是Button,但是x:Key不同,可是最后生成时还是发生了错误,提示重复。检查了代码,感觉以下两个方法有些问题,所以进行了修改,改为按照key、name、targertype的顺序进行键的取值,而不是先判断targertype,相关代码如下,修改部分用红色标出:
原文代码
1
public
static
DefaultStyle Load(
string
path)
2
{
3
DefaultStyle style
=
new
DefaultStyle();
4
style.DefaultStylePath
=
path;
5
6
string
xaml
=
File.ReadAllText(path);
7
XElement root
=
XElement.Parse(xaml, LoadOptions.PreserveWhitespace);
8
if
(root.Name.LocalName
==
RootElement)
9
{
10
//
Get the namespaces
11
foreach
(XAttribute attribute
in
root.Attributes())
12
{
13
if
(attribute.Name.LocalName
==
"
xmlns
"
)
14
{
15
style.Namespaces.Add(
""
, attribute.Value);
16
}
17
else
if
(attribute.Name.NamespaceName
==
XNamespace.Xmlns.NamespaceName)
18
{
19
style.Namespaces.Add(attribute.Name.LocalName, attribute.Value);
20
}
21
}
22
23
//
Get the styles and shared resources
24
foreach
(XElement element
in
root.Elements())
25
{
26
string
name
=
(element.Name.LocalName
==
"
Style
"
)
?
27
GetAttribute(element,
"
TargetType
"
,
"
Key
"
,
"
Name
"
) :
28
GetAttribute(element,
"
Key
"
,
"
Name
"
);
29
if
(style.Resources.ContainsKey(name))
30
{
31
throw
new
InvalidOperationException(
string
.Format(
32
CultureInfo.InvariantCulture,
33
"
Resource \
"
{
0
}\
"
is used multiple times in {1} (possibly as a Key, Name, or TargetType)!
"
,
34
name,
35
path));
36
}
37
style.Resources.Add(name, element);
38
style.MergeHistory[name]
=
path;
39
}
40
}
41
42
return
style;
43
}
44
45
private
static
string
GetAttribute(XElement element,
params
string
[] attributes)
46
{
47
foreach
(
string
name
in
attributes)
48
{
49
string
value
=
50
(from a
in
element.Attributes()
51
where
a.Name.LocalName
==
name
52
select a.Value)
53
.FirstOrDefault();
54
if
(name
!=
null
)
55
{
56
return
value;
57
}
58
}
59
return
""
;
60
}
61
修改后的代码
1
public
static
DefaultStyle Load(
string
path)
2
{
3
DefaultStyle style
=
new
DefaultStyle();
4
style.DefaultStylePath
=
path;
5
6
string
xaml
=
File.ReadAllText(path);
7
XElement root
=
XElement.Parse(xaml, LoadOptions.PreserveWhitespace);
8
if
(root.Name.LocalName
==
RootElement)
9
{
10
//
Get the namespaces
11
foreach
(XAttribute attribute
in
root.Attributes())
12
{
13
if
(attribute.Name.LocalName
==
"
xmlns
"
)
14
{
15
style.Namespaces.Add(
""
, attribute.Value);
16
}
17
else
if
(attribute.Name.NamespaceName
==
XNamespace.Xmlns.NamespaceName)
18
{
19
style.Namespaces.Add(attribute.Name.LocalName, attribute.Value);
20
}
21
}
22
23
//
Get the styles and shared resources
24
foreach
(XElement element
in
root.Elements())
25
{
26
//
此处进行了修改
27
string
name
=
(element.Name.LocalName
==
"
Style
"
)
?
28
GetAttribute(element,
"
Key
"
,
"
Name
"
,
"
TargetType
"
) :
29
GetAttribute(element,
"
Key
"
,
"
Name
"
);
30
if
(style.Resources.ContainsKey(name))
31
{
32
throw
new
InvalidOperationException(
string
.Format(
33
CultureInfo.InvariantCulture,
34
"
Resource \
"
{
0
}\
"
is used multiple times in {1} (possibly as a Key, Name, or TargetType)!
"
,
35
name,
36
path));
37
}
38
style.Resources.Add(name, element);
39
style.MergeHistory[name]
=
path;
40
}
41
}
42
43
return
style;
44
}
45
46
private
static
string
GetAttribute(XElement element,
params
string
[] attributes)
47
{
48
foreach
(
string
name
in
attributes)
49
{
50
string
value
=
51
(from a
in
element.Attributes()
52
where
a.Name.LocalName
==
name
53
select a.Value)
54
.FirstOrDefault();
55
//
此处进行了修改
56
if
(value
!=
null
)
57
{
58
return
value;
59
}
60
}
61
return
""
;
62
}
63
OK,代码添加完毕,编译后就得到了一个自定义任务的实现类,接下来就是对要进行自定任务运行的项目文件进行编辑。
首先,在VS中右键Unload你要编辑的项目,然后右键选择编辑改项目,在<Project节点的下面加上如下的自定义任务的声明,如下:
自定义任务的声明
1
<
Project
ToolsVersion
="3.5"
DefaultTargets
="Build"
xmlns
="http://schemas.microsoft.com/developer/msbuild/2003"
>
2
<
UsingTask
3
TaskName
="Engineering.Build.Tasks.MergeDefaultStylesTask"
4
AssemblyFile
="$(EngineeringResources)\Engineering.Build.dll"
/>
5
其中TaskName为刚才新建的Task类的全名,AssemblyFile为该类所在的程序集的物理地址,这里使用了一个预先的符号,你需要将其改成自己的实际地址。
然后,在之前的定义下面添加一个ItemGroup,这样可以让VS识别到这个Build Action,定义如下:
Build Action
1
<!--
Add "DefaultStyle" as a Build Action in Visual Studio
-->
2
<
ItemGroup
Condition
="'$(BuildingInsideVisualStudio)'=='true'"
>
3
<
AvailableItemName
Include
="DefaultStyle"
>
4
<
Visible
>
false
</
Visible
>
5
</
AvailableItemName
>
6
</
ItemGroup
>
7
注意这里跟原文不同的是添加了一个<Visible>false</Visible>属性,你可以尝试将其去掉,会发现在项目中多出了一个名为DefaultStyle的文件。
最后,添加自定义任务的执行,这也是我卡住的地方,最后发现可能是由于项目文件是在VS中生成的原因,原文中的Targer定义之后不起作用,而是需要将其定义放到VS生成的项目文件所指定的位置,具体位置如下:
Targer
1
<!--
To modify your build process, add your task inside one of the targets below and uncomment it.
2
Other similar extension points exist, see Microsoft.Common.targets.
-->
3
<
Target
Name
="BeforeBuild"
>
4
</
Target
>
5
<
Target
Name
="AfterBuild"
Inputs
="@(DefaultStyle)"
Outputs
="$(ProjectDir)\Themes\Generic.xaml"
>
6
<
MergeDefaultStylesTask
DefaultStyles
="@(DefaultStyle)"
ProjectDirectory
="$(ProjectDir)"
/>
7
</
Target
>
8
按照字面的意思,我选择了在编译成功后执行我们的自定义任务,关于为什么必须在这里添加的任务才会执行,由于我对于MSBuild方面知识的匮乏,无法给大家一个解释,我猜测可能是下面这句话的原因:
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Silverlight\v3.0\Microsoft.Silverlight.CSharp.targets" />
时间有限,暂时还不想将时间放在了解MSBuild,希望有知道的朋友能够给以回复。
好了,按照上面的流程就完成了整个任务的定义,以后你只需要在修改了某个资源文件后,点击Build,该任务就会自动帮你把所有Build Action设置为DefaultStyle的资源文件进行合并。
PS:目前只能在有相应的资源文件被修改后Build才会进行合并操作,而在原文中还有一个任务,能够在Rebuild的时候强制合并资源文件,但是我添加后并没有起作用,如果有了解的朋友,还希望能够在回复中给与指出,不甚感激!