XAML 如何在绑定到其他控件时将文本从TextBox获取到ViewModel?(MVVM)

tvokkenx  于 2022-12-07  发布在  其他
关注(0)|答案(2)|浏览(162)

我有一个简单的应用程序,应该添加SelectedItem从组合框到列表框。我有Model:Player

public class Player
{
    public int ID { get; set; }
    public string Name { get; set; }

    private bool _isSelected = false;
    public bool IsSelected
    {
        get { return _isSelected; }
        set { _isSelected = value; }
    }
}

和我的ViewModel中的ObservableCollection属性(玩家)

public class ViewModel
{
    public ObservableCollection<Player> Players { get; set; }
    public ObservableCollection<Player> PlayersInTournament { get; set; } = new ObservableCollection<Player>();
    public ICommand AddPlayerCommand { get; set; }
    public ViewModel()
    {            
        DataAccess access = new DataAccess();
        Players = new ObservableCollection<Player>(access.GetPlayers());//GetPlayers from DataBase
        AddPlayerCommand = new RelayCommand(AddPlayer, CanAddPlayer);
    }

    private void AddPlayer()
    {
        //Something like PlayersInTournamen.Add(SelectedPlayer);
    }

    private bool CanAddPlayer()
    {
        bool canAdd = false;
        foreach(Player player in Players)
        {
            if (player.IsSelected == true)
                canAdd = true;
        }

        return canAdd;
    }
}

属性ComboBox的(ItemSource)绑定到Players集合。当应用程序是Loaded时,ComboBox将填充数据库中的对象,当我选择其中一个对象时,它将显示在ReadOnlyTextBox中。我通过将Text属性绑定到ComboBoxItemSelected.Name属性来实现这一点。在应用程序中有一个添加按钮,将选定的球员添加到锦标赛(ListBox)(该应用程序是关于锦标赛的)。ListBoxItemSource是PlayersInTournament集合(见ViewModel中)。
XAML(在InitializeComponents()之后将窗口的DataContext设置为ViewModel示例):

<Window x:Class="ComboBoxDemoSQL.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:b="http://schemas.microsoft.com/xaml/behaviors"        
    xmlns:local="clr-namespace:ComboBoxDemoSQL"
    mc:Ignorable="d"
    Title="MainWindow" Height="450" Width="800">

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition/>   
        <RowDefinition/>   
    </Grid.RowDefinitions>
    <StackPanel>
        <StackPanel  HorizontalAlignment="Center"
                     Orientation="Horizontal" Margin="0 40 0 10">

            <TextBox x:Name="HoldPlayerTextBox"
                     Width="100"
                     Text="{Binding ElementName=PlayersComboBox, Path=SelectedItem.Name}"
                     IsReadOnly="True">
            </TextBox>

            <ComboBox Name="PlayersComboBox" 
                      VerticalAlignment="Top" 
                      Margin="10 0 0 0"
                      HorizontalAlignment="Center" Width="100"
                      ItemsSource="{Binding Players}"
                      DisplayMemberPath="Name"
                      Text="Select player"
                      IsEditable="True"
                      IsReadOnly="True"/>
        </StackPanel>

        <Button Content="Add" Margin="120 0 120 0"
                Command="{Binding AddPlayerCommand}"/>

        <ListBox Margin="10" ItemsSource="{Binding PlayersInTournament}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Name}"/>
                        <TextBlock Text="{Binding ID}"/>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</Grid>

照片更好理解:

所以基本上有两个问题:
1.我不知道如何将在ComboBox中选定的玩家添加到PlayersInTournament集合,因为我无法从TexBox获取该玩家的名称(因为其'Text属性绑定到另一个属性)
1.我不知道如何禁用Add Button(CanAddPlayer方法),当没有选择播放器时,我尝试添加IsSelected(见播放器模型)属性,但为了使它工作,我必须绑定到视图中的任何属性,这将改变它,但我不知道哪个属性可以用于这件事。
ICommand实现:

public class RelayCommand : ICommand
{
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }
    private Action methodToExecute;
    private Func<bool> canExecuteEvaluator;
    public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
    {
        this.methodToExecute = methodToExecute;
        this.canExecuteEvaluator = canExecuteEvaluator;
    }
    public RelayCommand(Action methodToExecute)
        : this(methodToExecute, null)
    {
    }
    public bool CanExecute(object parameter)
    {
        if (this.canExecuteEvaluator == null)
        {
            return true;
        }
        else
        {
            bool result = this.canExecuteEvaluator.Invoke();
            return result;
        }
    }
    public void Execute(object parameter)
    {
        this.methodToExecute.Invoke();
    }
}
mfuanj7w

mfuanj7w1#

请允许我提出以下建议。
您可以覆写Player类别的ToString()方法,以简化ComboBox中的显示,例如:

public class Player
{
    public string Name { get; set; }
    public override string ToString()
    {
        return Name;
    }
}

默认情况下,ComboBox绑定将调用它所绑定到的任何属性的ToString()方法。
如果将ComboBox.SelectedItem绑定到ViewModel中的新Player属性,则可以从ViewModel中的代码清除ComboBox中的选定播放器文本。
如果向Button绑定中添加CommandParameter,则可以将选定的播放器示例传递给命令,但如果ViewModel中有绑定属性,则并不严格需要这样做。
因此,您的XAML就变成了这样:

<ComboBox x:Name="ComboBox" 
          HorizontalAlignment="Left" 
          Margin="0,0,0,0" 
          VerticalAlignment="Top" 
          Width="100"
          Text="Select player"
          SelectedItem="{Binding SelectedPlayer}"
          ItemsSource="{Binding Players}"/>
<Button x:Name="ButtonAddPlayer" 
        Content="Add" 
        Command="{Binding AddPlayerCommand}" 
        CommandParameter="{Binding SelectedPlayer}" 
        HorizontalAlignment="Left" 
        Margin="62,176,0,0" 
        VerticalAlignment="Top" 
        Width="75"/>

您的ViewModel包含:

public ObservableCollection<Player> PlayersInTournament { get; set; }
public ObservableCollection<Player> Players { get; set; }
private Player _selectedPlayer;

public Player SelectedPlayer
{
    get => _selectedPlayer;
    set => SetField(ref _selectedPlayer, value);
}

public ICommand AddPlayerCommand { get; set; }

private bool CanAddPlayer(object obj)
{
    return SelectedPlayer != null;
}
private void AddPlayer(object param)
{

    if (param is Player player)
    {
        PlayersInTournament.Add(player);
        Players.Remove(player);
        SelectedPlayer = null;
    };
}

请注意,在上面的代码中,当一个玩家被添加到锦标赛列表中时,它将从可用玩家列表中删除,以防止重新选择同一个玩家。将SelectedPlayer属性设置为null不仅会清除ComboBox.SelectedItem显示,还会禁用Add按钮。
此外,如果您可能有多个属性,则可以实现一个helper函数来处理INotifyPropertyChanged事件。

protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
    if (EqualityComparer<T>.Default.Equals(field, value)) return false;
    field = value;
    OnPropertyChanged(propertyName);
    return true;
}
am46iovg

am46iovg2#

您可以在xaml中使用CommandParameter

<Button Content="Add" Margin="120 0 120 0"
         Command="{Binding AddPlayerCommand}"
         CommandParameter="{Binding Path=SelectedItem, Source=PlayersComboBox}"/>

在您的ViewModel中:

private ICommand _addPlayerCommand;
    public ICommand AddPlayerCommand
    {
        get
        {
            if (_addPlayerCommand== null)
            {
                _addPlayerCommand= new RelayCommand(param => OnAddPlayerClicked(param));
            }
            return _addPlayerCommand;
        }
    }
    private void AddPlayer(object param)
    {
        Player selectedPlayer = (player)param;
        PlayersInTournamen.Add(SelectedPlayer);
    }

中继命令:

public class RelayCommand : ICommand
{
    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    /// <summary>
    /// Creates a new command that can always execute.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    public RelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Creates a new command.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        _execute = execute ?? throw new ArgumentNullException("execute");
        _canExecute = canExecute;
    }

    [DebuggerStepThrough]
    public bool CanExecute(object parameters)
    {
        return _canExecute == null ? true : _canExecute(parameters);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameters)
    {
        _execute(parameters);
    }
}

我希望这能帮上忙。

相关问题