Emi
Emi

Reputation: 306

Set up a binding between two properties in code behind

In my view, I have a ListBox with some templated items that contain buttons.

<ListBox x:Name="MyListBox" ItemTemplate="{DynamicResource DataTemplate1}"
         ItemsSource="{Binding MyItems}">
</ListBox>

And the template for generated items:

<DataTemplate x:Key="DataTemplate1">
        <StackPanel Orientation="Horizontal">
            <Button Width="50" Click="Button_Click" />
        </StackPanel>
</DataTemplate>

When user clicks a button on one of those ListBox items, I want to send the index of that ListBox item to my ViewModel.

So figured to use Binding as it seems to be the way in MVVM. But I'm struggling to set up a binding in code between two properties.

My View code is as follows:

public partial class ItemView : UserControl
{
    ViewModel.ItemViewModel VM;
    public ItemView()
    {
        InitializeComponent();
        VM = new ViewModel.ItemViewModel();
        this.DataContext = VM;
    }

    private int clickedItemIndex;
    public int ClickedItemIndex { get => clickedItemIndex; set => clickedItemIndex = value; }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var ClickedItem = (sender as FrameworkElement).DataContext;
        ClickedItemIndex = MyListBox.Items.IndexOf(ClickedItem);
    }
}

I get the index and set it to ClickedItemIndex property,
I also have property in my ViewModel:

public int SomeInt { get; set; }

Now how do I set up a binding between these two properties?

I'm quite new to MVVM and still learning it. So, maybe this not the correct approach. But I need to have a way for each individual listbox item to be able to call upon an effect in more global viewmodel. For example, if I wanted to have a "Remove" button on each of the listbox items, I would somehow need to send the index to the viewmodel and call the removeItem method with index as the parameter. Or is there a better way to do similar things?

Upvotes: 0

Views: 687

Answers (2)

BionicCode
BionicCode

Reputation: 28968

You don't need to use a Button in order to select the item. When you click/tap on the item it will get automatically selected.
Then simply bind ListBox.SelectedIndex to your view model property SomeInt and it will update on every selection.

Data binding overview in WPF

You can also get the item itself by binding ListBox.SelectedItem to your view model.

You can handle new values by invoking a handler from the property's set method:

ViewModel.cs

class ViewModel : INotifyPropertyChanged
{
  private int currentItemIndex;
  public int CurrentItemIndex
  {
    get => this.currentItemIndex;
    set
    {
      this.currentItemIndex = value;
      OnPropertyChanged();
     
      // Handle property changes
      OnCurrentItemIndexChanged();
    }
  }

  private MyItem currentItem;
  public MyItem CurrentItem
  {
    get => this.currentItem;
    set
    {
      this.currentItem = value;
      OnPropertyChanged();
    }
  }

  protected virtual void OnCurrentItemIndexChanged()
  {
    // Handle the new this.CurrentItemIndex value
  }

  // Implementation of INotifyPropertyChanged
  public event PropertyChangedEventHandler PropertyChanged;
  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

ItemView .xaml

<UserControl>
  <UserControl.DataContext>
    <ViewModel />
  </UserControl.DataContext>


  <ListBox ItemsSource="{Binding MyItems}" 
           SelectedIndex="{Binding CurrentItemIndex}"
           SelectedItem="{Binding CurrentItem}" />
</UserControl>

Upvotes: 0

neelesh bodgal
neelesh bodgal

Reputation: 662

I have a sample app created just for this scenario. I know it seems a lot of code at first glance. Copy this code in your project, that will help debug and get a hang of it(MVVM, databinding, commands and so on).

usercontrol.xaml

<UserControl x:Class="WpfApplication1.UserControl1"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:WpfApplication1"
         mc:Ignorable="d" 
         d:DesignHeight="300" d:DesignWidth="300">
<Grid>
    <Grid.Resources>
        <DataTemplate DataType="{x:Type local:Model}">
            <StackPanel Orientation="Horizontal">
                <Label Content="{Binding Path=Name}"/>
                <Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.UpdateCommand}"
                        CommandParameter="{Binding}"
                        Content="Update"/>

                <Button Command="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=DataContext.RemoveCommand}"
                        CommandParameter="{Binding}"
                        Content="Remove"/>
            </StackPanel>
        </DataTemplate>
    </Grid.Resources>

    <ListBox ItemsSource="{Binding Models}">
    </ListBox>
</Grid>

usercontrol.cs

public partial class UserControl1 : UserControl
{
    public UserControl1()
    {
        InitializeComponent();
        DataContext = new ViewModel();
    }
}

View model

public class ViewModel : INotifyPropertyChanged
{
    private Models _Models;

    public Models Models
    {
        get { return _Models; }
        set { _Models = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(Models)));
        }
    }

    public ViewModel()
    {
        Models = new Models();
        UpdateCommand = new Command(o => true, UpdateItem);
        RemoveCommand = new Command(o => true, RemoveItem);
    }

    void RemoveItem(object item)
    {
        Model m = (item as Model);
        Models.Remove(m);
    }

    void UpdateItem(object item)
    {
        Model m = (item as Model);
        m.Name = m.Name + " updated";
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };

    public ICommand UpdateCommand { get; private set; }

    public ICommand RemoveCommand { get; private set; }
}

Icommand implementation

public class Command : ICommand
{
    private readonly Func<object, bool> _canExe;
    private readonly Action<object> _exe;

    public Command(Func<object,bool> canExecute,Action<object> execute)
    {
        _canExe = canExecute;
        _exe = execute;
    }

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

    public bool CanExecute(object parameter)
    {
        return _canExe(parameter);
    }

    public void Execute(object parameter)
    {
        _exe(parameter);
    }
}

Model and a collection of models

public class Models : ObservableCollection<Model>
{
    public Models()
    {
        Add(new Model ());
        Add(new Model ());
        Add(new Model ());
        Add(new Model ());
    }
}

public class Model : INotifyPropertyChanged
{
    static int count = 0;
    public Model()
    {
        Name = "Model "+ ++count;
    }
    private string _Name;

    public string Name
    {
        get { return _Name; }
        set { _Name = value;
            PropertyChanged(this, new PropertyChangedEventArgs(nameof(Name)));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged = delegate { };
}

Upvotes: 1

Related Questions