如何将新按钮绑定到WPF中的按钮列表

本文关键字:按钮 WPF 列表 绑定 新按钮 | 更新日期: 2023-09-27 18:14:28

我正在尝试动态地将按钮添加到WPF中的列表中。在做了一些研究之后,我找到了这段代码:

   <Button Content="Add New Button"
        VerticalAlignment="Center" HorizontalAlignment="Center"
        Command="{Binding AddNewButton}"/>
    <ItemsControl ItemsSource="{Binding ButtonsList}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <WrapPanel IsItemsHost="True"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Button Content="{Binding Title}"
                    Margin="2"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>

然后我想在代码上写一些东西来使这个工作。因为我找到的所有答案都只包括解决方案的XAML,而不包括背后的代码。我有网络背景,所以我对WPF完全陌生。但我的想法是这样的:

public void AddNewButton(){
 ButtonsList.add({title='New button Title});
}

我知道语法是错误的,我只是想试着表达我正在思考的代码…一些简单的. .这怎么可能呢?

如何将新按钮绑定到WPF中的按钮列表

一个简单的方法是:

XAML:

<Window x:Class="SO40212766.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SO40212766"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
  <Window.CommandBindings>
    <CommandBinding x:Name="AddNewButtonBinding" Command="{x:Static local:MainWindow.AddNewButton}" CanExecute="AddNewButtonBinding_CanExecute" Executed="AddNewButtonBinding_Executed" />
  </Window.CommandBindings>
    <DockPanel>
    <Button Content="Add New Button" DockPanel.Dock="Top"
        VerticalAlignment="Center" HorizontalAlignment="Center"
        Command="{x:Static local:MainWindow.AddNewButton}"/>
    <ItemsControl ItemsSource="{Binding ButtonsList}" DataContext="{Binding RelativeSource={RelativeSource AncestorType=Window}}">
      <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
          <WrapPanel IsItemsHost="True"/>
        </ItemsPanelTemplate>
      </ItemsControl.ItemsPanel>
    </ItemsControl>
  </DockPanel>
</Window>

代码:

using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace SO40212766
{
  /// <summary>
  /// Interaction logic for MainWindow.xaml
  /// </summary>
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
      ButtonsList = new ObservableCollection<Button>();
    }
    public static RoutedUICommand AddNewButton = new RoutedUICommand("Add Button", "AddNewButton", typeof(MainWindow));
    public ObservableCollection<Button> ButtonsList { get; private set; }
    private void AddNewButtonBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
    {
      e.CanExecute = true;
    }
    int index = 1;
    private void AddNewButtonBinding_Executed(object sender, ExecutedRoutedEventArgs e)
    {
      // You will of course set the command og click event on the button here...
      ButtonsList.Add(new Button() { Content = $"Button {index++}" });
    }
  }
}

注意从ItemsControl我已经删除了ItemTemplate。然后,您可以按照您想要的方式对按钮进行样式设计。

并且注意ItemsControl的DataContext

在处理WPF时应该遵循MVVM模式。

首先需要一个基类,实现WPF用于绑定通知的INotifyPropertyChanged:

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace WpfApplication1
{
    public class ObservableObject : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged( [CallerMemberName] string propertyName = "" )
        {
            var handler = PropertyChanged;
            handler?.Invoke( this, new PropertyChangedEventArgs( propertyName ) );
        }
        protected bool SetProperty<T>( ref T backingField, T newValue, [CallerMemberName] string propertyName = "" )
        {
            return SetProperty<T>( ref backingField, newValue, EqualityComparer<T>.Default, propertyName );
        }
        protected bool SetProperty<T>( ref T backingField, T newValue, IEqualityComparer<T> comparer, [CallerMemberName] string propertyName = "" )
        {
            if ( comparer.Equals( backingField, newValue ) ) return false;
            backingField = newValue;
            RaisePropertyChanged( propertyName );
            return true;
        }
        protected bool SetProperty<T>( ref T backingField, T newValue, IComparer<T> comparer, [CallerMemberName] string propertyName = "" )
        {
            if ( comparer.Compare( backingField, newValue ) == 0 ) return false;
            backingField = newValue;
            RaisePropertyChanged( propertyName );
            return true;
        }
    }
}

对于Buttons,你需要一个ICommand所以我们正在构建一个基类来实现它

using System;
using System.Windows.Input;
namespace WpfApplication1
{
    public abstract class CommandBase : ICommand
    {
        public virtual event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
        public virtual bool CanExecute( object parameter )
        {
            return true;
        }
        public void Execute( object parameter )
        {
            if ( CanExecute( parameter ) )
                DoExecute( parameter );
        }
        protected abstract void DoExecute( object parameter );
    }
}

下一类是继承自CommandBaseRelayCommand

using System;
namespace WpfApplication1
{
    public class RelayCommand : CommandBase
    {
        private readonly Func<object, bool> _canexecute;
        private readonly Action<object> _execute;
        public RelayCommand( Action<object> execute ) : this( execute, o => true )
        {
        }
        public RelayCommand( Action<object> execute, Func<object, bool> canexecute )
        {
            if ( execute == null ) throw new ArgumentNullException( nameof( execute ) );
            if ( canexecute == null ) throw new ArgumentNullException( nameof( canexecute ) );
            _execute = execute;
            _canexecute = canexecute;
        }
        public override bool CanExecute( object parameter )
        {
            return base.CanExecute( parameter ) && _canexecute( parameter );
        }
        protected override void DoExecute( object parameter )
        {
            _execute( parameter );
        }
    }
}

现在我们有一个小的基础,我们可以继续工作。按钮应该执行一些东西,并有一个文本,将显示。所以我们定义了一个ViewModel类来表示这个

using System.Windows.Input;
namespace WpfApplication1.ViewModel
{
    public class CommandViewModel : ObservableObject
    {
        private ICommand command;
        private string displayText;
        public ICommand Command
        {
            get { return command; }
            set { SetProperty( ref command, value ); }
        }
        public string DisplayText
        {
            get { return displayText; }
            set { SetProperty( ref displayText, value ); }
        }
    }
}

接下来我们需要一个ViewModel来保存列表和Add命令

using System;
using System.Collections.ObjectModel;
namespace WpfApplication1.ViewModel
{
    public class MainWindowViewModel : ObservableObject
    {
        public MainWindowViewModel()
        {
            AddNewCommand = new CommandViewModel
            {
                DisplayText = "Add",
                Command = new RelayCommand( DoAddNewCommand )
            };
            Commands = new ObservableCollection<CommandViewModel>();
        }
        public CommandViewModel AddNewCommand { get; }
        public ObservableCollection<CommandViewModel> Commands { get; }
        private void DoAddNewCommand( object obj )
        {
            Commands.Add( new CommandViewModel
            {
                DisplayText = "Foo",
                Command = new RelayCommand( DoFoo ),
            } );
        }
        private void DoFoo( object obj )
        {
            throw new NotImplementedException();
        }
    }
}

现在是时候在XAML

中绑定所有这些了
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication1"
        xmlns:vm="clr-namespace:WpfApplication1.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    <Grid>
        <StackPanel>
            <Button
                Content="{Binding Path=AddNewCommand.DisplayText}"
                Command="{Binding Path=AddNewCommand.Command}"/>
            <ItemsControl
                ItemsSource="{Binding Path=Commands}">
                <ItemsControl.ItemsPanel>
                    <ItemsPanelTemplate>
                        <WrapPanel IsItemsHost="True"/>
                    </ItemsPanelTemplate>
                </ItemsControl.ItemsPanel>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Button
                            Content="{Binding Path=DisplayText}"
                            Command="{Binding Path=Command}"
                            Margin="2"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>
        </StackPanel>
    </Grid>
</Window>
如您所见,根本没有CodeBehind。一切都在ViewModels中完成,View只是呈现