XAML Styler-WPF的好帮手
1. XAML 格式化主要的难题是下面几个:
如果所有属性都写在同一行,它太宽了很难看到后面的属性
如果每个属性单独一行,它又太长了很难看清楚它的结构
属性之间没有排序,重要属性的属性找起来很困难
团队没有统一的标准,不小心格式化一下代码的话全部都会变,CodeReview 烦死个人
如果不想得过且过忍受上述这些问题的话,可以试试用 XAML Styler 这个工具,它正好解决了我最想解决的问题。
2. 安装 XAML Styler
XAML Styler 是一个 VisualStudio插件(也可用于其它 IDE),这是它在 Github 上的地址:
https://github.com/Xavalon/XamlStyler
在这里你可以找到具体的文档,而这篇文章我只介绍我关心的其中几个属性,不一定满足到你。
在 VisualStudio 的管理扩展窗口中,输入 XamlStyle 搜索,点击“下载”然后关闭 VisualStudio 即可完成安装。
安装完成后重启 Visual Studio,可以在“选项”窗口中看到它的配置:
之后,每次在 XAML 编辑器中执行保存都会自动进行格式化操作。你也可以在 XAML 编辑器的右键菜单选择 Format XAML 或使用快捷键进行格式化。
3. 格式化
XAML 的格式主要有两种方式:所有属性放一行和每个属性单独一行。
如果选择所有属性放一行的时候,XAML 结构清晰,结构严谨,段落分明,而且文件也很短。
可是万一很多属性问题就出来了,一行 XAML 会变得很长。而且看看下面两个 ContentPresenter,同样都有 Margin 属性、HorizontalAlignment 属性,VerticalAlignment 属性,RecognizesAccessKey 属性,SnapsToDevicePixels 顺序ing,但你能看到第二个 ContentPresenter 后面偷偷塞了个 Margin 吗:
<ContentPresenter Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/> <ContentPresenter Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Margin="40"/>
如果在 VisualStudio 中“文本编辑器->XAML->格式化->间距->特性间距”这个选项中选择了“将各个属性分别放置”:
格式化文档后上面的 XAML 就会变成这样:
<ContentPresenter Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" /> <ContentPresenter Margin="{TemplateBinding Padding}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Margin="40" />
每个属性单独一行不仅不会看漏属性,而且编辑器本身也不会有横向和纵向两种方向的移动,只有从上到下的移动,这就舒服多了。
可是大部分情况下每个属性分行放置会破坏原本清晰的 XAML 层次结构,例如下面这种本来好好的 XAML:
<Setter Property="FontWeight" Value="Normal" /> <Setter Property="UseSystemFocusVisuals" Value="True" /> <Setter Property="FocusVisualMargin" Value="-3" /> <Setter Property="Height" Value="50" /> <Setter Property="Width" Value="50" /> <Setter Property="Maximum" Value="1" />
变成这样:
<Setter Property="FontWeight" Value="Normal" /> <Setter Property="UseSystemFocusVisuals" Value="True" /> <Setter Property="FocusVisualMargin" Value="-3" /> <Setter Property="Height" Value="50" /> <Setter Property="Width" Value="50" /> <Setter Property="Maximum" Value="1" />
XAML Styler 很好地解决了这个问题,它通过 “Attribute tolerance” 属性控制每一行的容许的最多的属性数量,如果一个元素的属性数量少于设定值,那就放在一行,如果超过就所有属性单独一行。通常我将这个属性设置为 2,再配合 “Keep first attribute on same line = true” 的设置,可以做到下面这种格式化效果:
<SolidColorBrush x:Key="NormalTextColor" Color="#2E2F33" /> <SolidColorBrush x:Key="PrimaryColor" Color="#FFED5B8C" /> <SolidColorBrush x:Key="LineColor" Color="#E1E1E1" /> <SolidColorBrush x:Key="TransparentBackground" Color="Transparent" /> <ControlTemplate x:Key="CompletedTemplate" TargetType="ContentControl"> <Grid x:Name="CompletedElement" Margin="-2"> <control:DropShadowPanel HorizontalContentAlignment="Stretch" VerticalContentAlignment="Stretch" BlurRadius="8" OffsetX="0" OffsetY="0" Color="#FFED5B8C"> <Ellipse x:Name="CompletedRectangle" Fill="{StaticResource PrimaryColor}" /> </control:DropShadowPanel> </Grid> </ControlTemplate>
4. 排序
如果元素有多个属性,要找到它的主要属性(通常是 Name 和 Grid.Row)需要颇费一番功夫。XAML Styler 根据一个可设定的规则自动将元素的各个属性排序,这个规则如下:
"AttributeOrderingRuleGroups": [ "x:Class", "xmlns, xmlns:x", "xmlns:*", "x:Key, Key, x:Name, Name, x:Uid, Uid, Title", "Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom", "Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight", "Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex", "*:*, *", "PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint", "mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText", "Storyboard.*, From, To, Duration" ],
排序结果大致如下:
<Button x:Name="Show" Grid.Row="1" Padding="40,20" HorizontalAlignment="Center" VerticalAlignment="Center" Background="#00aef1" Content="Show" Foreground="White" Style="{StaticResource BubbleButtonStyle}" />
5. 统一标准
最后,就算自己做好了格式化,团队中的其它成员使用了不同的格式化标准也会引起很多问题。针对这个问题 Xaml Styler 也提供了解决方案。
在项目的根目录创建一个名为“Settings.XamlStyler”的文件,内容参考这个网址:https://github.com/Xavalon/XamlStyler/wiki/External-Configurations 中的 Default Configuration。有了这个配置文件,XAML Styler 就会根据它而不是全局配置进行格式化,作为项目的统一格式化标准。
方便大家拷贝,Settings.XamlStyler整理内容及注释如下:
{ //****************属性格式化**************************// "AttributesTolerance": 2, //单行最大属性数,2【默认】,如果元素属性数不大于此数就不会换行 "KeepFirstAttributeOnSameLine": false,//第一个属性是否与开始标记在同一行,false【默认】 "MaxAttributeCharactersPerLine": 10, //多个属性大于多少个字符就该换行,0【默认】 "MaxAttributesPerLine": 6, //大于几个属性就该换行,1【默认】 //无需换行的元素(比较短的),"RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter" "NewlineExemptionElements": "RadialGradientBrush, GradientStop, LinearGradientBrush, ScaleTransform, SkewTransform, RotateTransform, TranslateTransform, Trigger, Condition, Setter", "AttributeIndentation": 0, //属性缩进空格字符数(-1不缩进;0【默认】缩进4个空格;其它个数则指定) "AttributeIndentationStyle": 1, //属性缩进风格(0混合,视情况使用制表符和空格;1【默认】使用空格) "RemoveDesignTimeReferences": false, //是否移除自动添加的控件和设计时参考内容,false【默认】 //****************属性排序**************************// "EnableAttributeReordering": true, //是否启用属性的自动排序,true【默认】 "SeparateByGroups": false, //是否应该按照属性的分组进行分隔,false【默认】 /*属性排序和分组规则 "x:Class", "xmlns, xmlns:x", "xmlns:*", "x:Key, Key, x:Name, Name, x:Uid, Uid, Title", "Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom", "Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight", "Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex", "*:*, *", "PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint", "mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText", "Storyboard.*, From, To, Duration" */ "AttributeOrderingRuleGroups": [ "x:Class", "xmlns, xmlns:x", "xmlns:*", "x:Key, Key, x:Name, Name, x:Uid, Uid, Title", "Grid.Row, Grid.RowSpan, Grid.Column, Grid.ColumnSpan, Canvas.Left, Canvas.Top, Canvas.Right, Canvas.Bottom", "Width, Height, MinWidth, MinHeight, MaxWidth, MaxHeight", "Margin, Padding, HorizontalAlignment, VerticalAlignment, HorizontalContentAlignment, VerticalContentAlignment, Panel.ZIndex", "*:*, *", "PageSource, PageIndex, Offset, Color, TargetName, Property, Value, StartPoint, EndPoint", "mc:Ignorable, d:IsDataSource, d:LayoutOverrides, d:IsStaticText", "Storyboard.*, From, To, Duration" ], "FirstLineAttributes": "x:Name,Grid.Row,Grid.Column", //应该在第一行的属性,例如Name x:Name 和x:Uid等等,None【默认】 "OrderAttributesByName": false//是否按照属性名称进行排序 //****************元素格式化**************************// "PutEndingBracketOnNewLine": false, //结束括号是否独占一行,false【默认】 "RemoveEndingTagOfEmptyElement": true, //是否移除空元素的结束标签,true【默认】 "SpaceBeforeClosingSlash": true, //自闭合元素的末尾斜杠前是否要有空格,true【默认】 "RootElementLineBreakRule": 0, //是否将根元素的属性分成多行(0【默认】;1始终;2从不) //****************元素排序**************************// "ReorderVSM": 2, //是否重新排序visual state Manager(0未定义;1【默认】移到最后;2移到最前) "ReorderGridChildren": false, //是否重新排序Grid的子元素,false【默认】 "ReorderCanvasChildren": false, //是否重新排序Canvas的子元素,false【默认】 "ReorderSetters": 0, //是否重新排序Setter(0【默认】不排序;1按属性名;2按目标名;3先按目标名再按属性名) //****************标记扩展**************************// "FormatMarkupExtension": true, //是否格式化标记扩展的属性,true【默认】 //始终放在一行上的标记扩展,"x:Bind, Binding"【默认】 "NoNewLineMarkupExtensions": "x:Bind, Binding", //****************属性排序**************************// "ThicknessSeparator": 2, //Thickness类型的属性应该用哪种分隔符(0不格式化;1空格;2【默认】逗号) //被认定为Thickness的元素应该是哪些,"Margin, Padding, BorderThickness, ThumbnailClipMargin"【默认】 "ThicknessAttributes": "Margin, Padding, BorderThickness, ThumbnailClipMargin", //****************杂项**************************// "FormatOnSave": true, //是否在保存时进行格式化,true【默认】 "CommentPadding": 2, //注释的间距应该是几个空格,2【默认】 //****************覆盖VS配置**************************// "IndentSize": 4, //缩进空格数,4【默认】 "IndentWithTabs": false //是否使用制表符进行缩进,false【默认】 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)