You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 12:27:03