2026/9/8 16:23:22

WPF中ComboBox控件样式定制:模板、触发器与MVVM实践

WPF中ComboBox控件样式定制:模板、触发器与MVVM实践 有没有遇到过这种情况WPF项目里拖了一个ComboBox跑起来发现不管你怎么设置Foreground、Background它都纹丝不动甚至还带有一圈系统默认的蓝色高亮边框。尤其在工控上位机或者一些需要定制界面风格的项目里这种“顽固”的默认样式会直接把整个界面的档次拉低。我早期做WPF开发时就在这上面花了不少时间一开始以为是属性没设对后来才知道ComboBox的视觉呈现根本不是靠那几个简单的属性控制的它的整个外观是由一套复杂的控件模板决定的。这篇博文就专门来拆解ComboBox的自定义样式这件事从模板结构、触发器原理到MVVM模式下踩过的坑一次性聊透希望能给正在跟WPF样式较劲的朋友一些实际参考。1. ComboBox的“外壳”为什么直接改属性经常无效WPF的控件和WinForm有一个本质区别WinForm的控件外观是“画死”的而WPF的控件外观是“可拆装”的。ComboBox的外观表现核心由ControlTemplate决定而不是依赖我们习惯性设置的那几个依赖属性。1.1 属性只是数据模板才是外观很多刚从WinForm转过来的朋友对WPF控件的理解往往是“控件本身自带样式属性是样式入口”。这个理解在WPF里并不完全正确。WPF控件的职责划分很明确控件的逻辑功能由ComboBox这个类负责管理下拉项的选中状态、处理键盘交互、维护ItemsSource集合控件的视觉呈现由ControlTemplate负责绘制背景、边框、弹层布局、滚动条等控件内部各视觉元素的细节比如背景色、文字颜色由Style中的Setter和Trigger负责但前提是控件模板里用了TemplateBinding或Binding把这些依赖属性“连接”到模板元素上。这就是为什么你设置BackgroundRed之后整个ComboBox仍然显示系统默认的浅灰色背景——默认模板里的背景元素根本没有绑定Background属性而是直接写死了一个SystemColors.WindowBrush之类的系统画刷。再往深一层说ComboBox的默认模板结构相当复杂拆开来看主要有这几个组成部分ToggleButton切换按钮负责响应鼠标点击展开和收起下拉区域ContentPresenter内容呈现器显示当前选中的项通过Content和ContentTemplate承接数据内容的视觉呈现Popup弹出层承载下拉列表的容器它的IsOpen属性由ComboBox控制这也是ComboBox能“悬浮”在界面上层的原因ScrollViewer滚动容器当列表项过多时提供滚动能力ItemsPresenter项呈现器把ItemsSource里的每一项按照ItemTemplate渲染出来。这五个部分协同工作才组成了我们平时看到的那个“点击后可以展开列表”的完整控件。1.2 区分Style、ControlTemplate和DataTemplate在动手改样式之前必须先把三个很容易混淆的概念理清楚。很多样式改不成功的案例都是因为概念边界没搞清楚改错了地方。概念作用对象控制内容类比Style控件本身控件的属性默认值、属性间的联动触发一套统一的着装规范ControlTemplate控件的“骨架”控件的整体视觉结构、布局、动画一套完整的穿搭模板DataTemplate数据项数据内容如何呈现同样的数据用什么衣服包装具体到ComboBox这个控件上三层各自的职责是这样的Style设置Width、Height、字体、鼠标悬停时的Background变化ControlTemplate决定这个控件看起来是“传统Windows下拉框”还是“圆角现代风格下拉框”DataTemplate决定ItemsSource里的字符串、自定义对象显示成什么样比如要不要加图标、要不要多列展示。很多初学者把三者混为一谈在Style里改了很久没效果其实问题出在ControlTemplate上。这个认知一旦纠正过来后面很多样式难题就能顺藤摸瓜找到症结。1.3 默认模板的“隐藏依赖”机制还有一个多数人没注意到的地方ComboBox的默认模板里大量使用了ThemeResource和系统笔刷资源。这些资源在不同Windows主题下看起来不一样。在Windows 10/11上你会发现默认下拉框在某些模式下是圆角在另外一些模式下是直角原因就在这里。这意味着你自定义ComboBox样式时如果只是部分覆盖模板资源不同机器上呈现的效果可能不一致。彻底的做法是——完全重写一个自己的ControlTemplate不依赖任何系统资源把所有颜色、尺寸、字体参数都掌握在自己手里。这也是这篇博文接下来要做的核心事情。2. 分步手写ComboBox自定义样式从边框到弹层我直接拿一个实际项目里用过的样式为例带大家一步一步把这个控件“拆了重装”。目标效果是经典直角边框、深浅适配主题、选中态和悬停态有明显反馈。不需要第三方UI库纯手工编写Style和ControlTemplate。2.1 第一步搭建基础Style框架任何控件样式都是从Style开始的。ComboBox的Style首先要把TargetType指定为ComboBox然后为ControlTemplate的Setter占位。前期可以先把控件的通用属性写好比如高度、字体、内边距。Style x:KeyCustomComboBoxStyle TargetTypeComboBox Setter PropertyHeight Value32 / Setter PropertyFontSize Value14 / Setter PropertyForeground Value#333333 / Setter PropertyPadding Value8,0 / Setter PropertyVerticalContentAlignment ValueCenter / !-- 核心设置自定义模板 -- Setter PropertyTemplate Setter.Value ControlTemplate TargetTypeComboBox !-- 这里开始填写模板结构 -- /ControlTemplate /Setter.Value /Setter /Style这里需要注意的是VerticalContentAlignment和Padding这两个属性如果不写默认模板里的ContentPresenter会忽略它们导致文字在ComboBox里偏上或偏下。写了之后模板里需要使用TemplateBinding去接收。这个阶段最容易犯的错误是把TemplateBinding漏掉。简单说TemplateBinding的作用就是“把控件外面的属性值传递到模板内部某个元素的属性上”。比如控件外部设置了Foreground#333但模板内部没有TextElement.Foreground{TemplateBinding Foreground}这句话那么外部设置就是无效的。2.2 第二步完整模板结构——五段式布局直接把现有的ToggleButton结构扔到一边自己重新组织。我实现的五段式结构是外层Border统一控制背景、圆角、边框ToggleButton承载点击、悬停、展开状态背景改为透明让外层Border来作主视觉ContentPresenter显示当前选中项Popup下拉弹层容器ScrollViewerItemsPresenter承载列表项。ControlTemplate TargetTypeComboBox Grid ToggleButton x:NameToggleButton FocusableFalse ClickModePress IsChecked{Binding IsDropDownOpen, ModeTwoWay, RelativeSource{RelativeSource TemplatedParent}} ToggleButton.Template ControlTemplate TargetTypeToggleButton !-- 透明背景因为真实背景由外层Border控制 -- Border x:NameToggleButtonBorder BackgroundTransparent BorderThickness0 / /ControlTemplate /ToggleButton.Template /ToggleButton ContentPresenter x:NameContentSite Margin{TemplateBinding Padding} VerticalAlignmentCenter HorizontalAlignmentLeft Content{TemplateBinding SelectionBoxItem} ContentTemplate{TemplateBinding SelectionBoxItemTemplate} ContentTemplateSelector{TemplateBinding ItemTemplateSelector} IsHitTestVisibleFalse / Path x:NameArrow HorizontalAlignmentRight VerticalAlignmentCenter Margin0,0,10,0 DataM 0,0 L 4,4 L 8,0 Z Fill{TemplateBinding Foreground} / Popup x:NamePART_Popup IsOpen{TemplateBinding IsDropDownOpen} AllowsTransparencyTrue PlacementBottom FocusableFalse PopupAnimationSlide Border x:NameDropDownBorder MinWidth{TemplateBinding ActualWidth} MaxHeight{TemplateBinding MaxDropDownHeight} BackgroundWhite BorderBrush#CCCCCC BorderThickness1 ScrollViewer x:NameDropDownScrollViewer ItemsPresenter / /ScrollViewer /Border /Popup /Grid /ControlTemplate这段代码有几个关键点需要展开说明ToggleButton的IsChecked绑定方式是ComboBox下拉开合的核心机制。它是一个相对源绑定指向TemplatedParent的IsDropDownOpen属性。这就是为什么点击这个按钮时下拉框会展开和收起。**Popup的IsOpen**同样绑定了IsDropDownOpen这是弹层与按钮状态联动的桥梁。这里需要注意AllowsTransparencyTrue是必须的否则弹层默认底色会是一个不透明的矩形面板无法做出圆角或阴影效果。**ContentPresenter绑定了SelectionBoxItem**而不是Content这是ComboBox特有的机制。ComboBox的Content属性它不直接用真正决定选中项显示的是SelectionBoxItem和SelectionBoxItemTemplate。2.3 第三步触发器——让交互状态“活”起来一个没有状态的控件是“死”的。现在加上四个关键触发器分别处理鼠标悬停、展开状态、鼠标按下这三个场景。ControlTemplate.Triggers !-- 鼠标悬停边框和背景都要有反馈 -- Trigger PropertyIsMouseOver ValueTrue Setter TargetNameMainBorder PropertyBorderBrush Value#4A90D9 / Setter TargetNameMainBorder PropertyBackground Value#F4F9FD / /Trigger !-- 下拉框展开高亮边框背景略微变化 -- Trigger PropertyIsDropDownOpen ValueTrue Setter TargetNameMainBorder PropertyBorderBrush Value#2C6BB2 / Setter TargetNameMainBorder PropertyBackground Value#E8F2FB / Setter TargetNameArrow PropertyRenderTransform Setter.Value RotateTransform Angle180 / /Setter.Value /Setter /Trigger !-- 按下状态轻微深色背景模拟物理按键反馈 -- Trigger PropertyIsPressed ValueTrue Setter TargetNameMainBorder PropertyBackground Value#DCE9F5 / /Trigger !-- 禁用状态降低透明度 -- Trigger PropertyIsEnabled ValueFalse Setter TargetNameMainBorder PropertyOpacity Value0.5 / /Trigger /ControlTemplate.Triggers悬停状态有个小细节WPF默认的悬停反馈即系统自带的淡蓝色背景会覆盖整个控件矩形区域非常丑。自定义模板的触发器生效后系统默认视觉会被ControlTemplate完全替代这是彻底重写模板的好处之一。IsDropDownOpen触发器的箭头旋转效果用的是RenderTransform而不是LayoutTransform。区别在于LayoutTransform会影响布局可能导致箭头在旋转时“挤开”相邻元素RenderTransform只在渲染层做视觉变化不影响布局。下拉箭头使用RenderTransform性能更好也不会引起布局抖动。2.4 第四步下拉列表项的自定义外观下拉弹层里的每一项样式同样不能裸奔。如果不做任何处理列表项的选中和悬停效果会退回系统默认值。正常情况下应该为列表项准备一套和主控件风格匹配的ItemContainerStyle。Style x:KeyCustomComboItemStyle TargetTypeComboBoxItem Setter PropertyForeground Value#333333 / Setter PropertyPadding Value10,7 / Setter PropertyHorizontalContentAlignment ValueLeft / Setter PropertyBackground ValueTransparent / Setter PropertyTemplate Setter.Value ControlTemplate TargetTypeComboBoxItem Border x:NameItemBorder Background{TemplateBinding Background} BorderThickness0 Padding{TemplateBinding Padding} ContentPresenter / /Border ControlTemplate.Triggers !-- 鼠标悬停颜色 -- Trigger PropertyIsMouseOver ValueTrue Setter TargetNameItemBorder PropertyBackground Value#E8F2FB / /Trigger !-- 选中项颜色 -- Trigger PropertyIsSelected ValueTrue Setter TargetNameItemBorder PropertyBackground Value#4A90D9 / Setter PropertyForeground ValueWhite / /Trigger /ControlTemplate.Triggers /ControlTemplate /Setter.Value /Setter /Style给ItemsSource设置数据之后把这个ItemContainerStyle挂到ComboBox上ComboBox ItemsSource{Binding DataList} ItemContainerStyle{StaticResource CustomComboItemStyle} /这一层容易被忽略因为很多人只关注主控件的ControlTemplate忽略了列表项的呈现。实际上下拉展开后的视觉体验很大程度上是由这一层决定的。如果列表项的选中态和悬停态没有处理好整个控件的完成度会大打折扣。2.5 完整组合把各部分拼在一起把所有组件拼成一个完整的Style结构大概是Style x:KeyCustomComboBoxStyle TargetTypeComboBox !-- 基本属性 模板Setter -- Setter PropertyItemContainerStyle Value{StaticResource CustomComboItemStyle} / Setter PropertyTemplate Setter.Value ControlTemplate TargetTypeComboBox Border x:NameMainBorder BackgroundWhite BorderBrush#CCCCCC BorderThickness1 Grid !-- ToggleButton -- !-- ContentPresenter -- !-- 下拉箭头 -- !-- Popup -- /Grid /Border ControlTemplate.Triggers !-- 四个状态触发器 -- /ControlTemplate.Triggers /ControlTemplate /Setter.Value /Setter /Style3. 让自定义样式适配MVVM绑定、命令与转换器样式写好了如果只是静态展示那不叫完整方案。在实际项目里ComboBox往往要和ViewModel打交道涉及数据源、选中项、命令触发这些联动逻辑。这一节把常见场景过一遍。3.1 数据绑定细节SelectedItem和SelectedValue在MVVM模式里ComboBox最核心的两个属性是ItemsSource和SelectedItem。比如一个简单的用户角色下拉框public class UserViewModel : ObservableObject { private ListRoleModel _roleList; public ListRoleModel RoleList { get _roleList; set { _roleList value; OnPropertyChanged(); } } private RoleModel _selectedRole; public RoleModel SelectedRole { get _selectedRole; set { _selectedRole value; OnPropertyChanged(); } } }XAML侧绑定ComboBox Style{StaticResource CustomComboBoxStyle} ItemsSource{Binding RoleList} SelectedItem{Binding SelectedRole} DisplayMemberPathName /这里要提醒一个坑SelectedItem的绑定模式默认是TwoWay所以ViewModel里的set访问器里如果需要联动其他属性直接在这里触发即可。但要注意初始赋值顺序——ViewModel构造函数里先赋值RoleList再赋值SelectedRole否则当SelectedItem设置时RoleList可能还是null列表选不中任何项界面上看起来就是空白。3.2 命令传递通过绑定触发ViewModel方法有些场景需要在选择变化时立刻触发后台操作——比如查询条件联动。可以考虑用System.Windows.Interactivity的事件触发器把SelectionChanged事件转成命令。ComboBox Style{StaticResource CustomComboBoxStyle} ItemsSource{Binding RoleList} SelectedItem{Binding SelectedRole} i:Interaction.Triggers i:EventTrigger EventNameSelectionChanged i:InvokeCommandAction Command{Binding RoleChangedCommand} / /i:EventTrigger /i:Interaction.Triggers /ComboBox这段写法来自Expression Blend的System.Windows.Interactivity命名空间。如果你用的项目是基于Prism框架的那还可以直接用prism:InvokeCommandAction来简化效果类似。这个做法能够把界面交互逻辑彻底从代码后置文件里抽离出去符合Prism框架强调的松耦合思想。3.3 用IValueConverter控制下拉项的显示内容很多时候数据模型和显示文本并不完全对等。比如一个状态值数据库里存的是数字1、2、3界面上要显示“进行中”、“已完成”、“已暂停”。这时候可以用一个IValueConverterpublic class StatusToTextConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return value switch { 1 进行中, 2 已完成, 3 已暂停, _ 未知状态 }; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }在ItemTemplate里引用转换器ComboBox Style{StaticResource CustomComboBoxStyle} ItemsSource{Binding StatusList} SelectedItem{Binding SelectedStatus} ComboBox.ItemTemplate DataTemplate TextBlock Text{Binding Value, Converter{StaticResource StatusToTextConverter}} / /DataTemplate /ComboBox.ItemTemplate /ComboBox这段的巧妙之处在于列表项显示的是转换后的文本但SelectedItem拿到的仍然是原始的数字对象ViewModel的逻辑判断依然基于数字两边互不干扰。3.4 可编辑模式下的复杂度ComboBox还有IsEditableTrue的用法允许用户手动输入内容不在列表中就自动匹配。启用可编辑后控件模板里的ContentPresenter会失效需要换成TextBox来承载用户输入。此时模板结构会变得更复杂因为你需要处理TextBox内部事件和焦点状态。我的建议是除非业务上确实需要用户自由输入否则优先使用IsEditableFalse这样可以少处理很多边界情况比如输入框光标位置、下拉项自动补全这些逻辑。4. 高频坑位排查我给自己的项目修过的三个典型问题样式方案落地之后运行中还是会遇到各种诡异情况。这里记录几个我实际遇到过并排查过的问题不一定多高级但每个都能让人卡上一阵子。4.1 鼠标悬停闪现“系统蓝”症状整个控件已经自定义了模板但鼠标移上去之后仍然会闪一下非常丑的系统默认蓝色。定位过程首先怀疑是不是外层Border没有设置Background导致鼠标事件穿透到了ToggleButton上。但我点击和悬停都能正常响应说明不是穿透问题。然后检查ToggleButton模板发现里面是空的Border背景为Transparent理论上不会产生视觉。最后才发现问题出在样式资源上目标ComboBox仍然引用了系统的Aero2主题样式只有部分属性被自定义覆盖。解决方法给Style显式设置BasedOn{StaticResource {x:Type ComboBox}}的替代思路不成立——这个方案恰恰会保留系统模板。真正干净的做法是不继承任何基础样式完全由ControlTemplate定义所有视觉。在项目中给这个ComboBox设置Style{StaticResource CustomComboBoxStyle}时确保没有其他隐式样式在全局范围内覆盖它。4.2 下拉弹层被窗口边缘裁剪症状ComboBox靠近窗口底部时打开的下拉列表被窗口底边裁掉无法完整显示。定位过程一开始以为是Popup放置位置问题反复调整Placement属性没有效果。后来发现项目主窗口设置了ClipToBoundsTrue这个属性会把所有子元素包括Popup裁剪到窗口边界内。解决方法把ClipToBounds改成False问题立刻解决。只要主容器是WindowPopup默认就在窗口级别之上弹层不受Grid或者Canvas裁剪影响。如果是用户控件嵌套还需要检查上层是否有ScrollViewer或Border带Clip效果。4.3 自定义模板后程序集资源引用失败症状直接复制Style到新项目运行时报StaticResource找不到或者DynamicResource在运行时全部解析失败。定位过程检查后发现Style里引用的画刷资源比如MainBorderBrush只存在于原项目的App.xaml中没有跟随Style一起迁移到目标项目。解决方法把所有自定义样式依赖的颜色、画刷、转换器资源统一放进一个独立的ResourceDictionary文件哪个项目要用就MergedDictionaries合并导入。这不仅是排查这个问题的方案也是项目发展到中期之后样式资源长期可维护的基本功。5. 样式进阶动画、阴影与整套控件的“主题化”当基础样式稳定之后ComboBox的下一个提升点就是视觉细节的打磨。这里分享几个让控件质感和整体性再上一个台阶的方向。5.1 Popup开合动画默认的PopupAnimationSlide虽然自带一点滑动效果但比较生硬。可以把动画做得更细腻一些比如给弹层加一个RenderTransform利用ScaleTransform做缩放展开Popup x:NamePART_Popup IsOpen{TemplateBinding IsDropDownOpen} AllowsTransparencyTrue PlacementBottom PopupAnimationFade Border x:NameDropDownBorder MinWidth{TemplateBinding ActualWidth} BackgroundWhite BorderBrush#CCCCCC BorderThickness1 RenderTransformOrigin0.5,0 Border.RenderTransform ScaleTransform x:NamePopupScale ScaleX1 ScaleY0.8 / /Border.RenderTransform ScrollViewer ItemsPresenter / /ScrollViewer /Border /Popup在触发器中控制缩放动画EventTrigger RoutedEventComboBox.DropDownOpened BeginStoryboard Storyboard DoubleAnimation Storyboard.TargetNamePopupScale Storyboard.TargetPropertyScaleY To1 Duration0:0:0.15 / /Storyboard /BeginStoryboard /EventTrigger配合PopupAnimationFade展开瞬间会有一个淡入加轻微缩放的复合效果观感柔和很多。5.2 下拉弹层的阴影如果项目允许一定的视觉层次感给下拉弹层加个DropShadowEffect是很常见的操作。加在DropDownBorder上即可阴影方向朝下营造“浮起来”的感觉Border.Effect DropShadowEffect Color#33000000 BlurRadius12 ShadowDepth2 Direction270 / /Border.Effect注意一个性能细节DropShadowEffect是GPU加速的在数量少的控件上没有压力但如果一个界面里同时有几十个弹出层就要考虑减少阴影的使用频率。另外AllowsTransparencyTrue的Popup和DropShadowEffect搭配时某些显卡驱动版本下会出现重影或锯齿低配工控机上要多留意实测效果。5.3 将ComboBox样式嵌入整套控件主题项目里如果打算统一整套明暗主题ComboBox的样式需要具备“可换肤”能力。做法是所有颜色、画刷、尺寸不要直接写死在模板里统一用DynamicResource引用资源。比如模板里不写BackgroundWhite而是写Background{DynamicResource ComboBoxBackgroundBrush}然后在LightTheme.xaml和DarkTheme.xaml里分别定义同一个Key的不同值。!-- LightTheme.xaml -- SolidColorBrush x:KeyComboBoxBackgroundBrush ColorWhite / SolidColorBrush x:KeyComboBoxBorderBrush Color#CCCCCC / SolidColorBrush x:KeyComboBoxHoverBrush Color#F4F9FD / SolidColorBrush x:KeyComboBoxSelectedBrush Color#E8F2FB / !-- DarkTheme.xaml -- SolidColorBrush x:KeyComboBoxBackgroundBrush Color#2D2D30 / SolidColorBrush x:KeyComboBoxBorderBrush Color#3F3F46 / SolidColorBrush x:KeyComboBoxHoverBrush Color#3E3E42 / SolidColorBrush x:KeyComboBoxSelectedBrush Color#4A90D9 /换肤时只需在App.xaml里切换MergedDictionaries整个界面的ComboBox风格瞬间全局切换。这也是为什么当初在做主题的时候花了一个下午把Style里所有写死的颜色全部抽到资源字典里的原因——一劳永逸后续调整配色只需要改资源文件不需要再翻模板代码。6. 关于性能、版本兼容和“要不要用第三方库”的取舍最后聊几个综合性问题没有标准答案都是实际项目里会碰到的选择题。6.1 手写模板和第三方UI库的对比HandyControl、MaterialDesignInXAML、MahApps.Metro这些开源UI库都有成熟的ComboBox样式开箱即用。那还有必要自己手写吗我的看法是分情况如果项目只追求“看起来还行”没有强烈的品牌个性要求直接用第三方库效率最高坑也少如果项目的设计规范有特殊要求比如特定配色、特定圆角度数、特定动画节奏第三方库反而会成为负担修改成本可能比自己从零写更高从学习角度出发自己手写一次完整模板对模板机制的理解会远超直接拖库。我个人的建议日常开发能用三方库就用三方库但至少花时间完整手写一次ComboBox模板。复杂度不高但对整个WPF控件模板的认知提升非常明显。理解了一根“骨头”以后改任何控件样式都不会心里发虚。6.2 性能上的几个注意点Popup的AllowsTransparencyTrue在极低配机器上打开动画时会有轻微掉帧实测如果只是简单列表建议直接去掉动画绑定数据源过大比如上万项时下拉框展开会卡顿这跟样式无关但可以考虑用虚拟化面板给ItemsPresenter的ItemsPanel设置为VirtualizingStackPanel如果每次切换选中项时都触发大量OnPropertyChanged界面成本会直线上升建议在ViewModel里做一下防抖或者稍晚触发不关键的联动逻辑。6.3 多版本.NET环境下的兼容性这套模板基于.NET Framework 4.7.2和.NET Core 3.1以上的WPF都验证过能在net6.0-windows和net8.0-windows下正常编译运行。需要注意的版本差异点是ResourceDictionary合并的写法、StaticResource解析的时机在.NET Core版本中更加严格。如果遇到“资源在XAML里明明定义了但运行时报找不到资源”的情况优先检查资源定义位置是否在App.xaml之前。从最开始被默认样式劝退到现在能根据需求自由定制ComboBox的每一个细节这个过程让我对WPF的控件模板机制有了完整的理解。回头来看自定义ComboBox样式并不复杂核心就是吃透控件模板的结构、掌握几个关键触发器、处理好MVVM场景下的绑定逻辑然后剩下的就是审美层面的打磨了。如果你正在被这个控件的默认样式困扰不妨照着这篇文章的思路动手重写一次哪怕第一版做出来不够精致也会比对着属性面板乱试要高效得多。