Search code examples
c#wpfxamldatatemplate

TextBox inside a ListView bound to an object, two way binding dosen't work


Edit:

Ok after finally playing around numerous times without no luck, I have created a very small Wpf application. You can directly copy this code. Notice when you change values in the TextBox and press the Test button, the values never get updated. I don't understand why the two way binding dosen't work. Please help.

Here is the xaml:

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <ListView Grid.Row="0" 
                 ItemsSource="{Binding Path=Demo.CurrentParameterValue,Mode=TwoWay}" 
                 HorizontalAlignment="Center" VerticalAlignment="Center">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextBox Text="{Binding Path=.,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="100"></TextBox>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <Button Grid.Row="1" Click="Button_Click">TEST</Button>
    </Grid>

Here is the xaml.cs:

namespace WpfApp9
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private VmServiceMethodsViewDataGridModel _demo;

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        public VmServiceMethodsViewDataGridModel Demo
        {
            get => _demo;
            set
            {
                _demo = value;
                OnPropertyChanged("Demo");
            }
        }

        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
            Demo = new VmServiceMethodsViewDataGridModel();
            Demo.CurrentParameterValue.Add(1);
            Demo.CurrentParameterValue.Add(2);
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var collection = Demo.CurrentParameterValue;
            MessageBox.Show(string.Format("Values are {0}, {1}", collection[0], collection[1]));
        }
    }

    public class VmServiceMethodsViewDataGridModel : INotifyPropertyChanged
    {
        private List<object> _currentParameterValue;
        public List<object> CurrentParameterValue
        {
            get => _currentParameterValue;
            set
            {
                _currentParameterValue = value;
                OnPropertyChanged("CurrentParameterValue");
            }
        }

        public VmServiceMethodsViewDataGridModel()
        {
            CurrentParameterValue = new List<object>();
        }

        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }

Solution

  • But when I change the values in the TextBox it dosen't update back the source that is the CurrentParameterValue property.

    Binding in ListView doesn't know how to update the Property of type object because it's ItemsSource and it can update only ICollection such as you can't interact with object like List in C#. for example:

    object MyList = new object();
    MyList.Add("something"); // Compile error
    

    And in my viewmodel the object which can be a list of long, list of double etc comes from an external API.

    You need this solution then.

    public class VmServiceMethodsViewDataGridModel : BindableBaseThreadSafe
    {
        private List<object> _currentParameterValue; // or ObservableCollection
        public List<object> CurrentParameterValue
        {
            get => _currentParameterValue;
            set => Set(ref _currentParameterValue, value);
        }
    }
    

    Additionally

    I have no idea what do you want to achieve or solve with this syntax

    <ListView ItemsSource="{x:Bind ViewModel.AtlasMethodParameterList,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}">
    

    Everything must work with this

    <ListView ItemsSource="{Binding AtlasMethodParameterList}">
    
    • Mode=TwoWay is default Mode, you may not include it here explicitly.
    • UpdateSourceTrigger=PropertyChanged (Default is LostFocus) is needed in UI->VM direction, not in a back way. So, it's useless here. You may apply it to the TextBox in template instead.

    EDIT

    Because Two-way Binding requires explicit Path and the target must be a Property which contains Setter.

    The workaround with your Demo app

    <ListView Grid.Row="0" 
              ItemsSource="{Binding Demo.CurrentParameterValue}" 
              HorizontalAlignment="Center" VerticalAlignment="Center">
        <ListView.ItemTemplate>
            <DataTemplate>
                <TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" Width="100"></TextBox>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
    
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private VmServiceMethodsViewDataGridModel _demo;
    
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
        public VmServiceMethodsViewDataGridModel Demo
        {
            get => _demo;
            set
            {
                _demo = value;
                OnPropertyChanged("Demo");
            }
        }
    
        public MainWindow()
        {
            InitializeComponent();
            DataContext = this;
            Demo = new VmServiceMethodsViewDataGridModel();
            Demo.CurrentParameterValue.Add(new MyItem { Value = 1 });
            Demo.CurrentParameterValue.Add(new MyItem { Value = 2 });
        }
    
        private void Button_Click(object sender, RoutedEventArgs e)
        {
            var collection = Demo.CurrentParameterValue;
            MessageBox.Show(string.Format("Values are {0}, {1}", collection[0].Value, collection[1].Value));
        }
    }
    
    // here it is
    public class MyItem
    {
        public object Value { get; set; }
    }
    
    public class VmServiceMethodsViewDataGridModel : INotifyPropertyChanged
    {
        private List<MyItem> _currentParameterValue;
        public List<MyItem> CurrentParameterValue
        {
            get => _currentParameterValue;
            set
            {
                _currentParameterValue = value;
                OnPropertyChanged("CurrentParameterValue");
            }
        }
    
        public VmServiceMethodsViewDataGridModel()
        {
            CurrentParameterValue = new List<MyItem>();
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged(string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }
    }
    

    Additionally you may implement INPC for the Value regarding to your needs.