以编程方式构建的按钮不会出现在主窗口中

问题描述 投票:0回答:0

背景

我有一个风险游戏学习项目的 MVVM 框架。我已决定尝试以编程方式创建 UI 的一部分,以“方便”/优雅并作为学习机会。有问题的部分是自定义按钮,代表游戏板的领土,最终应该接受大部分用户的输入。

关键的选择

复杂的根源:我尝试利用全局枚举 (TerrID_Enum.cs),这似乎是合乎逻辑的举动,因为游戏板是静态的。每个 Territory 都会收到一个 0 - 41 的 int ID 号。这样我就可以在任何框架(模型、VM、视图)中使用数组和 for 循环,其中索引与适当的 Territory 直接相关。 (正如我所说,一个 global 枚举)。我在这里和其他地方的网上看到了不同的看法,所以如果你认为关于这个选择有一些你认为我需要知道的事情,请随时插话。

从 For-Loop 索引/区域枚举关系中构建绑定路径的自定义方法

正如您将看到的,按钮的编程初始化程序依赖于自定义方法来编写绑定路径 (Board.MakeStringPath())。它似乎在某种程度上起作用——自定义按钮的内容网格内的 Path.Data 属性首先获得正确的几何形状——但在 IntializeComponent() 之后它似乎变为空?除此之外,由于某种原因,绑定路径和自定义按钮的 ActualHeight 和 ActualWidth 属性都为 0,我不知道这是如何相关的,但它似乎是。

最相关的代码片段:

这是我的 MainWindow.xaml 和 MainWindow.cs 包含的内容。为了清楚起见,我省略了一些触发器和杂项:

<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>
 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];

            // Programatic creation and detail of the Territory Buttons
            for (int index = 0; index < 42; index++)
            {
                // "Content" Grid and Shape, with associated Bindings, are created
                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);

                // Control Template brought in from Window Resources
                ControlTemplate buttonCT = (ControlTemplate)this.Resources["TerrButtonCT"];

                // Triggers 
                
                // Multi-Trigger for Mouse "Hover" behavior
                MultiTrigger hoverTrigger = new MultiTrigger();

                hoverTrigger.Conditions.Add(new() { Property = IsMouseOverProperty, Value = true} );
                hoverTrigger.Conditions.Add(new() { Property = Button.IsPressedProperty, Value = false }); ;

                // Setters for Trigger details. Shape and Fill need to be bound just like "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 });


                // Wrap everything into an individualized Style for the Button
                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 });

                // Feed custom details to the Button
                TerrButtons[index] = new Button
                {
                    Name = ((TerrID)index).ToString(),
                    Style = buttonStyle
                };
                // Add each Button to the MainWindow Child Canvas
                mainCanvas.Children.Add((TerrButtons[index]));
            }

            InitializeComponent();
        }
    }
}

到目前为止,这是 ViewModel:

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,其中包含每个领土的几何图形。我认为没有必要在这里包含,但你可以在 Github 上找到它。

自定义带有路径的按钮的基本设计是从这里改编/提升的。

如果需要查看更多:Full Project @ Github

Nuget 包包括:MS MVVM 社区工具包 8.2.0

谢谢!

我知道要经历很多事情,所以提前致谢!

过去的尝试和挫折

我尝试在 XAML 中设置自定义按钮样式,但我想创建一个自定义路径字符串以启用 for 循环初始化程序,这将需要嵌套绑定(Binding.Path 上的绑定,我发现它不需要工作,因为它不是 DependencyProperty),或者与 DataObjectProvider 进行的一些精心设计的工作,我还没有全神贯注。 (我也放弃了一系列不可行的转换器,但这可能是一个解决方案,我只是没有看到它)。

c# wpf button binding code-behind
© www.soinside.com 2019 - 2024. All rights reserved.