Reputation: 460
I got here a template combo box
<ComboBox x:Name="TryCombo" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="75"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Id}" Margin="4,0" Visibility="Collapsed" Grid.Column="0"/>
<TextBlock Text="{Binding Path=Name}" Margin="4,0" Grid.Column="1"/>
<Button x:Name="AddButton" Content="Add" Grid.Column="2"/>
</Grid>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
then the ItemsSource:
public void BindComboboxes()
{
itemMgr.Parameters = RetrieveFilter("");
itemMgr.EntityList = itemMgr.RetrieveMany(itemMgr.Parameters);
TryCombo.ItemsSource = itemMgr.EntityList; //collection;
}
the combo box will load this:
My problem is getting the selected item that I had clicked with the AddButton, I want to get the value of text block that was bound to Path=Id, but how?
Should I put x:Name for each of the TextBlocks?
Upvotes: 0
Views: 287
Reputation: 9394
Bind your Button to a ICommand-Property in your ViewModel and pass the selected Item as CommandParameter
I've made a small Demo-Application for you:
The MainWindow.xaml looks like:
<Window x:Class="ComboBoxDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
WindowStartupLocation="CenterScreen"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Button Content="Add Item to ComboBox" Margin="5" Command="{Binding AddItemCommand}"/>
<ComboBox Grid.Row="1" x:Name="TryCombo" ItemsSource="{Binding Parameters, UpdateSourceTrigger=PropertyChanged}" Margin="5">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="50"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Path=Id}" Margin="4,2" VerticalAlignment="Center" Grid.Column="0"/>
<TextBlock Text="{Binding Path=Name}" Margin="4,2" Grid.Column="1" VerticalAlignment="Center"/>
<Button x:Name="AddButton" Content="Add" Grid.Column="2" VerticalAlignment="Center" Margin="4,2"
Command="{Binding DataContext.ComboBoxItemAddCommand, RelativeSource={RelativeSource FindAncestor, AncestorType=Window}}"
CommandParameter="{Binding}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
</Window>
Important is the part in the Window-Declaration of: DataContext="{Binding RelativeSource={RelativeSource Self}}"
With this you tell the Window that it's DataContext is in the CodeBehind-File. You also can use another File as your DataContext. Than you have to write:
<Window.DataContext>
<loc:MyClassName/>
</Window.DataContext>
This only works if you add xmlns:loc="clr-namespace:YOURPROJECTNAMESPACE"
to the Window-Declaration. In the case of my Demo YOURPROJECTNAMESPACE would be ComboBoxDemo.
I've also created a class for the Parameter which is very simple and looks like:
public class Parameter
{
public string Id { get; set; }
public string Name { get; set; }
}
The CodeBehind of the Window (remember: This is the DataContext of the MainWindow.xaml) looks like:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Input;
namespace ComboBoxDemo
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
private ICommand addItemCommand;
private ICommand comboBoxItemAddCommand;
private ObservableCollection<Parameter> parameters;
public MainWindow()
{
InitializeComponent();
Parameters = new ObservableCollection<Parameter>();
AddItemCommand = new RelayCommand(AddItem);
ComboBoxItemAddCommand = new RelayCommand(ComboBoxItemAdd);
}
private void ComboBoxItemAdd(object parameter)
{
Parameter para = parameter as Parameter;
if (para != null)
{
// Now you can use your Parameter
}
}
public ObservableCollection<Parameter> Parameters
{
get { return parameters; }
set
{
parameters = value;
OnPropertyChanged();
}
}
public ICommand AddItemCommand
{
get { return addItemCommand; }
set
{
addItemCommand = value;
OnPropertyChanged();
}
}
public ICommand ComboBoxItemAddCommand
{
get { return comboBoxItemAddCommand; }
set
{
comboBoxItemAddCommand = value;
OnPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void AddItem(object parameter)
{
Parameters.Add(new Parameter
{
Id = Guid.NewGuid().ToString(),
Name = "Any Name"
});
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And I also created a very useful Helper-Class which you will likely always need if you are using command-binding. This is the class RelayCommand
. This class has a constructer which wants an object of type Action<object>
. This action later contains what will be executed when you click the button. The seconde optional parameter I will not explain now. The RelayCommand looks like:
using System;
using System.Windows.Input;
namespace ComboBoxDemo
{
public class RelayCommand : ICommand
{
private readonly Action<object> execute;
private readonly Predicate<object> canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute = null )
{
if (execute == null)
throw new ArgumentNullException("execute");
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (canExecute == null)
return true;
return canExecute(parameter);
}
public void Execute(object parameter)
{
execute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
}
So. Now I will explain you the process: Therefor I use the following abbreviations
The ComboBox
in the V has your given Itemtemplate. In the definition of the ComboBox I've added ItemsSource="{Binding Parameters, UpdateSourceTrigger=PropertyChanged}"
. This tells the ComboBox that it will get it's children from this collection which is located in the VM.
The collection in the VM is an ObservableCollection<Parameter>
. The advantage of this type of Collection is, that it implements the ICollectionChanged-Interface and so your V will get updated if there are items added or removed to this collection.
The Button in the V just adds a dummy-Parameter to the ObservableCollection<Parameter>
.
With Command="{Binding AddItemCommand}"
I tell the Button that it's command-property is bound to the AddItemCommand in the DataContext. In the constructor of the DataContext (MainWindow.xaml.cs) I'm creating this Command and provide the AddItem-Method which will be called if the command is executed.
The Binding of the Button in the DataTemplate must provide a RelativeSource
because inside the Template the DataContext is another one. With the RelativeSource
I can tell the Button that its Command-Property is bound to a Command which is located in the DataContext of the Window.
I hope this helps you.
If you want to go deeper into the MVVM-Pattern take a look at this Link
Upvotes: 1
Reputation: 4215
One quick way to do it is to simply bind the Tag
property of the button to the same property as the TextBlock
.
<Button Tag="{Binding Path=Id}" />
Then in the event handler for the button click event you can cast the sender to Button
and get the ID from the Tag
property.
int id = Convert.ToInt32((sender as Button).Tag);
Upvotes: 1