WPF样式内Canvas/Path复用异常:多按钮仅一个显示Path
搞定这个WPF按钮样式的小问题
兄弟,你这个问题我之前踩过坑!核心原因是Style.Resources里的Canvas属于Visual元素,而WPF规定一个Visual对象不能同时出现在多个可视化树中。当你把这个样式应用到多个按钮时,所有按钮都会尝试引用同一个Canvas实例,最终只有一个按钮能成功把它加入自己的可视化树,其他按钮就拿不到这个Path了。
解决方案1:换成DrawingBrush共享图形资源
把Canvas换成DrawingBrush就没问题了——DrawingBrush是Freezable类型,可以安全地在多个控件间共享,不会有Visual实例冲突的问题。修改后的样式示例如下:
<Style x:Key="ButtonStyleNew" TargetType="{x:Type Button}"> <Style.Resources> <DrawingBrush x:Key="Button_New_Normal" Viewbox="0,0,24,24" ViewboxUnits="Absolute"> <DrawingBrush.Drawing> <GeometryDrawing Brush="Black"> <GeometryDrawing.Geometry> <PathGeometry Figures="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z"/> </GeometryDrawing.Geometry> </GeometryDrawing> </DrawingBrush.Drawing> </DrawingBrush> <DrawingBrush x:Key="Button_New_MouseOver" Viewbox="0,0,24,24" ViewboxUnits="Absolute"> <DrawingBrush.Drawing> <GeometryDrawing Brush="DarkGray"> <GeometryDrawing.Geometry> <PathGeometry Figures="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z"/> </GeometryDrawing.Geometry> </GeometryDrawing> </DrawingBrush.Drawing> </DrawingBrush> </Style.Resources> <Setter Property="Background" Value="Transparent"/> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border Background="{TemplateBinding Background}" CornerRadius="4"> <Rectangle x:Name="IconRect" Width="24" Height="24" Fill="{StaticResource Button_New_Normal}"/> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="MouseOver"> <Storyboard> <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Fill" Storyboard.TargetName="IconRect"> <DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource Button_New_MouseOver}"/> </ObjectAnimationUsingKeyFrames> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
解决方案2:直接在模板里定义Path,每个按钮用独立实例
如果不想改资源类型,也可以直接把Path放到按钮的ControlTemplate里,这样每个应用样式的按钮都会创建自己的Path实例,彻底避免共享冲突:
<Style x:Key="ButtonStyleNew" TargetType="{x:Type Button}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Button}"> <Border Background="{TemplateBinding Background}" Padding="4"> <Canvas Width="24" Height="24"> <Path x:Name="IconPath" Data="M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M13,7H11V11H7V13H11V17H13V13H17V11H13V7Z" Fill="Black"/> </Canvas> <VisualStateManager.VisualStateGroups> <VisualStateGroup x:Name="CommonStates"> <VisualState x:Name="Normal"/> <VisualState x:Name="MouseOver"> <Storyboard> <ColorAnimation To="DarkGray" Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="IconPath" Duration="0:0:0.1"/> </Storyboard> </VisualState> <VisualState x:Name="Pressed"> <Storyboard> <ColorAnimation To="#FF555555" Storyboard.TargetProperty="(Path.Fill).(SolidColorBrush.Color)" Storyboard.TargetName="IconPath" Duration="0:0:0.1"/> </Storyboard> </VisualState> </VisualStateGroup> </VisualStateManager.VisualStateGroups> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
小补充
- 方案1更适合需要复用图形资源的场景,性能更好,因为资源是共享的,不会重复创建实例。
- 方案2更直观,适合简单的按钮样式,完全规避了共享Visual的冲突问题。
内容的提问来源于stack exchange,提问作者Peter Deussen
相关产品推荐
相关产品推荐

