在WPF中开发自定义控件时,可以将控件的默认样式放在以“<控件类型 >.Generic.xaml”的形式命名的资源文件中,从而分离各个自定义控件的默认样式的定义 ,减少单个Generic.xaml文件的复杂度,
Silverlight开发中的疑难杂症如何自动合并控件的默认样式
。但是在Silverlight控件开发时,却发现无法采用上面的方法来实现这一效果,尝试了许 久都没有找到其他的办法实现这一效果。郁闷之中,突然想起看一下Silverlight Toolkit中 是如何解决这一问题的,结果惊讶的发现它也是将所有的默认样式都堆积在了Generic.xaml 一个文件当中,感觉相当的不可思议。但是,仔细一看,发现在Generic.xaml文件的开头有 如下的一段话:
XML
这让我又感觉到了希望,于是祭出神器“谷歌”,以上面的话中的一部分为关键字,进行 了搜索,果然让我找到了相关的文章,原文地址如下: 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 ///
17 /// Build task to automatically merge the default styles for controls into
18 /// a single generic.xaml file.
19 ///
20 public class MergeDefaultStylesTask : Task
21 {
22 ///
23 /// Gets or sets the root directory of the project where the
24 /// generic.xaml file resides.
25 ///
26 [Required]
27 public string ProjectDirectory { get; set; }
28
29 ///
30 /// Gets or sets the project items marked with the "DefaultStyle" build
31 /// action.
32 ///
33 [Required]
34 public ITaskItem[] DefaultStyles { get; set; }
35
36 ///
37 /// Initializes a new instance of the MergeDefaultStylesTask class.
38 ///
39 public MergeDefaultStylesTask()
40 {
41 }
42
43 ///
44 /// Merge the project items marked with the "DefaultStyle" build action
45 /// into a single generic.xaml file.
46 ///
47 ///
48 /// A value indicating whether or not the task succeeded.
49 ///
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 riginalPath = 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 riginal = null;
64 Encoding encoding = Encoding.Default;
65 try
66 {
67 using (StreamReader reader = new StreamReader(File.Open (originalPath, FileMode.Open, FileAccess.Read)))
68 {
69 riginal = 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
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 ///
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 ///
21 public partial class DefaultStyle
22 {
23 ///
24 /// Root element of both the default styles and the merged generic.xaml.
25 ///
26 private const string RootElement = "ResourceDictionary";
27
28 ///
29 /// Gets or sets the file path of the default style.
30 ///
31 public string DefaultStylePath { get; set; }
32
33 ///
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 ///
38 public SortedDictionary
39
40 ///
41 /// Gets the elements in the XAML that include both styles and shared
42 /// resources.
43 ///
44 public SortedDictionary
45
46 ///
47 /// Gets or sets the history tracking which resources originated from
48 /// which files.
49 ///
50 private Dictionary
51
52 ///
53 /// Initializes a new instance of the DefaultStyle. class.
54 ///
55 protected DefaultStyle()
56 {
57 Namespaces = new SortedDictionary
58 Resources = new SortedDictionary
59 MergeHistory = new Dictionary
60 }
61
62 ///
63 /// Load a DefaultStyle. from the a project item.
64 ///
65 ///
66 /// Path of the default style. which is used for reporting errors.
67 ///
68 ///
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 ///
114 /// Get the value of the first attribute that is defined.
115 ///
116 /// Element with the attributes defined.
117 ///
118 /// Local names of the attributes to find.
119 ///
120 ///
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 ///
139 /// Merge a sequence of DefaultStyles into a single style.
140 ///
141 /// Sequence of DefaultStyles.
142 ///
143 public static DefaultStyle. Merge (IEnumerable
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 ///
157 /// Merge with another DefaultStyle.
158 ///
159 /// Other DefaultStyle. to merge.
160 private void Merge(DefaultStyle. other)
161 {
162 // Merge or lower namespaces
163 foreach (KeyValuePair
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
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 ///
194 /// Lower a namespace from the root ResourceDictionary to its child
195 /// resources.
196 ///
197 /// Prefix of the namespace to lower.
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
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 ///
224 /// Generate the XAML markup for the default style.
225 ///
226 ///
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
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
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 ///
281 /// Generate the XAML markup for the default style.
282 ///
283 ///
284 public override string ToString()
285 {
286 return GenerateXaml();
287 }
288 }
289 }
290
在实际操作中,发现当我定义了两个Button的自定义样式,他们的TargetType都是Button ,但是x:Key不同,可是最后生成时还是发生了错误,提示重复,
电脑资料
《Silverlight开发中的疑难杂症如何自动合并控件的默认样式》(https://www.unjs.com)。检查了代码,感觉以下两个 方法有些问题,所以进行了修改,改为按照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你要编辑的项目,然后右键选择编辑改项目,在 自定义任务的声明 其中TaskName为刚才新建的Task类的全名,AssemblyFile为该类所在的程序集的物理地址 ,这里使用了一个预先的符号,你需要将其改成自己的实际地址。 然后,在之前的定义下面添加一个ItemGroup,这样可以让VS识别到这个Build Action, 定义如下: Build Action 注意这里跟原文不同的是添加了一个 最后,添加自定义任务的执行,这也是我卡住的地方,最后发现可能是由于项目文件是在 VS中生成的原因,原文中的Targer定义之后不起作用,而是需要将其定义放到VS生成的项目 文件所指定的位置,具体位置如下: Targer 按照字面的意思,我选择了在编译成功后执行我们的自定义任务,关于为什么必须在这里 添加的任务才会执行,由于我对于MSBuild方面知识的匮乏,无法给大家一个解释,我猜测可 能是下面这句话的原因: 时间有限,暂时还不想将时间放在了解MSBuild,希望有知道的朋友能够给以回复。 好了,按照上面的流程就完成了整个任务的定义,以后你只需要在修改了某个资源文件后 ,点击Build,该任务就会自动帮你把所有Build Action设置为DefaultStyle的资源文件进行 合并。 PS:目前只能在有相应的资源文件被修改后Build才会进行合并操作,而在原文中还有一 个任务,能够在Rebuild的时候强制合并资源文件,但是我添加后并没有起作用,如果有了解 的朋友,还希望能够在回复中给与指出,不甚感激!1
2
4 AssemblyFile="$(EngineeringResources) \Engineering.Build.dll" />
51
2
3
4
5
6
71
3
4
5
6
7
8