Kokombads
Kokombads

Reputation: 460

How to get the value of a template combo box

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:

enter image description here

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

Answers (2)

Tomtom
Tomtom

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

  • V = MainWindow.xaml
  • VM = MainWindow.xaml.cs

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

Eirik
Eirik

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

Related Questions