WPF动态创建带Canvas的Button仅最后一个显示Canvas问题排查
首先,我一眼就发现问题所在了——你的BtnDeleteStackPanelStyle里直接把一个具体的Control实例赋值给了Button的Content属性,而WPF中一个UI元素只能属于一个视觉树,所以当你循环创建多个Button时,这个Control会被不断“移动”到最后一个Button里,导致前面的Button都没有内容,自然看不到Canvas。
具体问题分析
看你的Style代码:
<Style x:Key="BtnDeleteStackPanelStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource StackPanelLightButton}" x:Shared="False"> <Setter Property="Content"> <Setter.Value> <Control Template="{StaticResource PathDelete}"> <Control.Resources> <Style TargetType="{x:Type Path}" BasedOn="{StaticResource {x:Type Path}}"/> </Control.Resources> </Control> </Setter.Value> </Setter> </Style>
这里你直接实例化了一个<Control>作为Content的固定值,即便设置了x:Shared="False",这个Control实例还是唯一的。每次创建Button应用这个Style时,WPF会把这个Control从之前的Button的视觉树中移除,添加到新的Button里,最终只有最后一个Button能持有这个Control,所以只有它能显示Canvas。
修复方案
解决这个问题的核心是让每个Button都拥有自己独立的Content实例,有两种常用方法:
方法1:使用DataTemplate替代直接赋值Control
把Content的内容改成DataTemplate,这样每个Button应用Style时都会根据模板创建新的实例:
<Style x:Key="BtnDeleteStackPanelStyle" TargetType="{x:Type Button}" BasedOn="{StaticResource StackPanelLightButton}" x:Shared="False"> <Setter Property="ContentTemplate"> <Setter.Value> <DataTemplate> <Control Template="{StaticResource PathDelete}"> <Control.Resources> <Style TargetType="{x:Type Path}" BasedOn="{StaticResource {x:Type Path}}"/> </Control.Resources> </Control> </DataTemplate> </Setter.Value> </Setter> </Style>
这里把Content改成ContentTemplate,用DataTemplate包裹你的Control,这样每个Button都会基于模板生成自己的Content元素,就不会出现实例被复用的问题了。
方法2:在后台代码中为每个Button单独创建Content实例
如果你更倾向于在后台处理,可以去掉Style里的Content设置,在循环创建Button时手动生成Control:
// 去掉Style里的ContentSetter,然后在创建bntSupp时: Button bntSupp = new Button() { HorizontalAlignment = HorizontalAlignment.Left, VerticalAlignment = VerticalAlignment.Center, Width = 30, Height = 30, Margin = marginSupp, Style = bntSuppStyle, Visibility = Visibility.Visible, Tag = item, }; // 手动创建Content实例 var deleteControl = new Control(); deleteControl.Template = this.FindResource("PathDelete") as ControlTemplate; // 设置Path的样式 var pathStyle = new Style(typeof(Path)); pathStyle.BasedOn = this.FindResource(typeof(Path)) as Style; deleteControl.Resources.Add(typeof(Path), pathStyle); bntSupp.Content = deleteControl;
这样每个Button都有自己的Control实例,自然都能显示Canvas了。
额外小贴士
- 尽量避免在Style中直接赋值UI元素实例,优先使用DataTemplate或模板化的方式,这是WPF开发的最佳实践,能避免很多类似的视觉树复用问题。
x:Shared="False"的作用是让每次获取Style时都返回新的Style实例,但它不会自动复制Style里嵌套的UI元素实例,这点一定要搞清楚哦。
修改后测试一下,你会发现所有删除按钮的Canvas都能正常显示啦!
内容的提问来源于stack exchange,提问作者Marc

