I have a simple app, that should add SelectedItem
from ComboBox to ListBox.
I have 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; }
}
}
And ObservableCollection property in my ViewModel (Players)
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;
}
}
Property(ItemSource
) of my ComboBox is bound to the Players
collection. When the application is Loaded
my ComboBox
is filled with objects from DataBase and when I select one of them it is displayed in my ReadOnly
TextBox
. I achieved this by binding the Text
property to the ItemSelected.Name
property of ComboBox
. There is an Add button in the app that add selected player to the tournament(ListBox
)(the app is about tournament). ListBox
's ItemSource
is PlayersInTournament collection(see in ViewModel).
XAML(DataContext of Window is set to ViewModel instance after InitializeComponents()):
<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>
Photo to understand better:
So basically there are 2 problems:
- I don't know how to add to the PlayersInTournament collection a
player that is selected in
ComboBox
because I can't get the name of that Player fromTexBox
(because its' Text property is bound to another Property) - I don't know how to disable
Add Button
(CanAddPlayer method) when there is no Player selected, I tried by addingIsSelected
(see Player model) property, but for it to work I have to bind to any property in View that would change it, but I don't know which property can be used for this thing.
ICommand implementation:
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();
}
}
CodePudding user response:
You can use CommandParameter
in xaml:
<Button Content="Add" Margin="120 0 120 0"
Command="{Binding AddPlayerCommand}"
CommandParameter="{Binding Path=SelectedItem, Source=PlayersComboBox}"/>
in your 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);
}
I hope this helps.
CodePudding user response:
May I suggest the following.
You can override the ToString()
method of your Player class to ease display in your ComboBox
e.g.:
public class Player
{
public string Name { get; set; }
public override string ToString()
{
return Name;
}
}
By default ComboBox
binding will call the ToString()
method of whatever property it is bound to.
If you bind ComboBox.SelectedItem
to a new Player
property in the ViewModel, you can clear the selected player text in the ComboBox
from code in the ViewModel.
If you add a CommandParameter
to your Button binding, you can pass the selected player instance to the command, but this isn't strictly needed once you have a bound property in your ViewModel.
Thus your XAML becomes something like this:
<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"/>
And your ViewModel contains:
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;
};
}
Note that in the above code, as a player is added to the tournament list it is removed from the available players list preventing reselection of the same player.
Setting the SelectedPlayer
property to null
not only clears the ComboBox.SelectedItem
display but also disables the Add
button.
Also if you are likely to have several properties that you implement a helper function to handle your INotifyPropertyChanged
events.
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;
}