WPF中代码创建的领土按钮未在主窗口显示问题求助
问题描述
背景
我正在为一个Risk游戏学习项目搭建MVVM框架。为了便捷性/优雅性以及学习目的,我尝试通过代码创建部分UI,具体是代表游戏版图领土的自定义按钮,这些按钮最终将接收用户的大部分输入。
关键选择
我使用全局枚举TerrID_Enum.cs为每个领土分配0-41的整数ID——游戏版图是静态的,这样设计能让Model、VM、View各层级通过数组和for循环,直接用索引对应领土。如果您认为这个方案有需要注意的事项,欢迎提出建议。
核心问题
我通过自定义方法Board.MakeStringPath()构建绑定路径来初始化按钮,目前遇到两个问题:
- 自定义按钮内容Grid中的
Path.Data属性最初能获取正确的Geometry,但在InitializeComponent()之后变为null - 绑定的Path和自定义按钮的
ActualHeight、ActualWidth属性都为0,不确定两者是否相关
关键代码片段
MainWindow.xaml
<Window> <Window.Resources> <ControlTemplate x:Key="TerrButtonCT" TargetType="{x:Type Button}"> <Border Background="{TemplateBinding Background}" BorderBrush="{x:Null}" BorderThickness="0"> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" /> </Border> </ControlTemplate> </Window.Resources> </Window>
MainWindow.cs
public partial class MainWindow : Window { public MainWindow() { Board VM = new(NewGame); this.DataContext = VM; Canvas mainCanvas = new Canvas(); this.AddChild(mainCanvas); Button[] TerrButtons = new Button[42]; // 程序化创建并配置领土按钮 for (int index = 0; index < 42; index++) { // 创建包含绑定的内容Grid和图形 Grid defaultGrid = new Grid(); Binding terrButtonPathData = new(VM.MakePathString(index, nameof(VM.Shapes))); terrButtonPathData.Source = VM; Binding terrButtonFill = new(VM.MakePathString(index, nameof(VM.Color))); terrButtonFill.Source = VM; Path terrPath = new Path() { Stretch = Stretch.Fill }; BindingOperations.SetBinding(terrPath, Path.DataProperty, terrButtonPathData); BindingOperations.SetBinding(terrPath, Path.FillProperty, terrButtonFill); defaultGrid.Children.Add(terrPath); // 从Window资源中获取ControlTemplate ControlTemplate buttonCT = (ControlTemplate)this.Resources["TerrButtonCT"]; // 鼠标悬停行为的多触发器 MultiTrigger hoverTrigger = new MultiTrigger(); hoverTrigger.Conditions.Add(new() { Property = IsMouseOverProperty, Value = true} ); hoverTrigger.Conditions.Add(new() { Property = Button.IsPressedProperty, Value = false }); ; // 触发器设置:图形和填充需要和"Content"一样绑定 hoverTrigger.Setters.Add(new Setter() { Property = BackgroundProperty, Value = null }); hoverTrigger.Setters.Add(new Setter() { Property = BorderBrushProperty, Value = null }); Path hoverPath = new() { Stretch = Stretch.Fill }; Binding hoverColor = new Binding(VM.MakePathString(index, nameof(VM.HoverColor))); hoverColor.Source = VM; BindingOperations.SetBinding(hoverPath, Path.DataProperty, terrButtonPathData); BindingOperations.SetBinding(hoverPath, Path.FillProperty, hoverColor); Grid hoverGrid = new Grid(); hoverGrid.Children.Add(hoverPath); hoverTrigger.Setters.Add(new Setter() { Property = ContentProperty, Value = hoverGrid }); // 将所有配置封装到按钮的个性化Style中 Style buttonStyle = new Style(); buttonStyle.Triggers.Add(hoverTrigger); buttonStyle.Setters.Add(new Setter() { Property = ContentProperty, Value = defaultGrid }); buttonStyle.Setters.Add(new Setter() { Property = TemplateProperty, Value = buttonCT }); // 为按钮设置自定义属性 TerrButtons[index] = new Button { Name = ((TerrID)index).ToString(), Style = buttonStyle }; // 将每个按钮添加到主窗口的Canvas中 mainCanvas.Children.Add((TerrButtons[index])); } InitializeComponent(); } }
ViewModel代码(Board.cs)
internal class Board : ViewModelBase { private ObservableCollection<SolidColorBrush> _color; private ObservableCollection<SolidColorBrush> _hoverColor; public Board(Game newGame) { CurrentGame = newGame; Shapes = new List<Geometry>(); Color = new ObservableCollection<SolidColorBrush>(); HoverColor = new ObservableCollection<SolidColorBrush>(); for (int index = 0; index < BaseArmies.Count; index++) { if (Application.Current.FindResource(((TerrID)index).ToString() + "Geometry") != null) Shapes.Add(Application.Current.FindResource(((TerrID)index).ToString() + "Geometry") as Geometry); Color.Add(Brushes.Black); HoverColor.Add(Brushes.AntiqueWhite); } } public List<Geometry> Shapes { get; init; } public ObservableCollection<SolidColorBrush> Color { get { return _color; } set { _color = value; OnPropertyChanged(nameof(Color)); } } public ObservableCollection<SolidColorBrush> HoverColor { get { return _hoverColor; } set { _hoverColor = value; OnPropertyChanged(nameof(HoverColor)); } } ... internal string MakePathString(int index, string propertyName) { if (index >= 0 && index < 42 && propertyName != null) { string indexStr = index.ToString(); switch (propertyName) { case nameof(Color): return "Color[" + indexStr + "]"; case nameof(HoverColor): return "HoverColor[" + indexStr + "]"; case nameof(PressedColor): return "PressedColor[" + indexStr + "]"; case nameof(DisabledColor): return "DisabledColor[" + indexStr + "]"; case nameof(Shapes): return "Shapes[" + indexStr + "]"; } return "Property not found!"; } else return "MakePathString() requires 0 < int index < 42 and string propertyName."; } }
补充说明
- 全局
TerrID枚举用于标识领土,App.xaml中定义了所有领土的Geometry - 项目使用NuGet包:MS MVVM Community Toolkit 8.2.0
- 自定义按钮的Path形状设计参考了WPF自定义按钮实现方案
过往尝试
我曾尝试在XAML中设置自定义按钮样式,但需要创建自定义路径字符串以支持for循环初始化——这需要嵌套绑定(Binding.Path不是依赖属性,无法实现)或使用DataObjectProvider的复杂操作,我尚未理解;也尝试过转换器但认为不可行,希望得到解决方案。
内容的提问来源于stack exchange,提问作者jmcknight00
相关产品推荐
相关产品推荐

