Silverlight开发中的疑难杂症如何自动合并控件的默认样式 -电脑资料

电脑资料 时间:2019-01-01 我要投稿
【www.unjs.com - 电脑资料】

在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 Namespaces { get; private set; }
39
40         /// 
41         ///  Gets the elements in the XAML that include both styles and  shared
42         /// resources.
43         /// 

44         public SortedDictionary Resources { get; private set; }
45
46          /// 
47         /// Gets or sets the history  tracking which resources originated from
48         /// which  files.
49         /// 

50          private Dictionary MergeHistory { get; set; }
51
52         /// 
53         ///  Initializes a new instance of the DefaultStyle. class.
54          /// 

55         protected DefaultStyle()
56          {
57             Namespaces = new  SortedDictionary(StringComparer.OrdinalIgnoreCase);
58              Resources = new SortedDictionary(StringComparer.OrdinalIgnoreCase);
59              MergeHistory = new Dictionary (StringComparer.OrdinalIgnoreCase);
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         /// The  DefaultStyle.
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         /// Value of the first  attribute found.
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          /// Merged DefaultStyle.
143          public static DefaultStyle. Merge (IEnumerable 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         /// 
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 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 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         /// 
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 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         /// 
224         /// Generate the XAML markup for  the default style.
225         /// 

226          /// Generated XAML markup.
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 @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 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         /// 
281         /// Generate the XAML markup for  the default style.
282         /// 

283          /// Generated XAML markup.
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你要编辑的项目,然后右键选择编辑改项目,在

自定义任务的声明

1 
2    3    TaskName="Engineering.Build.Tasks.MergeDefaultStylesTask"
4    AssemblyFile="$(EngineeringResources) \Engineering.Build.dll" />
5

其中TaskName为刚才新建的Task类的全名,AssemblyFile为该类所在的程序集的物理地址 ,这里使用了一个预先的符号,你需要将其改成自己的实际地址。

然后,在之前的定义下面添加一个ItemGroup,这样可以让VS识别到这个Build Action, 定义如下:

Build Action

1   
2   
3     
4        false
5     
6   
7

注意这里跟原文不同的是添加了一个false属性,你可 以尝试将其去掉,会发现在项目中多出了一个名为DefaultStyle的文件。

最后,添加自定义任务的执行,这也是我卡住的地方,最后发现可能是由于项目文件是在 VS中生成的原因,原文中的Targer定义之后不起作用,而是需要将其定义放到VS生成的项目 文件所指定的位置,具体位置如下:

Targer

1 
3    
4   
5   
6     
7    
8

按照字面的意思,我选择了在编译成功后执行我们的自定义任务,关于为什么必须在这里 添加的任务才会执行,由于我对于MSBuild方面知识的匮乏,无法给大家一个解释,我猜测可 能是下面这句话的原因:

时间有限,暂时还不想将时间放在了解MSBuild,希望有知道的朋友能够给以回复。

好了,按照上面的流程就完成了整个任务的定义,以后你只需要在修改了某个资源文件后 ,点击Build,该任务就会自动帮你把所有Build Action设置为DefaultStyle的资源文件进行 合并。

PS:目前只能在有相应的资源文件被修改后Build才会进行合并操作,而在原文中还有一 个任务,能够在Rebuild的时候强制合并资源文件,但是我添加后并没有起作用,如果有了解 的朋友,还希望能够在回复中给与指出,不甚感激!

最新文章