Reputation: 3796
I have the following XAML code:
<Window x:Class="WpfApplication1.MainWindow"
x:Name="window"
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:local="clr-namespace:WpfApplication1"
mc:Ignorable="d"
Title="MainWindow" Height="327" Width="213"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding Strings}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBox Text="{Binding Path=., UpdateSourceTrigger=PropertyChanged}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<TextBox Grid.Row="1" Text="{Binding TheString}" />
<Button Click="ButtonBase_OnClick" Grid.Row="2">Check strings</Button>
</Grid>
</Window>
Pretty simple. Now here's my code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
this.InitializeComponent();
}
public List<string> Strings { get; } = new List<string> { "Hello world1", "Hello world2", "Hello world3" };
public string TheString { get; set; } = "Helloo";
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
MessageBox.Show(string.Join("\n", this.Strings) + $"\n\n{this.TheString}");
}
}
The problem is if I update the string values in the Strings
list from the UI, it never gets updated when I click the button. What am I doing wrong?
I am correctly binding the Listbox
's ItemsSource
to the Strings
List. I am also correctly binding the TextBox
in the ItemTemplate
.
Upvotes: 2
Views: 1609
Reputation: 39
yeah, I faced with this problem too! It seems to be related to the immutable behavior of the string type. I wrote also an article with a solution on my blog.
Hope it would help :)
Upvotes: -1
Reputation: 38343
You have two issues here.
Firstly, your UI will never know if you replace the list with a new list as it will not get notified if the List<> gets re-instantiated.
Secondly, any changes to the items within the List, whether replacing the value or adding / deleting values, will go unnoticed as List does not raise change notifications.
Your best solution is a combination of the answers given by @qqww2 and @Lorek.
Move all data away from your Window class and put it in another class that implements INotifyPropertyChanged. Change your string collection to be ObservableCollection. And also raise PropertyChanged events within the setters of any properties, although this may not be required for the ObservableCollection as long as you only instantiate in the constructor and only call clear if you need to remove all the entries.
public class DataViewModel : INotifyPropertyChanged
{
private string _theString;
public DataViewModel()
{
Strings = new ObservableCollection<string>();
}
public event PropertyChangedEventHandler PropertyChanged;
public ObservableCollection<string> Strings { get; set; }
public string TheString
{
get
{
return _theString;
}
set
{
_theString = value;
NotifyPropertyChanged(nameof(TheString));
}
}
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}
Inside your Window class, change the constructor:
public MainWindow()
{
this.InitializeComponent();
this.ViewModel = new DataViewModel();
}
And add a new property:
public DataViewModel ViewModel { get; set; }
And change the DataContext binding on the Window tag to point to the ViewModel property.
Additional information:
On the view model, add the following method
public void AddString()
{
Strings.Add(TheString) ;
TheString = string.Empty;
}
And then call that from the button click handler.
Upvotes: 1
Reputation: 3315
The problem you have is about the immutability of string
. When you change the text in TextBox
you create a new string
, the old one does not change. Therefore List always contain the same values.
To update it you should wrap it in a class which implements INotifyPropertyChanged and fire a PropertyChanged event in the setter. Also you should change your binding to this new property. For example:
public class Wrapper : INotifyPropertyChanged
{
private string _value;
public string Value
{
get { return _value; }
set
{
_value = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
}
}
//INotifyPropertyChanged implementation...
}
Your list should be:
public List<MyClass> Strings { get; } = new List<MyClass>
{
new Wrapper { Value = "Hello world1" },
new Wrapper { Value = "Hello world2" },
new Wrapper { Value = "Hello world3" }
};
and the binding should be:
<TextBox Text="{Binding Path=Value, UpdateSourceTrigger=PropertyChanged}" />
Upvotes: 4
Reputation: 855
I think your problem is the type of list you are binding. Try using an ObservableCollection instead.
public ObservableCollection<string> Strings { get; } = new ObservableCollection<string> { "Hello world1", "Hello world2", "Hello world3" };
Maybe that will work.
Upvotes: 0
Reputation: 546
change
public List<string> Strings { get; } = new List<string> { "Hello world1", "Hello world2", "Hello world3" };
to
public List<string> Strings { get; set;} = new List<string> { "Hello world1", "Hello world2", "Hello world3" };
Without a setter, there's no way to update that property even if it's bound.
Upvotes: 0